Skip to content

Commit

Permalink
Merge pull request #55 from lidofinance/feat/update-lib-for-pectra
Browse files Browse the repository at this point in the history
feat: update lib and refactor types
  • Loading branch information
vgorkavenko authored Jan 16, 2025
2 parents f8e626f + 48251b1 commit 38ab12b
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 262 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
"@lido-nestjs/constants": "^5.2.1",
"@lido-nestjs/execution": "^1.12.0",
"@lido-nestjs/logger": "^1.3.2",
"@lodestar/params": "^1.16.0",
"@lodestar/types": "^1.15.0",
"@lodestar/params": "^1.25.0",
"@lodestar/types": "^1.25.0",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
Expand Down
36 changes: 19 additions & 17 deletions src/common/helpers/proofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,35 @@ import { createHash } from 'node:crypto';

import { ProofType, SingleProof, Tree, concatGindices, createProof } from '@chainsafe/persistent-merkle-tree';
import { ContainerTreeViewType } from '@chainsafe/ssz/lib/view/container';
import type { ssz as sszType } from '@lodestar/types';

let ssz: typeof import('@lodestar/types').ssz;
let anySsz: typeof ssz.phase0 | typeof ssz.altair | typeof ssz.bellatrix | typeof ssz.capella | typeof ssz.deneb;
let ssz: typeof sszType;

