vlladin
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # eODS - from a developer's point of view eODS proposes a way to have delegations natively --in protocol-- without causing too much disturbance in the consensus layer. As we moved forward with the specs we've taken into consideration: 1. The cost/benefit ratio of every decision taken. 2. The overhead this will bring to the system. 3. The safety implications of such a feature. 4. The UX improvements this will introduce. I was quite adamant with number 4. I wanted ANYBODY with a wallet and some capital to be able to delegate -- start contributing to the protocol's financial safety, and of course generate some rewards. Delegating should be intuitive, simple, safe and fast. We built the proposal on top of those pillars. ## Prerequisites to becoming a Delegator There should be as little friction as possible between wanting to delegate any amount and actually doing it. No advanced knowledge on how a node is operated, no special CLI's or tools or processes. All you should need is: 1. A wallet / execution address. 2. An amount you want to delegate. ## The nutshell ### Changes We added as little things as possible in the existing EL / CL code. ![eODS nutshell.001](https://hackmd.io/_uploads/S1bw_KC8ex.jpg) #### Execution Layer: 1. Added a `DelegationOperationsContract`. This contract will handle most of the interactions between an execution address and the delegation operations (Delegate, Undelegate, etc). 2. Added a `DepositToDelegateContract`. This contract will handle moving ETH from EL to CL. It will follow the logic and implementation of THE `DepositContract`. #### Consensus Layer: 1. Added the `Delegator` as an entity. We needed this to store the `execution_address` of a delegator and so to be able to track him in the delegation flow. 2. Added the `DelegatedValidator` as a wrapper around the existing Validator - to avoid messing with the existing code too much. This holds the amounts delegated for a specific `Validator`. Many delegators to one Validator wrapped in one entity. 3. Added the `BeaconChainAccounting` widget - a set of methods that are used to keep track of delegated amounts and is also used to settle balances when needed. Of course we had to modify the `BeaconState` container, delegation adds new queues, and some methods in the Consensus Layer have also been modified to accommodate the new features. > Delegating is not a mandatory action. For any existing validator, nothing will change in its normal way of operation unless the node operator decides to enable receiving delegations. ### Delegations - TL/DR ![eODS dev post.002](https://hackmd.io/_uploads/r1vBpTkDxe.jpg) A naive and quick way of explaining delegations would be to say that each execution address that wants to delegate will be assigned a Delegator in CL. That will act like an account where undelegated Gwei will reside. That account can be topped up, withdrawn from, or certain amounts can be delegated/undelegated/redelegated. Delegating/undelegating/redelegating implies moving an amount of Gwei between a Delegator's non delegated balance and a Validator's effective balance. #### 1. Deposit to delegate Using the `DepositToDelegate` contract, deposit some amount into the Delegator. #### 2. Delegate Using the Delegation Operations Contract, delegate an amount to a Validator. This will increase the Validator's effective balance (and its weight). ![eODS dev post.003](https://hackmd.io/_uploads/Byt_aakDxl.jpg) #### 3. Undelegate Using the Delegation Operations Contract, undelegate some amount from a Validator. This will decrease the Validator's effective_balance and reduce its weight. #### 4. Withdraw from delegator Using the Delegation Operations Contract, withdraw some amount from the undelegated balance, and move it to the EL into a wallet. ## Delegation Operations Contract Based on [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) this contract allows an Execution Address to interface with delegations, without the need for a CLI or other tools/flows. ![eODS contract.005](https://hackmd.io/_uploads/rJXztdRLge.jpg) This contract will be executed when a delegation interaction is desired. It supports different actions: - `ACTIVATE_OPERATOR` - used to activate an operator (allow an existing Validator to accept delegations) - `DELEGATE` - used to delegate an amount (move an amount from the non delegated balance of a Delegator to the delegated balance of a Validator) - `UNDELEGATE` - used to undelegate an amount (move an amount from the delegated balance of a Validator to the non delegated balance of a Delegator) - `REDELEGATE` - used to redelegate an amount (move an amount from an Validator's delegated balance to another's Validator's delegated balance) - `WITHDRAW_FROM_DELEGATOR` - used to withdraw an amount (move it from the non delegated balance of a Delegator to a wallet - EL) Every time the contract is invoked it will generate a `DelegationOperationRequest` that is sent to CL via the `ExecutionRequests`, in a list of `DelegationOperationRequest`. ```python class ExecutionRequests(Container): deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] delegation_operations: List[DelegationOperationRequest, MAX_DELEGATION_OPERATIONS_REQUESTS_PER_PAYLOAD] # <== here ``` ```python class DelegationOperationRequest(Container): type: Bytes1 source_pubkey: BLSPubkey target_pubkey: BLSPubkey amount: Gwei execution_address: ExecutionAddress ``` The `DelegationOperationRequest` is a generic data wrapper used only to transport data from EL to CL. The data is unbundled and parsed in the consensus layer using the `type` parameter present in each `DelegationOperationRequest`. For example, if `type=UNDELEGATE_REQUEST_TYPE`, the parser will create a `PendingUndelegateRequest`, fill the data it needs from the `DelegationOperationRequest`, and queue it in the state. ```python class PendingUndelegateRequest(Container): execution_address: ExecutionAddress validator_pubkey: BLSPubkey amount: Gwei ``` The method that handles the unbundling is: ```python def process_delegation_operation_request(state: BeaconState, delegation_operation_request: DelegationOperationRequest) -> None: if delegation_operation_request.type == ACTIVATE_OPERATOR_REQUEST_TYPE: state.pending_operator_activations.append(PendingActivateOperator( validator_pubkey=delegation_operation_request.target_pubkey, execution_address=delegation_operation_request.execution_address, fee_quotient=delegation_operation_request.fee_quotient )) elif delegation_operation_request.type == DEPOSIT_TO_DELEGATE_REQUEST_TYPE: state.pending_deposits_to_delegate.append(PendingDepositToDelegate( execution_address=delegation_operation_request.execution_address, amount=delegation_operation_request.amount )) elif delegation_operation_request.type == DELEGATE_REQUEST_TYPE: state.pending_delegations.append(PendingDelegateRequest( validator_pubkey=delegation_operation_request.target_pubkey, execution_address=delegation_operation_request.execution_address, amount=delegation_operation_request.amount, slot=state.slot )) ... etc, handles more types ``` This method ONLY converts a `DelegationOperationRequest` to its proper CL counterpart, and queues that into its own buffer type in the state. #### To sum up: - an `execution address` via a contract sends a bunch of `DelegationOperationRequests` -- with different types -- in `execution_requests.delegation_operations` - the `delegation_operations` is parsed in its entirety, for each block by `process_delegation_operation_request` - each parsed request goes into the state, in its buffer. It waits there to be picked up and used later. The buffers from the state accumulate and drain in a predictable manner, using the timing described below: #### Timing: - Adding to buffers - ACTIVATE_OPERATOR: `on block` - DELEGATE: `on block` - UNDELEGATE: `on block` - REDELEGATE: `on block` - DEPOSIT_TO_DELEGATE: `on block` - WITHDRAW_FROM_DELEGATOR: `on block` - Draining from buffers - ACTIVATE_OPERATOR: `on epoch` - DELEGATE: `on epoch` - UNDELEGATE: `on epoch` - REDELEGATE: `on epoch` - DEPOSIT_TO_DELEGATE: `on epoch` - WITHDRAW_FROM_DELEGATOR: `on block` <== !! With **one exception**, on each block we queue up requests in the buffers, and on each epoch we consume the buffers (practically emptying them). The how and why will be explained below. ## Containers eODS adds a few containers and modifies a few containers. Most noteworthy are: ### Modified BeaconState ```python class BeaconState(Container): # [...BeaconState] # The unique index used for withdrawals next_withdrawal_from_delegators_index: WithdrawalIndex # The list of Delegators delegators: List[Delegator, DELEGATOR_REGISTRY_LIMIT] # The list of non delegated balances. The index is parallel with `delegators` delegators_balances: List[Gwei, DELEGATOR_REGISTRY_LIMIT] # The list of DelegatedValidators, the wrappers on top of Validators that enable delegations delegated_validators: List[DelegatedValidator, VALIDATOR_REGISTRY_LIMIT] # The Delegation operations requests buffers pending_operator_activations: List[PendingActivateOperator, PENDING_DELEGATION_OPERATIONS_LIMIT] pending_deposits_to_delegate: List[PendingDepositToDelegate, PENDING_DELEGATION_OPERATIONS_LIMIT] pending_delegations: List[PendingDelegateRequest, PENDING_DELEGATION_OPERATIONS_LIMIT] pending_undelegations: List[PendingUndelegateRequest, PENDING_DELEGATION_OPERATIONS_LIMIT] pending_redelegations: List[PendingRedelegateRequest, PENDING_DELEGATION_OPERATIONS_LIMIT] pending_withdrawals_from_delegators: List[PendingWithdrawFromDelegatorRequest, PENDING_DELEGATION_OPERATIONS_LIMIT] # Delegation exit queue delegation_exit_queue: List[DelegationExitItem, PENDING_DELEGATION_OPERATIONS_LIMIT] ``` ### Modified Validator ```python class Validator(Container): # [...Validator] # This is a flag used to determine if a Validator accepts delegations is_operator: boolean ``` ### Added Delegator ```python class Delegator(Container): # The execution address bound to this Delegator execution_address: ExecutionAddress # The epoch in which this Delegator was activated. Used to calculate the withdrawal cooldown delegator_entry_epoch: Epoch ``` ### Added DelegatedValidator In order to avoid excessive changes to Validator, the big changes have been added to a wrapper container. Here we store the delegated balances and quotas. ```python class DelegatedValidator(Container): # The Validator wrapped by this DelegatedValidator validator: Validator # The quota of the validator's own balance, from the total balance (own + delegations) delegated_validator_quota: uint64 # A list of quotas for each delegator that delegated an amount here. This list is parallel with state.delegators delegators_quotas: List[Quota, DELEGATOR_REGISTRY_LIMIT] # A list of amounts for each delegator that delegated an amount here. This list is parallel with state.delegators delegated_balances: List[Gwei, DELEGATOR_REGISTRY_LIMIT] # The sum of all entries in delegated_balances. Used as a performance improvement - so we don't always sum the list total_delegated_balance: Gwei # The fee (as percentage) taken by the Validator from Delegators fee_quotient: uint64 ``` ## Effective Balance, Validator Balance, Delegated Balances and Quotas ![eODS balances.006](https://hackmd.io/_uploads/HJa4qTJPll.jpg) ### Balances For a Validator that is delegated, the `effective_balance` now contains the sum of delegated balances for that Validator. ### Quotas To manage splitting of rewards/penalties/slashings a quota system was introduced in the DelegatedValidator. Assuming that `1` (as in 100%) is the total quota of that DelegatedValidator, it gets split between the Validator's own balance: `state.balances[index]` and the delegations it received. That means that every time the delegator's balance changes or a new delegation/undelegation/redelegation occurs, the quotas change and they must be recalculated. ```python delegated_validator.delegated_validator_quota = (state.balances[validator_index] / (delegated_validator.total_delegated_balance + state.balances[validator_index])) for index in range(len(delegated_validator.delegators_quotas)): delegated_validator.delegators_quotas[index] = delegated_validator.delegated_balances[index] / delegated_validator.total_delegated_balance * (1 - delegated_validator.delegated_validator_quota) ``` We use the `delegated_balances` list to recompute the quotas, when needed. ## Delegation Actions This chapter aims to explain how each action is funneled and processed by the system. It is required to understand how requests are accumulated in their respective buffers in the state (if not already, please read above). ### Activate Operator `type: ACTIVATE_OPERATOR_REQUEST_TYPE` `parsed to: PendingActivateOperator` `queued to: state.pending_operator_activations` `buffered on: block` `drained on: block` ```python class PendingActivateOperator(Container): validator_pubkey: BLSPubkey fee_quotient: uint64 source_address: ExecutionAddress ``` This action is used to enable delegations for a given validator. Requests are drained from the state by: ```python def process_pending_activate_operators(state: BeaconState) -> None: ``` This method checks: - if there exists a validator with given pubkey (`validator_pubkey`) - if the validator is not already an operator - if the validator has the correct credentials (compound) - if the given `source_address` matches the one from the validator's withdraw credentials - if the validator meets the `MIN_OPERATOR_BALANCE` - if the validator is not exiting or slashed Having all these conditions met, delegations can be enabled for this validator: ```python class Validator(Container): pubkey: BLSPubkey withdrawal_credentials: Bytes32 effective_balance: Gwei slashed: boolean activation_eligibility_epoch: Epoch activation_epoch: Epoch exit_epoch: Epoch withdrawable_epoch: Epoch is_operator: boolean <== set this to True ``` Also a new `DelegatedValidator` is created and appended to the state, since this is the container that wraps around a Validator that accepts delegations: ```python validator.is_operator = True delegated_validator = DelegatedValidator( validator=validator, delegated_validator_quota=1, delegators_quotas=[], delegated_balances=[], total_delegated_balance=0, fee_quotient=pending_activation.fee_quotient ) state.delegated_validators.append(delegated_validator) ``` --- ### Deposit to Delegate `type: DEPOSIT_TO_DELEGATE_REQUEST_TYPE` `parsed to: PendingDepositToDelegate` `queued to: state.pending_deposits_to_delegate` `buffered on: block` `drained on: epoch` ```python class PendingDepositToDelegate(Container): execution_address: ExecutionAddress amount: Gwei ``` **This action is used to credit the non delegated balance of the delegator. The amount of ETH that was burned in the Deposit to Delegate Contract. A Delegator will be created and its balance set to the amount, OR if a Delegator already exists a top up will be performed.** The method that drains the buffer is: ```python def process_pending_deposits_to_delegate(state: BeaconState) -> None: ``` This method loops all items in the buffer, and creates a new `Delegator` and appends it to the state, OR performs a top-up for the given amount. --- ### Delegate `type: DELEGATE_REQUEST_TYPE` `parsed to: PendingDelegateRequest` `queued to: state.pending_delegations` `buffered on: block` `drained on: epoch` ```python class PendingDelegateRequest(Container): execution_address: ExecutionAddress validator_pubkey: BLSPubkey amount: Gwei slot: Slot ``` **Delegate means moving balance from the non delegated balance of a Delegator into the effective balance of a Validator that accepts delegations.** The method that drains the requests and processes them is: ```python def process_pending_delegations(state: BeaconState) -> None: ``` For each item in the buffer, this method makes some checks: - checks if there is a Delegator and Validator for the given data - checks if the validator is delegable - checks how much we can process, to stay below churn ```python available_for_processing = get_activation_exit_churn_limit(state) processed_amount = 0 delegations_to_postpone = [] # processing starts here... ``` This follows the deposit logic and churn. We compute how much we can delegate, and what does not fit will be postponed for later (added back in the buffer). There is a difference between the normal `deposit` and this, because here we can process the **entire amount** from the request and we can also process **a fraction of the amount** from the request, if that fraction fits in the remainder of the churn. > Once a fraction or the entire amount has been processed and delegated, the request is discarded from the buffer. --- ### Undelegate `type: UNDELEGATE_REQUEST_TYPE` `parsed to: PendingUndelegateRequest` `queued to: state.pending_undelegations` `buffered on: block` `drained on: epoch` ```python class PendingUndelegateRequest(Container): execution_address: ExecutionAddress validator_pubkey: BLSPubkey amount: Gwei ``` The method that drains the requests and processes them is: ```python def process_pending_undelegations(state: BeaconState) -> None: ``` **Undelegate means moving balance from the effective balance of a Validator that accepts delegations into the the non delegated balance of a Delegator.** Undelegations are affecting the financial security of the protocol, because the given amount must leave the staking pool. For this reason we need a way to control the amount that can exit. For that reason we introduce the [ExitQueue](#The-Delegation-Exit-Queue). The `process_pending_undelegations` method drains the `pending_undelegations` buffer and for each item: - checks if the execution address belongs to the Delegator and if there is anything delegated under that identifier - computes the `exit_epoch` and `withdrawable_epoch` for this request - creates and queues a new `DelegationExitItem` into the delegation exit queue All the items in `state.pending_undelegations` are processed in the same loop. The churning and limiting happens when the exit and withdrawability epochs are calculated: ```python # Calculates the undelegation's exit and withdrawability epochs exit_epoch = compute_exit_epoch_and_update_churn(state, requested_to_undelegate) withdrawable_epoch = Epoch(exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) ``` The `process_pending_undelegations` method stops here. Further processing of the DelegationExitItems is done in the [ExitQueue](#The-Delegation-Exit-Queue) pipeline. --- ### Redelegate `type: REDELEGATE_REQUEST_TYPE` `parsed to: PendingRedelegateRequest` `queued to: state.pending_redelegations` `buffered on: block` `drained on: epoch` ```python class PendingUndelegateRequest(Container): execution_address: ExecutionAddress validator_pubkey: BLSPubkey amount: Gwei ``` The method that drains the requests and processes them is: ```python def process_pending_undelegations(state: BeaconState) -> None: ``` **Redelegate is reallocating balance from the effective balance of a Validator that accepts delegations into the effective balance of another Validator that accepts delegations.** Redelegation is an Undelegate + Delegate. The only difference is that the requested amount to be redelegated does not go through the Delegator's balance, it exits from `Source Validator` and delegates to `Target Validator`. The draining method, for each item in the buffer: - checks the source address and `Source Validator` - checks if there is any balance delegated in `Source Validator` - computes the `exit_epoch` and `withdrawable_epoch` for this request - creates and queues a new `DelegationExitItem` into the delegation exit queue The main difference here is the fact that the `DelegationExitItem` has some extra fields that are used for redelegation, and they are filled here. ```python class DelegationExitItem(Container): amount: Gwei total_amount_at_exit: Gwei execution_address: ExecutionAddress validator_pubkey: BLSPubkey exit_epoch: Epoch withdrawable_epoch: Epoch is_redelegation: boolean # <== redelegation redelegate_to_validator_pubkey: BLSPubkey # <== redelegation ``` The `process_pending_undelegations` method stops here. Further processing of the DelegationExitItems is done in the [ExitQueue](#The-Delegation-Exit-Queue) pipeline. --- ### Withdraw from delegator `type: WITHDRAW_FROM_DELEGATOR_REQUEST_TYPE` `parsed to: PendingWithdrawFromDelegatorRequest` `queued to: state.pending_withdrawals_from_delegators` `buffered on: block` `drained on: block` ```python class PendingWithdrawFromDelegatorRequest(Container): execution_address: ExecutionAddress amount: Gwei ``` **Withdrawing from delegator is the action of removing balance from the non delegated balance of a Delegator into a wallet, on EL. In a way it's the inverse of Deposit To Delegate.** This was implemented to mimic [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) functionality. For that reason we need to drain the buffer on block. A new method was created for that reason: ```python def get_expected_withdrawals_from_delegators(state: BeaconState, execution_address: ExecutionAddress) -> None: ``` This method will pick item from the buffer based on: - if they fit in the allocated slots - `MAX_PENDING_WITHDRAWALS_FROM_DELEGATOR_PER_PAYLOAD=16` withdrawals per block, to keep the payload overhead small enough so that extra gas fees can be avoided - the delegator is withdrawable - `MIN_DELEGATOR_WITHDRAWABILITY_DELAY=2048` epochs was introduced. It forces a Delegator to keep his balance in the CL for a given amount of epochs, post creation, to avoid a needless ping-pong with ETH between EL and CL - the delegator has enough balance `prepare_execution_payload` gathers those items and sends them to the EL via: ```python payload_attributes = PayloadAttributes( timestamp=compute_time_at_slot(state, state.slot), prev_randao=get_randao_mix(state, get_current_epoch(state)), suggested_fee_recipient=suggested_fee_recipient, withdrawals=withdrawals, withdrawals_from_delegators=withdrawals_from_delegators, # <== here parent_beacon_block_root=hash_tree_root(state.latest_block_header), ) ``` Withdrawals are then processed by `process_block` in `process_withdrawals_from_delegators`. It checks if `prepare_execution_payload` returns the same set of withdrawals that it got in the `ExecutionPayload` and if so, it dequeues the requests and decreaseses the non delegated balance of the Delegator. ## The Delegation Exit Queue The delegation exit queue is a list that lives in the state: ```python class BeaconState(Container): # [...] # Delegation exit queue delegation_exit_queue: List[DelegationExitItem, PENDING_DELEGATION_OPERATIONS_LIMIT] ``` When a delegated amount must become undelegated, it has to wait in a buffer somewhere. That amount is still accountable for the actions of the Validator for N epochs --that represent the churn + M epochs -- that handle Weak Subjectivity. We use the Delegation Exit Queue for this purpose. This list is used by 2 actions: - Undelegate - Redelegate (because of the Undelegate part of Redelegate) Inside the list we find `DelegationExitItem`: ```python class DelegationExitItem(Container): amount: Gwei total_amount_at_exit: Gwei execution_address: ExecutionAddress validator_pubkey: BLSPubkey exit_epoch: Epoch withdrawable_epoch: Epoch is_redelegation: boolean redelegate_to_validator_pubkey: BLSPubkey ``` - **amount** defines the amount that should be undelegated - **total_amount_at_exit** this is set when the `exit_epoch` arrives. That is the point in which the delegation is *subtracted* from the Validator's delegated balance. We store here how much was delegated + the balance of the Validator at that point, to be able to determine later (for rewards, penalties and slashings) what was the quota of this delegated amount - **exit_epoch** is the epoch in which the `amount` will be subtracted from the `delegated_balances` of the Delegated Validator - **withdrawable_epoch** is the epoch in which the `amount` will be credited to the non delegated balance of the Delegator - **is_redelegation** defines if the `DelegationExitItem` will be redelegated after the `withdrawable_epoch` arrives - **redelegate_to_validator_pubkey** defines the target Validator for a future Delegation (the second part of Redelegate) --- **The exit queue is used by multiple pipelines:** 1. Undelegate 2. Redelegate 3. Slashings 4. Validator exit #### Undelegate and Redelegate Each Undelegate or Redelegate request from the buffer is eventually added to the delegation exit queue. On each epoch boundary `process_delegation_exit_queue` runs. It checks each `DelegationExitItem` in the queue and: - `if current_epoch >= delegation_exit.exit_epoch` it executes the *exit*. The `amount` is withdrawn from the `delegated_balances`, and the `total_amount_at_exit` is set. The item remains in the queue, awaiting the next milestone: the withdrawable_epoch. - `if current_epoch >= delegation_exit.withdrawable_epoch` it's time to *settle* the undelegation. This means that the `amount` is credited to the non delegated balance of the Delegator. The `DelegationExitItem` is removed from the queue since it's done its part. - `if delegation_exit.is_redelegation` we don't actually credit the Delegator, we queue a new `PendingDelegateRequest` into `state.pending_delegations`. Basically we create a new Delegation Request, which will be processed by the Delegation pipeline from here on. The `DelegationExitItem` is removed from the queue since it's done its part. #### Validator exit When a validator that has delegations enabled exits it must undelegate those delegations. `initiate_validator_exit` has been modified to account for this scenario: ```python def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: #[...] if validator.is_operator: initiate_delegated_balances_exit(state, validator.pubkey, validator.exit_epoch, validator.withdrawable_epoch) ``` The method `initiate_delegated_balances_exit` loops through every delegated amount and queues a new `DelegationExitItem` in the Delegation Exit Queue. ## Slashings, rewards and penalties The delegated amounts must be slashable. And they must accrue rewards and penalties. The balance that is not delegated (the non delegated balance of the Delegator) does not participate in the protocol and is not held accountable. It does not get slashed and it does not produce rewards. It is idle, ready to be delegated or withdrawn to a wallet. An amount **is slashable** while delegated, but also inside the delegation exit queue, until `withdrawable_epoch` is reached. An amount **generates rewards and penalties** while delegated, but NOT inside the delegation exit queue, untill `exit_epoch` is reached. ### Slashing ![eODS dev post.008](https://hackmd.io/_uploads/B1Ytp6xDex.jpg) Slashing must now also take into consideration the qualifying DelegationExitItems from the delegation exit queue. A delegated amount can NOT leave a Validator instantly, and it MUST remain accountable for an extra period of time (WS) even in the delegation exit queue. To be more specific, it is slashable **in the delegation exit queue** if: `exit_epoch <= current_epoch <= withdrawability_epoch` If `current_epoch < exit_epoch`, it is slashable in the DelegatedValidator, because that amount is still participating in the protocol, and will be slashed only there. The way slashing is handled in eODS is as follows: #### 1. We slash the Delegation Exit Queue ![eODS dev post.007](https://hackmd.io/_uploads/BkzTgAkvee.jpg) Assuming Validator with pubkey `abcd` must be slashed, we pick from the delegation exit queue the items that qualify. We look at the Validator pubkey and epochs. To slash an DelegationExitItem we calculate the delegated_quota of that item. This is important to know because for any slashing we only know the total amount of slashing we must apply, and we need to divide that between every DelegationExitItem that qualifies, the participating delegated balances, and the Validator's own balance. So: ```python total_slashed_in_queue = 0 # we filter the delegation exit queue into list_to_slash for index in range(len(list_to_slash )): exit_item = state.list_to_slash[index] # figure out how much of that total slash we apply to THIS exit item delegated_quota = exit_item.amount / exit_item.total_amount_at_exit to_slash = delegated_quota * penalty # we accumulate all slashings applied so far to the delegation exit queue items total_slashed_in_queue += to_slash state.delegation_exit_queue[index].undelegated_amount -= to_slash # return this value, because be need to see what's left for the active delegated balances and the Validator's balance return total_slashed_in_queue ``` #### 2. We slash the active delegated balances and the Validator's own balance ```python # slash the delegation exit queue and grab the total slashed there slashed_in_queue = slash_exit_queue(state, validator_pubkey, penalty) # this we split between active delegated balances and Validator's own balance rest_to_slash = penalty - slashed_in_queue delegated_validator = get_delegated_validator(state, validator_pubkey) # calculate the validator's slashing validator_penalty = delegated_validator.delegated_validator_quota * rest_to_slash # calculate the amount of slashing to be applied for the active delegated balances delegators_penalty = rest_to_slash - validator_penalty # slash the operator decrease_balance(state, ValidatorIndex(validator_index), validator_penalty) # slash the delegations apply_delegations_slashing(delegated_validator, delegators_penalty) ``` ```python def apply_delegations_slashing(delegated_validator: DelegatedValidator, penalty: Gwei, ) -> None: delegated_validator.total_delegated_balance -= penalty # slash the delegated balances based on the quota they own from the total effective_balance for index in range(len(delegated_validator.delegated_balances)): delegated_validator.delegated_balances[index] -= penalty * delegated_validator.delegators_quotas[index] ``` So by using a quota system we can figure out exactly how much slashing we must apply for all accountable parts. All this functionality is encapsulated in `slash_delegated_validator_and_exit_queue`, part of the Beacon Chain Accounting. ```python def slash_delegated_validator_and_exit_queue(state: BeaconState, validator_index: ValidatorIndex, validator_pubkey: BLSPubkey, penalty: Gwei) -> None: ``` ### Rewards and penalties Delegated balances accumulate rewards and penalties. This is true from the moment that delegation becomes active, up to its exit epoch. Unlike slashings, the delegation exit queue elements are not subject to receiving rewards and penalties because rewards and penalties do not occur unless the balance is active. The rewards and penalties are applied only in the DelegatedValidator, on top of the `delegated_balances`. ![eODS dev post.009](https://hackmd.io/_uploads/ryMsGn4Del.jpg) The rewards are split between the Validator and the participating delegated balances, based on their contribution quotas. ```python def reward_delegated_validator(state: BeaconState, validator_index: ValidatorIndex, validator_pubkey: BLSPubkey, reward: Gwei) -> None: delegated_validator = get_delegated_validator(state, validator_pubkey) validator_reward = delegated_validator.delegated_validator_quota * reward delegators_reward = reward - validator_reward # reward the operator increase_balance(state, ValidatorIndex(validator_index), validator_reward) # reward the delegations apply_delegations_rewards(delegated_validator, delegators_reward) ``` ```python def apply_delegations_rewards(amount: Gwei, delegated_validator: DelegatedValidator) -> None: delegated_validator.total_delegated_balance += amount for index in range(len(delegated_validator.delegators_quotas)): delegated_validator.delegated_balances[index] += amount * delegated_validator.delegators_quotas[index] ``` Applying penalties follow the same principle. ## Closing thoughts These spec changes aim to be a starting point for describing a minimal delegation integration within the exiting feature set. eODS in itself has a medium to large footprint in the exiting model, but the intersection points with said model are kept to a minimum. There are a few notable changes brought: - The EL-CL data transfer bus, further using [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) - The introduction of a [delegation exit queue](#The-Delegation-Exit-Queue) for the ability to keep delegated amounts accountable throughout the exit process, WS included - The addition of the [Delegated Validator](#Added-DelegatedValidator) as a wrapper on top of the existing Validator, keeping Validator changes to a minimum - The [Delegations Operations Contract](#Delegation-Operations-Contract), and the idea that we can massively improve UX and adoption just by using the tools we already have

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully