# radCAD-Ethereum Economic Model Study Note In this notebook, I study [CADLabs Ethereum Economic Model](https://github.com/CADLabs/ethereum-economic-model) in detail. The goal of this study is to understand the simulation model in detail and apply it to my own research on Ethereum blockchain (and layer 2) economics by extending the model to specific scenarios that I am interested in. One high level question we can explore in our research is as follows: >**What are the effects of Ethereum Proof of Stake Consensus Mechanism on Validators Revenues and Yields under Different Validator Environments and Network Scenario?** ## High Level Overview of the Model The first step is to have a high-level overview of the model before diving deep into its individual components. In my view, the entire CADLabs Ethereum economic model consists of three main parts: 1. **Data**: Historical data on ETH price, gas price, block rewards, used gas, and ether supply. 2. **Model**: Design and implementation of Ethereum economics using cadCAD model. 3. **Experiment**: Conduct experiments of our own using the model designed in previous step with different experimental settings. This is where the users can set up their own experimentation based on the problem thwy want to explore. For our research purpose, probabbly this is where we spend most of our time to design our won experiment. The model is both empirical in the sense that it uses historical real data, and numerical in the sense that it conducts various experiments using simulation. The questions I have for understanding the general structure of the model are listed below: - What are the main assumptions of the model? - What are the state variables used in the model? - What are the system parameters used in the model? - How does the system evolve over time? What is the unit of the basic time step? - What are the events happening in each time step? - What are the outputs from the model? ### Question 1: What are the main assumptions of the model? The model makes assumptions on two main levels: network-level assumptions and validator-level assumptions. On the network-level assumptions, it focuses on: - ETH price - PoW ETH issuance - Ethereum blockchain upgrade stage dates (not an assumption anymore since the dates are all confirmed as of this writing) - Average block size - Average base fee - Average priority fee - Maximum extractable value (hard to get good data) On the validator-level, the model's assumptions highlight four different aspects of validators: - Validator adoption - Max validator cap (Not so relevant) - Validator environments (DIY, Pool, Staking-as-a-Service) - Validator performance. Values of those variables are mostly assumed to be static, derived from historical data. The base fee is adjusted dynamically post EIP-1559. As a result, a improved model would use dynamically adjusted base fee rather than statis base fee. ### Question 2: What are the system state variables used in the model? There are total 47 system state variables in the model, spanning 8 different categories of Ethereum blockchain, including: *time, Ethereum ETH token, validators, POS reward and penalty, POS slashing, EIP-1559, MEV, and system metrics*. Overall, the model considers a wide range of system state variables in the model. The detailed exploration of those state variables is provided below. ### Question 3: What are the system parameters used in the model? Again, this is another big list of variables. The model considers 36 system parameters in the model, which makes it a very versatile. The users can fine-tune the model according to their unique needs and data they have access to. There are seven sets of parameters, including *time parameters, environmental processes, Ethereum system parameters, parameters from Eth2 specificaitons, validator parameters, rewards, penalties and slashing, and EIP-1559 transaction pricing parameters*. We will also discuss all of these parameters in great detail in the following sections. ### Question 4: How does the system evolve over time? What is the unit of the basic time step? First of all, the model uses one day (255 epochs) as a time step for the system evolution, for performance reasons. We can easily change it to per epoch (6 minutes 24 seconds) by configuring simulation runs. The system evolution over time is specified in the differential specification diagram ([Mathematical model specification](https://hackmd.io/@CADLabs/ryLrPm2T_) post provides detailed explanation of the mathematical model used to specify the model.). Here what we are interested in is how the system evolves with time. For instance, from one epoch to next epoch, how are states updated? In cadCAD model, this is governed by policy functions. Policy functions drive the process and the output of those policy functions will be used as input for state update functions. On a high level, the state update logic is as follows: ![](https://i.imgur.com/QXD0ECL.jpg) There are three processes that are updated by the update logic: - Ethereum blockchain stage, such as pre EIP-1559, post EIP-1559, etc. - Validator, such as number of active validators, number of awake validators, etc. - Ethereum blockchain system, such as ETH price, ETH staked ### Question 5: What are the events happening in each time step? During the simulation period, the model passes through ten processes, listed as in the top half of the graph above, in each time step: 1. Simulation starts. 2. Upgrade stage process. This process handles the transitions for one stage of the Ethereum upgrade stage to the next. Recall that the model has three three major milestones: Beacon chain, EIP-1559, and PoS. 3. Validator process. Validators who deposit their stake will first enter an activation queue. 4. ETH price and ETH staking processes. ETH price is calculated by taking the average of the past 12 months. Staking process is simple the number of validators multiplied by average effective balance of each validator. This process will update ETH price and ETH staked. 5. Base reward process. Since a validator performing optimally will earn one base reward, the different rewards earned and penalties suffered by a validator depends on the base reward. Thus, base reward needs to be updated prior to calculating individual rewards and penaltied each validatator will have. 6. Next four processes are concerned with rewards each validator could earn and penalties they could have and the aggregation of rewards and penalties across all validators in the network. For rewards, there are attestation rewards, sync committee rewards and block proposal rewards. For penalties, there are attestation penalty and sync committee penalty. 7. slashing process. This process deals with slashing rewards (i.e., whistleblower rewards) and penalties. This is when a validator is found to be behaving maliciously. Technically, the whistleblower get a rewrad and block proposer who includes this slashing in a block also gets a reward. But in its current design, only the block proposer gets the reward. Penalty also has two components but we don't get into the details yet. 8. EIP-1558 transaction fee process. This process deals with EIP-1559 related params, such as base fee per gas, total base fee, and total priority fee. These are all the processes that the model simulation goes through in each time step. The processes involve Ethereum system decisions such as base reward for validator reward/penalty calculation, system stage transitions, and EIP-1559 metrics, user deicisons such as validator rewards and penalties, and environmental processes such as validator adoption, ETH staking and ETH price. As I explained earlier, those processes drive the system. What happends next in the model is to update the system states according to those processes for each time step. This is how the model models system progression with time. ### Question 6: What are the outputs from the model? The outputs from the model are all system metrics in four different categories, including: *total online validator rewards, ETH issuance, total validator costs to maintain their validator status, and validator yields*. In terms of the radCAD python model used for implementing Ethereum economic model, it follows the general structure of cadCAD modeling framework: 1. Define system state variables. 2. Define system parameters. 3. Define policy functions. 4. Define state update functions. 5. Define partial state update blocks. 6. Simulation configuration. 7. Run the simulation for different experiements. I will cover the experiment architecture later. >[Main GitHub Repository](https://github.com/CADLabs/ethereum-economic-model) ## References & Useful Resources >[Blockchain Resource Pricing](https://ethresear.ch/uploads/default/original/2X/1/197884012ada193318b67c4b777441e4a1830f49.pdf) [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) [EIP-1559 FAQ](https://notes.ethereum.org/@vbuterin/eip-1559-faq#Why-would-miners-include-transactions-at-all) by Vitalik Buterin [Ethereum Agent Based Simulation](https://github.com/ethereum/abm1559) [Blockchain dynamic pricing of non-fungible resources](https://angeris.github.io/papers/block-space.pdf) and the [GitHub code](https://angeris.github.io/papers/block-space.pdf) for replicating the result. It is written in Julia but we can convert it to Python using ChatGPT. [Flashbots-EIP1559-MEV](https://github.com/flashbots/research-mev-eip1559/blob/main/eip1559-fb-collusion.ipynb) [Approximating user welfare and surplus with transaction data](https://ethresear.ch/t/approximating-user-welfare-and-surplus-with-transaction-data/14766/5) [EIP-4844 fee market analysis](https://ethresear.ch/t/eip-4844-fee-market-analysis/15078) and [GitHub repo for implementation using cadCAD](https://github.com/dcrapis/blockchain-dynamic-pricing) [How does the new Ethereum work anyway?](https://www.preethikasireddy.com/post/how-does-the-new-ethereum-work) [On distributed communication networks](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1088883) (very first article illustrating three types of network: Centralized, decentralized, and distributed) [Upgrading Ethereum](https://eth2book.info/bellatrix/) [Serenity design rationale](https://notes.ethereum.org/@vbuterin/serenity_design_rationale) --- ## Prerequisites ### Thoughts 1. The model currently does not consider variables such as network throughput, network latency, user waiting times, etc. The network analysis mainly focuses on validator metrics, like revenue and profit. This can be an area we can explore in our work. ### Nodes and Clients Reference: https://ethereum.org/en/developers/docs/nodes-and-clients/ Reference: https://www.geeksforgeeks.org/what-are-nodes-and-clients-in-ethereum/ Ethereum is a distributed network of computers (known as *nodes*) running software that can verify blocks and transactions. Let's tear this definition down to its individual components: - Ethereum is comprised of hundreds of thousands of computers. - Those computers are connected in a distributed manner. Check out the article [On distributed communication networks](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=1088883) for three types of network: Centralized, decentralized, and distributed. - Each computer connected to the Ethereum network is called a "node". Therefore, a "node" is simply a computer that performs certain functions on the Ethereum network. But in order to perform those functions, this node (i.e., computer) must have a specific type of software installed. - This software application is known as "client". In the computing world, a "client" refers to any type of software that is downloaded to our computer and used to communicate with other software or the server. For instance, Gmail is a software client that connects to Google gmail server and allows user to send and receive emails. - This specific software's job is to verify blocks and transactions. We can use clients to talk to the network, interact with the network, and communicate with other nodes in the network using various programming languages. In order to run a node, the following operations must be conducted in sequence: 1. Have a computer (hardware). 2. Install an operating system. 3. Download the client software application and install. 4. Setup and run the node. ### Attestation An attesttion is a vote for a block proposal. --- ## Model Assumptions In this model, there are multiple assumptions regarding __network__ and __validators__. We first list all those assumptions below. 1. Network level assumptions. - ETH token price. - PoW ETH token issuance. - Upgrade stage dates. - Simulation start date. - EIP-1559 activation date. - Proof of stake activation date. - Average block size. - Average base fee. - Average priority fee. - Pre-PoS. - Post-Pos. - maximum extractable value (MEV). 3. Validator level assumptions. - Validator adoption. - Normal adoption. - Low adoption. - High adoption. - Maximum validator cap. - Validator environments. - Validator environment categories and cost structure - Validate by running own hardware. - Validate by running own cloud. - Validate via a pool Staking-as-a-Service provider. - Validate via a pool hardware service provider. - Validate via a pool cloud service provider. - Validate via a custodial Staking-as-a-Service provider. - Validate via a self-custodial Staking-as-a-Service provider. - Validator environment relative weights. - validator environment equal-slashing. - Validator environment equal-uptime. - Validator performance. - Average uptime. - Frequency of slashing. - Participation rate. We analyze each asumption in great detail next. - Network level assumptions. A good resource about validator lifecycle in Ethereum 2 is provided [here](https://notes.ethereum.org/7CFxjwMgQSWOHIxLgJP2Bw#A-note-on-Ethereum-20-phase-0-validator-lifecycle). - ETH token price. The ETH price is set to the average daily price over the last 12 months. This value is constant, which is not ideal. In order to change this, we can update `eth_price_process` in `system_parameters.py` under `model` directory of the repo. - PoW ETH token issuance.This is the block rewards under Proof of Work consensus mechanism. It is set to the average daily block rewards over the last 12 months from Etherscan. In order to change this assumption, we can update `daily_pow_issuance` in `system_parameters.py` under `model` directory. - Upgrade stage dates. This assumption specifes different stages of the Ethereum protocol according to Ethereum roadmap. We can configure the model to reflect protocol behavious at different stage of the protocol. We achieve this by changing dates of different stages. - Simulation start date. The default start date is set to the current date. Apparently we need to modify this to start the simulation at a much earlier date so that we can start analyzing protocol behavior from pre-EIP1559 stage. The simulation start date can be set using `date_start` in `system_parameters.py` under `model` directory of the repo. - EIP-1559 activation date. It is stored in `date_eip1559` varibale in `system_parameters.py` file under `model` directory. The default value for this spec was August 4, 2021. Since the exact date for this [London mainnet upgrade](https://blog.ethereum.org/2021/07/15/london-mainnet-announcement) was on August 5, 2021, we have changed its value to the exact date. - Proof of stake activation date. This value is stored in `date_pos` in `system_parameters.py` file under `model` directory of the repo. The default value was March 1st, 2022. Since [The Merge](https://ethereum.org/en/upgrades/merge/) has happened on September 15, 2022, we have changed the default value to match the exact date. - Average block size. Under EIP-1559, the pre EIP-1559 block size (gas limit) has been replaced by two values: (1) a "long-term average target", which equals to pre EIP-1559 gas limit of 15 million gas per block, and a "hard per block cap" which is set to twice the pre EIP-1559 gas limit, i.e., 30 million gas per block. By default this model assumes that the block size equals to the long term average target gas, which is 15 million gas per block. To change this value, we can adjust the variable `gas_target_process` in `system_parameters.py` under `model` directory. __One improvement we can make here is to extend the model for dynamic block size and base fee__. Most ideal scenario is to do automatic updates via live data. - Average base fee. EIP-1559 defines a variable `max_fee_per_gas` to set a cap for the fee a user needs to pay for each transaction. This cap is the maximum fee in Gwei per gas a user will pay for the transaction. From a user perspective, a transaction under EIP-1559 includes a base fee and a priority fee (informally, tip). The transaction will be included in a block only if its fee cap is at least the block's base fee. That implies a user will pay at least a base fee no matter what. Now let's break it down to two simple questions: 1. *Who pays what?* If a transaction with priority fee/tip $\delta$, fee cap $c$, and gas limit $g$ is included in a block with base fee $r$, the transaction creator or user pays $$ g\cdot\min\{r+\delta, c\} $$ in Ether. 2. *Who gets the payment?* Revenue from the base fee which equals $g\cdot r$ is burned and the remainder $g\cdot\min\{\delta, c-r\}$ is transferred to the miner/proposer of the block. Since when the model was created, EIP-1559 has not been implemented yet, what the cadCAD team did was to take the 90-day median gas price by transaction using a [Dune Analytics query](https://dune.com/queries/91241). At that time this query returned median gas price of 30 Gwei/gas. Therefore, they set the default value of `base_fee_process` in `system_parameters.py` to be 30. Again, this is not ideal any more since we have actual on-chain data available for the base fee. Thus, we should explore the opportunity to update this data using on-chain anaylytics. - Average priority fee. To change the default value of this variable, we can update the value of `priority_fee_process` in `system_parameetrs.py` file under `model`directory of the repo. The model uses a staged approach for average priority fee estimation. - Pre-PoS: Priority fee = 2 Gwei/gas. A higher value is used for pre-POS to account for the compensation for uncle risk and to account for the possible sporadic transaction inclusion auctions. There are two resources to explore this further: (1)[Uncle risk/MEV miner fee calculation](https://notes.ethereum.org/@barnabe/rk5ue1WF_) and (2)[Why would miners include transactions at all](https://notes.ethereum.org/@vbuterin/eip-1559-faq#Why-would-miners-include-transactions-at-all) - Post-PoS: Priority fee = 1 Gwei/gas. - Maximum extractable value (MEV). The last one in the network-level assumptions is regarding MEV. In the model it is assumed that MEV will be extracted via some off-chain mechanism. As a result, it will be treated as a separate mechanism to EIP-1559. Basically, the assumption is that there is ano interaction between EIP-1559 and MEV, which is not quite true. So, one possible exploration for us would be to refine this model assumption by reviewing research and data on this topic. In the model this variable is stored in `mev_per_block` in `system_parameetrs.py` under `model` directory. Its measurement is in ETH token. By default this value is set to be zero ETH. In some analysis, a 3-month median value is used accoridng to the data from [Flashbots MEV Explore](https://explore.flashbots.net/). MEV is something that we need to dive deep into in order to have a better understanding of how it works and how we can incorporate into our model so that we can realistically estimate its impact on different system metrics and validator economics. Here I just list a few points: - What is Maximum Extractable Value (MEV, in PoW it was referred to as Miner Extractable Value)? MEV refers to the economic value a miner (or validator, sequencer, etc.) can extract/make through their ability to include, exclude, or re-order transactions within the blocks they produce. They can do this because they have this ability. The term "maximum" extractable value refers to the total opportunity space for value extraction within a given block. In terms of value extraction strategies employed by the MEV orchestrators, it can be "neutral" or "toxic". Neutral MEV extraction strategies usually result in better price (lower transaction fees) for the end users. Toxic MEV strategies, on the other hand, refers to practices such as frontrunning, sandwich attackes, result in worse price for end users. - Some resources on MEV: (1)[MEV and me](https://research.paradigm.xyz/MEV); (2)[Miner Extractable Value with Phil & Georgios](https://www.youtube.com/watch?v=tv0CkmcoGkM); (3)[ Frontrunning in Decentralized Exchanges, Miner Extractable Value, and Consensus Instability](https://www.youtube.com/watch?v=vR1v7AQ8i3k); (4)[MEV-research](https://github.com/flashbots/mev-research/blob/main/resources.md); (5)[Flashbots MEV Overview](https://docs.flashbots.net/flashbots-auction/overview); (6)[MEV and EIP-1559](https://hackmd.io/@flashbots/MEV-1559) - Validator level assumptions. Four different aspects of validators are considered in the model. They include: (1) __Validator adoption__; (2)__maximum validator cap__; (3) __validator environments__; and (4) __validator performance__. Next, we will dive deep into each one. 1. *Validator adoption*. It is the rate at which new validators enter activation queue per epoch. This adoption rate will result in an implied ETH staked value over time on the network. Three linear adoption scenarios are considered in `Validator Revenue and Profit Yields` notebook in `experiments` directory (see figure below). Historical adoption rate has been approximately linear. ![](https://i.imgur.com/DArXoiJ.png) - Normal adoption. Assume an average 3 new validators each epoch. This rate corresponds to the historical average number of newly activated validators per epoch between January 15, 2021 and July 15, 2021 as per [beaconscan](https://beaconscan.com/stat/validator). - Low adoption. Assume an average of 1.5 new validators each epoch, i.e., 50% lower than the normal adoption rate. - High adoption. Assume an average of 4.5 new validators each epoch, i.e., 50% higher than the normal adoption rate. Here is how the activation queue is modelled: Validators who deposit 32ETH are activated and allowed to validate once they pass through the activation queue. The activation queue has a maximum rate at which it can process and activate new validators. Thus, the rate at which it can process the new validators determine the adoption rate. There may be times where the number of validators who want to be activated and validated are more than what the queue can handle per epoch. - The normal adoption rate is used as the default adoption rate for all experiments in the model. We can change its default value by changing `validator_process` variable in `system_parameters.py` under `model` directory. 2. *Maximum validator cap*. This is the limit on the number of validators that are validating ("awake") at any given time. Here, we need to differentiate two definitions regarding validators: - Active validators: They refer to all the validators with ETH staked. Not all active validators are validating at any given time. - Awake validators: They are a subset of active validators that are actually validating and receiving rewards. In the model the value of this variable is stored in `MAX_VALIDATOR_COUNT` in `system_parameters.py` file under `model` directory and it's default value is set to `None`. Thus the maximum validator cap feature is disabled in the model. 3. *Validator environments*. The validator environments feature accounts for different deployment approaches validators are using to access the network. It has important implications for validator economics since different setup strategies affect the validator cost thus profit. There are three main deployment types to participate Ethereum 2.0. They include: - **Do-It-Yourself (DIY)**: Individuals choose to run their own software, either on a hardware or cloud service. - **Staking-as-a-Service (Saas)**: Validators might prefer delegating staking responsibilities to a Staking-as-a-Serivce provider. This type might come in two flavors: - A validator might want to be fully serviced. - A validator might custody (i.e., give up their ownership for the duration of custody) their stake with a custodian, while connecting in via API to run their own validator client. For instance, major exchanges such as Coinbase and Binance offer Saas to their retail clients, accepting all sizes of stakes. Large institutions and individuals with large ETH might also prefer this option to avoid technical requirements and security of their assets. - **Staking pools**: As its name suggests, this is a way for ETH holders with less than 32ETH to participate in the decentralization of the network. DIY approach requires an individual to have at least 32ETH due to the network requirements. Those individuals with less than 32ETH can join the network by pooling their ETH with other individuals to meet the requirement. One difference of staking pools from Saas is that individuals can participate in the network in a decenralized way, as opposed to relying on centralized exchanges as in Saas. For instance, Rocket pool and Lido are examples of staking pools. The radCAD model implements seven different validator environment categories that were suggested by [Horban/Borgers Ethereum 2.0 Economic Review](https://tomborgers.medium.com/ethereum-2-0-economic-review-1fc4a9b8c2d9). These categorizations are based on the current active validator staking services. Details can be found on [Ethereum Staking Services Overview](https://beaconcha.in/stakingServices). For the respective hardware setup cost and cost assumptions, please refer to [Cost of Validating](https://docs.google.com/spreadsheets/d/1y18MoYSBLlHZ-ueN9m0a-JpC6tYjqDtpISJ6_WdicdE/edit#gid=1146360926) spreadsheet. - Run own hardware validator (`DIY-Hardware`). Individuals choose to run the software themselves on a hardware. - Setup: Individual running a beacon node and validator client with their own hardware. - Economics: - Revenue: Receive full revenue yields. - Cost: Hardware (one time), elctricity (variable) and bandwidth (variable). - Run own cloud validator (`DIY-Cloud`). Individuals choose to run the software themselves on a cloud service. - Setup: Individual running a beacon node and validator client on a cloud service. - Economics: - Revenue: Receve full revenue yields. - Cost: Cloud service cost (variable). - vs. `DIY-Hardware`: Individual may run multiple validators on a cloud service and the cost/validator might be lower than `DIY-Hardware`. - Validate via a pool staking-as-a-service provider (`Pool-StaaS`). The Saas provider provides all the infrastructure (validator client and beacon node). In this environment, the keys are managed by the SaaS provider, which means individuals give up the ownership for the duration of staking. - Setup: Individual staking via a SaaS service provider with all the infrastructure. - Economics: The validator pays a fee for the SaaS service, usually as a percentage of the full revenue yield that would have been earned under DIY. - Validate via a pool hardware service provider (`Pool-Hardware`). - Setup: A node operator sets up both beacon node and validator client on their own hardware infrastructure and pools ETH from stakers to create multiple validators. - Economics: Costs (hardware, electricity, bandwidth) and revenue are shared amongest validators in the pool. - validate via a pool cloud service provider (`Pool-Cloud`). - Setup: A node operator sets up both beacon node and validator client on their own cloud infrastructure, and pools ETH from stakers to create multiple validators. - Economics: Costs (hardware, electricity, bandwith) and revenue are shared amongest validators in the pool. - Validate via a custodial staking-as-a-service providerm (`StaaS-Full`). - Setup: Validator stakes via a custodial Staking-as-a-Service provider, who sets up both beacon node and validator client. - Economics: The StaaS provider charges a fee (a certain percentage of the staking yiled) to the validator. Note that the staker will give up the ownership of their stakes during the staking period. - Validate via a non-custodial staking-as-a-service provider (`StaaS-Self-Custodied`). - Setup: The validator runs its own validator client software, but uses a beacon node operated by a StaaS provider via API. Basically, this is a half-half scenario. Platforms like [Infura](https://www.infura.io/) and [Alchemy](https://www.alchemy.com/) provide such services. - Economics: StaaS provider charges a fee to the validator (as a percentage of the staking revenue), but this percentage usually is lower than `StaaS-Full`. The radCAD model allows user to specify their own validator environments and cost structure. This can be configured via `validator_environments` variable in `system_parameters.py` file under `model` directory. Let's try to understand one of the setup in the model for validator environments. The assumption is as follows: ``` ValidatorEnvironment( type="diy_hardware", percentage_distribution=0.37, hardware_costs_per_epoch=0.0014, ), ``` The assumption corresponds to the situation `DIY-Hardware`, where the validator runs its won beacon node and validator client on its own computer. 37% of all validators of the network are assumed to be validators of such kind. [Eth2 Economic Review](https://drive.google.com/file/d/1pwt-EdnjhDLc_Mi2ydHus0_Cm14rs1Aq/view)(page 26) estimate that average cost per validator is about $9.47/month. Since the unit here is per epoch basis, we can calculate the cost per epoch as follows, assuming 30 days in a month: $$ \begin{align} \text{hardware cost per epoch}&=\dfrac{$9.47}{\text{Number of epochs/month}}\\ &=\dfrac{$9.47}{\dfrac{30(days)\times 24(hours)\times 60(mins)\times 60(secs)}{12(sec)\times 32(slots)}}\\ &=\dfrac{$9.47}{6750}\\ &=$0.0014/epoch \end{align} $$ Thus, `hardware_costs_per_epoch` is assumed to be $0.0014/epoch in the model. The second variable for each of the validator environments is `percentage_distribution`. This variable involves validator environments relative weights. The assumption for those weights in the model is given below: - `DIY-Hardware`: 37% - `DIY-Cloud`: 13% - `Pool-StaaS`: 27% - `Pool-Hardware`: 5% - `Pool-Cloud`: 2% - `StaaS-Full`: 8% - `StaaS-Self-Custodied`: 8% It should add up to 100%. The values can be changed accordingly via `percentage_distribution` variable mentioned earlier. Another variable associated with different types of validator environments is slashing. The assumption of the model is equal slashing, which means slashing happens equally among all 7 types of validator environments. In reality, however, slashing happens more often in some validatos environments (e.g., institutional validators getting their setup wrong). For simplicity, the model assumes slashing events happen euqally among all types of validator environments considered in the model. One more aspect of validator environment we need to cover before we move on, which is validator uptime. Realistically, we would expect certain types of validators have a better uptime than others (e.g., cloud based validators have better uptime than hardware based validators). For simplicity, the model assumes equal validator uptime for all 7 types of environments considered. Once the respective data available in the future, this assumption can be dropped and empirical values can be used intstead. 4. *Validator performence*. Three variables are considered for validator performance: (1) average uptime, (2) frequency of slashing events, and (3) participation rate. - Average uptime. An average uptime of 98% is assumed in the model. In reality, the validator uptime varies between 95% and 99.7%, with average of 98%. The value of this variable is stored in `validator_uptime_process` variable in `model_parameters.py` file under `model` directory. - Frequency of slashing. By default, the model assumes one slashing event per 1000 epochs. It's value is stored in `slashing_events_per_1000_epochs` in `system_parameters.py` file under `model` directory. As more data is available regarding different slashing frequencies for different validator environments, this value can be updated accordingly. - Participation rate. This variable refers to the percentage of validators online. For instance, if the network has 100K total activated validators and 98k of them online at a certain time, then the participation rate is 98% at that time. The model assumes that validators are either online, fulfilling their duties perfectly, or offile and not fulfilling their duties. It does not consider validators that fulfill some of their duties, and not others. Its value is captured in `validator_uptime_process` variable in `model_parameters.py` file under `model` directory. Essentially, the value of this variable is the same as the average uptime of a validator, kind of bernoulli and binomial situation. Okay, so much for the model assumptions. To recap, the model assumption involves two aspects of the network with each aspect concerning with multiple variables: --- ## Structure of the software architecture ![](https://i.imgur.com/0lD0Xiw.png) This is a typical structure for complex systems modeling. It containts two main components: 1. `data`, `docs`, `logs`, `tests` folders that contain data documentations, log information, and validation tests, respectively. 2. `experiments` and `model`. They contain payload of the model. We will look at each module one by one. ### `model` directory `model` |`parts` :-----------------------------------:|:-----------------------------------: ![](https://i.imgur.com/fXeQK5P.png) |![](https://i.imgur.com/f661KbG.png) #### `state_variables.py` module This module defines all state variables, their types, and default values. The format is `name: type = default value`. It is used to instantiate a dataclass containing all the state variables and their initial states. There are total 47 state variables in this module in 8 different categories: - *Time state variables*: `stage` and `timestamp`. - *Ethereum state variables*: `eth_price`, `eth_staked`, `eth_supply`, `supply_inflation`, `network_issuance`, `pow_issuance`. - *Validator state variables*: ` number_of_validators_in_activation_queue`, `average_effective_balance`, `number_of_active_validators`, `number_of_avake_validators`, `validator_uptime`. - *Reward and penalty state variables*: `base_reward`, `validating_rewards`, `validating_penalties`, `source_reward`, `target_reward`, `head_reward`, `block_proposer_reward`, `sync_reward`, `attestation_penalties`, `sync_committee_penalties`. - *Slashing state variables*: `amount_slashed`, `whistleblower_reward`. - *EIP-1559 state variables*: `base_fee_per_gas`, `total_base_fee`, `total_priority_fee_to_miners`, `total_priority_fee_to_validators`. - *MEV state variables*: `total_realized_mev_to_miners`, `total_realized_mev_to_validators`. - *System metric state variables*: `validator_eth_staked`, `validator_revenue`, `validator_profit`, `validator_revenue_yields`, `validator_profit_yields`, `validator_count_distribution`, `validator_hardware_costs`, `validator_cloud_costs`, `validator_third_party_costs`, `validator_costs`, `total_online_validator_rewards`, `total_newtrok_costs`, `total_revenue`, `total_profit`, `total_revenue_yields`, `total_profit_yields`. Let's explore them one by one. 1. `stage`: the stage of the network upgrade process. It has four possible values: - `ALL=1`: Transition through all stages. - `BEACON_CHAIN=2`: Pre-1559. - `EIP1559=3`: Post-1559, Pre-POS. - `PROOF_OF_STAKE=4`: Post-POS (Post-Merge) 2. `timestamp`:Python `datetime` object. It starts from `date_start` parameter. 3. `eth_price`: Imported from `historical_values.py` under `data` folder 4. `eth_supply`: Obtained using `etherscan` API. The details of this API is in `api` folder under `data` directory. 5. `eth_staked`: Obtained using `beaconchain` API. The details of this API is in `api` folder under `data` directory. 6. `supply_inflation`: The annualized ETH supply inflation rate as a percentage. It is calculated in `historical_values.py` within `data` folder using historical daily ETH supply data. 7. `network_issuance`: Total network issuance in ETH. Not sure yet if this is daily network issuance or annual network issuance. I am guessing this data is obtained from `ether-block_rewards.csv` file under `data` directory. This data is processed in `historical_values.py` within the same directory. 8. `pow_issuance`: Total PoW issuance in ETH. I believe this data is obtained in the same way as `network_issuance`. 9. `number_of_validators_in_activation_queue`: The number of validators in the activation queue, waiting for confirmation to become new validator in the network. 10. `average_effective_balance`: The validator's average effective balance. Since each validator stakes 32 ETH to become a validator, its value is initialized to Gwei equivalent of 32 ETH. 11. `number_of_active_validators`: Total number of active validators. These are all the validators staked in Ethereum blockchain. Note that not all active validators are validating all the time. In the _maximum validator cap_ assumption of the model, there is another relevant concept called _awake validator_, which is a subset of active validators that are actively validating and doing attestation in an epoch. This value is obtained by calling beacon chain API. We can get at least three types of information using this API: - *number of active validators*. - *data for a specific epoch*. - *total validator balance*. 12. `number_of_awake_validators`: There was a proposal to cap the number of active validators validating at a given time and the proposed cap was $2^{19}=524,288$. Those validators actively validating at a given time are called "awake" validators and the ones not actively validating (maybe forcefully or offline) are called "slept" validators. Slept validators do not earn rewards. Since each validator needs to stake 32 ETH, the cap corresponds to at least ~16.78M ETH staked. As of this writing, total 17.7M has been staked, which is larger than the cap. In this case, this cap implies that at any given time, a small fraction of validators will be selected probabilistically and forced to sleep. They don't earn any rewards during this 'sleep' period. As a result, the value of this variable is initialized by `number_of_awake_validators=min{number_of_active_validators, MAX_VALIDATOR_COUNT}`. The `MAX_VALIDATOR_COUNT` is a parameter from Eth2 Specification whose value is defined in `system_parameters.py` file. It is one of the 18 Eth2 parameters specified in the same file. 13. `validator_uptime`: Percentage of time that a validator is online. Factors such as validator internet, power and hardware impact this variable. By default, the value is 1 (100%). 14. `base_reward`: All validator rewards and penalties are calculated in terms of base reward. Under perfect condition where a validator is online 100% of the time and behaves optimally without being penalized and slashed, it should get 1 base reward. Its default value is set to zero in the model. The total theoretical long-run averae reward per epoch that a validator can get is determined by the `get_base_reward` function. The assumption is that a validator can optimally earn one base reward per epoch over a long time horizon. This takes into account both per-epoch (attestation) duties and intermittent (sync committee vote, and propose a block) duties. The base reward is split up into five pieces where each piece is allocated to one of the five duties a avalidator has: ![](https://i.imgur.com/Q4KdP46.png) 15. `validating_rewards`: The total rewards received by the validator. For details or layman's explanations for many terms that follow, check out this article [Phase 0 for Humans](https://notes.ethereum.org/@djrtwo/Bkn3zpwxB) and [this one](https://vitalik.ca/general/2018/12/05/cbc_casper.html). Let's look into validator duties, rewards, and penalties a little bit further in detail. There are two types of validator duties: - Attestation duties: duties to make an attestation in each epoch that gets included quickly and attests to the correct chain. What this implies is that if a validator does not attest to the correct chain, that validator may get penalized. - Non-attestation duties: Tasks that are not connected to attesting. ANny individual usually is assigned to those kind of duties more rarely. - Overall, the complete list of duties is: - `TIMELY_HEAD`: referred to as `head_reward` below. This the duty to submit an attestation that correctly identifies the head of the chain. - `TIMELY_TARGET`: Referred to as `target_reward`. This is the duty to submit an attestation that correctly identifes the Casper FFG (Friendly Finality Gadget) target. - `TIMELY_SOURCE`: Referred to as `source_reward`. This is the task to submit an attestation that correctly identifies the Casper FFG source. - Sync committee participation. First of all, sync committees allow light clients to easily sync up the chain with very low ocmputational and data cost. How is this achieved? That is due to those sync committees. A sync committee is a group of 512 validators, randomly selected by the network. A new committee will be choses in every 256 epochs (approx. 27 hours). What this committee does is to continually sign block headers for each new slot in the beacon chain. Light clients can trust those block headers. The light clients do not need to know the entire validator set or see the full beacon chain. Why is this important? Remember that light clients are small nodes that run on lower resource kit, e.g., mobile devices. Those clients are not capable of seeing the entire blockchain due to computational and data limitations. Thus, those light clients can sync the chain using the output of the sync committees. In terms of reward, if you are a validator of a sync committee, you need to submit the sync committee signature. - Proposing blocks: You are tasked with proposing a block, i.e., you are randomly selected by the network to be the proposer of the block for a slot. The total reward includes the following: - attestation rewards, including `head_reward`, `source_reward`, and `target_reward`. - block proposal reward, which is `block_proposer_reward` below. - sync vote reward, whichi is the `sync_reward` below. 16. `validating_penalties`: The total penalty received due to failing to fulfill validator duties (attestation, sync vote, and block proposal). When a validator fails to perform any of the duties that the validator is assigned to, it will get penalized. The value is initialized to be zero in the model. 17. `source_reward`: See the explanation in `validating_rewards`. Its default values is zero in the model. 18. `target_reward`: See the explanation in `validating_rewards`. Its default values is zero in the model. 19. `head_reward`: See the explanation in `validating_rewards`. Its default values is zero in the model. 20. `block_proposer_reward`: See the explanation in `validating_rewards`. Its default values is zero in the model. 21. `sync_reward`: See the explanation in `validating_rewards`. Its default values is zero in the model. 22. `attestation_penalties`: See the explanation in `validating_rewards`. Its default values is zero in the model. 23. `sync_committee_penalties`: See the explanation in `validating_rewards`. Its default values is zero in the model. 24. `amount_slashed`: This is the total penalty applied to a slashing behavior by validators. This is another important penalty that we need to analyze in detail. First of all, this is a severe penalty. Slashing occurs when validators make attestations and block proposals that violate specific rules of protocols. That could be a behaviour that is potentially an attack to the protocol. Getting slashed means losing a significant amount of stake and ejection from the protocol. The slashing conditions are: - Proposing two different (conflicting) blocks in the same slot. - Proposing two different (conflicting) Casper FFG attestations in a single epoch. There should be only one Casper FFG attestations in a block. - Submitting a Casper attestation that completely surrounds or is surrounded by another Casper FFG attestation. For details, refer to the article [here](https://docs.google.com/document/d/1r640UQOm2z-Q9nsJzqBq3BVgCtTL1_Yc7WnPp4jEBgk/edit#heading=h.7omdnplqtme3). This is more of a "punishment" than penalty. [Upgrading Ethereum](https://eth2book.info/bellatrix/part2/incentives/slashing/) provides all the details about slashing. When a validator gets slashed, the validator immediately lose 1/32 of its stake. Since the stake is 32 ETH per validator, that means it loses 1 ETH immediately upon getting slashed. This is the initial penalty. This validator might get slahed even further to the degree of its entire stake in a correlation penalty if it was found that the slashing might be the result of an attempt to finalize a conflicting block (an attack on the protocol). 25. `whistleblower_rewards`: In order to verify slashing and take actions against the offender, the evidence needs to be included in a block. How is it done? The answer is to incentivise validators to make effort on including evidence on a block. This reward is `B/52` where B is effective balance of the slashed validator. Thus, a block proposer who includes the slashing in a block will get rewarded for doing so. In order to incentivise nodes searching for and discover slashable behaviour, in the code implementaiton of this aspect of the network, "whistleblower reward" has two components: with the whistleblower receiving 7/8 of the above mentioned reward (B/52) and the proposer receiving 1/8 of the total reward. However, in its current functionality of Beacon chain, the proposer of a block incliding slashing gets all the reward. 26. `base_fee_per_gas`: It is base fee burned, in Gwei per gas. It's dynamically updated for each block. Its default value is 1 in the model. 27. `total_base_fee`: The total base fee burend for all transactions included in "blockspace"(what does blockspace mean here?). 28. `total_priority_fee_to_miners`: It is the total priority fee to the miners pre-PoS for all transactions in blockspace. Its value is initialized to zero. 29. `total_priority_fee_to_validators`: It is the total priority fee to the validators post-PoS for all transactions in blockspace. Its value is also initialized to zero. 30. `total_realized_mev_to_miners`: This variable captures total realized MEV to miner pre-PoS. 31. `total_realized_mev_to_validators`: This variable captures total realized MEV to validators post-PoS. Both of those mev values are initialized to zero in the model. The following list includes system metric state variables. 32. `validator_eth_staked`: This variable captures validator ETH staked per validator environment. Recall the model considers seven validator environments. Thus, this is a 7D-vector. For instance, the first element of the list contains the total number of ETH staked by the validators who run their own hardware (`DIY-Hardware`). We know that they accoount for 37% of the entire validator set of the network. Approx. 17.7M ETH are staked as of this writing. So, the ETH staked for `DIY-Hardware` validator should be roughly about 6.55 ETH, which is the first entry of this list. The values of this list is initialized to be a numpy array with all zeros in its elements. 33. `validator_revenue`: This variable captures total revenue (income received = rewards - penalties) for performing PoS duties per validator environment. Again, it is a 7D vector, corresponding to seven validator environments discussed earlier. This list is also initialized with zeros (numpy array). 34. `validator_profit`: It captures the total profit (revenue - cost) per validator environment, initialized to zero. 35. `validator_revenue_yields`: It is the total annualized revenue (income received = rewards - penalties) as a percentage of investment amount per validator environment. Since the investment amount per validator is 32 ETH, it is equal to total annualized revenue divided by 32 ETH. It's initialized by a numpy array of zero values. 36. `validator_profit_yields`: This is essentially return on investment (ROI), the ratio of total annualized profit to the initial investment, which equals 32 ETH per validator. This is also based on per validator environment and initialized by a numpy array of zero values. 37. `validator_count_distribution`: This variable captures total number of validators per validator environment. 38. `validator_hardware_costs`: This variable contains total validator hardware operations costs per validator environment. For validator with cloud service, this value should be zero. Again, it is initialized to a numpy array with zeros everywhere. One thing I am not certain at the moment is that if this is per epoch, or annual. 39. `validator_cloud_costs`: This state variable contains total validator cloud operation cost per validator environment. The value of this variable should be zero for validators who run their own hardware. 40. `validator_third_party_costs`: This state variable captures total third-party fee costs per validator environment. It is usually a certain percentage of the staking yield. The value of this variable should be zero for validators who run their own node in hardware or cloud. 41. `validator_costs`: This variable captures total validator cost per validator environment. It is the total sum of `validator_hardware_costs`, `validator_cloud_costs`, and `validator_third_party_costs`. For some hybrid validator environments, the total cost comprises of more than one costs listed above. For instance, some validators run their own Client node on their own hardware but connects to a beacon node using a third-party API, for which they pay a fee. In this situation, the total validator cost will be the sum pof hardware cost and third-party cost. 42. `total_online_validator_rewards`: This variable captures total rewards received by the entire online validators. Its values is initialized to zero. 43. `total_network_costs`: This is the total validator operational costs securing the network. It is the summation of `validator_costs` for all validators of the entire blockchain. 44. `total_revenue`: This variable captures total validator revenue, not by per validator operating environment. It is calculated as `np.sum(validator_costs)`. 45. `total_profit`: This state variable captures the total validator profit, which is equal to `total_revenue-total_network_costs`. Its default value is set to zero as well. 46. `total_revenue_yields`: This variable contains total annualized revenue for all validators, as a pencentage of total investment (32 ETH multiplied by the number of active validators in the network.). It is the ratio of the sum of all rewards distributed minus all penalties given across all validators in an annual bases to the total staked ETH in the network. 47. `total_profit_yields`: It contains total annualized profit for all active validators in the network, as a percentage of the total ETH staked. Both of these variables are initialized to zero. #### `system_parameters.py` \& `stochastic_processes.py` module The `system_parameters.py` module defines total 36 system parameters of the module, their data types and default values. What we do next is to understand each of the system parameter definitions in detail. Each system parameters is defined in the form of **syatem parameter key: system parameter type = default value** But before that, let's quickly go through the `stochastic_process.py` module. ##### `stochastic_processes.py` This module create two stochastic processes, one for ETH token price and one for validator adoption rates. - ETH token price ('create_eth_price_process'). In this stochastic price setting, ETH price is set to be between [1500, 3000]. The minimum ETH price is $1,500. If we want to use a different value, we can change its value within this module. - Validator adoption (`create_validator_process`). This stochastic process is a Poisson arrival process with the rate of arrival is set to be 1/validator_adoption_rate. The validator_adoption_rate is set to be 4. If we want to use a different adoption rate, we can change its value within this module. This module also generates a random number for validator uptime from a uniform distribution in the domain of [0.96, 0.99]. If we want to use a different domain, we can also change the values within this module. At last, `create_stochastic_process_realizations` function in this module creates 5 samples for ETH price, validator adoption, and validator uptime. ##### `system_parameters.py` Now, let's focus on the system parameters, another important component of the model. This module defines systen parameters, their types and default values using Python dataclass. As mentioned earlier, the model considers 37 system paramaters. We will understand them in detail one by one. Our deep understanding of those parameters will be very valuable for doing various parameter sweeping in the experiment stage. 0. Configure validator environment distribution. Recall from state variables definition that the model considers seven different validator categories, with each one having different cost structure and percentage of total validator set. This module first initialize those percentages and cost structure associated with each type of validator. For instance, `DIY-Hardware` consitutes 37% of the entire validators of the network and their hardware cosr per epoch is $0.0014. Similar information for all other 6 validator types are provided here. All those percentages should add up to 1. If not, the percentage distribution is normalized to a total of 100%. One note here is that the type of `validator_third_party_costs_per_epoch` is `Percentage_per_epoch`, not `USD_per_epoch`. This is because third party validator service providers will charge a percentage of the staking yield, not the dollar value of the staking yield. For instance, if we use COinbase staking service, they charge 20% of the staking yield. Thus, the type of the third-party cost is percentage. 1. `dt`: time parameter. This is the simulation timestep of the process, in epochs. For instance, if dt=100, each timestep is 10o epochs. Its default value is set to be `DELTA_TIME`, which is defined in the `simulation_configuration` module under `experiments` folder and set to be equal to 255 epochs, which is a day. Therefore, the default timestep of the model is one day. 2. `stage`: This is the different stages of the the Ethereum network upgrade process. By default, it is set to "ALL", which simulates the transition from the current network stage at today's date onwards. Those stages are defined in `types.py` in `model` folder. 3. `date_start`: start date for the simulation. The default value is set to the current date. In order to simulate the entire transition, we have to reset this default value to a date prior to EIP-1559. London upgarde was implemented on August 5, 2021 and Beacon chain genesis was initiated on December 1st, 2020. So we can set the start time of the simulation to January 1, 2020 so that the simulation will include about a year of PoW stage. 4. `date_eip1559`: the date when EIP-1559 was implemented, which is August 5, 2021. 5. `date_pos`: the date when proof of stake was switched on, which is September 15, 2022. 6. `eth_price_process`: a process that returns the ETH spot price at each epoch. Currently, its default value is static, equals to the average ETH price in the past twelve months from Etherscan. 7. `eth_staked_process`: a process that returns the ETH staked at each epoch. When its value is set to `None`, the model is driven by validator process, where new validators enter the system and stake accordingly. 8. `validator_process`: a process that returns the number of new validators in the activation queue per epoch. This is used when the model is not driven by `eth_staked_process`. In the model, its value is static, set to be euqal to the average value of the last 6 months. 9. `daily_pow_issuance`: The average daily PoW issuance. Its value is obtained from beaconscan and derived in `data.historical_values.py` module. 10. `mev_per_block`: By default this parameter value is set to zero. Thus, the model only considers the influence of PoS incentives on validator yields. To investigate its influence on validator yields, we can set it to a reasonale value. One valid assumption could be 30-day realized MEV from https://explore.flashbots.net/. We then can use the value from flashbots to set the `mev_per_block` parameter. The following 18 uppercase parameters are all from Eth2 specification. Thus, usually we don't touch those parameter default values. 11. `BASE_REWARD_FACTOR`: A denomination that is used to change the issuance rate of the PoS system. Most validator rewards and penalties are calculated in terms of the base reward. Its value is set to 64. The graph below illustrates the weights used for validator rewards and penalties: ![](https://i.imgur.com/Q4KdP46.png) 12. `MAX_EFFECTIVE_BALANCE`: A max effective balance is 32 ETH under PoS. A closely related concept is validator's effective balance. A validator's effective balance is a value less than validator's total stake, which is 32 ETH. This effective balance is used to calculate incentives and for voting. 14. `EFFECTIVE_BALANCE_INCREMENT`: A validator's effective balance can only change in steps of `EFFECTIVE_BALANCE_INCREMENT`. Its default value is 1 ETH. 15. `PROPOSER_REWARD_QUOTIENT`: Used to calculate the proportion of rewards distributed to proposer. Its default value is 8. It means that under ideal situation, a validator who proposes a block will earn 8/64 portion of the total base reward for each block. Basically, the total base reward for a block is 64 slices of pie and a block proposer will be rewarded with 8 slices of pie. Similar explanations apply to other quotient as well. 16. `WHISTLEBLOWER_REWARD_QUOTIENT`: Used to calculate the proportion of the effective balance of the slashed validator distributed between whistleblower and proposer. Its default value is set to be 512. 17. `MIN_SLASHING_PENALTY_QUOTIENT`: Used to calculate the penalty applied to a slashable offense. Its default value is set to $2^6=64$. 18. `PROPORIONAL_SLASHING_MULTIPLIER`: In addition to the minimum slashing penalty applied to a slashable offense, there is another penalty (much severe) if it is found that this slashable offense is a coordinated attack on the protocol. This paramater implements this additional penalty by scaling the penalty proportional to the total slashing for the current epoch. 19. `TIMELY_HEAD_WEIGHT`: This parameter is used to calculate the reward received for getting a head vote right in time and correctly. Its default value is 14. The formula is `head_reward = (TIMELY_HEAD_WEIGHT/WEIGHT_DENOMINATOR) * base_reward` 20. `TIMELY_SOURCE_WEIGHT`: This parameter is used to calculate the reward received for getting a sourcevote right in time and correctly. The default value is 14 as well. The formula is `hsource_reward = (TIMELY_source_WEIGHT/WEIGHT_DENOMINATOR) * base_reward` 21. `TIMELY_TARGET_WEIGHT`: This parameter is used to calculate the reward received for getting a target vote right in time and correctly. The default value is 26. The formula is `target_reward = (TIMELY_HEAD_WEIGHT/WEIGHT_DENOMINATOR) * base_reward` 22. `SYNC_REWARD_WEIGHT`: This parameter is used to calculate the reward for attesting as part of a sync committee. The default value is set to be 2. 23. `PROPOSER_WEIGHT`: This paramater is used to calculate the reward for successfully proposing a block. Its default value is 8. 24. `WEIGHT_DENOMINATOR`: It is used as the denominator in incentive calculations, and set to be 64. It implies that the base reward is split into 64 slices. Rewards and penalties are calcuted by saying how many slices a validator would get or would be penalized. 25. `MIN_PER_EPOCH_CHURN_LIMIT`: It is the minimum number of exits per epoch. The reason to have such limit is that in PoS system, it is important that validator set does not change too quickly. Its default valu is set to be 4. This minimum number guarantees that some small amount of churn is always allowed. 26. `CHURN_LIMIT_QUOTIENT`: This parameter is used in conjunction with `MIN_PER_EPOCH_CHURN_LIMIT` to calculate the actual number of validator exits and validator activation allowed in an epoch. The number of exits allowed is calculated as follows: `max(MIN_PER_EPOCH_CHURN_LIMIT, n // CHURN_LIMIT_QUOTIENT)`, where `n` is the number of active validators. Its default value is set to be $2^{16}=65,536$. 27. `BASE_FEE_MAX_CHANGE_DENOMINATOR`: This parameter sets the max rate EIP-1559 base fee can change per block. Its default value is 8. Recall that base reward denomation is 64, thus this max change suggests a max change of 12.5% per block. 28. `ELASTICITY_MULTIPLIER`: This parameter is used to calculate gas limit from EIP-1559 gas target. Its default value is set to 2, which is doubling gas limit of the parent block. 29. `MAX_VALIDATOR_COUNT`: This parameters is the cap on the "awake" validators. Not all active validators may be validating at a given time. Currently the limit is set to be $2^{19}=524,288$ validators. Each validator stakes 32 ETH, thus this cap translates to 16,777,216 ETH staked. The model disables this cap by setting its default value to `None`. Next set of parameters are validator parameters. 30. `validator_uptime_process`: This is the combination of validator internet, power and technical uptime. Its default value is calculated as `max(0.98, 2/3)`. The minimum uptime is the inactivity leak threshold of 2/3 but the model does not consider inactivity leak proces. 31. `validator_percentage_distribution`: The percentage of validatos in each of the seven environments, normalized to 100%. This parameter is a 7-D numpy array. 32. `validator_hardware_costs_per_epoch`: The validator hardware cost per epoch in **dollars**. It is also a 7-D numpy array. 33. `validator_cloud_costs_per_epoch`: The validator cloud cost per epoch in **dollars**. It is also a 7-D numpy array. 34. `validator_third_party_costs_per_epoch`: The validator third-party cost per epoch as a percentage of validator staking rewards It is also a 7-D numpy array. It is used as Staking-as-a-Service fee. The unit for this cost is percentage. 35. `slashing_events_per_1000_epochs`: The number of slashing events per 1000 epochs. Its default value is set to be 1. The following three parameters are related to EIP-1559 transaction fee mechanism. 36. `base_fee_process`: This is the parameter for EIP-1559 transaction base fee that is burned. This is also the minimum fee that a user will pay in order for the transaction to be included in a block. A "full block" (i.e., a block whose size is 2X the target) will increase the BASEFEE by 12.5% to 1.125. Therefore, if there are a series of full blocks, let's say 20 full blocks (approx. 4 minutes), the BASEFEE will increase by 10 folds. Therefore, periods of heavy-chain load will not usually last any longer than 5 minutes. Its default value is set to 30 Gwei per gas. 37. `priority_fee_process`: This paramater is the EIP-1559 transaction average priority fee, or tip. Its default value is set to 2. We can analyze on chain data to get a better estimate of this average priority fee. 38. `gas_target_process`: This is the long-term average of tha gas target, currently set to 15 million gas. EIP-1559 has a long-term gas target and a short term hard cap. The long term block gas target it achieves is 15M per block. But for each block, the hard cap is twice the gas target, 30M gas per block. --- #### `parts` folder What we discuss next is the core of the radCAD model, which is policy functions, state update function and partical state update blocks. We first talk about policy update function. The articheture for this part is illustrated below: <img src="https://i.imgur.com/fCtjf68.png" width="400"> The four python files `ethereum_system.py`, `pos_incentives.py`, `system_metrics.py`, and `validators.py` include all the policy functions that drive the process over time and state update functions. Let's look at each of them in detail. 1. `ethereum_system.py`: This python file includes 4 four policy functions that govern Ethereum stage transitions, network ETH issuance, MEV, and EIP-1559 transaction fee mechanism. Two state update functions are also defined here: update ETH price and update ETH supply. <img src="https://i.imgur.com/uOfJPD2.png" width="500"> - `policy_upgrade_stages`: This policy function is associated with the differential specification below. Once we correctly set up the dates that those upgrades happened, we don't touch this policy function. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/QnPs0ZA.png" width="250"> |<img src="https://i.imgur.com/yzUkMco.png" height="750"> - `policy_network_issuance`: This policy function drives the network ETH issuance. Final ETH issuance is a aggreagted result of ETH new issuance and ETH burn. We first need to exclude priority fee from total online validator reward since the priority fee is transferred within the network, not newly minted. In addition, slashing penalty is effectively burned, so its effect on issuance is negative. Base fee is also burned, thus it has negative effect on ETH supply as well. The policy function should implement this relationship. This relationship is also fixed, as a result we don't touch this function in any experimentation of our own. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/Exv5l3q.png" width="250"> |<img src="https://i.imgur.com/UmAqR3T.png" width="500"> - `policy_mev`: The current model sets `mev_per_block` to zero, thus does not consider MEV affect. This is where we can try to incorporate non-zero mev to our own experimentation to evaluate its affect of network issuance and other related system metrics. [Pintail' Notes](https://pintail.xyz/) has some posts on MEV, which might be useful in our analysis of MEV. <img src="https://i.imgur.com/ItucbY8.png" width="500"> - `policy_eip1559_transaction_pricing`: This policy function drives the process of EIP-1559 transaction fee mechanism. EIP-1559 includes a fixed fee per block network fee that is burned and dynamically expands/contracts block sizes to deal with the tranffic congestion. Currently the model assumes a fixed 30 Gwei per gas for `base_fee_per_gas` according to 90-day median gas price. This is one of the assumptions of the model. Therefore, we can explore options to relax this assumption. The ideal case is to periodically update this parameter value using on-chain data. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/OXroUR6.png" width="250"> |<img src="https://i.imgur.com/W9yecJ3.png" width="500"> Next, we have two state update functions. - `update_eth_price`: This state update function is used to drive the ETH price update in the model. One of the assumptions governs this update process. This model assumption states that average ETH price in the past 12-month period will be used, thus it is a static value. Again, we can relax this assumption to evaluate a more dynamic price update mechanism for ETH price. <img src="https://i.imgur.com/kbQFsxB.png" width="500"> - `update_eth_supply`: This function updates the ETH supply from the network issuance policy function. <img src="https://i.imgur.com/4UTxHJ3.png" width="500"> 2. `pos_incentives.py`: This module implements the computation of PoS incentive such as attestation and block proposal rewards and penalties. It includes 6 policy functions and 3 state update functions. Let's look at those functions one by one in detail. - `policy_attestation_rewards`: This policy function calculates source reward, head reward and target reward a validator can get. It is derived from official Eth2 spec. Thus, we don't touch this policy function in our own experimentation. One note is that this policy function returns aggregarated attestation rewards for all online validators, not individual validator attestation rewards. The following functions also return aggregated rewards or penalties for all relevant validators. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/0AK2IaS.png" width="250"> | <img src="https://i.imgur.com/ZLH9yk7.png" width="500"> - `policy_attestation_penalties`: It calculates attestation penalties due to validators being offline. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/aI5Gn3K.png" width="250"> | <img src="https://i.imgur.com/l2jGPLE.png" width="500"> - `policy_sync_committee_reward`: This policy function calculates sync committe reward. Its implementation also derived from official Eth2 spec. Thus, we also don't touch this policy function in our experimentation. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/0AK2IaS.png" width="250"> | <img src="https://i.imgur.com/5ySi3KK.png" width="500"> - `policy_sync_committee_penalties`: This policy function is for calculating sync committee penalties applied to sync committee members who are offline. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/aI5Gn3K.png" width="250"> | <img src="https://i.imgur.com/OToaWVn.png" width="750"> - `policy_block_proposal_reward`: This policy function drives the process of block proposal reward, a reward a validator will get when proposing a block. Its implementation is also derived from official Eth2 spec, thus we don't touch this policy function as well in our experimentation. One note is that block proposer will get a reward for including sync committee attestations in the block. Therefore, in the differential specification diagram there is a dependency/signal from sync committee reward to block proposal reward. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/RcfdIHH.png" width="250"> | <img src="https://i.imgur.com/JyndftR.png" width="750"> - `policy_slashing`: This policy function calculates slashing rewards and penalties. Again this function is derived from official Eth2 specification. In our experimentation, we don't touch this function. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/vT6i0qN.png" width="250"> | <img src="https://i.imgur.com/03fjU8T.png" width="750"> Next, we will discuss three update functions related to PoS incentives. - `update_base_reward`: This state update function used to calculate and update base reward **per validator**. Its value depends on the average effective balance of a validator. <img src="https://i.imgur.com/KfnUzA7.png" width="500"> - `update_validating_rewards`: This state update function calculates and updated total validating rewards, including attestation rewards, sync committee rewards, and block proposal rewards. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/Zzdk42L.png" width="250"> | <img src="https://i.imgur.com/I3n1J2Z.png" width="750"> - `update_validating_penalties`: This state update function is used to calculate and update validating penalties, including penalties due to failing to attest or fail to perform sync committee duties. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/Zzdk42L.png" height="250"> | <img src="https://i.imgur.com/wQqmo5n.png" width="750"> 3. `validators.py`: This file implemens three policy functions related to validators, such as validator staking, activation, and uptime. <img src="https://i.imgur.com/1yDqNml.png" width="500"> - `policy_staking`: This policy function is used to calculate staked ETH in the system. One important thing to note in the code implementation. It includes a `if-else` statement. There are two ways to calculate staked ETH. One is driven by this `eth_staked_process`. If we define this parameter, then we use this process to define ETH staked in the system. If we don't define this process, then we use number of validators to compute ETH staked. The team claims that this will allow the user to either drive the model using a set of discrete ETH staked values, which might be useful when doing parameter sweeping, or using a validator adoption process. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/12DpkQe.png" height="400"> | <img src="https://i.imgur.com/ruCsQW9.png" width="750"> - `policy_validators`: This policy function calculates the number of validators. This function also has an `if-else` statement that is used to decide if `eth_staked_process` is used or `validator_process` is used. Basically there are two ways to calculate number of validators in the network. One is based on total ETH staked divided by the average effective balance and the other one is based on validator entry and exit of the system. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/5XBRRDb.png" height="600"> | <img src="https://i.imgur.com/sZiaSA0.png" width="750"> - `policy_average_effective_balance`: This policy function calculates validator average effective balance. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/sat3tfI.png" height="400"> | <img src="https://i.imgur.com/hZqmVuk.png" width="750"> Next, we focus on the last set of policy functions and state update function. 5. `system_metrics.py`: This module contains policy functions for calculating validator operational costs and yields. <img src="https://i.imgur.com/eUqMIfH.png" width="500"> - `policy_validator_costs`: This policy function calculates and updates validator operational costs, such as hardware costs, cloud costs and third-party costs. One important note is that those costs are in USD, not in ETH or Gwei denomination. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/JDwFxAa.png" width="400"> | <img src="https://i.imgur.com/3ufeReW.png" width="750"> One question here: In the validator process, how are those newly activated validators assigned to different validator environments? From the calculation of the `validator_count_distribution` in the `policy_validator_costs` distribution, it seems it is assumed that the validator distribution percentage will be maintained. That means, newly activated validators will be assumed to have the same distribution percentages as the activated validators in the system. - `policy_validator_yields`: This policy function calculates aggregated validator revenue and yields. Again validator aggregated revenues and profits are in USD, not in ETH. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/gUqsaIp.png" width="300"> | <img src="https://i.imgur.com/Nu6Mdce.png" width="750"> - `policy_total_online_validator_rewards`: This policy function calculates total online validator rewards, including validating rewards (+), penalties (-), priority fee paid to the validators (+), and whistleblower rewards (+). Those rewards are denominated in GWei. In the diagram MEV is not included but if we look at the code implementation, there is MEV part. But since this value is set to zero, it does not have any impact. But once we set `mev_per_block` to a non-zero value, it will have a positive impact on validator rewards. For code implementation perspective, we don't need to change anything. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/YVt5kfe.png" width="400"> | <img src="https://i.imgur.com/vSu0AVv.png" width="750"> - `update_supply_inflation`: This state update function updates annualized ETH supply inflation in percentage. The output of the network issuance policy function is used as input. One thing we could do here is to verify if the model can validate the current ETH defaltion scenario the network has been experiencing since the merge. differential specification diagram |code implementation :-----------------------------------:|:-----------------------------------: <img src="https://i.imgur.com/v1xuQtU.png" width="400"> | <img src="https://i.imgur.com/HxFnmjn.png" width="750"> There are two more pieces we need to cover here. One is Ethereum network specification, which is derived from official Eth2 specification. Again, since they are from official documents, we don't touch those functions for the purpose of this analysis. The other one is `state_update_blocks.py` module. This module implements partial state update blocks in sequence within each timestep in one place. 5. `ethereum_spec.py`: 6. `state_update_blocks`: