owned this note
owned this note
Published
Linked with GitHub
# Table of Contents
[TOC]
import pandas as pd
import numpy as np
# Assumptions & Key Considerations
### 1. Base reward per increment
> The number of validators considered is 491,875 (As on 23rd Dec’22) and the corresponding base reward per increment is 510.
>
num_validators = 491875
base_reward_factor = 64 #constant
base_reward_ = (10** 9 * base_reward_factor) / np.sqrt(32*10**9 * num_validators)
print(base_reward_)
base_reward = base_reward_
Ws=14
Wt=26
Wh=14
Wsum = 64
### 2. Performance Metrics for Permissionless node operators - bottom performers
**Basis of Permissionless node operators - bottom performers**
* Considered the bottom 20th percentile of deposit addresses from rated.network, arranged by uptime
* Source: [https://www.rated.network/o/Rocketpool?network=mainnet&view=pool](https://)
* The analysis was performed in Nov’22 and to ensure the base data was still relevant at the time of publication of this analysis, the performance metrics of the bottom 20th percentile was recalculated for the last 30 days on 7th Jan’23. Since the validator performance improved from 89.24% to 90.11%, the data for Oct’22 was considered to be sufficient.
**Performance Metrics**
Rsth = (Ws+Wt+Wh)/Wsum # percent reward for correct source + target + head
Rnst = (-Ws-Wt)/Wsum # penality for wrong source + target
Rsnt = (Ws-Wt)/Wsum #Penality for correct source, wrong target
Rstnh = (Ws+Wt)/Wsum # Not included due to lack of data on Permissionless node operators - bottom performers
k= (Rsth*base_reward)/(10**9)
k1=(Rnst*base_reward)/(10** 9)
k2 =(Rsnt*base_reward)/(10** 9)
![](https://i.imgur.com/Y2gxDfO.png)
### 3. Performance Metrics for Permissionless node operators - Bottom Performers in a DVT Network
DVT significantly improves operator uptime:
![](https://i.imgur.com/9q0xEju.png)
DVT technology makes a validator more efficient by improving uptime to 94% when compared to a stand-alone validator with an uptime of 89.2%, given the same level of validator performance.
In a DVT-network, for a validator to perform duties (correctly & timely), atleast 3 out 4 nodes will need to correctly perform duties ie, we will need to calculate:
> Probability of atleast 3 nodes performing duties correctly = probability of all 4 nodes performing duties correctly + probability of 3 out of 4 nodes performing duties correctly
![](https://i.imgur.com/Cm5SWQe.png)
### 4. Penalties and Inactivity Leak
It is assumed that the period of poor performance that attracts penalties and the inactivity leak occursconsecutively in the first set of epochs, and this repeats for 6 cycles of 30 days each.
The implications are as follows:
1. In all scenarios, has the effect of being more conservative on the effective balance - The effective balance is reduced right at the start leading to a lower balance at the end of the penalty epoch and hence has a lower effective balance on which the rewards are applied.
1. In case of an inactivity leak, has the effect of assuming the worst-case in the calculation of the inactivity score - This would cover the worst-case scenario with the inactivity score increasing every epoch
1. Inactivity leak penalty quotient = 16777216
### 5. Slashing
1. Simulations for slashing is the same as assuming a validator is offline throughout the entire duration (36 days). However, in addition, there are 2 subtractions to the active balance
2. The first is initial slash of 1 ETH at the 1st epoch ( It is assumed to occur at the first epoch)
3. The second is correlation penalty applied at the 4050 epoch (18 days)
**Correlation Penalty Calculation:** To calculate correlation penalty we need to get the sum of effective balances of slashed validators in the past 36 days. In order to do so we need the probability of slash of a validator in a 36 day window.
**Probability of a validator being slashed in a 36 day window**
![](https://i.imgur.com/hvfs8MO.png)
1. Let p be the probability of a slashing event per epoch per validator
1. The probability of a validator being slashed in 36 days would be 1 - (1-p)^(36*225)
1. Calculating probability of a slash event per epoch per validator
3. There have been 220 slashing events over 168120 epochs of the beacon chain. The number of validators have increased from 21,063 to 490,751
4. P = (total number of slashing incidents)/(area under the curve)
5. The area under the graph is calculated by approximating it to a trapezium
6. P =0.000000005102789204 [5.10E-09]
7. Based on the value of p the value of Probability of a validator being slashed in a 36 day window comes out to be 0.00004133173837
8. Correlation penalty =min( B, 3* S/T * B), S/T
9. S= 0.00004133173837 *n *32
10. T =n*32
11. N is the number of validators
12. S/T =0.00004133173837 ->represented in the code as corr_penallity_sbyt
13. Correlation penalty then becomes 3*0.00004133173837 *b_eff where b_eff is the effective balance at the 4050th epoch
### 6. Attestor & Sync Committee Rewards
1. Proposer rewards are calculated using the formula wp/(wt-wp)*R where R is the value of a single attestation, wt and wp and the total and poposer weights.
1. Combining the probability of being selected to propose a block with the reward earned we get the total proposer reward for a validator to be 0.01377385714 ETH for 30 days and 0.08264314286 for 180 days.
1. The sync committee reward per validator per slot is calculated using Wy*T*b/(32*512*100) where Wy is is the sync reward weight, T is the total number of validators and b is the base reward. Combining this with the probability of being included in the sync committee we get the sync committee rewards to be 0.0034425 ETH for 30 days and 0.020655 ETH for 180 days
1. The total rewards combining Proposer and sync committee rewards are as follows:
![](https://i.imgur.com/Ah82hvD.png)
# Simulation Code
## 1. Normal Network Conditions
### Normal Network conditions (Non-DVT)
def thirty_day_cycle_no_inactivity(b,k1,k2,k3,b_eff):
b_new=b
b_eff=b_eff
inactivity_score=0
attest_penality=0
k=k1
for i in range(1,6751):
if i <519:
k =k1
inactivity_score =inactivity_score+4
inactivity_score =inactivity_score-16
elif i <727:
k =k2
inactivity_score =inactivity_score+4
inactivity_score =inactivity_score-16
else :
k = k3
inactivity_score =inactivity_score-1
inactivity_score =inactivity_score-16
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new, b_eff
#### Simulation Results for Normal Network Conditions
# Simualtion for Permissionless node operators - Bottom Performers with no inactivity leak
b_new=32
b_eff=32
for i in range(1,7):
b_new,b_eff =thirty_day_cycle_no_inactivity(b_new,k1,k2,k,b_eff)
print(b_new, i*30,'days')
Result:
32.077049457807114 30 days
32.15409891561533 60 days
32.231148373423544 90 days
32.30819783123176 120 days
32.385247289039974 150 days
32.46229674684819 180 days
### Normal network conditions (DVT Infra)
def thirty_day_cycle_ssv(b,k1,k2,b_eff):
b_new=b
b_eff =b_eff
inactivity_score=0
attest_penality=0
k=k1
for i in range(1,6751):
if i <406:
k =k1
inactivity_score =inactivity_score+4
inactivity_score =inactivity_score-16
else :
k = k2
inactivity_score =inactivity_score-1
inactivity_score =inactivity_score-16
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new,b_eff
#### Simulation results for Normal Network Conditions (DVT Infra)
b_new=32
b_eff=32
for i in range(1,7):
b_new,b_eff =thirty_day_cycle_ssv(b_new,k1,k,b_eff)
print(b_new, i*30,'days')
Result:
32.08326024401464 30 days
32.16652048803072 60 days
32.2497807320468 90 days
32.33304097606288 120 days
32.416301220078964 150 days
32.499561464095045 180 days
## 2. Inactivity leak for 7 days
### Inactivity Leak for 7 days (Non-DVT)
def thirty_day_cycle_with_inactivity(b,k1,k2,k3,b_eff):
b_new=b
b_eff =b
inactivity_score=0
attest_penality=0
k=k1
for i in range(1,6751):
if i <519 :
k =k1
inactivity_score =inactivity_score+4
elif i <727:
k =k2
inactivity_score =inactivity_score+4
elif i <=1575 :
k = 0
inactivity_score =inactivity_score-1
else:
k=k3
inactivity_score=inactivity_score-17 # 16 from no inactivity and 1 from being active in epoch participation so total 17
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
return b_new, b_eff
#### Simulation results for Inactivity leak for 7 days (Non-DVT)
# Permissionless node operators - Bottom Performers with inactivity leak for 7 days
b_new=32
b_eff=32
for i in range(1,7):
if i==1:
b_new,b_eff =thirty_day_cycle_with_inactivity(b_new,k1,k2,k,b_eff)
else:
b_new,b_eff=thirty_day_cycle_no_inactivity(b_new,k1,k2,k,b_eff)
print(b_new, i*30,'days')
Result:
30.545535629329766 30 days
30.617769496015566 60 days
30.690003362701365 90 days
30.762237229387164 120 days
30.834471096072964 150 days
30.906704962758763 180 days
### Inactivity leak for 7 days (DVT Infra)
def thirty_day_cycle_ssv_inactivity(b,k1,k2,b_eff):
b_new=b
b_eff=b_eff
inactivity_score=0
attest_penality=0
k=k1
for i in range(1,6751):
if i <406: #worst case
k =k1
inactivity_score =inactivity_score+4
elif i <1575:#1575 ->7 days
k = 0 # during inactivity leak you do not receive rewards
inactivity_score =inactivity_score-1
else:
k = k2 #good case
inactivity_score =inactivity_score-17
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new,b_eff
#### Simulation results for Inactivity leak for 7 days (DVT Infra)
b_new=32
b_eff=32
for i in range(1,7):
if i==1:
b_new,b_eff =thirty_day_cycle_ssv_inactivity(b_new,k1,k,b_eff)
else:
b_new,b_eff=thirty_day_cycle_ssv(b_new,k1,k,b_eff)
print(b_new, i*30,'days')
Result:
31.34376643054227 30 days
31.424424791915733 60 days
31.505083153289196 90 days
31.585741514662658 120 days
31.66639987603612 150 days
31.747058237409583 180 days
#### Simulation extended for 180 days
b_new=32
b_eff=32
for i in range(1,7):
b_new,b_eff =thirty_day_cycle_ssv(b_new,k1,k,b_eff)
print(b_new, i*30,'days')
Result:
32.08326024401464 30 days
32.16652048803072 60 days
32.2497807320468 90 days
32.33304097606288 120 days
32.416301220078964 150 days
32.499561464095045 180 days
## 3. Isolated slashing event
### Isolated slashing event without inactivity leak
def slashing_thirty_day_cycle_no_inactivity(b,k1,epoch,b_eff):
b_new=b
b_eff=b_eff
attest_penality=0
k=k1
initial_slash=1
corr_penality_sbyt=0.00004133173837
for i in range(1,epoch):
if i==1:
b_new=b_new-initial_slash
if i==4050:
corr_penality =3*corr_penality_sbyt*b_eff
b_new =b_new-corr_penality
k =k1
attest_penality =k*b_eff
b_new = b_new + attest_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new,b_eff
#### Simulation Results
```
#30 day
slashing_thirty_day_cycle_no_inactivity(32,k1,30*225,32)
Result:
(30.929450774080273, 31)
#36 day (180 day)
slashing_thirty_day_cycle_no_inactivity(32,k1,36*225,32)
Result:
(30.916107786256845, 31)
```
### Isolated slashing event with inactivity leak for 7 days
```
def slashing_with_inactivity(b,epoch,k1,b_eff):
b_new=b
b_eff =b_eff
inactivity_score=0
attest_penality=0
k=k1
initial_slash=1
corr_penality_sbyt=0.00004133173837
for i in range(1,epoch):
# inactivity leak lasts for 7 days-our assumption
if i==1:
b_new=b_new-initial_slash
if i==4050:
corr_penality =3*corr_penality_sbyt*b_eff
print(corr_penality,'-',corr_penality_sbyt,'-',b_eff)
b_new =b_new-corr_penality
if i <1575 :
k=k1
inactivity_score =inactivity_score+4
else:
k=k1
inactivity_score =inactivity_score+4
inactivity_score=inactivity_score-16
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new,b_eff
```
#### Simulation Results
```
slashing_with_inactivity(32,30*225,k1,32)
Result:
(28.05433309931474, 28)
slashing_with_inactivity(32,36*225,k1,32)
Result:
(28.042281368379925, 28)
```
## 4. Non-Isolated Slashing Event
### Non-Isolated slashing without inactivity leak
```
def slashing_thirty_day_cycle_no_inactivity(b,k1,epoch,b_eff):
b_new=b
b_eff=b_eff
inactivity_score=0
attest_penality=0
k=k1
initial_slash=1
corr_penality_sbyt=0.00004133173837 ->Normal Slash
corr_penality_sbyt=0.0009689140088817117 #->Staked.us
for i in range(1,epoch):
if i==1:
b_new=b_new-initial_slash
if i==4050:
corr_penality =3*corr_penality_sbyt*b_eff
b_new =b_new-corr_penality
k =k1
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_new)/(4*50331648)
b_new = b_new + attest_penality
print(i,'-',inactivity_score,'-', round(inactivity_penality,10),'-', round(attest_penality,10), round(b_new,5))
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
print(i,'-',b_prev,'-',b_new,'-',b_prev-b_new,'-',b_eff,'-',i,'-',inactivity_score)
b_eff =min(b_eff,32)
b_prev =b_new
return b_new,b_eff
```
#### Simulation Result
#### 36 day or 180 day slashing
```
slashing_thirty_day_cycle_no_inactivity(32,k1,36*225,32)
(30.829842635099254, 31)
```
### Non-Isolated slashing with inactivity leak 7 days
```
def slashing_with_inactivity(b,epoch,k1,b_eff):
b_new=b
b_eff =b_eff
inactivity_score=0
attest_penality=0
k=k1
initial_slash=1
corr_penality_sbyt=0.00004133173837 ->normal slash
corr_penality_sbyt=0.0009689140088817117 #staked.us
for i in range(1,epoch):
inactivity leak lasts for 7 days-our assumption
if i==1:
b_new=b_new-initial_slash
if i==4050:
corr_penality =3*corr_penality_sbyt*b_eff
print(corr_penality,'-',corr_penality_sbyt,'-',b_eff)
b_new =b_new-corr_penality
if i <1575 :
k=k1
inactivity_score =inactivity_score+4
else:
k=k1
inactivity_score =inactivity_score+4
inactivity_score=inactivity_score-16
inactivity_score =max(inactivity_score,0)
attest_penality =k*b_eff
inactivity_penality = (inactivity_score*b_eff)/(4*16777216)
b_new = b_new + attest_penality -inactivity_penality
if b_new -b_eff >=1.25:
b_eff =b_eff+1
b_eff =min(b_eff,32)
if b_eff-b_new>=0.25:
b_eff =b_eff-1
b_eff =max(b_eff,0)
b_eff =min(b_eff,32)
b_prev =b_new
print(i,'-',inactivity_score,'-',b_new,'-',b_eff)
return b_new,b_eff
```
#### Simulation Results
```
slashing_with_inactivity(32,30*225,k1,32)
(27.976416188591756, 28)
slashing_with_inactivity(32,36*225,k1,32)
(27.96436445765694, 28)
```