Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Review sync committee rewards + penalties as PR 2453 #36

Merged
merged 5 commits into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions model/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class Parameters:
Used to scale calculations that depend on the number of epochs that have passed.

For example, for dt = 100, each timestep equals 100 epochs.

By default set to constants.epochs_per_day (225)
"""

Expand Down Expand Up @@ -198,7 +198,7 @@ class Parameters:
)
"""
A process that returns the ETH staked at each epoch.

If set to `none`, the model is driven by the validator process,
where new validators enter the system and stake accordingly.

Expand All @@ -213,7 +213,7 @@ class Parameters:
)
"""
A process that returns the number of new validators per epoch.

Used if model not driven using `eth_staked_process`.

By default set to a static value from https://beaconscan.com/statistics.
Expand Down Expand Up @@ -260,25 +260,25 @@ class Parameters:
"""
Used to calculate the penalty applied for a slashable offence.
"""
TIMELY_HEAD_WEIGHT: List[int] = default([12])
TIMELY_HEAD_WEIGHT: List[int] = default([14])
"""
Used to calculate the reward received for getting a head vote in time and correctly.

`head_reward = (TIMELY_HEAD_WEIGHT / WEIGHT_DENOMINATOR) * base_reward`
"""
TIMELY_SOURCE_WEIGHT: List[int] = default([12])
TIMELY_SOURCE_WEIGHT: List[int] = default([14])
"""
Used to calculate the reward received for getting a source vote in time and correctly.

`source_reward = (TIMELY_SOURCE_WEIGHT / WEIGHT_DENOMINATOR) * base_reward`
"""
TIMELY_TARGET_WEIGHT: List[int] = default([24])
TIMELY_TARGET_WEIGHT: List[int] = default([26])
"""
Used to calculate the reward received for getting a target vote in time and correctly.

`target_reward = (TIMELY_TARGET_WEIGHT / WEIGHT_DENOMINATOR) * base_reward`
"""
SYNC_REWARD_WEIGHT: List[int] = default([8])
SYNC_REWARD_WEIGHT: List[int] = default([2])
"""
Used to calculate the reward received for attesting as part of a sync committee.
"""
Expand All @@ -294,7 +294,7 @@ class Parameters:
"""
Used to calculate the churn limit for validator entry and exit. The maximum number of validators that can
enter or exit the system per epoch.

In this system it is used for the validator activation queue process.
"""
CHURN_LIMIT_QUOTIENT: List[int] = default([2 ** 16])
Expand Down Expand Up @@ -332,7 +332,7 @@ class Parameters:
)
"""
The validator hardware costs per epoch in dollars.

A vector with a value for each validator environment.
"""
validator_cloud_costs_per_epoch: List[np.ndarray] = default(
Expand All @@ -358,7 +358,7 @@ class Parameters:
slashing_events_per_1000_epochs: List[int] = default([1]) # 1 / 1000 epochs
"""
The number of slashing events per 1000 epochs.

Asssumption from Hoban/Borgers report.
"""

Expand All @@ -368,14 +368,14 @@ class Parameters:
)
"""
The basefee burned, in Gwei per gas, for each transaction.

An average of 100 Gwei per gas expected to be set as transaction fee cap,
split between the basefee and tips - the fee cap less the basefee is sent as a tip to miners/validators.

Approximated using average gas price from https://etherscan.io/gastracker as of 20/04/21

An extract from https://notes.ethereum.org/@vbuterin/eip-1559-faq

