From 91abe0978a1b175d4215546135e4261b80a7042f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Apr 2022 16:15:00 +0200 Subject: [PATCH 01/24] docs/pos: add the new staking rewards and commissions system --- .../explore/design/ledger/pos-integration.md | 32 ++++++++++--------- .../proof-of-stake/bonding-mechanism.md | 6 ++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index d441e5ddca..8bf0c36c07 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -14,8 +14,12 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - for any validator, all the following fields are required: - `validator/{validator_address}/consensus_key` - `validator/{validator_address}/state` - - `validator/{validator_address}/total_deltas` + - `validator/{validator_address}/total_deltas`: sum of self-bonds and delegations to this validator - `validator/{validator_address}/voting_power` + - `validator/{validator_address}/validator_rewards_product`: a multiplier of the `total_deltas` that yields the updated amount with staking rewards added to this validator + - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but with delegation commissions are subtracted + - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator + - `validator/{validator_address}/commission_rate`: a validator chosen commission rate that is a fraction of delegation rewards charged by this validator - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` - `unbond/{unbond_source}/{unbond_validator} (optional)` @@ -23,8 +27,8 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `total_voting_power (required)` - standard validator metadata (these are regular storage values, not epoched data): - - `validator/{validator_address}/staking_reward_address` (required): an address that should receive staking rewards - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash + - `validator/{validator_address}/max_commission_rate_change` (required): a validator chosen maximum commission rate change per epoch, set when the validator account is created and cannot be changed - TBA (e.g. alias, website, description, delegation commission rate, etc.) Only XAN tokens can be staked in bonds. The tokens being staked (bonds and unbonds amounts) are kept in the PoS account under `{xan_address}/balance/{pos_address}` until they are withdrawn. @@ -33,16 +37,6 @@ Only XAN tokens can be staked in bonds. The tokens being staked (bonds and unbon The PoS system is initialized via the shell on chain initialization. The genesis validator set is given in the genesis configuration. On genesis initialization, all the epoched data is set to be immediately active for the current (the very first) epoch. -## Staking rewards and transaction fees - -Staking rewards for validators are rewarded in Tendermint's method `BeginBlock` in the base ledger. A validator must specify a `validator/{validator_address}/staking_reward_address` for its rewards to be credited to this address. - -To a validator who proposed a block (`block.header.proposer_address`), the system rewards tokens based on the `block_proposer_reward` PoS parameter and each validator that voted on a block (`block.last_commit_info.validator` who `signed_last_block`) receives `block_vote_reward`. - -All the fees that are charged in a transaction execution (DKG transaction wrapper fee and transactions applied in a block) are transferred into a fee pool, which is another special account controlled by the PoS module. Note that the fee pool account may contain tokens other than the staking token XAN. - -- TODO describe the fee pool, related to , and - ## Transactions The transactions are assumed to be applied in epoch `n`. Any transaction that modifies [epoched data](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#epoched-data) updates the structure as described in [epoched data storage](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage). @@ -53,9 +47,10 @@ For slashing tokens, we implement a [PoS slash pool account](vp.md#pos-slash-poo The validator transactions are assumed to be applied with an account address `validator_address`. -- `become_validator(consensus_key, staking_reward_address)`: +- `become_validator(consensus_key, commission_rate, max_commission_rate_change)`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` - - creates a record in `validator/{validator_address}/staking_reward_address` + - creates a record in `validator/{validator_address}/commission_rate` in epoch `n + pipeline_length` + - sets `validator/{validator_address}/max_commission_rate_change` - sets `validator/{validator_address}/state` to `candidate` in epoch `n + pipeline_length` - `deactivate`: - sets `validator/{validator_address}/state` to `inactive` in epoch `n + pipeline_length` @@ -93,8 +88,10 @@ The validator transactions are assumed to be applied with an account address `va - burn the slashed tokens (`amount - amount_after_slash`), if not zero - `change_consensus_key`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` +- `change_commission_rate`: + - creates a record in `validator/{validator_address}/commission_rate` in epoch `n + pipeline_length` -For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator` and `change_consensus_key` the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key` we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. +For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator` `change_consensus_key` and `change_commission_rate` the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key` we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. ### Delegator transactions @@ -216,6 +213,11 @@ The validity predicate triggers a validation logic based on the storage keys mod - find the difference between the pre-state and post-state values and add it to the `total_deltas` accumulator and update `total_stake_by_epoch`, `expected_voting_power_by_epoch` and `expected_total_voting_power_delta_by_epoch` - `validator/{validator_address}/voting_power`: - find the difference between the pre-state and post-state value and insert it into the `voting_power_by_epoch` accumulator +- `validator/{validator_address}/validator_rewards_product`: cannot be changed by a transaction, updated by the protocol +- `validator/{validator_address}/delegation_rewards_product`: cannot be changed by a transaction, updated by the protocol +- `validator/{validator_address}/commissions`: cannot be changed by a transaction, updated by the protocol +- `validator/{validator_address}/max_commission_rate_change`: cannot be changed by a transaction, set when the validator account is created +- `validator/{validator_address}/commission_rate`: The commission rate may only change at pipeline offset. Additionally, the difference from the predecessor epoch (pipeline offset - 1) must be within the limits of `max_commission_rate_change` - `bond/{bond_source}/{bond_validator}/delta`: - for each difference between the post-state and pre-state values: - if the difference is not in epoch `n` or `n + pipeline_length`, panic diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index ab0449e026..62724febf5 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -38,7 +38,7 @@ For each validator (in any state), the system also tracks total bonded tokens as #### Validator actions - *become validator*: - Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. + Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key. For the action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. - *deactivate*: Only a validator whose state at or before the `pipeline_length` offset is *candidate* account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. - *reactivate*: @@ -51,6 +51,8 @@ For each validator (in any state), the system also tracks total bonded tokens as Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). - *change consensus key*: Set the new consensus key. When applied in epoch `n`, the key is set for epoch `n + pipeline_length`. +- *change commission rate*: + Set the new commission rate. When applied in epoch `n`, the new value will be set for epoch `n + pipeline_length`. The commission rate change must be within the `max_commission_rate_change` limit set by the validator. #### Active validator set @@ -132,8 +134,6 @@ The default values that are relative to epoch duration assume that an epoch last - `pipeline_len`: Pipeline length in number of epochs, default `2` (see ) - `unboding_len`: Unbonding duration in number of epochs, default `6` - `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) -- `block_proposer_reward`: Amount of tokens rewarded to a validator for proposing a block -- `block_vote_reward`: Amount of tokens rewarded to each validator that voted on a block proposal - `duplicate_vote_slash_rate`: Portion of validator's stake that should be slashed on a duplicate vote - `light_client_attack_slash_rate`: Portion of validator's stake that should be slashed on a light client attack From 0aff2c86c450f8f7e560f0a8953ad8ad165d68d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 30 Aug 2022 17:05:59 +0200 Subject: [PATCH 02/24] doc/specs/pos/bonding: fmt and typos --- .../proof-of-stake/bonding-mechanism.md | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 62724febf5..3fa0b172d5 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -8,6 +8,7 @@ An epoch is a range of blocks or time that is defined by the base ledger and mad Epoched data is data associated with a specific epoch that is set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: + - [System parameters](#system-parameters). A single value for each epoch. - [Active validator set](#active-validator-set). A single value for each epoch. - Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. @@ -28,6 +29,7 @@ Additionally, any account may submit evidence for [a slashable misbehaviour](#sl A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). A validator may be in one of the following states: + - *inactive*: A validator is not being considered for block creation and cannot receive any new delegations. - *candidate*: @@ -92,30 +94,33 @@ An "unbond" with epoch set to `n` may be withdrawn by the bond's source address Note that unlike bonding and unbonding where token changes are delayed to some future epochs (pipeline or unbonding offset), the token withdrawal applies immediately. This because when the tokens are withdrawable, they are already "unlocked" from the PoS system and do not contribute to voting power. ### Staking rewards + Until we have programmable validity predicates, rewards can use the mechanism outlined in the [F1 paper](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf), but it should use the exponential model, so that withdrawing rewards more frequently provides no additional benefit (this is a design constraint we should follow in general, we don't want to accidentally encourage transaction spam). This should be written in a way that allows for a natural upgrade to a validator-customisable rewards model (defaulting to this one) if possible. To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. ### Slashing -An important part of the security model of Namada is based on making attacking the system very expensive. To this end, the validator who has bonded stake will be slashed once an offence has been detected. +An important part of the security model of Namada is based on making attacking the system very expensive. To this end, the validator who has bonded stake will be slashed once an offense has been detected. + +These are the types of offenses: + +- Equivocation in consensus + - voting: meaning that a validator has submitted two votes that are conflicting + - block production: a block producer has created two different blocks for the same height +- Invalidity: + - block production: a block producer has produced invalid block + - voting: validators have voted on invalid block -These are the types of offences: -* Equivocation in consensus - * voting: meaning that a validator has submitted two votes that are confliciting - * block production: a block producer has created two different blocks for the same height -* Invalidity: - * block production: a block producer has produced invalid block - * voting: validators have voted on invalid block - -Unavailability is not considered an offense, but a validator who hasn't voted will not receive rewards. +Unavailability is not considered an offense, but a validator who hasn't voted will not receive rewards. + +Once an offense has been reported: -Once an offence has been reported: 1. Kicking out 2. Slashing - - Individual: Once someone has reported an offence it is reviewed by validarors and if confirmed the offender is slashed. - - [cubic slashing](./cubic-slashing.md): escalated slashing +- Individual: Once someone has reported an offense it is reviewed by validators and if confirmed the offender is slashed. +- [cubic slashing](./cubic-slashing.md): escalated slashing Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. However, because slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). @@ -125,7 +130,6 @@ A valid evidence reduces the validator's total bonded token amount by the slash The invariant is that the sum of amounts that may be withdrawn from a misbehaving validator must always add up to the total bonded token amount. - ## System parameters The default values that are relative to epoch duration assume that an epoch last about 24 hours. @@ -148,6 +152,7 @@ type Validators = HashMap; ``` Epoched data are stored in the following structure: + ```rust,ignore struct Epoched { /// The epoch in which this data was last updated @@ -202,6 +207,7 @@ To update a value in `Epoched` data with delta values in epoch `n` with value `d The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. For the active validator set, we store all the active and inactive validators separately with their respective voting power: + ```rust,ignore type VotingPower = u64; From d4629449f7eae1a18534b9e55bc6536a06abb330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 30 Aug 2022 19:29:30 +0200 Subject: [PATCH 03/24] doc/spec/pos/bonding: updating unbonding offsets --- .../specs/src/economics/proof-of-stake/bonding-mechanism.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 3fa0b172d5..283b470ab5 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -15,7 +15,7 @@ The data relevant to the PoS system in the ledger's state are epoched. Each data - [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. - [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. -Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. +Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + pipeline_length + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. ## Entities @@ -85,9 +85,9 @@ The tokens put into a bond are immediately deducted from the source account. ### Unbond -An unbonding action (validator *unbond* or delegator *undelegate*) requested by the bond's source account in epoch `n` creates an "unbond" with epoch set to `n + unbounding_length`. We also store the epoch of the bond(s) from which the unbond is created in order to determine if the unbond should be slashed if a fault occurred within the range of bond epoch (inclusive) and unbond epoch (exclusive). +An unbonding action (validator *unbond* or delegator *undelegate*) requested by the bond's source account in epoch `n` creates an "unbond" with epoch set to `n + pipeline_length + unbounding_length`. We also store the epoch of the bond(s) from which the unbond is created in order to determine if the unbond should be slashed if a fault occurred within the range of bond epoch (inclusive) and unbond epoch (exclusive). The "bond" from which the tokens are being unbonded is decremented in-place (in whatever epoch it was created in). -Any unbonds created in epoch `n` decrements the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + unbonding_length`. +Any unbonds created in epoch `n` decrements the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + pipeline_length`. An "unbond" with epoch set to `n` may be withdrawn by the bond's source address in or any time after the epoch `n`. Once withdrawn, the unbond is deleted and the tokens are credited to the source account. From e8ec719dd8ac2d5d7b1d0ad794a22021c6f2e766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 31 Aug 2022 10:23:05 +0200 Subject: [PATCH 04/24] doc/dev/pos-integration: update unbonding offsets --- .../dev/src/explore/design/ledger/pos-integration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 8bf0c36c07..1633289884 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -71,11 +71,11 @@ The validator transactions are assumed to be applied with an account address `va - let `pre_unbond = read(unbond/{validator_address}/{validator_address}/delta)` - if `total(bond) - total(pre_unbond) < amount`, panic - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch at the unbonding offset) until whole `amount` is decremented - - for each decremented `bond` value write a new `unbond` in epoch `n + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + unbonding_length` - - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` - - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` - - update the `total_voting_power` in epoch `n + unbonding_length` - - update `validator_set` in epoch `n + unbonding_length` + - for each decremented `bond` value write a new `unbond` in epoch `n + pipeline_length + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + pipeline_length + unbonding_length` + - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` + - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` + - update the `total_voting_power` in epoch `n + pipeline_length` + - update `validator_set` in epoch `n + pipeline_length` - `withdraw_unbonds`: - let `unbond = read(unbond/{validator_address}/{validator_address}/delta)` - if `unbond` doesn't exist, panic @@ -225,7 +225,7 @@ The validity predicate triggers a validation logic based on the storage keys mod - add it to the `bond_delta` accumulator - `unbond/{unbond_source}/{unbond_validator}/deltas`: - for each difference between the post-state and pre-state values: - - if the difference is not in epoch `n` or `n + unboding_length`, panic + - if the difference is not in epoch `n` or `n + pipeline_length + unboding_length`, panic - find slashes for the `bond_validator`, if any, and apply them to the delta value - add it to the `unbond_delta` accumulator - `validator_set`: From 9ebf75dcab8e14055e9ed5d6b26c7cbf0aa8a143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 31 Aug 2022 13:53:43 +0200 Subject: [PATCH 05/24] doc/pos: add total_unbonded field to validators needed for slashing --- .../dev/src/explore/design/ledger/pos-integration.md | 3 ++- .../specs/src/economics/proof-of-stake/bonding-mechanism.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 1633289884..4e21bc10f8 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -14,7 +14,8 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - for any validator, all the following fields are required: - `validator/{validator_address}/consensus_key` - `validator/{validator_address}/state` - - `validator/{validator_address}/total_deltas`: sum of self-bonds and delegations to this validator + - `validator/{validator_address}/total_deltas`: sum of self-bonds and delegations to this validator, may contain negative delta values when unbonding + - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - `validator/{validator_address}/voting_power` - `validator/{validator_address}/validator_rewards_product`: a multiplier of the `total_deltas` that yields the updated amount with staking rewards added to this validator - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but with delegation commissions are subtracted diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 283b470ab5..2d8046f051 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -206,7 +206,7 @@ To update a value in `Epoched` data with delta values in epoch `n` with value `d The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. -For the active validator set, we store all the active and inactive validators separately with their respective voting power: +We store all the active and inactive validators in two separate sets, ordered by their voting power. Conceptually, this may look like this: ```rust,ignore type VotingPower = u64; @@ -248,13 +248,14 @@ When any validator's voting power changes, we attempt to perform the following u 1. if `power_delta < 0 && power_after < min_active.voting_power`, update the validator in `inactive` set with `voting_power = power_after` 1. else, remove the validator from `inactive`, insert it into `active` and remove `min_active.address` from `active` and insert it into `inactive` -Within each validator's address space, we store public consensus key, state, total bonded token amount and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): +Within each validator's address space, we store public consensus key, state, total bonded token amount, total unbonded token amount (needed for applying of slashes) and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): ```rust,ignore struct Validator { consensus_key: Epoched, state: Epoched, total_deltas: Epoched, + total_unbonded: Epoched, voting_power: Epoched, } From 8ebf9a7a7c985577a57869d89c293f6fad0d32c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 31 Aug 2022 13:54:22 +0200 Subject: [PATCH 06/24] doc/specs/pos: update how epoched data is stored --- .../proof-of-stake/bonding-mechanism.md | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 2d8046f051..e41cdbab58 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -151,60 +151,66 @@ The validators' data are keyed by the their addresses, conceptually: type Validators = HashMap; ``` -Epoched data are stored in the following structure: +Epoched data are stored in a structure, conceptually looking like this: ```rust,ignore struct Epoched { /// The epoch in which this data was last updated last_update: Epoch, - /// Dynamically sized vector in which the head is the data for epoch in which - /// the `last_update` was performed and every consecutive array element is the - /// successor epoch of the predecessor array element. For system parameters, - /// validator's consensus key and state, `LENGTH = pipeline_length + 1`. - /// For all others, `LENGTH = unbonding_length + 1`. - data: Vec> + /// How many epochs of historical data to keep, this is `0` in most cases + /// except for validator `total_deltas` and `total_unbonded`, in which + /// historical data for up to `pipeline_length + unbonding_length - 1` is + /// needed to be able to apply any slashes that may occur. + /// The value is not actually stored with the data, it's either constant + /// value or resolved from PoS parameters on which it may depends. + past_epochs_to_store: u64, + /// An ordered map in which the head is the data for epoch in which + /// the `last_update - past_epochs_to_store` was performed and every + /// consecutive epoch up to a required length. For system parameters, + /// and all the epoched data + /// `LENGTH = past_epochs_to_store + pipeline_length + 1`, + /// with exception of unbonds, for which + /// `LENGTH = past_epochs_to_store + pipeline_length + unbonding_length + 1`. + data: Map> } ``` -Note that not all epochs will have data set, only the ones in which some changes occurred. +Note that not all epochs will have data set, only the ones in which some changes occurred. The only exception to this is validator sets, which are written on a new epoch from the latest state into the new epoch by the protocol. This so that a +transaction never has to update the whole validator set when it hasn't changed yet in the current epoch, which would require a copy of the last epoch data and that copy would additionally have to be verified by the PoS validity predicate. -To try to look-up a value for `Epoched` data with independent values in each epoch (such as the active validator set) in the current epoch `n`: +To try to look-up a value for `Epoched` data with discrete values in each epoch (such as the active validator set) in the current epoch `n`: -1. let `index = min(n - last_update, pipeline_length)` -1. read the `data` field at `index`: - 1. if there's a value at `index` return it - 1. else if `index == 0`, return `None` - 1. else decrement `index` and repeat this sub-step from 1. +1. read the `data` field at epoch `n`: + 1. if there's a value at `n` return it + 1. else if `n == last_update - past_epochs_to_store`, return `None` + 1. else decrement `n` and repeat this sub-step from 1. To look-up a value for `Epoched` data with delta values in the current epoch `n`: -1. let `end = min(n - last_update, pipeline_length) + 1` -1. sum all the values that are not `None` in the `0 .. end` range bounded inclusively below and exclusively above +1. sum all the values that are not `None` in the `last_update - past_epochs_to_store .. n` epoch range bounded inclusively below and above -To update a value in `Epoched` data with independent values in epoch `n` with value `new` for epoch `m`: +To update a value in `Epoched` data with discrete values in epoch `n` with value `new` for epoch `m`: -1. let `shift = min(n - last_update, pipeline_length)` -1. if `shift == 0`: - 1. `data[m - n] = new` +1. let `epochs_to_clear = min(n - last_update, LENGTH)` +1. if `epochs_to_clear == 0`: + 1. `data[m] = new` 1. else: - 1. for `i in 0 .. shift` range bounded inclusively below and exclusively above, set `data[i] = None` - 1. rotate `data` left by `shift` - 1. set `data[m - n] = new` + 1. for `i in last_update - past_epochs_to_store .. last_update - past_epochs_to_store + epochs_to_clear` range bounded inclusively below and exclusively above, set `data[i] = None` + 1. set `data[m] = new` 1. set `last_update` to the current epoch To update a value in `Epoched` data with delta values in epoch `n` with value `delta` for epoch `m`: -1. let `shift = min(n - last_update, pipeline_length)` -1. if `shift == 0`: - 1. set `data[m - n] = data[m - n].map_or_else(delta, |last_delta| last_delta + delta)` (add the `delta` to the previous value, if any, otherwise use the `delta` as the value) +1. let `epochs_to_sum = min(n - last_update, LENGTH)` +1. if `epochs_to_sum == 0`: + 1. set `data[m] = data[m].map_or_else(delta, |last_delta| last_delta + delta)` (add the `delta` to the previous value, if any, otherwise use the `delta` as the value) 1. else: - 1. let `sum` to be equal to the sum of all delta values in the `i in 0 .. shift` range bounded inclusively below and exclusively above and set `data[i] = None` - 1. rotate `data` left by `shift` - 1. set `data[0] = data[0].map_or_else(sum, |last_delta| last_delta + sum)` - 1. set `data[m - n] = delta` + 1. let `sum` to be equal to the sum of all delta values in the `last_update - past_epochs_to_store .. last_update - past_epochs_to_store + epochs_to_sum` range bounded inclusively below and exclusively above and set `data[i] = None` + 1. set `data[n - past_epochs_to_store] = data[n - past_epochs_to_store].map_or_else(sum, |last_delta| last_delta + sum)` to add the sum to the last epoch that will be stored + 1. set `data[m] = data[m].map_or_else(delta, |last_delta| last_delta + delta)` to add the new delta 1. set `last_update` to the current epoch -The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. +The invariants for updates in both cases are that `m >= n` (epoched data cannot be updated in an epoch lower than the current epoch) and `m - n <= LENGTH - past_epochs_to_store` (epoched data can only be updated at the future-most epoch set by the `LENGTH - past_epochs_to_store` of the data). We store all the active and inactive validators in two separate sets, ordered by their voting power. Conceptually, this may look like this: From a0978131d61e4b81d9cec50c4671da4dac3d9ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 09:27:26 +0200 Subject: [PATCH 07/24] spec/pos/reward: update unbonding offset --- .../src/economics/proof-of-stake/reward-distribution.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 70f662f97a..12af208dd6 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -14,10 +14,10 @@ Consider a system with - a set of validators $V_i$. - a set of delegations $D_{i, j}$, each to a particular validator and in a particular (initial) amount. - epoched proof-of-stake, where changes are applied as follows: - - bonding after the pipeline length - - unbonding after the unbonding length - - rewards are paid out at the end of each epoch, to wit, in each epoch $e$, $R_{e,i}$ is paid out to validator $V_i$ - - slashing is applied as described in [slashing](cubic-slashing.md). + - bonding after the pipeline length + - unbonding after the pipeline + unbonding length + - rewards are paid out at the end of each epoch, to wit, in each epoch $e$, $R_{e,i}$ is paid out to validator $V_i$ + - slashing is applied as described in [slashing](cubic-slashing.md). We wish to approximate as exactly as possible the following ideal delegator reward distribution system: From 556bc62ccf3c53e7fbe171dbd95298964a2f7a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 09:27:46 +0200 Subject: [PATCH 08/24] spec/pos/reward: small fixes --- .../economics/proof-of-stake/reward-distribution.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 12af208dd6..654436128d 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -76,12 +76,12 @@ updateProducts -> HashMap> updateProducts validatorProducts activeSet currentEpoch = - let stake = PoS.readValidatorTotalDeltas validator currentEpoch - reward = PoS.reward stake currentEpoch - entries = lookup validatorProducts validator - lastProduct = lookup entries (Epoch (currentEpoch - 1)) - in insert currentEpoch (product*(1+rsratio)) entries - + let stake = PoS.readValidatorTotalDeltas validator currentEpoch + reward = PoS.reward stake currentEpoch + rsratio = reward / stake + entries = lookup validatorProducts validator + lastProduct = lookup entries (Epoch (currentEpoch - 1)) + in insert currentEpoch (lastProduct*(1+rsratio)) entries ``` From 1cba41d75eeeb2eebe4a974ea78e1c45d546a9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 09:46:38 +0200 Subject: [PATCH 09/24] docs/dev/pos-integration: more about rewards products and commissions --- .../dev/src/explore/design/ledger/pos-integration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 4e21bc10f8..14c41d39ef 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -17,9 +17,9 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `validator/{validator_address}/total_deltas`: sum of self-bonds and delegations to this validator, may contain negative delta values when unbonding - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - `validator/{validator_address}/voting_power` - - `validator/{validator_address}/validator_rewards_product`: a multiplier of the `total_deltas` that yields the updated amount with staking rewards added to this validator - - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but with delegation commissions are subtracted - - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator + - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely + - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely + - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to epoched data, just an accumulator of the total - `validator/{validator_address}/commission_rate`: a validator chosen commission rate that is a fraction of delegation rewards charged by this validator - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` From 52ddffd50530e43f8ab73e0249e96fa05a7f7c03 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 13 Sep 2022 00:14:14 +0200 Subject: [PATCH 10/24] bonding: remove voting powers, rename validator/total_deltas -> validator/deltas, total_voting_power -> total_deltas --- .../explore/design/ledger/pos-integration.md | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 14c41d39ef..01a8ce28f1 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -14,18 +14,17 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - for any validator, all the following fields are required: - `validator/{validator_address}/consensus_key` - `validator/{validator_address}/state` - - `validator/{validator_address}/total_deltas`: sum of self-bonds and delegations to this validator, may contain negative delta values when unbonding + - `validator/{validator_address}/deltas`: the change in the amount of self-bonds and delegations to this validator per epoch, may contain negative delta values when unbonding - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - - `validator/{validator_address}/voting_power` - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to epoched data, just an accumulator of the total - - `validator/{validator_address}/commission_rate`: a validator chosen commission rate that is a fraction of delegation rewards charged by this validator + - `validator/{validator_address}/commission_rate`: a validator-chosen commission rate that is some fraction of the delegation rewards charged by this validator - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` - `unbond/{unbond_source}/{unbond_validator} (optional)` - `validator_set (required)` -- `total_voting_power (required)` +- `total_deltas (required)` - standard validator metadata (these are regular storage values, not epoched data): - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash @@ -62,9 +61,8 @@ The validator transactions are assumed to be applied with an account address `va - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` - else, create a new record with bond amount in epoch `n + pipeline_length` - debit the token `amount` from the `validator_address` and credit it to the PoS account - - add the `amount` to `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` - - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` - - update the `total_voting_power` in epoch `n + pipeline_length` + - add the `amount` to `validator/{validator_address}/deltas` in epoch `n + pipeline_length` + - update the `total_deltas` in epoch `n + pipeline_length` - update `validator_set` in epoch `n + pipeline_length` - `unbond(amount)`: - let `bond = read(bond/{validator_address}/{validator_address}/delta)` @@ -73,9 +71,8 @@ The validator transactions are assumed to be applied with an account address `va - if `total(bond) - total(pre_unbond) < amount`, panic - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch at the unbonding offset) until whole `amount` is decremented - for each decremented `bond` value write a new `unbond` in epoch `n + pipeline_length + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + pipeline_length + unbonding_length` - - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` - - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` - - update the `total_voting_power` in epoch `n + pipeline_length` + - decrement the `amount` from `validator/{validator_address}/deltas` in epoch `n + pipeline_length` + - update the `total_deltas` in epoch `n + pipeline_length` - update `validator_set` in epoch `n + pipeline_length` - `withdraw_unbonds`: - let `unbond = read(unbond/{validator_address}/{validator_address}/delta)` @@ -103,9 +100,8 @@ The delegator transactions are assumed to be applied with an account address `de - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` - else, create a new record with bond amount in epoch `n + pipeline_length` - debit the token `amount` from the `delegator_address` and credit it to the PoS account - - add the `amount` to `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` - - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` - - update the `total_voting_power` in epoch `n + pipeline_length` + - add the `amount` to `validator/{validator_address}/deltas` in epoch `n + pipeline_length` + - update the `total_deltas` in epoch `n + pipeline_length` - update `validator_set` in epoch `n + pipeline_length` - `undelegate(validator_address, amount)`: - let `bond = read(bond/{delegator_address}/{validator_address}/delta)` @@ -114,9 +110,8 @@ The delegator transactions are assumed to be applied with an account address `de - if `total(bond) - total(pre_unbond) < amount`, panic - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch) until whole `amount` is decremented - for each decremented `bond` value write a new `unbond` with the key set to the epoch of the source value - - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` - - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` - - update the `total_voting_power` in epoch `n + unbonding_length` + - decrement the `amount` from `validator/{validator_address}/deltas` in epoch `n + unbonding_length` + - update the `total_deltas` in epoch `n + unbonding_length` - update `validator_set` in epoch `n + unbonding_length` - `redelegate(src_validator_address, dest_validator_address, amount)`: - `undelegate(src_validator_address, amount)` From 785cc67b512926e32a45ca3a7ea8fb3cae93c33c Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 13 Sep 2022 00:15:23 +0200 Subject: [PATCH 11/24] langauge edits --- .../dev/src/explore/design/ledger/pos-integration.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 01a8ce28f1..70eaf37da7 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -18,7 +18,7 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely - - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to epoched data, just an accumulator of the total + - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to be epoched data, just an accumulator of the total - `validator/{validator_address}/commission_rate`: a validator-chosen commission rate that is some fraction of the delegation rewards charged by this validator - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` @@ -28,10 +28,10 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - standard validator metadata (these are regular storage values, not epoched data): - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash - - `validator/{validator_address}/max_commission_rate_change` (required): a validator chosen maximum commission rate change per epoch, set when the validator account is created and cannot be changed - - TBA (e.g. alias, website, description, delegation commission rate, etc.) + - `validator/{validator_address}/max_commission_rate_change` (required): a validator-chosen maximum commission rate change per epoch, set when the validator account is created and cannot be changed + - TBA (e.g. alias, website, description, etc.) -Only XAN tokens can be staked in bonds. The tokens being staked (bonds and unbonds amounts) are kept in the PoS account under `{xan_address}/balance/{pos_address}` until they are withdrawn. +Only NAM tokens can be staked in bonds. The tokens being staked (amounts in the bonds and unbonds) are kept in the PoS account under `{nam_address}/balance/{pos_address}` until they are withdrawn. ## Initialization @@ -89,7 +89,7 @@ The validator transactions are assumed to be applied with an account address `va - `change_commission_rate`: - creates a record in `validator/{validator_address}/commission_rate` in epoch `n + pipeline_length` -For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator` `change_consensus_key` and `change_commission_rate` the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key` we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. +For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator`, `change_consensus_key`, and `change_commission_rate`, the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key`, we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. ### Delegator transactions From 4f2cad2d55abde1eea47d4b2c9aaec341c4b65a1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 13 Sep 2022 00:15:49 +0200 Subject: [PATCH 12/24] update math bc after removal of BasisPoints struct --- documentation/dev/src/explore/design/ledger/pos-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 70eaf37da7..83ddb50e7a 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -81,7 +81,7 @@ The validator transactions are assumed to be applied with an account address `va - for each `((bond_start, bond_end), amount) in unbond where unbond.epoch <= n`: - let `amount_after_slash = amount` - for each `slash in read(slash/{validator_address})`: - - if `bond_start <= slash.epoch && slash.epoch <= bond_end)`, `amount_after_slash *= (10_000 - slash.rate) / 10_000` + - if `bond_start <= slash.epoch && slash.epoch <= bond_end)`, `amount_after_slash *= (1 - slash.rate)` - credit the `amount_after_slash` to the `validator_address` and debit the whole `amount` (before slash, if any) from the PoS account - burn the slashed tokens (`amount - amount_after_slash`), if not zero - `change_consensus_key`: From 9fd525f7cb26803ff92b9ef939854e43bbe18348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 12:12:07 +0200 Subject: [PATCH 13/24] specs/pos/rewards: fmt --- .../proof-of-stake/reward-distribution.md | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 654436128d..32bff07c39 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -2,9 +2,9 @@ Namada uses the automatically-compounding variant of [F1 fee distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf). -Rewards are given to validators for voting on finalizing blocks: the fund for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how much is staked and our desired yearly inflation. When the total of the tokens staked is very low, the return rate per validator needs to increase, but as the total amount of stake rises, validators will receive less rewards. Once we have acquired the desired stake percentage, the amount minted will just be the desired yearly inflation. +Rewards are given to validators for voting on finalizing blocks: the fund for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how much is staked and our desired yearly inflation. When the total of the tokens staked is very low, the return rate per validator needs to increase, but as the total amount of stake rises, validators will receive less rewards. Once we have acquired the desired stake percentage, the amount minted will just be the desired yearly inflation. -The validator and the delegator must have agreed on a commission rate between themselves. Delegators pay out rewards to validators based on a mutually-determined commission rate that both parties must have agreed upon beforehand. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once we have calculated the total that needs to be minted at the end of the epoch, we split the minted tokens according to the stake the relevant validators and delegators contributed and distribute them to validators and their delegators. This is similar to what Cosmos does. +The validator and the delegator must have agreed on a commission rate between themselves. Delegators pay out rewards to validators based on a mutually-determined commission rate that both parties must have agreed upon beforehand. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once we have calculated the total that needs to be minted at the end of the epoch, we split the minted tokens according to the stake the relevant validators and delegators contributed and distribute them to validators and their delegators. This is similar to what Cosmos does. ## Basic algorithm @@ -30,7 +30,7 @@ where $r_V(e)$ and $s_V(e)$ respectively denote the reward and stake of validato In this system, rewards are automatically rebonded to delegations, increasing the delegation amounts and validator voting powers accordingly. -However, we wish to implement this without actually needing to iterate over all delegations each block, since this is too computationally expensive. We can exploit this constant multiplicative factor $(1 + r_V(e) / s_V(e))$ which does not vary per delegation to perform this calculation lazily, storing only a constant amount of data per validator per epoch, and calculate revised amounts for each individual delegation only when a delegation changes. +However, we wish to implement this without actually needing to iterate over all delegations each block, since this is too computationally expensive. We can exploit this constant multiplicative factor $(1 + r_V(e) / s_V(e))$ which does not vary per delegation to perform this calculation lazily, storing only a constant amount of data per validator per epoch, and calculate revised amounts for each individual delegation only when a delegation changes. We will demonstrate this for a delegation $D$ to a validator $V$. Let $s_D(e)$ denote the stake of $D$ at epoch $e$. @@ -40,7 +40,7 @@ $$ p(n, m) = \prod_{e = m}^{n} \Big(1 + \frac{r_V(e)} {s_V(e)}\Big). $$ -Denote $p(n, 0)$ as $p_n$. The function $p$ has a useful property. +Denote $p(n, 0)$ as $p_n$. The function $p$ has a useful property. $$ p(n,m) = \frac{p_n}{p_m}\tag{1} @@ -63,10 +63,10 @@ $$ s_D(n) = s_D(m) * \frac{p_n}{p_m}. $$ - Clearly, the quantity $p_n/p_m$ does not depend on the delegation $D$. Thus, for a given validator, we need only store this product $p_e$ at each epoch $e$, with which updated amounts for all delegations can be calculated. The product $p_e$ at the end of each epoch $e$ is updated as follows. + ```haskell= updateProducts @@ -84,7 +84,6 @@ updateProducts validatorProducts activeSet currentEpoch = in insert currentEpoch (lastProduct*(1+rsratio)) entries ``` - - - From 64cc00b6ee49f350627faac856850926cc22ff4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 12:33:38 +0200 Subject: [PATCH 14/24] specs/pos: add `min_validator_stake` param and redefine validator sets --- .../explore/design/ledger/pos-integration.md | 3 +- .../proof-of-stake/bonding-mechanism.md | 72 +++++++++++-------- .../proof-of-stake/cubic-slashing.md | 2 +- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 83ddb50e7a..2402ebb84d 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -23,7 +23,8 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` - `unbond/{unbond_source}/{unbond_validator} (optional)` -- `validator_set (required)` +- `validator_set/consensus (required)`: up to `max_validator_slots` (parameter) validators, ordered by their voting power +- `validator_set/below_capacity (required)`: validators below consensus capacity, but above the `min_validator_stake` parameter, also ordered by their voting power - `total_deltas (required)` - standard validator metadata (these are regular storage values, not epoched data): diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index e41cdbab58..696e69d1da 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -9,9 +9,9 @@ An epoch is a range of blocks or time that is defined by the base ledger and mad Epoched data is data associated with a specific epoch that is set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: -- [System parameters](#system-parameters). A single value for each epoch. -- [Active validator set](#active-validator-set). A single value for each epoch. -- Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. +- [System parameters](#system-parameters). Discrete values for each epoch in which the parameters have changed. +- [Validator sets](#validator-sets). Discrete values for each epoch. +- Total voting power. A sum of all validators' voting power, excluding jailed validators. A delta value for each epoch. - [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. - [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. @@ -56,9 +56,17 @@ For each validator (in any state), the system also tracks total bonded tokens as - *change commission rate*: Set the new commission rate. When applied in epoch `n`, the new value will be set for epoch `n + pipeline_length`. The commission rate change must be within the `max_commission_rate_change` limit set by the validator. -#### Active validator set +#### Validator sets -From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_validator_slots` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. +A *candidate* validator that is not jailed (see [slashing](#slashing)) can be in one of the three sets: + +- `consensus` - consensus validator set, capacity limited by the `max_validator_slots` [parameter](#system-parameters) +- `below_capacity` - validators below consensus capacity, but above the threshold set by `min_validator_stake` [parameter](#system-parameters) +- `below_threshold` - validators with stake below `min_validator_stake` [parameter](#system-parameters) + +From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_validator_slots` [parameter](#system-parameters) are selected for the `consensus` validator set. Whenever stake of a validator is changed, the validator sets must be updated at the appropriate offset matching the stake update. + +The limit on `min_validator_stake` [parameter](#system-parameters) is introduced, because the protocol needs to iterate through the validator sets in order to copy the last known state into a new epoch when epoch changes (to avoid offloading this cost to a transaction that is unlucky enough to be the first one to update the validator set(s) in some new epoch) and also to [distribute rewards](./reward-distribution.md) to `consensus` validators and to record unchanged validator products for validators `below_capacity`, who do not receive rewards in the current epoch. ### Delegator @@ -93,12 +101,6 @@ An "unbond" with epoch set to `n` may be withdrawn by the bond's source address Note that unlike bonding and unbonding where token changes are delayed to some future epochs (pipeline or unbonding offset), the token withdrawal applies immediately. This because when the tokens are withdrawable, they are already "unlocked" from the PoS system and do not contribute to voting power. -### Staking rewards - -Until we have programmable validity predicates, rewards can use the mechanism outlined in the [F1 paper](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf), but it should use the exponential model, so that withdrawing rewards more frequently provides no additional benefit (this is a design constraint we should follow in general, we don't want to accidentally encourage transaction spam). This should be written in a way that allows for a natural upgrade to a validator-customisable rewards model (defaulting to this one) if possible. - -To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. - ### Slashing An important part of the security model of Namada is based on making attacking the system very expensive. To this end, the validator who has bonded stake will be slashed once an offense has been detected. @@ -134,7 +136,8 @@ The invariant is that the sum of amounts that may be withdrawn from a misbehavin The default values that are relative to epoch duration assume that an epoch last about 24 hours. -- `max_validator_slots`: Maximum active validators, default `128` +- `max_validator_slots`: Maximum consensus validators, default `128` +- `min_validator_stake`: Minimum stake of a validator that allows the validator to enter the `consensus` or `below_capacity` [sets](#validator-sets), in number of native tokens. Because the [inflation system](../inflation-system.md#proof-of-stake-rewards) targets a bonding ratio of 2/3, the minimum should be somewhere around `total_supply * 2/3 / max_validator_slots`, but it can and should be much lower to lower the entry cost, as long as it's enough to prevent validation account creation spam that could slow down PoS system update on epoch change - `pipeline_len`: Pipeline length in number of epochs, default `2` (see ) - `unboding_len`: Unbonding duration in number of epochs, default `6` - `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) @@ -175,10 +178,9 @@ struct Epoched { } ``` -Note that not all epochs will have data set, only the ones in which some changes occurred. The only exception to this is validator sets, which are written on a new epoch from the latest state into the new epoch by the protocol. This so that a -transaction never has to update the whole validator set when it hasn't changed yet in the current epoch, which would require a copy of the last epoch data and that copy would additionally have to be verified by the PoS validity predicate. +Note that not all epochs will have data set, only the ones in which some changes occurred. The only exception to this are the `consensus` and `below_capacity` validator sets, which are written on a new epoch from the latest state into the new epoch by the protocol. This is so that a transaction never has to update the whole validator set when it hasn't changed yet in the current epoch, which would require a copy of the last epoch data and that copy would additionally have to be verified by the PoS validity predicate. -To try to look-up a value for `Epoched` data with discrete values in each epoch (such as the active validator set) in the current epoch `n`: +To try to look-up a value for `Epoched` data with discrete values in each epoch (such as the consensus validator set) in the current epoch `n`: 1. read the `data` field at epoch `n`: 1. if there's a value at `n` return it @@ -212,7 +214,11 @@ To update a value in `Epoched` data with delta values in epoch `n` with value `d The invariants for updates in both cases are that `m >= n` (epoched data cannot be updated in an epoch lower than the current epoch) and `m - n <= LENGTH - past_epochs_to_store` (epoched data can only be updated at the future-most epoch set by the `LENGTH - past_epochs_to_store` of the data). -We store all the active and inactive validators in two separate sets, ordered by their voting power. Conceptually, this may look like this: +We store the `consensus` validators and validators `below_capacity` in two set, ordered by their voting power. We don't have to store the validators `below_threshold` in a set, because we don't need to know their order. + +Note that we still need to store `below_capacity` set in order of their voting power, because when e.g. one of the `consensus` validator's voting power drops below that of a maximum `below_capacity` validator, we need to know which validator to swap in into the `consensus` set. The protocol new epoch update just disregards validators who are not in `consensus` or `below_capacity` sets as `below_threshold` validators and so iteration on unbounded size is avoided. Instead the size of the validator set that is regarded for PoS rewards can be adjusted by the `min_validator_stake` parameter via governance. + +Conceptually, this may look like this: ```rust,ignore type VotingPower = u64; @@ -229,30 +235,34 @@ struct WeightedValidator { struct ValidatorSet { /// Active validator set with maximum size equal to `max_validator_slots` - active: BTreeSet, - /// All the other validators that are not active - inactive: BTreeSet, + consensus: BTreeSet, + /// Other validators that are not in `consensus`, but have stake above `min_validator_stake` + below_threshold: BTreeSet, } type ValidatorSets = Epoched; -/// The sum of all active and inactive validators' voting power +/// The sum of all validators voting power (including `below_threshold`) type TotalVotingPower = Epoched; ``` -When any validator's voting power changes, we attempt to perform the following update on the `ActiveValidatorSet`: +When any validator's voting power changes, we attempt to perform the following update on the `ValidatorSet`: 1. let `validator` be the validator's address, `power_before` and `power_after` be the voting power before and after the change, respectively +1. find if the `power_before` and `power_after` are above the `min_validator_stake` theshold + 1. if they're both below the threshold, nothing else needs to be done 1. let `power_delta = power_after - power_before` -1. let `min_active = active.first()` (active validator with lowest voting power) -1. let `max_inactive = inactive.last()` (inactive validator with greatest voting power) -1. find whether the validator is active, let `is_active = power_before >= max_inactive.voting_power` - 1. if `is_active`: - 1. if `power_delta > 0 && power_after > max_inactive.voting_power`, update the validator in `active` set with `voting_power = power_after` - 1. else, remove the validator from `active`, insert it into `inactive` and remove `max_inactive.address` from `inactive` and insert it into `active` - 1. else (`!is_active`): - 1. if `power_delta < 0 && power_after < min_active.voting_power`, update the validator in `inactive` set with `voting_power = power_after` - 1. else, remove the validator from `inactive`, insert it into `active` and remove `min_active.address` from `active` and insert it into `inactive` +1. let `min_consensus = consensus.first()` (consensus validator with lowest voting power) +1. let `max_below_capacity = below_capacity.last()` (below_capacity validator with greatest voting power) +1. find whether the validator was in consensus set, let `was_in_consensus = power_before >= max_below_capacity.voting_power` + 1. if `was_in_consensus`: + 1. if `power_after >= max_below_capacity.voting_power`, update the validator in `consensus` set with `voting_power = power_after` + 1. else if `power_after < min_validator_stake`, remove the validator from `consensus`, insert the `max_below_capacity.address` validator into `consensus` and remove `max_below_capacity.address` from `below_capacity` + 1. else, remove the validator from `consensus`, insert it into `below_capacity` and remove `max_below_capacity.address` from `below_capacity` and insert it into `consensus` + 1. else (`!was_in_consensus`): + 1. if `power_after <= min_consensus.voting_power`, update the validator in `below_capacity` set with `voting_power = power_after` + 1. else if `power_after < min_validator_stake`, remove the validator from `below_capacity` + 1. else, remove the validator from `below_capacity`, insert it into `consensus` and remove `min_consensus.address` from `consensus` and insert it into `below_capacity` Within each validator's address space, we store public consensus key, state, total bonded token amount, total unbonded token amount (needed for applying of slashes) and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): @@ -313,4 +323,4 @@ struct Slash { An initial validator set with self-bonded token amounts must be given on system initialization. -This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. +This set is used to initialize the state on the first epoch (naturally, there is no pipeline offset on genesis validators). diff --git a/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md b/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md index 4d4ac07523..5406301d36 100644 --- a/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md +++ b/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md @@ -29,6 +29,6 @@ calculateSlashRate slashes = 4. Update the validators' stored voting power appropriately. 5. Delegations to the validator can now be redelegated / start unbonding / etc. -Validator can later submit a transaction to unjail themselves after a configurable period. When the transaction is applied and accepted, the validator updates its state to "candidate" and is added back to the validator set starting at the epoch at pipeline offset (active or inactive, depending on its voting power). +Validator can later submit a transaction to unjail themselves after a configurable period. When the transaction is applied and accepted, the validator updates its state to "candidate" and is added back to the validator set starting at the epoch at pipeline offset (into `consensus`, `below_capacity` or `below_threshold` set, depending on its voting power). At present, funds slashed are sent to the governance treasury. In the future we could potentially reward the slash discoverer with part of the slash, for which some sort of commit-reveal mechanism will be required to prevent front-running. From 8ad61ec90ef2d89fec744214f12dbf6d3814b2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 15 Sep 2022 17:12:53 +0200 Subject: [PATCH 15/24] spec/pos/bonding: fix missing case and reward products handling Added logic for reward products handing when validator's voting power changes to cross from or into `below_threshold` validator set. --- .../src/explore/design/ledger/pos-integration.md | 1 + .../economics/proof-of-stake/bonding-mechanism.md | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 2402ebb84d..e063ac3ad8 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -18,6 +18,7 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely + - `validator/{validator_address}/last_known_product_epoch`: optionally set when a validator crosses from `consensus` or `below_capacity` validator set into `below_threshold` set, at which point the protocol will stop updating their rewards products during epoch change - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to be epoched data, just an accumulator of the total - `validator/{validator_address}/commission_rate`: a validator-chosen commission rate that is some fraction of the delegation rewards charged by this validator - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 696e69d1da..98ad8b7d99 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -249,20 +249,30 @@ type TotalVotingPower = Epoched; When any validator's voting power changes, we attempt to perform the following update on the `ValidatorSet`: 1. let `validator` be the validator's address, `power_before` and `power_after` be the voting power before and after the change, respectively -1. find if the `power_before` and `power_after` are above the `min_validator_stake` theshold +1. find if the `power_before` and `power_after` are above the `min_validator_stake` threshold 1. if they're both below the threshold, nothing else needs to be done 1. let `power_delta = power_after - power_before` 1. let `min_consensus = consensus.first()` (consensus validator with lowest voting power) 1. let `max_below_capacity = below_capacity.last()` (below_capacity validator with greatest voting power) 1. find whether the validator was in consensus set, let `was_in_consensus = power_before >= max_below_capacity.voting_power` +1. find whether the validator was in below capacity set, let `was_below_capacity = power_before > min_validator_stake` 1. if `was_in_consensus`: 1. if `power_after >= max_below_capacity.voting_power`, update the validator in `consensus` set with `voting_power = power_after` 1. else if `power_after < min_validator_stake`, remove the validator from `consensus`, insert the `max_below_capacity.address` validator into `consensus` and remove `max_below_capacity.address` from `below_capacity` 1. else, remove the validator from `consensus`, insert it into `below_capacity` and remove `max_below_capacity.address` from `below_capacity` and insert it into `consensus` - 1. else (`!was_in_consensus`): + 1. else if `was_below_capacity`: 1. if `power_after <= min_consensus.voting_power`, update the validator in `below_capacity` set with `voting_power = power_after` 1. else if `power_after < min_validator_stake`, remove the validator from `below_capacity` 1. else, remove the validator from `below_capacity`, insert it into `consensus` and remove `min_consensus.address` from `consensus` and insert it into `below_capacity` + 1. else (if validator was below minimum stake): + 1. if `power_after > min_consensus.voting_power`, remove the `min_consensus.address` from `consensus`, insert the `min_consensus.address` into `below_capacity` and insert the validator in `consensus` set with `voting_power = power_after` + 1. else if `power_after >= min_validator_stake`, insert the validator into `below_capacity` set with `voting_power = power_after` + 1. else, do nothing + +Additionally, for [rewards distribution](./reward-distribution.md): + +- When a validator moves from `below_threshold` set to either `below_capacity` or `consensus` set, the transaction must also fill in the validator's reward products from its last known value, if any, in all epochs starting from their `last_known_product_epoch` (exclusive) up to the `current_epoch + pipeline_len - 1` (inclusive) in order to make their look-up cost constant (assuming that validator's stake can only be increased at `pipeline_len` offset). +- And on the opposite side, when a stake of a validator from `consensus` or `below_capacity` drops below `min_validator_stake`, we record their `last_known_product_epoch`, so that it can be used if and when the validator's stake goes above `min_validator_stake`. Within each validator's address space, we store public consensus key, state, total bonded token amount, total unbonded token amount (needed for applying of slashes) and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): From 9c123988ab57297911af4053672d0dce2c651a06 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 21 Sep 2022 01:05:49 -0400 Subject: [PATCH 16/24] edit pos-integration and reward-distribution --- .../explore/design/ledger/pos-integration.md | 31 +++++++++---------- .../proof-of-stake/reward-distribution.md | 28 ++++++++--------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index e063ac3ad8..3174795b5f 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -1,10 +1,10 @@ # PoS integration -The [PoS system](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html) is integrated into Namada ledger at 3 different layers: +The [PoS system](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html) is integrated into the Namada ledger at 3 different layers: -- base ledger that performs genesis initialization, validator set updates on new epoch and applies slashes when they are received from ABCI +- a base ledger that performs the genesis initialization, validator set updates on a new epoch, and applies slashes when they are received from ABCI - an account with an internal address and a [native VP](vp.md#native-vps) that validates any changes applied by transactions to the PoS account state -- transaction WASMs to perform various PoS actions, also available as a library code for custom made transactions +- transaction WASMs that perform various PoS actions, also available as a library code for custom made transactions The `votes_per_token` PoS system parameter must be chosen to satisfy the [Tendermint requirement](https://github.com/tendermint/spec/blob/60395941214439339cc60040944c67893b5f8145/spec/abci/apps.md#validator-updates) of `MaxTotalVotingPower = MaxInt64 / 8`. @@ -14,7 +14,7 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - for any validator, all the following fields are required: - `validator/{validator_address}/consensus_key` - `validator/{validator_address}/state` - - `validator/{validator_address}/deltas`: the change in the amount of self-bonds and delegations to this validator per epoch, may contain negative delta values when unbonding + - `validator/{validator_address}/deltas`: the change in the amount of self-bonds and delegations to this validator per epoch, may contain negative delta values due to unbonding - `validator/{validator_address}/total_unbonded`: sum of unbonded bonds from this validator, needed to determine the amount slashed in each epoch that it affects when a slash is applied - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely @@ -24,8 +24,8 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate - `bond/{bond_source}/{bond_validator} (optional)` - `unbond/{unbond_source}/{unbond_validator} (optional)` -- `validator_set/consensus (required)`: up to `max_validator_slots` (parameter) validators, ordered by their voting power -- `validator_set/below_capacity (required)`: validators below consensus capacity, but above the `min_validator_stake` parameter, also ordered by their voting power +- `validator_set/consensus (required)`: the set of up to `max_validator_slots` (parameter) validators, ordered by their bonded stake +- `validator_set/below_capacity (required)`: the set of validators below consensus capacity, but with bonded stake above the `min_validator_stake` parameter, also ordered by their bonded stake - `total_deltas (required)` - standard validator metadata (these are regular storage values, not epoched data): @@ -59,8 +59,8 @@ The validator transactions are assumed to be applied with an account address `va - `reactivate`: - sets `validator/{validator_address}/state` to `candidate` in epoch `n + pipeline_length` - `self_bond(amount)`: - - let `bond = read(bond/{validator_address}/{validator_address}/delta)` - - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` + - let `bond = read(bond/{validator_address}/{validator_address}/deltas)` + - if `bond` exists, update it with the new bond amount in epoch `n + pipeline_length` - else, create a new record with bond amount in epoch `n + pipeline_length` - debit the token `amount` from the `validator_address` and credit it to the PoS account - add the `amount` to `validator/{validator_address}/deltas` in epoch `n + pipeline_length` @@ -136,15 +136,14 @@ For `delegate`, `undelegate`, `redelegate` and `withdraw_unbonds` the transactio Evidence for byzantine behaviour is received from Tendermint ABCI on `BeginBlock`. For each evidence: - append the `evidence` into `slash/{evidence.validator_address}` -- calculate the slashed amount from deltas in and before the `evidence.epoch` in `validator/{validator_address}/total_deltas` for the `evidence.validator_address` and the slash rate -- deduct the slashed amount from the `validator/{validator_address}/total_deltas` at `pipeline_length` offset -- update the `validator/{validator_address}/voting_power` for the `evidence.validator_address` in and after epoch `n + pipeline_length` -- update the `total_voting_power` in and after epoch `n + pipeline_length` +- calculate the slashed amount from the deltas in and before the `evidence.epoch` in `validator/{validator_address}/deltas` for the `evidence.validator_address` and the slash rate +- deduct the slashed amount from the `validator/{validator_address}/deltas` at epoch `n + pipeline_length` +- update the `total_deltas` in epoch `n + pipeline_length` - update `validator_set` in and after epoch `n + pipeline_length` ## Validity predicate -In the following description, "pre-state" is the state prior to transaction execution and "post-state" is the state posterior to it. +In the following description, "pre-state" is the state prior to transaction execution and "post-state" is the state after execution. Any changes to PoS epoched data are checked to update the structure as described in [epoched data storage](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage). @@ -153,7 +152,7 @@ Because some key changes are expected to relate to others, the VP also accumulat - `balance_delta: token::Change` - `bond_delta: HashMap` - `unbond_delta: HashMap` -- `total_deltas: HashMap` +- `validator_deltas: HashMap` - `total_stake_by_epoch: HashMap>` - `expected_voting_power_by_epoch: HashMap>`: calculated from the validator's total deltas - `expected_total_voting_power_delta_by_epoch: HashMap`: calculated from the validator's total deltas @@ -207,10 +206,8 @@ The validity predicate triggers a validation logic based on the storage keys mod } ``` -- `validator/{validator_address}/total_deltas`: +- `validator/{validator_address}/deltas`: - find the difference between the pre-state and post-state values and add it to the `total_deltas` accumulator and update `total_stake_by_epoch`, `expected_voting_power_by_epoch` and `expected_total_voting_power_delta_by_epoch` -- `validator/{validator_address}/voting_power`: - - find the difference between the pre-state and post-state value and insert it into the `voting_power_by_epoch` accumulator - `validator/{validator_address}/validator_rewards_product`: cannot be changed by a transaction, updated by the protocol - `validator/{validator_address}/delegation_rewards_product`: cannot be changed by a transaction, updated by the protocol - `validator/{validator_address}/commissions`: cannot be changed by a transaction, updated by the protocol diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 32bff07c39..9cd3bda727 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -2,21 +2,21 @@ Namada uses the automatically-compounding variant of [F1 fee distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf). -Rewards are given to validators for voting on finalizing blocks: the fund for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how much is staked and our desired yearly inflation. When the total of the tokens staked is very low, the return rate per validator needs to increase, but as the total amount of stake rises, validators will receive less rewards. Once we have acquired the desired stake percentage, the amount minted will just be the desired yearly inflation. +Rewards are given to validators for proposing blocks, for voting on finalizing blocks, and for being in the [consensus validator set](pos-integration.md): the funds for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how many staking tokens are locked (staked) and some maximum annual inflation rate. The rewards mechanism is implemented as a [PD controller](../inflation-system.md#detailed-inflation-calculation-model) that dynamically adjusts the inflation rate to achieve a target staking token ratio. When the total fraction of tokens staked is very low, the return rate per validator needs to increase, but as the total fraction of stake rises, validators will receive fewer rewards. Once the desired staking fraction is achieved, the amount minted will just be the desired annual inflation. -The validator and the delegator must have agreed on a commission rate between themselves. Delegators pay out rewards to validators based on a mutually-determined commission rate that both parties must have agreed upon beforehand. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once we have calculated the total that needs to be minted at the end of the epoch, we split the minted tokens according to the stake the relevant validators and delegators contributed and distribute them to validators and their delegators. This is similar to what Cosmos does. +Each delegation to a validator is initiated at an agreed-upon commission rate charged by the validator. Validators pay out rewards to delegators based on this mutually-determined commission rate. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once the protocol determines the total amount of tokens to mint at the end of the epoch, the minted tokens are effectively divided among the relevant validators and delegators according to their proportional stake. In practice, the reward products, which are the fractional increases in staked tokens claimed, are stored for the validators and delegators, and the reward tokens are only transferred to the validator’s or delegator’s account upon withdrawal. This is described in the following sections. The general system is similar to what Cosmos does. ## Basic algorithm Consider a system with - a canonical singular staking unit of account. -- a set of validators $V_i$. -- a set of delegations $D_{i, j}$, each to a particular validator and in a particular (initial) amount. +- a set of validators $\{V_i\}$. +- a set of delegations $\{D_{i, j}\}$, where $i$ indicates the associated validator, each with a particular initial amount. - epoched proof-of-stake, where changes are applied as follows: - - bonding after the pipeline length - - unbonding after the pipeline + unbonding length - - rewards are paid out at the end of each epoch, to wit, in each epoch $e$, $R_{e,i}$ is paid out to validator $V_i$ + - bonding is processed after the pipeline length + - unbonding is processed after the pipeline + unbonding length + - rewards are paid out at the end of each epoch, i.e., in each epoch $e$, a reward $R_{e,i}$ is paid out to validator $V_i$ - slashing is applied as described in [slashing](cubic-slashing.md). We wish to approximate as exactly as possible the following ideal delegator reward distribution system: @@ -30,17 +30,17 @@ where $r_V(e)$ and $s_V(e)$ respectively denote the reward and stake of validato In this system, rewards are automatically rebonded to delegations, increasing the delegation amounts and validator voting powers accordingly. -However, we wish to implement this without actually needing to iterate over all delegations each block, since this is too computationally expensive. We can exploit this constant multiplicative factor $(1 + r_V(e) / s_V(e))$ which does not vary per delegation to perform this calculation lazily, storing only a constant amount of data per validator per epoch, and calculate revised amounts for each individual delegation only when a delegation changes. +However, we wish to implement this without actually needing to iterate over all delegations each block, since this is too computationally expensive. We can exploit this constant multiplicative factor $(1 + r_V(e) / s_V(e))$, which does not vary per delegation, to perform this calculation lazily. In this lazy method, only a constant amount of data per validator per epoch is stored, and revised amounts are calculated for each individual delegation only when a delegation changes. We will demonstrate this for a delegation $D$ to a validator $V$. Let $s_D(e)$ denote the stake of $D$ at epoch $e$. -For two epochs $m$ and $n$ with $m Date: Wed, 21 Sep 2022 01:49:57 -0400 Subject: [PATCH 17/24] WIP: reward distribution per block based on different block behaviors --- .../proof-of-stake/reward-distribution.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 9cd3bda727..fae83f5ab5 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -150,6 +150,49 @@ The commission rate $c_V(e)$ is the same for all delegations to a validator $V$ While rewards are given out at the end of every epoch, voting power is only updated after the pipeline offset. According to the [proof-of-stake system](bonding-mechanism.md#epoched-data), at the current epoch `e`, the validator sets an only be updated for epoch `e + pipeline_offset`, and it should remain unchanged from epoch `e` to `e + pipeline_offset - 1`. Updating voting power in the current epoch would violate this rule. + +## Distribution to validators + +A validator can earn a portion of the block rewards in three different ways: + +- Proposing the block +- Providing a signature on the constructed block (voting) +- Being a member of the consensus validator set + +The reward mechanism calculates fractions of the total block reward that are given for the above-mentioned three behaviors, such that + +$$ R_p + R_s + R_b = 1, $$ + +where $R_p$ is the proposer reward fraction, $R_s$ is the reward fraction for the set of signers, and $R_b$ is the reward fraction for the whole active validator set. + +The reward for proposing a block is dependent on the combined voting power of all validators whose signatures are included in the block. This is to incentivize the block proposer to maximize the inclusion of signatures, as blocks with more signatures are (JUSTIFY THIS POINT HERE). + +The block proposer reward is parameterized as + +$$ R_p = r_p\Big(f - \frac{2}{3}\Big) + 0.01, $$ + +where $f$ is the ratio of the combined stake of all block signers to the combined stake of all consensus validators. The value of $f$ is bounded from below at 2/3, since a block requires this amount of signing stake to be verified. We currently enforce that the block proposer reward is a minimum of 1%. + +The block signer reward for a validator $V_i$ is parameterized as + +$$ R_s^i = r_s \frac{s_i}{s_{sign}} = r_s \frac{s_i}{fs_{tot}}, $$ + +where $s_i$ is the stake of validator $V_i$, $s_{sign} is the combined stake of all signers, and $s_{tot} is the combined stake of all consensus validators. + +Finally, the remaining reward just for being in the consensus validator set is parameterized as + +$$ R_b^i = (1 - R_p - R_s) \frac{s_i}{s_{tot}}. $$ + +The values of the parameters $r_p$ and $r_s$ are set in the proof-of-stake storage and can only change via governance. The values are chosen relative to each other such that a block proposer is always incentivized to include as much signing stake as possible. These values at genesis are currently: + +- $r_s = 0.1$ +- $r_p = 0.125$ + +TODO describe / figure out: + +- how reward products will be stored +- how reward fractions will be properly stored for all blocks in an epoch before the inflation rate is determined at the end of the epoch + ## Slashes Slashes should lead to punishment for delegators who were contributing voting power to the validator at the height of the infraction, _as if_ the delegations were iterated over and slashed individually. From 5626b57b73cfd3c957757ba034363a9dac237209 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 22 Sep 2022 11:28:20 -0400 Subject: [PATCH 18/24] describe reward products and block reward accumulator --- .../proof-of-stake/reward-distribution.md | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index fae83f5ab5..9f0d7bbb9b 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -34,7 +34,7 @@ However, we wish to implement this without actually needing to iterate over all We will demonstrate this for a delegation $D$ to a validator $V$. Let $s_D(e)$ denote the stake of $D$ at epoch $e$. -For two epochs $m$ and $n$ with $m Date: Thu, 22 Sep 2022 15:09:48 -0400 Subject: [PATCH 19/24] Fix bugs and update accumulator description --- .../src/economics/proof-of-stake/reward-distribution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 9f0d7bbb9b..23cd2dec1f 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -189,16 +189,16 @@ $$ R_b^i = (1 - R_p - R_s) \frac{s_i}{s_{cons}}. $$ Thus, as an example, the total fraction of the block reward for the proposer (assuming they include their own signature in the block) would be: -$$ R_{prop} = r_p\Big(f - \frac{2}{3}\Big) + 0.01 + r_s \frac{s_i}{fs_{cons}} + \Big(1 - r_p\Big(f - \frac{2}{3}\Big) - r_s\Big) \frac{s_i}{s_{cons}}. $$ +$$ R_{prop} = r_p\Big(f - \frac{2}{3}\Big) + 0.01 + r_s \frac{s_i}{fs_{cons}} + \Big(1 - r_p\Big(f - \frac{2}{3}\Big) -0.01 - r_s\Big) \frac{s_i}{s_{cons}}. $$ The values of the parameters $r_p$ and $r_s$ are set in the proof-of-stake storage and can only change via governance. The values are chosen relative to each other such that a block proposer is always incentivized to include as much signing stake as possible. These values at genesis are currently: - $r_s = 0.1$ - $r_p = 0.125$ -These rewards must be determined for every single block, but the inflationary token rewards are only minted at the end of an epoch. Thus, the rewards products are only updated at the end of an epoch as well. In order to maintain a record of the block rewards over the course of an epoch, a reward fraction accumulator is held in a storage key `#{PoS}/validator/{validator_address}/rewards_accumulator` (TODO: THINK ABT THIS!) for each consensus validator. +These rewards must be determined for every single block, but the inflationary token rewards are only minted at the end of an epoch. Thus, the rewards products are only updated at the end of an epoch as well. -When finalizing each block, the accumulator for each consensus validator is incremented with the fraction of that block's reward owed to the validator. At the end of the epoch when the rewards products are updated, the accumulator value is divided by the number of blocks in that epoch, which yields the fraction of the newly minted inflation tokens owed to the validator. The next entry of the rewards products for each validator can then be created. The accumulator values are then reset to 0 for every validator in preparation for the next epoch. +In order to maintain a record of the block rewards over the course of an epoch, a reward fraction accumulator is implemented as a `Map` and held in the storage key `#{PoS}/validator_set/consensus/rewards_accumulator`. When finalizing each block, the accumulator value for each consensus validator is incremented with the fraction of that block's reward owed to the validator. At the end of the epoch when the rewards products are updated, the accumulator value is divided by the number of blocks in that epoch, which yields the fraction of the newly minted inflation tokens owed to the validator. The next entry of the rewards products for each validator can then be created. The map is then reset to be empty in preparation for the next epoch and consensus validator set. TODO describe / figure out: From d853bcc0daf31b05bc1939ef6c412ccb33209512 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 29 Sep 2022 17:26:07 -0400 Subject: [PATCH 20/24] finish change_commission_rate tx --- .../dev/src/explore/design/ledger/pos-integration.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 3174795b5f..a6d6169d1a 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -88,8 +88,14 @@ The validator transactions are assumed to be applied with an account address `va - burn the slashed tokens (`amount - amount_after_slash`), if not zero - `change_consensus_key`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` -- `change_commission_rate`: - - creates a record in `validator/{validator_address}/commission_rate` in epoch `n + pipeline_length` +- `change_commission_rate(new_rate)`: + - let `max_change = read(validator/{validator_address}/max_commission_rate_change)` + - let `rates = read(validator/{validator_address}/commission_rate)` + - let `rate_at_pipeline = rates[n + pipeline_length]` + - let `rate_before_pipeline = rates[n + pipeline_length - 1]` + - if `new_rate = rate_at_pipeline` or `new_rate < 0`, panic + - if `abs(new_rate - rate_before_pipeline) > max_change`, panic + - update `validator/{validator_address}/commission_rate` with `new_rate` at epochs `n + pipeline_length` and beyond For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator`, `change_consensus_key`, and `change_commission_rate`, the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key`, we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. From 265dede2660db10a2247b016aa6bfd78ead1575d Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 29 Sep 2022 17:27:46 -0400 Subject: [PATCH 21/24] update storage key section and validator bonds --- .../explore/design/ledger/pos-integration.md | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index a6d6169d1a..37b0bb34f2 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -10,8 +10,7 @@ The `votes_per_token` PoS system parameter must be chosen to satisfy the [Tender All [the data relevant to the PoS system](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage) are stored under the PoS account's storage sub-space, with the following key schema (the PoS address prefix is omitted for clarity): -- `params` (required): the system parameters -- for any validator, all the following fields are required: +- For any validator, all the following fields are required: - `validator/{validator_address}/consensus_key` - `validator/{validator_address}/state` - `validator/{validator_address}/deltas`: the change in the amount of self-bonds and delegations to this validator per epoch, may contain negative delta values due to unbonding @@ -21,17 +20,20 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `validator/{validator_address}/last_known_product_epoch`: optionally set when a validator crosses from `consensus` or `below_capacity` validator set into `below_threshold` set, at which point the protocol will stop updating their rewards products during epoch change - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to be epoched data, just an accumulator of the total - `validator/{validator_address}/commission_rate`: a validator-chosen commission rate that is some fraction of the delegation rewards charged by this validator -- `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate -- `bond/{bond_source}/{bond_validator} (optional)` -- `unbond/{unbond_source}/{unbond_validator} (optional)` -- `validator_set/consensus (required)`: the set of up to `max_validator_slots` (parameter) validators, ordered by their bonded stake -- `validator_set/below_capacity (required)`: the set of validators below consensus capacity, but with bonded stake above the `min_validator_stake` parameter, also ordered by their bonded stake -- `total_deltas (required)` - -- standard validator metadata (these are regular storage values, not epoched data): - - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash - `validator/{validator_address}/max_commission_rate_change` (required): a validator-chosen maximum commission rate change per epoch, set when the validator account is created and cannot be changed - - TBA (e.g. alias, website, description, etc.) + - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash +- For the validator sets, there are several required storage sub-keys: + - `validator_set/consensus/rewards_accumulator` (required): a map of the current epoch's consensus validator addresses to their individual rewards fractions accumulated over all blocks seen in the current epoch. This is reset to an empty map at the very end of each epoch. + - `validator_set/consensus/consensus_set` (required): the set of up to `max_validator_slots` (parameter) validators, ordered by their bonded stake + - `validator_set/below_capacity` (required): the set of validators with bonded stake below that of the top `max_validator_slots` validators, but with bonded stake above the `min_validator_stake` parameter, also ordered by their bonded stake + - `validator_set/below_threshold` (required): the set of validators with bonded stake below the `min_validator_stake` value +- Other PoS storage keys: + - `params` (required): the system parameters + - `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate + - `bond/{bond_source}/{bond_validator}` (optional) + - `unbond/{unbond_source}/{unbond_validator}` (optional) + - `total_deltas (required)` + - (TODO) other standard validator metadata TBA (e.g. alias, website, description, etc.) Only NAM tokens can be staked in bonds. The tokens being staked (amounts in the bonds and unbonds) are kept in the PoS account under `{nam_address}/balance/{pos_address}` until they are withdrawn. @@ -47,7 +49,7 @@ For slashing tokens, we implement a [PoS slash pool account](vp.md#pos-slash-poo ### Validator transactions -The validator transactions are assumed to be applied with an account address `validator_address`. +The validator transactions are assumed to be applied with an account address `validator_address` and the the current epoch `n`. - `become_validator(consensus_key, commission_rate, max_commission_rate_change)`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` @@ -59,25 +61,25 @@ The validator transactions are assumed to be applied with an account address `va - `reactivate`: - sets `validator/{validator_address}/state` to `candidate` in epoch `n + pipeline_length` - `self_bond(amount)`: - - let `bond = read(bond/{validator_address}/{validator_address}/deltas)` + - let `bond = read(bond/{validator_address}/{validator_address})` - if `bond` exists, update it with the new bond amount in epoch `n + pipeline_length` - else, create a new record with bond amount in epoch `n + pipeline_length` - debit the token `amount` from the `validator_address` and credit it to the PoS account - - add the `amount` to `validator/{validator_address}/deltas` in epoch `n + pipeline_length` - - update the `total_deltas` in epoch `n + pipeline_length` + - update the `validator/{validator_address}/deltas` with `amount` in epoch `n + pipeline_length` + - update the `total_deltas` with `amount` in epoch `n + pipeline_length` - update `validator_set` in epoch `n + pipeline_length` - `unbond(amount)`: - - let `bond = read(bond/{validator_address}/{validator_address}/delta)` + - let `bond = read(bond/{validator_address}/{validator_address}/)` - if `bond` doesn't exist, panic - - let `pre_unbond = read(unbond/{validator_address}/{validator_address}/delta)` - - if `total(bond) - total(pre_unbond) < amount`, panic + - let `existing_unbond = read(unbond/{validator_address}/{validator_address}/)` + - if `total(bond) - total(existing_unbond) < amount`, panic - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch at the unbonding offset) until whole `amount` is decremented - for each decremented `bond` value write a new `unbond` in epoch `n + pipeline_length + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + pipeline_length + unbonding_length` - decrement the `amount` from `validator/{validator_address}/deltas` in epoch `n + pipeline_length` - - update the `total_deltas` in epoch `n + pipeline_length` + - decrement the `amount` from `total_deltas` in epoch `n + pipeline_length` - update `validator_set` in epoch `n + pipeline_length` - `withdraw_unbonds`: - - let `unbond = read(unbond/{validator_address}/{validator_address}/delta)` + - let `unbond = read(unbond/{validator_address}/{validator_address}/)` - if `unbond` doesn't exist, panic - if no `unbond` value is found for epochs <= `n`, panic - for each `((bond_start, bond_end), amount) in unbond where unbond.epoch <= n`: @@ -135,7 +137,7 @@ The delegator transactions are assumed to be applied with an account address `de - credit the `amount_after_slash` to the `delegator_address` and debit the whole `amount` (before slash, if any) from the PoS account - burn the slashed tokens (`amount - amount_after_slash`), if not zero -For `delegate`, `undelegate`, `redelegate` and `withdraw_unbonds` the transaction must be signed with the delegator's public key. Note that for `delegate`, signature verification is also performed because there are tokens debited from the delegator's account. +For `delegate`, `undelegate`, `redelegate` and `withdraw_unbonds`, the transaction must be signed with the delegator's public key. Note that for `delegate`, signature verification is also performed because there are tokens debited from the delegator's account. ## Slashing From 29e4f7eae013159fbfe58bace035475a29433819 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 29 Sep 2022 17:28:05 -0400 Subject: [PATCH 22/24] remove validator commissions from storage --- documentation/dev/src/explore/design/ledger/pos-integration.md | 1 - 1 file changed, 1 deletion(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 37b0bb34f2..86e6511577 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -18,7 +18,6 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `validator/{validator_address}/validator_rewards_product`: a rewards product that is used to find the updated amount for self-bonds with staking rewards added to this validator - the past epoched data has to be kept indefinitely - `validator/{validator_address}/delegation_rewards_product`: similar to `validator_rewards_product`, but for delegations with commissions subtracted - the past epoched data also has to be kept indefinitely - `validator/{validator_address}/last_known_product_epoch`: optionally set when a validator crosses from `consensus` or `below_capacity` validator set into `below_threshold` set, at which point the protocol will stop updating their rewards products during epoch change - - `validator/{validator_address}/commissions`: the total amount of delegations commissions collected by this validator - this doesn't need to be epoched data, just an accumulator of the total - `validator/{validator_address}/commission_rate`: a validator-chosen commission rate that is some fraction of the delegation rewards charged by this validator - `validator/{validator_address}/max_commission_rate_change` (required): a validator-chosen maximum commission rate change per epoch, set when the validator account is created and cannot be changed - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash From 615f6844ab085b57aeb09264fbc8943c59be6de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 2 Nov 2022 18:21:12 +0100 Subject: [PATCH 23/24] specs/pos/reward-distribution: update link to validator sets --- .../specs/src/economics/proof-of-stake/reward-distribution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index a5e9a88643..674d125e93 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -2,7 +2,7 @@ Namada uses the automatically-compounding variant of [F1 fee distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/pdf/OASIcs-Tokenomics-2019-10.pdf). -Rewards are given to validators for proposing blocks, for voting on finalizing blocks, and for being in the [consensus validator set](pos-integration.md): the funds for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how many staking tokens are locked (staked) and some maximum annual inflation rate. The rewards mechanism is implemented as a [PD controller](../inflation-system.md#detailed-inflation-calculation-model) that dynamically adjusts the inflation rate to achieve a target staking token ratio. When the total fraction of tokens staked is very low, the return rate per validator needs to increase, but as the total fraction of stake rises, validators will receive fewer rewards. Once the desired staking fraction is achieved, the amount minted will just be the desired annual inflation. +Rewards are given to validators for proposing blocks, for voting on finalizing blocks, and for being in the [consensus validator set](bonding-mechanism.md#validator-sets): the funds for these rewards can come from **minting** (creating new tokens). The amount that is minted depends on how many staking tokens are locked (staked) and some maximum annual inflation rate. The rewards mechanism is implemented as a [PD controller](../inflation-system.md#detailed-inflation-calculation-model) that dynamically adjusts the inflation rate to achieve a target staking token ratio. When the total fraction of tokens staked is very low, the return rate per validator needs to increase, but as the total fraction of stake rises, validators will receive fewer rewards. Once the desired staking fraction is achieved, the amount minted will just be the desired annual inflation. Each delegation to a validator is initiated at an agreed-upon commission rate charged by the validator. Validators pay out rewards to delegators based on this mutually-determined commission rate. The minted rewards are auto-bonded and only transferred when the funds are unbonded. Once the protocol determines the total amount of tokens to mint at the end of the epoch, the minted tokens are effectively divided among the relevant validators and delegators according to their proportional stake. In practice, the reward products, which are the fractional increases in staked tokens claimed, are stored for the validators and delegators, and the reward tokens are only transferred to the validator’s or delegator’s account upon withdrawal. This is described in the following sections. The general system is similar to what Cosmos does. From ef67f2b751cdae4994d63eab3e3a424926a43f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 2 Nov 2022 18:27:44 +0100 Subject: [PATCH 24/24] doc/dev/pos: add little more details about PoS balance and txs --- .../dev/src/explore/design/ledger/pos-integration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index ba0ed4327d..b95726c72a 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -34,7 +34,7 @@ All [the data relevant to the PoS system](https://specs.namada.net/economics/pro - `total_deltas (required)` - (TODO) other standard validator metadata TBA (e.g. alias, website, description, etc.) -Only NAM tokens can be staked in bonds. The tokens being staked (bonds and unbonds amounts) are kept in the PoS account under `{nam_address}/balance/{pos_address}` until they are withdrawn. +Only NAM tokens can be staked in bonds. The tokens being staked (bonds and unbonds amounts) together with accumulated rewards (as they are auto-bonded) are kept in the PoS account under `{nam_address}/balance/{pos_address}` until they are withdrawn. ## Initialization @@ -59,7 +59,7 @@ For slashing tokens, we implement a [PoS slash pool account](vp.md#pos-slash-poo ### Validator transactions -The validator transactions are assumed to be applied with an account address `validator_address` and the the current epoch `n`. +The validator transactions are assumed to be applied with an account address `validator_address` and the current epoch `n` (a transaction can use host environment function to find the current epoch). - `become_validator(consensus_key, commission_rate, max_commission_rate_change)`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length`