Skip to content

Commit

Permalink
feat: new DENCUN_FORK_EPOCH env variable
Browse files Browse the repository at this point in the history
Add a new `DENCUN_FORK_EPOCH` environmental variable that allows users
to set the custom epoch number of the Dencun fork for networks other
than officially supported ones (Mainnet, Goerli, and Holesky).

Add the appropriate validation for this environmental variable. It is
possible not to set a value for the `DENCUN_FORK_EPOCH` variable if the
`ETH_NETWORK` is Mainnet, Goerli, or Holesky. Even if the `ETH_NETWORK`
is one of these 3 networks, it is also possible to set the valid Dencun
hard fork epoch as the value of the `DENCUN_FORK_EPOCH` variable. If the
`ETH_NETWORK` variable has a value other than 1, 5, or 17000, the value
of the `DENCUN_FORK_EPOCH` must be set, otherwise the the validation
error will be returned.

NOTE. We need to apply the `@Expose()` decorator to the
`DENCUN_FORK_EPOCH` field of the `EnvironmentVariables` class to force
calling the handler specified in the `@Transform` decorator when the
`DENCUN_FORK_EPOCH` is not presented in the `.env` file.
https://github.com/typestack/class-transformer?tab=readme-ov-file#enforcing-type-safe-instance
The `transformDencunEpoch()` function is responsible for setting the
default values for the `DENCUN_FORK_EPOCH` variable and must be called
regardless of the `DENCUN_FORK_EPOCH` variable is presented in the
`.env` file or not.

Once the `DENCUN_FORK_EPOCH` variable is set in the config, the
application assumes that this value is correct and uses this value as
the primary source of truth.
  • Loading branch information
AlexanderLukin committed Dec 27, 2023
1 parent 58558f8 commit 77952e2
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 45 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ Independent of `CL_API_MAX_RETRIES`.
* **Required:** false
* **Default:** 155000
---
`DENCUN_FORK_EPOCH` - Ethereum consensus layer epoch when the Dencun hard fork has been released. This value must be set
only for networks other than Mainnet, Goerli and Holesky.
* **Required:** false
---
`VALIDATOR_REGISTRY_SOURCE` - Validators registry source.
* **Required:** false
* **Values:** lido (Lido NodeOperatorsRegistry module keys) / keysapi (Lido keys from multiple modules) / file
Expand Down
67 changes: 65 additions & 2 deletions src/common/config/env.validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transform, plainToInstance } from 'class-transformer';
import { plainToInstance, Expose, Transform } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
Expand All @@ -9,15 +9,19 @@ import {
IsNumber,
IsObject,
IsPort,
IsPositive,
IsString,
Max,
Min,
MinLength,
ValidateIf,
ValidationArguments,
registerDecorator,
validateSync,
} from 'class-validator';
import { Epoch } from 'common/consensus-provider/types';

import { Environment, LogFormat, LogLevel } from './interfaces';
import { DencunForkEpoch, Environment, LogFormat, LogLevel } from './interfaces';

export enum Network {
Mainnet = 1,
Expand Down Expand Up @@ -164,6 +168,13 @@ export class EnvironmentVariables {
@ValidateIf((vars) => vars.ETH_NETWORK === Network.Mainnet)
public START_EPOCH = 155000;

@IsInt()
@IsPositive()
@IsValidDencunEpoch()
@Expose()
@Transform(transformDencunEpoch)
public DENCUN_FORK_EPOCH: Epoch;

@IsNumber()
@Min(32)
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
Expand Down Expand Up @@ -292,3 +303,55 @@ export function validate(config: Record<string, unknown>) {

return validatedConfig;
}

// ====================================================================================================================
// PRIVATE FUNCTIONS
// ====================================================================================================================
function IsValidDencunEpoch() {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isValidDencunEpoch',
target: object.constructor,
propertyName: propertyName,
validator: {
validate(value: Epoch, args: ValidationArguments) {
switch ((args.object as any).ETH_NETWORK) {
case Network.Mainnet:
return value === DencunForkEpoch.Mainnet;
case Network.Holesky:
return value === DencunForkEpoch.Holesky;
case Network.Goerli:
return value === DencunForkEpoch.Goerli;
default:
return true;
}
},
},
});
};
}

function transformDencunEpoch({ value, obj }) {
if (value == null) {
value = '';
}

value = value.trim();

if (value === '') {
const chainId = parseInt(obj.ETH_NETWORK, 10);

switch (chainId) {
case Network.Mainnet:
return DencunForkEpoch.Mainnet;
case Network.Holesky:
return DencunForkEpoch.Holesky;
case Network.Goerli:
return DencunForkEpoch.Goerli;
default:
return null;
}
}

return parseInt(value, 10);
}
9 changes: 9 additions & 0 deletions src/common/config/interfaces/environment.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ export enum LogFormat {
json = 'json',
simple = 'simple',
}

export enum DencunForkEpoch {
/**
* @todo This should be corrected once the particular epoch of the Dencun hard fork on Mainnet is known.
*/
Mainnet = 300000,
Goerli = 231680,
Holesky = 29696,
}
28 changes: 14 additions & 14 deletions src/duty/attestation/attestation.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ export const TIMELY_TARGET_WEIGHT = 26; // Wt
export const TIMELY_HEAD_WEIGHT = 14; // Wh
const WEIGHT_DENOMINATOR = 64; // W sigma