> Each “full block” (ie. a block whose gas is 2x the TARGET) increases the BASEFEE by 1.125x,
> so a series of constant full blocks will increase the gas price by a factor of 10 every
> ~20 blocks (~4.3 min on average).
Expand All @@ -387,9 +387,9 @@ class Parameters:
)
"""
EIP1559 transaction pricing tip, in Gwei per gas.

Due to MEV, average tips expected to be higher than usual as bid for inclusion in blockscpace market.

The tip is the difference between the fee cap set per transaction, and the basefee.

For PoW system without MEV influence, the tip level compensates for uncle risk:
Expand All @@ -416,7 +416,7 @@ class Parameters:

fees_per_day = daily_transactions * transaction_average_gas * (basefee + tip) / 1e9 ~= 10k ETH
(see https://etherscan.io/chart/transactionfee)

Where:
* daily_transactions ~= 1_400_000
* transaction_average_gas ~= 73_123
Expand Down
65 changes: 59 additions & 6 deletions model/parts/incentives.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,23 @@ def policy_attestation_penalties(
base_reward = previous_state["base_reward"]
number_of_validators_offline = previous_state["number_of_validators_offline"]

# Calculate validating penalties
validating_penalties = (
# Calculate attestation penalties
attestation_penalties = (
(TIMELY_SOURCE_WEIGHT + TIMELY_TARGET_WEIGHT + TIMELY_HEAD_WEIGHT)
/ WEIGHT_DENOMINATOR
* base_reward
)
# Aggregation over all offline validators
validating_penalties *= number_of_validators_offline
attestation_penalties *= number_of_validators_offline

return {"validating_penalties": validating_penalties}
return {"attestation_penalties": attestation_penalties}


def policy_sync_committee_reward(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
"""Sync Committee Reward Policy Function
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#sync-committee-processing
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#sync-aggregate-processing