export function generateValidatorProof(
stateView: ContainerTreeViewType<typeof anySsz.BeaconState.fields>,
valIndex: number,
): SingleProof {
export type SupportedStateView =
| ContainerTreeViewType<typeof ssz.capella.BeaconState.fields>
| ContainerTreeViewType<typeof ssz.deneb.BeaconState.fields>
| ContainerTreeViewType<typeof ssz.electra.BeaconState.fields>;

export type SupportedBlockView =
| ContainerTreeViewType<typeof ssz.capella.BeaconBlock.fields>
| ContainerTreeViewType<typeof ssz.deneb.BeaconBlock.fields>
| ContainerTreeViewType<typeof ssz.electra.BeaconBlock.fields>;

export function generateValidatorProof(stateView: SupportedStateView, valIndex: number): SingleProof {
const gI = stateView.type.getPathInfo(['validators', Number(valIndex)]).gindex;
return createProof(stateView.node, { type: ProofType.single, gindex: gI }) as SingleProof;
}

export function generateWithdrawalProof(
stateView: ContainerTreeViewType<typeof anySsz.BeaconState.fields>,
blockView: ContainerTreeViewType<typeof anySsz.BeaconBlock.fields>,
stateView: SupportedStateView,
blockView: SupportedBlockView,
withdrawalOffset: number,
): SingleProof {
// NOTE: ugly hack to replace root with the value to make a proof
const patchedTree = new Tree(stateView.node);
const stateWdGindex = stateView.type.getPathInfo(['latestExecutionPayloadHeader', 'withdrawalsRoot']).gindex;
patchedTree.setNode(
stateWdGindex,
(blockView as ContainerTreeViewType<typeof ssz.capella.BeaconBlock.fields>).body.executionPayload.withdrawals.node,
);
const withdrawalGI = (
blockView as ContainerTreeViewType<typeof ssz.capella.BeaconBlock.fields>
).body.executionPayload.withdrawals.type.getPropertyGindex(withdrawalOffset) as bigint;
patchedTree.setNode(stateWdGindex, blockView.body.executionPayload.withdrawals.node);
const withdrawalGI = blockView.body.executionPayload.withdrawals.type.getPropertyGindex(withdrawalOffset) as bigint;
const gI = concatGindices([stateWdGindex, withdrawalGI]);
return createProof(patchedTree.rootNode, {
type: ProofType.single,
Expand All @@ -37,8 +39,8 @@ export function generateWithdrawalProof(
}

export function generateHistoricalStateProof(
finalizedStateView: ContainerTreeViewType<typeof anySsz.BeaconState.fields>,
summaryStateView: ContainerTreeViewType<typeof anySsz.BeaconState.fields>,
finalizedStateView: SupportedStateView,
summaryStateView: SupportedStateView,
summaryIndex: number,
rootIndex: number,
): SingleProof {
Expand Down
22 changes: 10 additions & 12 deletions src/common/prover/duties/slashings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common';

import { CsmContract } from '../../contracts/csm-contract.service';
import { VerifierContract } from '../../contracts/verifier-contract.service';
import { Consensus } from '../../providers/consensus/consensus';
import { BlockHeaderResponse, BlockInfoResponse } from '../../providers/consensus/response.interface';
import { Consensus, SupportedBlock } from '../../providers/consensus/consensus';
import { BlockHeaderResponse } from '../../providers/consensus/response.interface';
import { WorkersService } from '../../workers/workers.service';
import { KeyInfo, KeyInfoFn } from '../types';

Expand All @@ -20,7 +20,7 @@ export class SlashingsService {
protected readonly verifier: VerifierContract,
) {}

public async getUnprovenSlashings(blockInfo: BlockInfoResponse, keyInfoFn: KeyInfoFn): Promise<InvolvedKeys> {
public async getUnprovenSlashings(blockInfo: SupportedBlock, keyInfoFn: KeyInfoFn): Promise<InvolvedKeys> {
const slashings = {
...this.getSlashedProposers(blockInfo, keyInfoFn),
...this.getSlashedAttesters(blockInfo, keyInfoFn),
Expand Down Expand Up @@ -59,14 +59,12 @@ export class SlashingsService {
}

private getSlashedAttesters(
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
keyInfoFn: (valIndex: number) => KeyInfo | undefined,
): InvolvedKeys {
const slashed: InvolvedKeys = {};
for (const att of blockInfo.message.body.attester_slashings) {
const accused = att.attestation_1.attesting_indices.filter((x) =>
att.attestation_2.attesting_indices.includes(x),
);
for (const att of blockInfo.body.attesterSlashings) {
const accused = att.attestation1.attestingIndices.filter((x) => att.attestation2.attestingIndices.includes(x));
for (const valIndex of accused) {
const keyInfo = keyInfoFn(Number(valIndex));
if (!keyInfo) continue;
Expand All @@ -77,14 +75,14 @@ export class SlashingsService {
}

private getSlashedProposers(
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
keyInfoFn: (valIndex: number) => KeyInfo | undefined,
): InvolvedKeys {
const slashed: InvolvedKeys = {};
for (const prop of blockInfo.message.body.proposer_slashings) {
const keyInfo = keyInfoFn(Number(prop.signed_header_1.proposer_index));
for (const prop of blockInfo.body.proposerSlashings) {
const keyInfo = keyInfoFn(Number(prop.signedHeader1.message.proposerIndex));
if (!keyInfo) continue;
slashed[prop.signed_header_1.proposer_index] = keyInfo;
slashed[prop.signedHeader1.message.proposerIndex] = keyInfo;
}
return slashed;
}
Expand Down
48 changes: 21 additions & 27 deletions src/common/prover/duties/withdrawals.service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { ForkName } from '@lodestar/params';
import { Inject, Injectable, LoggerService } from '@nestjs/common';

import { CsmContract } from '../../contracts/csm-contract.service';
import { VerifierContract } from '../../contracts/verifier-contract.service';
import { Consensus } from '../../providers/consensus/consensus';
import {
BlockHeaderResponse,
BlockInfoResponse,
RootHex,
Withdrawal,
} from '../../providers/consensus/response.interface';
import { Consensus, State, SupportedBlock, SupportedWithdrawal } from '../../providers/consensus/consensus';
import { BlockHeaderResponse, RootHex } from '../../providers/consensus/response.interface';
import { WorkersService } from '../../workers/workers.service';
import { KeyInfo, KeyInfoFn } from '../types';

// according to the research https://hackmd.io/1wM8vqeNTjqt4pC3XoCUKQ?view#Proposed-solution
const FULL_WITHDRAWAL_MIN_AMOUNT = 8 * 10 ** 9; // 8 ETH in Gwei

type WithdrawalWithOffset = Withdrawal & { offset: number };
export type InvolvedKeysWithWithdrawal = { [valIndex: string]: KeyInfo & { withdrawal: WithdrawalWithOffset } };
type WithdrawalWithOffset = SupportedWithdrawal & { offset: number };
export type InvolvedKeysWithWithdrawal = { [valIndex: number]: KeyInfo & { withdrawal: WithdrawalWithOffset } };

@Injectable()
export class WithdrawalsService {
Expand All @@ -31,15 +25,15 @@ export class WithdrawalsService {
) {}

public async getUnprovenWithdrawals(
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
keyInfoFn: KeyInfoFn,
): Promise<InvolvedKeysWithWithdrawal> {
const withdrawals = this.getFullWithdrawals(blockInfo, keyInfoFn);
if (!Object.keys(withdrawals).length) return {};
const unproven: InvolvedKeysWithWithdrawal = {};
for (const [valIndex, keyWithWithdrawalInfo] of Object.entries(withdrawals)) {
const proved = await this.csm.isWithdrawalProved(keyWithWithdrawalInfo);
if (!proved) unproven[valIndex] = keyWithWithdrawalInfo;
if (!proved) unproven[Number(valIndex)] = keyWithWithdrawalInfo;
}
const unprovenCount = Object.keys(unproven).length;
if (!unprovenCount) {
Expand All @@ -52,7 +46,7 @@ export class WithdrawalsService {

public async sendWithdrawalProofs(
blockRoot: RootHex,
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
finalizedHeader: BlockHeaderResponse,
withdrawals: InvolvedKeysWithWithdrawal,
): Promise<void> {
Expand All @@ -72,8 +66,8 @@ export class WithdrawalsService {

private async sendGeneralWithdrawalProofs(
blockHeader: BlockHeaderResponse,
blockInfo: BlockInfoResponse,
state: { bodyBytes: Uint8Array; forkName: keyof typeof ForkName },
blockInfo: SupportedBlock,
state: State,
withdrawals: InvolvedKeysWithWithdrawal,
): Promise<void> {
// create proof against the state with withdrawals
Expand All @@ -96,8 +90,8 @@ export class WithdrawalsService {

private async sendHistoricalWithdrawalProofs(
blockHeader: BlockHeaderResponse,
blockInfo: BlockInfoResponse,
state: { bodyBytes: Uint8Array; forkName: keyof typeof ForkName },
blockInfo: SupportedBlock,
state: State,
finalizedHeader: BlockHeaderResponse,
withdrawals: InvolvedKeysWithWithdrawal,
): Promise<void> {
Expand Down Expand Up @@ -131,33 +125,33 @@ export class WithdrawalsService {
}

private getFullWithdrawals(
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
keyInfoFn: (valIndex: number) => KeyInfo | undefined,
): InvolvedKeysWithWithdrawal {
const fullWithdrawals: InvolvedKeysWithWithdrawal = {};
const withdrawals = blockInfo.message.body.execution_payload?.withdrawals ?? [];
const withdrawals = blockInfo.body.executionPayload.withdrawals;
for (let i = 0; i < withdrawals.length; i++) {
const keyInfo = keyInfoFn(Number(withdrawals[i].validator_index));
const keyInfo = keyInfoFn(withdrawals[i].validatorIndex);
if (!keyInfo) continue;
if (Number(withdrawals[i].amount) < FULL_WITHDRAWAL_MIN_AMOUNT) continue;
fullWithdrawals[withdrawals[i].validator_index] = { ...keyInfo, withdrawal: { ...withdrawals[i], offset: i } };
fullWithdrawals[withdrawals[i].validatorIndex] = { ...keyInfo, withdrawal: { ...withdrawals[i], offset: i } };
}
return fullWithdrawals;
}

private isHistoricalBlock(blockInfo: BlockInfoResponse, finalizedHeader: BlockHeaderResponse): boolean {
private isHistoricalBlock(blockInfo: SupportedBlock, finalizedHeader: BlockHeaderResponse): boolean {
const finalizationBufferEpochs = 2;
const finalizationBufferSlots = this.consensus.epochToSlot(finalizationBufferEpochs);
return (
Number(finalizedHeader.header.message.slot) - Number(blockInfo.message.slot) >=
Number(finalizedHeader.header.message.slot) - Number(blockInfo.slot) >=
Number(this.consensus.beaconConfig.SLOTS_PER_HISTORICAL_ROOT) - finalizationBufferSlots
);
}

private calcSummaryIndex(blockInfo: BlockInfoResponse): number {
private calcSummaryIndex(blockInfo: SupportedBlock): number {
const capellaForkSlot = this.consensus.epochToSlot(Number(this.consensus.beaconConfig.CAPELLA_FORK_EPOCH));
const slotsPerHistoricalRoot = Number(this.consensus.beaconConfig.SLOTS_PER_HISTORICAL_ROOT);
return Math.floor((Number(blockInfo.message.slot) - capellaForkSlot) / slotsPerHistoricalRoot);
return Math.floor((blockInfo.slot - capellaForkSlot) / slotsPerHistoricalRoot);
}

private calcSlotOfSummary(summaryIndex: number): number {
Expand All @@ -166,8 +160,8 @@ export class WithdrawalsService {
return capellaForkSlot + (summaryIndex + 1) * slotsPerHistoricalRoot;
}

private calcRootIndexInSummary(blockInfo: BlockInfoResponse): number {
private calcRootIndexInSummary(blockInfo: SupportedBlock): number {
const slotsPerHistoricalRoot = Number(this.consensus.beaconConfig.SLOTS_PER_HISTORICAL_ROOT);
return Number(blockInfo.message.slot) % slotsPerHistoricalRoot;
return blockInfo.slot % slotsPerHistoricalRoot;
}
}
10 changes: 5 additions & 5 deletions src/common/prover/prover.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common';
import { SlashingsService } from './duties/slashings.service';
import { WithdrawalsService } from './duties/withdrawals.service';
import { KeyInfoFn } from './types';
import { Consensus } from '../providers/consensus/consensus';
import { BlockHeaderResponse, BlockInfoResponse, RootHex } from '../providers/consensus/response.interface';
import { Consensus, SupportedBlock } from '../providers/consensus/consensus';
import { BlockHeaderResponse, RootHex } from '../providers/consensus/response.interface';

@Injectable()
export class ProverService {
Expand All @@ -18,7 +18,7 @@ export class ProverService {

public async handleBlock(
blockRoot: RootHex,
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
finalizedHeader: BlockHeaderResponse,
keyInfoFn: KeyInfoFn,
): Promise<void> {
Expand All @@ -28,7 +28,7 @@ export class ProverService {

public async handleWithdrawalsInBlock(
blockRoot: RootHex,
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
finalizedHeader: BlockHeaderResponse,
keyInfoFn: KeyInfoFn,
): Promise<void> {
Expand All @@ -42,7 +42,7 @@ export class ProverService {
}

public async handleSlashingsInBlock(
blockInfo: BlockInfoResponse,
blockInfo: SupportedBlock,
finalizedHeader: BlockHeaderResponse,
keyInfoFn: KeyInfoFn,
): Promise<void> {
Expand Down
Loading

0 comments on commit 38ab12b

Please sign in to comment.