forked from CADLabs/ethereum-economic-model
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pos_incentives.py
407 lines (333 loc) · 17 KB
/
pos_incentives.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
"""
# Proof of Stake Incentives
Calculation of PoS incentives such as attestation and block proposal rewards and penalties.
"""
import typing
import model.parts.utils.ethereum_spec as spec
from model.types import Gwei
def policy_attestation_rewards(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
"""
## Attestation Rewards Policy Function
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#get_flag_index_deltas
Extract from spec:
```python
reward_numerator = base_reward * weight * unslashed_participating_increments
rewards[index] += Gwei(reward_numerator // (active_increments * WEIGHT_DENOMINATOR))
```
"""
# Parameters
TIMELY_SOURCE_WEIGHT = params["TIMELY_SOURCE_WEIGHT"]
TIMELY_TARGET_WEIGHT = params["TIMELY_TARGET_WEIGHT"]
TIMELY_HEAD_WEIGHT = params["TIMELY_HEAD_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_online = previous_state["number_of_validators_online"]
# Calculate total source reward
# All submitted attestations have to match source vote
source_reward = (TIMELY_SOURCE_WEIGHT / WEIGHT_DENOMINATOR) * base_reward
# Scale reward by the proportion of validators who also got the attestation in time and correctly
source_reward *= number_of_validators_online / number_of_validators
# Aggregation over all online validators; assumes one correct vote per online validator per epoch
source_reward *= number_of_validators_online
# Calculate total target reward
target_reward = (TIMELY_TARGET_WEIGHT / WEIGHT_DENOMINATOR) * base_reward
# Scale reward by the proportion of validators who also got the attestation in time and correctly
target_reward *= number_of_validators_online / number_of_validators
# Aggregation over all online validators; assumes one correct vote per online validator per epoch
target_reward *= number_of_validators_online
# Calculate total head reward
head_reward = (TIMELY_HEAD_WEIGHT / WEIGHT_DENOMINATOR) * base_reward
# Scale reward by the proportion of validators who also got the attestation in time and correctly
head_reward *= number_of_validators_online / number_of_validators
# Aggregation over all online validators; assumes one correct vote per online validator per epoch
head_reward *= number_of_validators_online
return {
"source_reward": source_reward,
"target_reward": target_reward,
"head_reward": head_reward,
}
def policy_attestation_penalties(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
"""
## Attestation Penalties Policy Function
Validators are penalized for not attesting to the source, target, and head.
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#get_flag_index_deltas
Extract from spec:
```python
penalties[index] += Gwei(base_reward * weight // WEIGHT_DENOMINATOR)
```
"""
# Parameters
TIMELY_SOURCE_WEIGHT = params["TIMELY_SOURCE_WEIGHT"]
TIMELY_TARGET_WEIGHT = params["TIMELY_TARGET_WEIGHT"]
TIMELY_HEAD_WEIGHT = params["TIMELY_HEAD_WEIGHT"]
WEIGHT_DENOMINATOR = params["WEIGHT_DENOMINATOR"]
# State Variables
base_reward = previous_state["base_reward"]
number_of_validators_offline = previous_state["number_of_validators_offline"]
# Calculate attestation penalties
attestation_penalties = (
(TIMELY_SOURCE_WEIGHT + TIMELY_TARGET_WEIGHT + TIMELY_HEAD_WEIGHT)
/ WEIGHT_DENOMINATOR
* base_reward
)
# Aggregation over all offline validators
attestation_penalties *= number_of_validators_offline
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-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_online = previous_state["number_of_validators_online"]
# Calculate total base rewards
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
sync_reward *= number_of_validators_online / number_of_validators
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]:
"""
## Block Proposal Reward Policy Function
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#modified-process_attestation
Extract from spec:
```python
# Participation flag indices
participation_flag_indices = []
if is_matching_head and is_matching_target and state.slot == data.slot + MIN_ATTESTATION_INCLUSION_DELAY:
participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX)
if is_matching_source and state.slot <= data.slot + integer_squareroot(SLOTS_PER_EPOCH):
participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX)
if is_matching_target and state.slot <= data.slot + SLOTS_PER_EPOCH:
participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX)
# Update epoch participation flags
proposer_reward_numerator = 0
for index in get_attesting_indices(state, data, attestation.aggregation_bits):
for flag_index, weight in get_flag_indices_and_weights():
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
proposer_reward_numerator += get_base_reward(state, index) * weight
# Reward proposer
proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator)
increase_balance(state, get_beacon_proposer_index(state), proposer_reward)
```
"""
# Parameters
WEIGHT_DENOMINATOR = params["WEIGHT_DENOMINATOR"]
TIMELY_SOURCE_WEIGHT = params["TIMELY_SOURCE_WEIGHT"]
TIMELY_TARGET_WEIGHT = params["TIMELY_TARGET_WEIGHT"]
TIMELY_HEAD_WEIGHT = params["TIMELY_HEAD_WEIGHT"]
PROPOSER_WEIGHT = params["PROPOSER_WEIGHT"]
# State Variables
base_reward = previous_state["base_reward"]
sync_reward = previous_state["sync_reward"]
number_of_validators_online = previous_state["number_of_validators_online"]
# Calculate block proposer reward
proposer_reward_numerator = base_reward * (
TIMELY_SOURCE_WEIGHT + TIMELY_TARGET_WEIGHT + TIMELY_HEAD_WEIGHT
)
# Aggregate over all attestations in the epoch
# Assumes every online validator gets one correct source, target, and head vote per epoch
proposer_reward_numerator *= number_of_validators_online
# Normalize by the sum of weights so that proposer rewards are 1/8th of base reward
proposer_reward_denominator = (
(WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT
)
block_proposer_reward = Gwei(
proposer_reward_numerator // proposer_reward_denominator
)
# Add block proposer reward for including sync committee attestations
# See https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#sync-committee-processing
block_proposer_reward += (
sync_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)
)
return {"block_proposer_reward": block_proposer_reward}
def policy_slashing(
params, substep, state_history, previous_state
) -> typing.Dict[str, Gwei]:
"""
## Slashing Policy Function
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#modified-slash_validator
Extract from spec:
```python
state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance
decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR)
# Apply proposer and whistleblower rewards
proposer_index = get_beacon_proposer_index(state)
if whistleblower_index is None:
whistleblower_index = proposer_index
whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT)
proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR)
increase_balance(state, proposer_index, proposer_reward)
increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward))
```
Derived from https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/beacon-chain.md#slashings
Extract from spec:
```python
def process_slashings(state: BeaconState) -> None:
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)
adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, total_balance)
for index, validator in enumerate(state.validators):
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
penalty = penalty_numerator // total_balance * increment
decrease_balance(state, ValidatorIndex(index), penalty)
```
"""
# Parameters
dt = params["dt"]
slashing_events_per_1000_epochs = params["slashing_events_per_1000_epochs"]
MIN_SLASHING_PENALTY_QUOTIENT = params["MIN_SLASHING_PENALTY_QUOTIENT"]
PROPORTIONAL_SLASHING_MULTIPLIER = params["PROPORTIONAL_SLASHING_MULTIPLIER"]
EFFECTIVE_BALANCE_INCREMENT = params["EFFECTIVE_BALANCE_INCREMENT"]
WHISTLEBLOWER_REWARD_QUOTIENT = params["WHISTLEBLOWER_REWARD_QUOTIENT"]
PROPOSER_WEIGHT = params["PROPOSER_WEIGHT"]
WEIGHT_DENOMINATOR = params["WEIGHT_DENOMINATOR"]
# State Variables
average_effective_balance = previous_state["average_effective_balance"]
# Calculate slashing, whistleblower reward, and proposer reward for a single slashing event
slashing = Gwei(average_effective_balance // MIN_SLASHING_PENALTY_QUOTIENT)
whistleblower_reward = Gwei(
average_effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT
)
proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR)
whistleblower_reward = Gwei(whistleblower_reward - proposer_reward)
# Calculate number of slashing events for current epoch
number_of_slashing_events = slashing_events_per_1000_epochs / 1000
# Calculate the individual penalty proportional to total slashings
# in current time period using `PROPORTIONAL_SLASHING_MULTIPLIER`
total_balance = spec.get_total_active_balance(params, previous_state)
adjusted_total_slashing_balance = min(
slashing * number_of_slashing_events * PROPORTIONAL_SLASHING_MULTIPLIER,
total_balance,
)
increment = EFFECTIVE_BALANCE_INCREMENT
penalty_numerator = (
average_effective_balance // increment * adjusted_total_slashing_balance
)
proportional_penalty = penalty_numerator // total_balance * increment
# Scale penalty by the number of slashing events per epoch
amount_slashed = (slashing + proportional_penalty) * number_of_slashing_events
# Scale rewards by the number of slashing events per epoch
whistleblower_reward *= number_of_slashing_events
proposer_reward *= number_of_slashing_events
# The whistleblower and the block proposer who includes the slashing receive a reward
whistleblower_rewards = whistleblower_reward + proposer_reward
return {
"amount_slashed": amount_slashed * dt,
"whistleblower_rewards": whistleblower_rewards * dt,
}
def update_base_reward(
params, substep, state_history, previous_state, policy_input
) -> typing.Tuple[str, Gwei]:
"""
## Base Reward State Update Function
Calculate and update base reward per validator
"""
# Parameters
dt = params["dt"]
# Get base reward per validator
base_reward_per_validator: Gwei = spec.get_base_reward(params, previous_state)
# By scaling the base reward by our unit of time dt (in epochs),
# we can scale all rewards and penalties by the same unit of time
return "base_reward", Gwei(base_reward_per_validator) * dt
def update_validating_rewards(
params, substep, state_history, previous_state, policy_input
) -> typing.Tuple[str, Gwei]:
"""
## Validating Rewards State Update Function
Calculate and update total validating rewards
i.e. rewards received for block proposal, attesting, and being a member of sync committee
"""
# State Variables
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"]
base_reward = previous_state["base_reward"]
number_of_validators_online = previous_state["number_of_validators_online"]
# Calculate total validating rewards
validating_rewards = (
block_proposer_reward
+ source_reward
+ target_reward
+ head_reward
+ sync_reward
)
# Assert validating rewards should be less than equal to the maximum validating rewards
max_validating_rewards = number_of_validators_online * base_reward
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