Skip to content

Commit

Permalink
Merge pull request #229 from lidofinance/feature/dencun-support
Browse files Browse the repository at this point in the history
feat: support Dencun hard fork
  • Loading branch information
infloop authored Feb 5, 2024
2 parents 4e68e8c + c43a2aa commit 0fdb8cf
Showing 5 changed files with 75 additions and 29 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -239,6 +239,12 @@ 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 custom networks that support the Dencun hard fork. If the value of this variable is not specified for a custom
network, it is supposed that this network doesn't support Dencun. For officially supported networks (Mainnet, Goerli and
Holesky) this value should be omitted.
* **Required:** false
---
`VALIDATOR_REGISTRY_SOURCE` - Validators registry source.
* **Required:** false
* **Values:** lido (Lido NodeOperatorsRegistry module keys) / keysapi (Lido keys from multiple modules) / file
1 change: 1 addition & 0 deletions src/app/app.service.ts
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ export class AppService implements OnModuleInit, OnApplicationBootstrap {
this.logger.log(`DRY RUN ${this.configService.get('DRY_RUN') ? 'enabled' : 'disabled'}`);
this.logger.log(`Slot time: ${this.configService.get('CHAIN_SLOT_TIME_SECONDS')} seconds`);
this.logger.log(`Epoch size: ${this.configService.get('FETCH_INTERVAL_SLOTS')} slots`);
this.logger.log(`Dencun fork epoch: ${this.configService.get('DENCUN_FORK_EPOCH')}`);
}

public async onApplicationBootstrap(): Promise<void> {
23 changes: 22 additions & 1 deletion 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,
@@ -9,13 +9,15 @@ import {
IsNumber,
IsObject,
IsPort,
IsPositive,
IsString,
Max,
Min,
MinLength,
ValidateIf,
validateSync,
} from 'class-validator';
import { Epoch } from 'common/consensus-provider/types';

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

@@ -37,6 +39,15 @@ export enum WorkingMode {
Head = 'head',
}

const dencunForkEpoch = {
/**
* @todo This should be corrected once the particular epoch of the Dencun hard fork on Mainnet is known.
*/
'1': 300000,
'5': 231680,
'17000': 29696,
};

const toBoolean = (value: any): boolean => {
if (typeof value === 'boolean') {
return value;
@@ -164,6 +175,16 @@ export class EnvironmentVariables {
@ValidateIf((vars) => vars.ETH_NETWORK === Network.Mainnet)
public START_EPOCH = 155000;

@IsInt()
@IsPositive()
@Expose()
@Transform(
({ value, obj }) =>
dencunForkEpoch[obj.ETH_NETWORK] || (value != null && value.trim() !== '' ? parseInt(value, 10) : Number.MAX_SAFE_INTEGER),
)
@ValidateIf((vars) => vars.NODE_ENV !== Environment.test)
public DENCUN_FORK_EPOCH: Epoch;

@IsNumber()
@Min(32)
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
38 changes: 23 additions & 15 deletions src/duty/attestation/attestation.constants.ts
Original file line number Diff line number Diff line change
@@ -4,25 +4,33 @@ export const TIMELY_TARGET_WEIGHT = 26; // Wt
export const TIMELY_HEAD_WEIGHT = 14; // Wh
const WEIGHT_DENOMINATOR = 64; // W sigma

export 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;
};
export const timelyTarget = (att_inc_delay: number, att_valid_source: boolean, att_valid_target: boolean): boolean => {
return att_valid_source && att_valid_target && att_inc_delay <= 32;

const timelyTarget = (attIncDelay: number, attValidSource: boolean, attValidTarget: boolean): boolean => {
return attValidSource && attValidTarget && attIncDelay <= 32;
};
export 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 timelyTargetDencun = (attValidSource: boolean, attValidTarget: boolean): boolean => {
return attValidSource && attValidTarget;
};
export const getFlags = (att_inc_delay: number, att_valid_source: boolean, att_valid_target: boolean, att_valid_head: boolean) => {

const timelyHead = (attIncDelay: number, attValidSource: boolean, attValidTarget: boolean, attValidHead: boolean): boolean => {
return attValidSource && attValidTarget && attValidHead && attIncDelay === 1;
};

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

36 changes: 23 additions & 13 deletions src/duty/attestation/attestation.service.ts
Original file line number Diff line number Diff line change
@@ -23,9 +23,9 @@ interface SlotAttestation {
bits: BitArray;
head: string;
target_root: string;
target_epoch: number;
target_epoch: Epoch;
source_root: string;
source_epoch: number;
source_epoch: Epoch;
slot: number;
committee_index: number;
}
@@ -34,7 +34,9 @@ interface SlotAttestation {
export class AttestationService {
private processedEpoch: number;
private readonly slotsInEpoch: number;
private readonly dencunEpoch: Epoch;
private readonly savedCanonSlotsAttProperties: Map<number, string>;

public constructor(
@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService,
protected readonly config: ConfigService,
@@ -43,6 +45,7 @@ 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>();
}

@@ -59,7 +62,9 @@ export class AttestationService {
for (const attestation of attestations) {
// Each attestation corresponds to committee. Committee may have several aggregate attestations
const committee = committees.get(`${attestation.committee_index}_${attestation.slot}`);
if (!committee) continue;
if (!committee) {
continue;
}
await this.processAttestation(epoch, attestation, committee);
// Long loop (2048 committees will be checked by ~7k attestations).
// We need to unblock event loop immediately after each iteration
@@ -79,14 +84,17 @@ 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 flags = getFlags(att_inc_delay, att_valid_source, att_valid_target, att_valid_head);
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 isDencunFork = epoch >= this.dencunEpoch;
const flags = getFlags(attIncDelay, attValidSource, attValidTarget, attValidHead, isDencunFork);
for (const [valCommIndex, validatorIndex] of committee.entries()) {
const att_happened = attestation.bits.get(valCommIndex);
if (!att_happened) continue;
const attHappened = attestation.bits.get(valCommIndex);
if (!attHappened) {
continue;
}
const processed = this.summary.epoch(attestation.target_epoch).get(validatorIndex);
if (!processed?.att_valid_source && flags.source) {
attestationFlags.source.push(validatorIndex);
@@ -100,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,
@@ -116,7 +124,9 @@ export class AttestationService {

protected async getCanonSlotRoot(slot: Slot) {
const cached = this.savedCanonSlotsAttProperties.get(slot);
if (cached) return cached;
if (cached) {
return cached;
}
const root = (await this.clClient.getBeaconBlockHeaderOrPreviousIfMissed(slot)).root;
this.savedCanonSlotsAttProperties.set(slot, root);
return root;

0 comments on commit 0fdb8cf

Please sign in to comment.