forked from CADLabs/ethereum-economic-model
-
Notifications
You must be signed in to change notification settings - Fork 0
/
system_parameters.py
410 lines (346 loc) · 13.5 KB
/
system_parameters.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
408
409
410
"""
Definition of System Parameters, their types, and default values.
By using a dataclass to represent the System Parameters:
* We can use types for Python type hints
* Set default values
* Ensure that all System Parameters are initialized
"""
import logging
import numpy as np
from dataclasses import dataclass
from datetime import datetime
import model.constants as constants
import model.simulation_configuration as simulation
from model.types import (
Run,
Timestep,
Percentage,
Gwei,
Gas,
Gwei_per_Gas,
ETH,
USD_per_epoch,
Percentage_per_epoch,
ValidatorEnvironment,
List,
Callable,
Epoch,
Stage,
)
from model.utils import default
from data.historical_values import eth_price_mean
# Configure validator environment distribution
validator_environments = [
# Configure a custom validator environment using the following as a template:
# ValidatorEnvironment(
# type="custom",
# percentage_distribution=0.01, # 1%
# hardware_costs_per_epoch=0.0014,
# cloud_costs_per_epoch=0,
# third_party_costs_per_epoch=0,
# ),
ValidatorEnvironment(
type="diy_hardware",
percentage_distribution=0.37,
hardware_costs_per_epoch=0.0014,
),
ValidatorEnvironment(
type="diy_cloud",
percentage_distribution=0.13,
cloud_costs_per_epoch=0.00027,
),
ValidatorEnvironment(
type="pool_staas",
percentage_distribution=0.27,
third_party_costs_per_epoch=0.12,
),
ValidatorEnvironment(
type="pool_hardware",
percentage_distribution=0.05,
hardware_costs_per_epoch=0.0007,
),
ValidatorEnvironment(
type="pool_cloud",
percentage_distribution=0.02,
cloud_costs_per_epoch=0.00136,
),
ValidatorEnvironment(
type="staas_full",
percentage_distribution=0.08,
third_party_costs_per_epoch=0.15,
),
ValidatorEnvironment(
type="staas_self_custodied",
percentage_distribution=0.08,
third_party_costs_per_epoch=0.12,
),
]
"""Validator environment configuration
See ASSUMPTIONS.md document for details of validator environment configuration and assumptions.
"""
# Normalise percentage distribution to a total of 100%
total_percentage_distribution = sum(
[validator.percentage_distribution for validator in validator_environments]
)
if total_percentage_distribution < 1:
logging.warning(
"""
Parameter validator.percentage_distribution normalized due to sum not being equal to 100%
"""
)
for validator in validator_environments:
validator.percentage_distribution /= total_percentage_distribution
# Using list comprehension, map the validator types to each parameter
validator_percentage_distribution = [
np.array(
[validator.percentage_distribution for validator in validator_environments],
dtype=Percentage,
)
]
validator_hardware_costs_per_epoch = [
np.array(
[validator.hardware_costs_per_epoch for validator in validator_environments],
dtype=USD_per_epoch,
)
]
validator_cloud_costs_per_epoch = [
np.array(
[validator.cloud_costs_per_epoch for validator in validator_environments],
dtype=USD_per_epoch,
)
]
validator_third_party_costs_per_epoch = [
np.array(
[validator.third_party_costs_per_epoch for validator in validator_environments],
dtype=Percentage_per_epoch,
)
]
@dataclass
class Parameters:
"""System Parameters
Each System Parameter is defined as:
system parameter key: system parameter type = default system parameter value
Because lists are mutable, we need to wrap each parameter list in the `default(...)` method.
"""
# Time parameters
dt: List[Epoch] = default([simulation.DELTA_TIME])
"""
Simulation timescale / timestep unit of time, in epochs.
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)
"""
stage: List[Stage] = default([Stage.PROOF_OF_STAKE])
"""
Which stage or stages of the network upgrade process to simulate.
By default set to PROOF_OF_STAKE stage, where EIP1559 is enabled and POW issuance is disabled.
See model.types.Stage Enum for further documentation.
"""
date_start: List[datetime] = default([datetime.now()])
"""Start date for simulation as Python datetime"""
date_eip1559: List[datetime] = default(
[datetime.strptime("2021/08/04", "%Y/%m/%d")]
)
"""
Expected EIP1559 activation date as Python datetime.
"""
date_pos: List[datetime] = default([datetime.strptime("2021/12/1", "%Y/%m/%d")])
"""
Eth1/Eth2 merge date as Python datetime, after which POW is disabled and POS is enabled.
Source: https://twitter.com/drakefjustin/status/1379052831982956547
"""
# Environmental processes
eth_price_process: List[Callable[[Run, Timestep], ETH]] = default(
[lambda _run, _timestep: eth_price_mean]
)
"""
A process that returns the ETH spot price at each epoch.
By default set to average ETH price over the last 12 months from Etherscan.
"""
eth_staked_process: List[Callable[[Run, Timestep], ETH]] = default(
[lambda _run, _timestep: None]
)
"""
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.
This process is used for simulating a series of ETH staked values directly.
"""
validator_process: List[Callable[[Run, Timestep], int]] = default(
[
# From https://beaconscan.com/statistics as of 20/04/21
lambda _run, _timestep: 3,
]
)
"""
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.
"""
# Ethereum system parameters
daily_pow_issuance: List[ETH] = default([13_550])
"""
The average daily Proof of Work issuance in ETH.
See https://etherscan.io/chart/blockreward
"""
# Parameters from the Eth2 specification
# Uppercase used for all parameters from Eth2 specification
BASE_REWARD_FACTOR: List[int] = default([64])
"""
A parameter used to change the issuance rate of the Ethereum PoS system.
Most validator rewards and penalties are calculated in terms of the base reward.
"""
MAX_EFFECTIVE_BALANCE: List[Gwei] = default([32 * constants.gwei])
"""
A validators effective balance is used to calculate incentives, and for voting,
and is a value less than the total stake/balance.
The max effective balance of a validator is 32 ETH.
"""
EFFECTIVE_BALANCE_INCREMENT: List[Gwei] = default([1 * constants.gwei])
"""
A validators effective balance can only change in steps of EFFECTIVE_BALANCE_INCREMENT,
which reduces the computational load for state updates.
"""
PROPOSER_REWARD_QUOTIENT: List[int] = default([8])
"""
Used to calculate the proportion of rewards distributed between attesters and proposers.
"""
WHISTLEBLOWER_REWARD_QUOTIENT: List[int] = default([512])
"""
Used to calculate the proportion of the effective balance of the slashed validator
distributed between the whistleblower and the proposer.
"""
MIN_SLASHING_PENALTY_QUOTIENT: List[int] = default([2 ** 6])
"""
Used to calculate the penalty applied for a slashable offence.
"""
PROPORTIONAL_SLASHING_MULTIPLIER: List[int] = default([2])
"""
Scales the slashing penalty proportional to the total slashings for the current epoch
i.e. the more slashing events there are, the greater the individual penalty
"""
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([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([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([2])
"""
Used to calculate the reward received for attesting as part of a sync committee.
"""
PROPOSER_WEIGHT: List[int] = default([8])
"""
Used to calculate the reward received for successfully proposing a block.
"""
WEIGHT_DENOMINATOR: List[int] = default([64])
"""
Used as the denominator in incentive calculations to calculate reward and penalty proportions.
"""
MIN_PER_EPOCH_CHURN_LIMIT: List[int] = default([4])
"""
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])
"""
Used in the calculation of the churn limit to set a point at which the limit increases.
"""
BASE_FEE_MAX_CHANGE_DENOMINATOR: List[int] = default([8])
"""
Used to set the maximum rate at which the EIP1559 base fee can change per block, approx. 12.5%.
"""
ELASTICITY_MULTIPLIER: List[int] = default([2])
"""
Used to calculate gas limit from EIP1559 gas target
"""
# Validator parameters
validator_uptime_process: List[Percentage] = default(
[lambda _run, _timestep: max(0.98, 2 / 3)]
)
"""
The combination of validator internet, power, and technical uptime, as a percentage.
Minimum uptime is inactivity leak threshold = 2/3, as this model doesn't model the inactivity leak process.
"""
validator_percentage_distribution: List[np.ndarray] = default(
validator_percentage_distribution
)
"""
The percentage of validators in each environment, normalized to a total of 100%.
A vector with a value for each validator environment.
"""
validator_hardware_costs_per_epoch: List[np.ndarray] = default(
validator_hardware_costs_per_epoch
)
"""
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(
validator_cloud_costs_per_epoch
)
"""
The validator cloud costs per epoch in dollars.
A vector with a value for each validator environment.
"""
validator_third_party_costs_per_epoch: List[np.ndarray] = default(
validator_third_party_costs_per_epoch
)
"""
The validator third-party costs as a percentage of total online validator rewards.
Used for expected Staking-as-a-Service fees.
A vector with a value for each validator environment.
"""
# Rewards, penalties, and slashing
slashing_events_per_1000_epochs: List[int] = default([1]) # 1 / 1000 epochs
"""
The number of slashing events per 1000 epochs.
"""
# EIP1559 transaction pricing parameters
base_fee_process: List[Callable[[Run, Timestep], Gwei_per_Gas]] = default(
[lambda _run, _timestep: 70] # Gwei per gas
)
"""
The base fee 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 base fee and priority fee - the fee cap less the base fee is sent as a priority fee 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).
> Hence, periods of heavy on-chain load will not realistically last longer than ~5 minutes.
"""
priority_fee_process: List[Callable[[Run, Timestep], Gwei_per_Gas]] = default(
[lambda _run, _timestep: 30] # Gwei per gas
)
"""
EIP1559 transaction pricing priority fee, in Gwei per gas.
Due to MEV, average priority fee expected to be higher than usual as bid for inclusion in blockscpace market.
The priority fee is the difference between the fee cap set per transaction, and the base fee.
For PoW system without MEV influence, the priority fee level compensates for uncle risk:
See https://notes.ethereum.org/@vbuterin/BkSQmQTS8#Why-would-miners-include-transactions-at-all
"""
gas_target_process: List[Callable[[Run, Timestep], Gas]] = default(
[lambda _run, _timestep: 15e6] # Gas per block
)
"""
The long-term average gas target per block.
The current gas limit is replaced by two values:
* a “long-term average target” (equal to the current gas limit) == gas target
* a “hard per-block cap” (twice the current gas limit) == gas limit
EIP1559 gas limit = gas_target * ELASTICITY_MULTIPLIER
See https://eips.ethereum.org/EIPS/eip-1559
"""
# Initialize Parameters instance with default values
parameters = Parameters().__dict__