const timelySource = (att_inc_delay: number, att_valid_source: boolean): boolean => {
return att_valid_source && att_inc_delay <= 5;
const timelySource = (attIncDelay: number, attValidSource: boolean): boolean => {
return attValidSource && attIncDelay <= 5;
};
const timelyTarget = (att_inc_delay: number, att_valid_source: boolean, att_valid_target: boolean, before_dencun): boolean => {
return att_valid_source && att_valid_target && (!before_dencun || att_inc_delay <= 32);
const timelyTarget = (attIncDelay: number, attValidSource: boolean, attValidTarget: boolean, beforeDencun): boolean => {
return attValidSource && attValidTarget && (!beforeDencun || attIncDelay <= 32);
};
const timelyHead = (att_inc_delay: number, att_valid_source: boolean, att_valid_target: boolean, att_valid_head: boolean): boolean => {
return att_valid_source && att_valid_target && att_valid_head && att_inc_delay == 1;
const timelyHead = (attIncDelay: number, attValidSource: boolean, attValidTarget: boolean, attValidHead: boolean): boolean => {
return attValidSource && attValidTarget && attValidHead && attIncDelay == 1;
};

export const getFlags = (
att_inc_delay: number,
att_valid_source: boolean,
att_valid_target: boolean,
att_valid_head: boolean,
before_dencun: boolean,
attIncDelay: number,
attValidSource: boolean,
attValidTarget: boolean,
attValidHead: boolean,
beforeDencun: boolean,
) => {
return {
source: timelySource(att_inc_delay, att_valid_source),
target: timelyTarget(att_inc_delay, att_valid_source, att_valid_target, before_dencun),
head: timelyHead(att_inc_delay, att_valid_source, att_valid_target, att_valid_head),
source: timelySource(attIncDelay, attValidSource),
target: timelyTarget(attIncDelay, attValidSource, attValidTarget, beforeDencun),
head: timelyHead(attIncDelay, attValidSource, attValidTarget, attValidHead),
};
};

Expand Down
41 changes: 12 additions & 29 deletions src/duty/attestation/attestation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { pick } from 'stream-json/filters/Pick';
import { streamArray } from 'stream-json/streamers/StreamArray';
import { batch } from 'stream-json/utils/Batch';

import { ConfigService, Network } from 'common/config';
import { ConfigService } from 'common/config';
import { AttestationCommitteeInfo, ConsensusProviderService } from 'common/consensus-provider';
import { Epoch, Slot } from 'common/consensus-provider/types';
import { allSettled } from 'common/functions/allSettled';
Expand Down Expand Up @@ -45,25 +45,8 @@ export class AttestationService {
protected readonly summary: SummaryService,
) {
this.slotsInEpoch = this.config.get('FETCH_INTERVAL_SLOTS');
this.dencunEpoch = this.config.get('DENCUN_FORK_EPOCH');
this.savedCanonSlotsAttProperties = new Map<number, string>();

const chainId = this.config.get('ETH_NETWORK');
switch (chainId) {
case Network.Mainnet:
/**
* @todo This should be corrected once the particular epoch of the Dencun hard fork on Mainnet is known.
*/
this.dencunEpoch = 300000;
break;
case Network.Goerli:
this.dencunEpoch = 231680;
break;
case Network.Holesky:
this.dencunEpoch = 29696;
break;
default:
throw Error(`Dencun hard fork epoch is unknown for chain ID ${chainId}`);
}
}

@TrackTask('check-attestation-duties')
Expand Down Expand Up @@ -101,15 +84,15 @@ export class AttestationService {
this.getCanonSlotRoot(attestation.target_epoch * this.slotsInEpoch),
this.getCanonSlotRoot(attestation.source_epoch * this.slotsInEpoch),
]);
const att_valid_head = attestation.head == canonHead;
const att_valid_target = attestation.target_root == canonTarget;
const att_valid_source = attestation.source_root == canonSource;
const att_inc_delay = Number(attestation.included_in_block - attestation.slot);
const before_dencun = epoch < this.dencunEpoch;
const flags = getFlags(att_inc_delay, att_valid_source, att_valid_target, att_valid_head, before_dencun);
const attValidHead = attestation.head == canonHead;
const attValidTarget = attestation.target_root == canonTarget;
const attValidSource = attestation.source_root == canonSource;
const attIncDelay = Number(attestation.included_in_block - attestation.slot);
const beforeDencun = epoch < this.dencunEpoch;
const flags = getFlags(attIncDelay, attValidSource, attValidTarget, attValidHead, beforeDencun);
for (const [valCommIndex, validatorIndex] of committee.entries()) {
const att_happened = attestation.bits.get(valCommIndex);
if (!att_happened) {
const attHappened = attestation.bits.get(valCommIndex);
if (!attHappened) {
continue;
}
const processed = this.summary.epoch(attestation.target_epoch).get(validatorIndex);
Expand All @@ -125,8 +108,8 @@ export class AttestationService {
this.summary.epoch(attestation.target_epoch).set({
val_id: validatorIndex,
epoch: attestation.target_epoch,
att_happened,
att_inc_delay: processed?.att_inc_delay || att_inc_delay,
att_happened: attHappened,
att_inc_delay: processed?.att_inc_delay || attIncDelay,
att_valid_source: processed?.att_valid_source || flags.source,
att_valid_target: processed?.att_valid_target || flags.target,
att_valid_head: processed?.att_valid_head || flags.head,
Expand Down

0 comments on commit 77952e2

Please sign in to comment.