---
# System prepended metadata

title: Table of Contents

---



# 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)
```