Extract from spec:
```python
Expand All @@ -127,7 +127,7 @@ def policy_sync_committee_reward(
number_of_validators_online = previous_state["number_of_validators_online"]

# Calculate total base rewards
total_base_rewards = base_reward * number_of_validators_online
total_base_rewards = base_reward * number_of_validators
# Set sync reward to proportion of total base rewards
sync_reward = total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR
# Scale reward by the percentage of online validators
Expand All @@ -136,6 +136,42 @@ def policy_sync_committee_reward(
return {"sync_reward": sync_reward}


def policy_sync_committee_penalties(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
"""Sync Committee Penalty Policy Function
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#sync-aggregate-processing

Extract from spec:
```python
# Compute participant and proposer rewards
total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT
total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments)
max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH)
participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE)
proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT))
```
"""

# Parameters
SYNC_REWARD_WEIGHT = params["SYNC_REWARD_WEIGHT"]
WEIGHT_DENOMINATOR = params["WEIGHT_DENOMINATOR"]

# State Variables
base_reward = previous_state["base_reward"]
number_of_validators = previous_state["number_of_validators"]
number_of_validators_offline = previous_state["number_of_validators_offline"]

# Calculate total base rewards
total_base_rewards = base_reward * number_of_validators
# Set sync penalty to proportion of total base rewards
sync_penalty = total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR
# Scale penalty by the percentage of offline validators
sync_penalty *= number_of_validators_offline / number_of_validators

return {"sync_committee_penalties": sync_penalty}


def policy_block_proposal_reward(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
Expand Down Expand Up @@ -308,3 +344,20 @@ def update_validating_rewards(
assert validating_rewards <= max_validating_rewards

return "validating_rewards", validating_rewards


def update_validating_penalties(
params, substep, state_history, previous_state, policy_input
) -> typing.Tuple[str, Gwei]:
"""Validating Penalties State Update Function
Calculate and update total validating penalties
i.e. penalties received for failing to attest, or failing to perform sync committee duties
"""
# State Variables
attestation_penalties = previous_state["attestation_penalties"]
sync_committee_penalties = previous_state["sync_committee_penalties"]

# Calculate total validating penalties
validating_penalties = attestation_penalties + sync_committee_penalties

return "validating_penalties", validating_penalties
4 changes: 2 additions & 2 deletions model/parts/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ def policy_total_online_validator_rewards(
) -> typing.Dict[str, Gwei]:
# State Variables
validating_rewards = previous_state["validating_rewards"]
whistleblower_rewards = previous_state["whistleblower_rewards"]
validating_penalties = previous_state["validating_penalties"]
whistleblower_rewards = previous_state["whistleblower_rewards"]
total_tips_to_validators = previous_state["total_tips_to_validators"]

# Calculate total rewards for online validators
total_online_validator_rewards = (
validating_rewards
+ whistleblower_rewards
- validating_penalties
+ whistleblower_rewards
+ total_tips_to_validators
)

Expand Down
19 changes: 15 additions & 4 deletions model/state_update_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@
"sync_reward": update_from_signal("sync_reward"),
},
},
{
"description": """
Sync committee and attestation penalties
""",
"policies": {
"attestation": incentives.policy_attestation_penalties,
"sync_committee": incentives.policy_sync_committee_penalties,
},
"variables": {
"attestation_penalties": update_from_signal("attestation_penalties"),
"sync_committee_penalties": update_from_signal("sync_committee_penalties"),
},
},
{
"description": """
Block proposal rewards
Expand All @@ -101,12 +114,10 @@
"description": """
Total validating rewards and penalties
""",
"policies": {
"penalties": incentives.policy_attestation_penalties,
},
"policies": {},
"variables": {
"validating_rewards": incentives.update_validating_rewards,
"validating_penalties": update_from_signal("validating_penalties"),
"validating_penalties": incentives.update_validating_penalties,
},
},
{
Expand Down
6 changes: 5 additions & 1 deletion model/state_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class StateVariables:
validating_rewards: Gwei = 0
"""The total rewards received for PoS validation (attestation, block proposal, sync vote)"""
validating_penalties: Gwei = 0
"""The total penalties received for failing to perform PoS validation duties"""
"""The total penalties received for failing to perform PoS validation duties (attestation, sync vote)"""
source_reward: Gwei = 0
"""The total rewards received for getting a source vote in time and correctly"""
target_reward: Gwei = 0
Expand All @@ -106,6 +106,10 @@ class StateVariables:
"""The total rewards received for successfully proposing a block"""
sync_reward: Gwei = 0
"""The total rewards received for attesting as part of a sync committee"""
attestation_penalties: Gwei = 0
"""The total penalties received for failing to perform attestation duties"""
sync_committee_penalties: Gwei = 0
"""The total penalties received for failing to perform sync committee duties"""

# Slashing state variables
amount_slashed: Gwei = 0
Expand Down
18 changes: 12 additions & 6 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from radcad import Simulation

import experiments.base as base
from model.parameters import parameters


def test_dt():
Expand All @@ -27,21 +28,26 @@ def test_dt():


def check_validating_rewards(params, substep, state_history, previous_state):
# Parameters
WEIGHT_DENOMINATOR = params["WEIGHT_DENOMINATOR"]
PROPOSER_REWARD_QUOTIENT = params["PROPOSER_REWARD_QUOTIENT"]
SYNC_REWARD_WEIGHT = params["SYNC_REWARD_WEIGHT"]

# State Variables
validating_rewards = previous_state["validating_rewards"]
block_proposer_reward = previous_state["block_proposer_reward"]
sync_reward = previous_state["sync_reward"]
source_reward = previous_state["source_reward"]
target_reward = previous_state["target_reward"]
head_reward = previous_state["head_reward"]

# Assert sync reward is 1/8 of validating rewards
assert math.isclose(sync_reward, (1 / 8) * validating_rewards)

# Assert block proposer reward is 1/8 of validating rewards
assert math.isclose(block_proposer_reward, (1 / 8) * validating_rewards)
assert math.isclose(block_proposer_reward, (PROPOSER_REWARD_QUOTIENT / WEIGHT_DENOMINATOR) * validating_rewards)
# Assert sync reward is 1/8 of validating rewards
assert math.isclose(sync_reward, (SYNC_REWARD_WEIGHT / WEIGHT_DENOMINATOR) * validating_rewards)
# Assert source reward is 3/4 of validating rewards
assert math.isclose(source_reward + target_reward + head_reward, (3 / 4) * validating_rewards)
assert math.isclose(source_reward + target_reward + head_reward, ((WEIGHT_DENOMINATOR - PROPOSER_REWARD_QUOTIENT - SYNC_REWARD_WEIGHT) / WEIGHT_DENOMINATOR) * validating_rewards)

return


Expand Down