Skip to content

Commit

Permalink
docs: added docs calculation withdrawal frame of validators
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarens2 committed Sep 24, 2024
1 parent b15c5af commit d9eec2d
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 29 deletions.
15 changes: 11 additions & 4 deletions how-estimation-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,21 @@ where `unfinalized` is the amount of the withdrawal request considered summed wi

If there is not enough ether to fulfill the withdrawal request (`unfinalized > totalBuffer`), the previous case might be appended with the known validators are to be withdrawn (when the `withdrawable_epoch` is assigned).

It's needed to select the Lido-participating validators which are already in process of withdrawal and group them by `withdrawable_epoch` to `frameBalances`, allowing to find the oracle report frame containing enough funds from:
It's needed to select the Lido-participating validators which are already in process of withdrawal and group them by calculated frame of expected withdrawal to `frameBalances`, allowing to find the oracle report frame containing enough funds from:

- buffer (`totalBuffer`)
- projectedRewards (`rewardsPerEpoch * epochsTillTheFrame`)
- frameBalances (`object { [frame]: [sum of balances of validators with withdrawable_epoch for certain frame] }`)
- frameBalances (`object { [frame]: [sum of balances of validators with calculated withdrawal frame] }`)

So the final formula for that case looks like this:
`frame (which has engough validator balances) + sweepingMean`. More about `sweepingMean` [here](#sweeping mean).
#### Algorithm of calculation withdrawal frame of validators:

1. The cursor goes from 0 to the last validator index in infinite loop.
2. When the cursor reaches a withdrawable validator, it withdraws ETH from that validator.
3. The cursor can withdraw from a maximum of 16 validators per slot.
4. We assume that all validators in network have to something to withdraw (partially or fully)
5. `percentOfActiveValidators` is used to exclude inactive validators from the queue, ensuring more accurate calculations.
6. Formula to get number of slots to wait is `(number of validators to withdraw before cursor get index of validator) / 16`
7. By knowing number slots we can calculate frame of withdrawal

---

Expand Down
17 changes: 14 additions & 3 deletions src/common/execution-provider/execution-provider.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { CHAINS } from '@lido-nestjs/constants';
import { Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
import { ConfigService } from '@nestjs/config';
import { PrometheusService } from '../prometheus';

@Injectable()
export class ExecutionProviderService {
constructor(
protected readonly provider: SimpleFallbackJsonRpcBatchProvider,
protected readonly prometheusService: PrometheusService,
protected readonly configService: ConfigService,
) {}

Expand All @@ -28,9 +30,18 @@ export class ExecutionProviderService {
return chainId;
}

// using ethers.JsonRpcProvider direct request to "eth_getBlockByNumber"
// default @ethersproject provider getBlock does not contain "withdrawals" property
public async getLatestWithdrawals(): Promise<Array<{ validatorIndex: string }>> {
const provider = new ethers.JsonRpcProvider(this.configService.get('EL_RPC_URLS')[0]);
const block = await provider.send('eth_getBlockByNumber', ['latest', false]);
return block.withdrawals;
const endTimer = this.prometheusService.elRpcRequestDuration.startTimer();
try {
const provider = new ethers.JsonRpcProvider(this.configService.get('EL_RPC_URLS')[0]);
const block = await provider.send('eth_getBlockByNumber', ['latest', false]);
endTimer({ result: 'success' });
return block.withdrawals;
} catch (error) {
endTimer({ result: 'error' });
throw error;
}
}
}
40 changes: 22 additions & 18 deletions src/jobs/validators/utils/get-validator-withdrawal-timestamp.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { BigNumber } from '@ethersproject/bignumber';
import { WITHDRAWALS_VALIDATORS_PER_SLOT } from '../validators.constants';
import { SECONDS_PER_SLOT } from '../../../common/genesis-time';
import { SECONDS_PER_SLOT } from 'common/genesis-time';

/*
algorithm:
1. cursor goes from 0 to last validator index in queue
2. when cursor comes to withdrawable validator, it withdraws eth from it
3. cursor can withdraw only 16 validators per slot
4. percentOfActiveValidators is used to get rid of inactive validators in queue
and make more accurate calculation
examples:
1. if current cursor is 50 and total validators 100,
then if we want to know when will be withdrawn validator with index 75
(75 - 50) / 16 = 2 slots
2. if current cursor is 50 and total validators 100,
then if we want to know when will be withdrawn validator with index 25
(cursor will go to the end and start from 0)
(100 - 50 + 25) / 16 = 5 slots
#### Algorithm of calculation withdrawal frame of validators:
1. The cursor goes from 0 to the last validator index in infinite loop.
2. When the cursor reaches a withdrawable validator, it withdraws ETH from that validator.
3. The cursor can withdraw from a maximum of 16 validators per slot.
4. We assume that all validators in network have to something to withdraw (partially or fully)
5. `percentOfActiveValidators` is used to exclude inactive validators from the queue, ensuring more accurate calculations.
6. Formula to get number of slots to wait is `(number of validators to withdraw before cursor get index of validator) / 16`
7. By knowing number slots we can calculate frame of withdrawal
Examples:
1. If the current cursor is 50 and the total number of validators is 100,
then if we want to know when the validator with index 75 will be withdrawn:
(75 - 50) / 16 = 2 slots.
2. If the current cursor is 50 and the total number of validators is 100,
and we want to know when the validator with index 25 will be withdrawn
(since the cursor will go to the end and start from 0):
(100 - 50 + 25) / 16 = 5 slots.
*/
export function getValidatorWithdrawalTimestamp(
index: BigNumber,
Expand Down
8 changes: 4 additions & 4 deletions src/jobs/validators/validators.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { ValidatorsStorageService } from 'storage';
import { FAR_FUTURE_EPOCH, ORACLE_REPORTS_CRON_BY_CHAIN_ID, MAX_SEED_LOOKAHEAD } from './validators.constants';
import { BigNumber } from '@ethersproject/bignumber';
import { processValidatorsStream } from 'jobs/validators/utils/validators-stream';
import { unblock } from '../../common/utils/unblock';
import { unblock } from 'common/utils/unblock';
import { LidoKeysService } from './lido-keys';
import { ResponseValidatorsData, Validator } from './validators.types';
import { parseGweiToWei } from '../../common/utils/parse-gwei-to-big-number';
import { parseGweiToWei } from 'common/utils/parse-gwei-to-big-number';
import { ValidatorsCacheService } from 'storage/validators/validators-cache.service';
import { CronExpression } from '@nestjs/schedule';
import { PrometheusService } from '../../common/prometheus';
import { stringifyFrameBalances } from '../../common/validators/strigify-frame-balances';
import { PrometheusService } from 'common/prometheus';
import { stringifyFrameBalances } from 'common/validators/strigify-frame-balances';
import { getValidatorWithdrawalTimestamp } from './utils/get-validator-withdrawal-timestamp';

export class ValidatorsService {
Expand Down

0 comments on commit d9eec2d

Please sign in to comment.