-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4101fb2
commit dabea50
Showing
25 changed files
with
794 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# CLI working mode | ||
#EL_RPC_URLS=https://mainnet.infura.io/v3/... | ||
#CL_API_URLS=https://quiknode.pro/... | ||
#TX_SENDER_PRIVATE_KEY=... | ||
|
||
# Daemon working mode | ||
#ETH_NETWORK=1 | ||
#EL_RPC_URLS=https://mainnet.infura.io/v3/... | ||
#CL_API_URLS=https://quiknode.pro/... | ||
#KEYSAPI_API_URLS=https://keys-api.lido.fi/ | ||
#LIDO_STAKING_MODULE_ADDRESS="0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320" | ||
#TX_SENDER_PRIVATE_KEY=... | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,10 @@ | |
# ENV | ||
/.env | ||
|
||
# Storage | ||
/.keys-indexer-* | ||
/.roots-stack-* | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v18.17.1 | ||
v20.11.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"singleQuote": true, | ||
"trailingComma": "all" | ||
} | ||
"trailingComma": "all", | ||
"printWidth": 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,126 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { LOGGER_PROVIDER } from '@lido-nestjs/logger'; | ||
import { Inject, Injectable, LoggerService } from '@nestjs/common'; | ||
|
||
import { BlockInfoResponse, RootHex } from '../providers/consensus/response.interface'; | ||
|
||
export interface KeyInfo { | ||
operatorId: number; | ||
keyIndex: number; | ||
pubKey: string; | ||
withdrawableEpoch: number; | ||
} | ||
|
||
type KeyInfoFn = (valIndex: number) => KeyInfo | undefined; | ||
|
||
@Injectable() | ||
export class HandlersService {} | ||
export class HandlersService { | ||
constructor(@Inject(LOGGER_PROVIDER) protected readonly logger: LoggerService) {} | ||
|
||
public async prove(blockRoot: RootHex, blockInfo: BlockInfoResponse, keyInfoFn: KeyInfoFn): Promise<void> { | ||
const slashings = await this.getUnprovenSlashings(blockRoot, blockInfo, keyInfoFn); | ||
const withdrawals = await this.getUnprovenWithdrawals(blockRoot, blockInfo, keyInfoFn); | ||
if (!slashings.length && !withdrawals.length) return; | ||
const payload = await this.buildProvePayload(slashings, withdrawals); | ||
// TODO: ask before sending if CLI or daemon in watch mode | ||
await this.sendProves(payload); | ||
} | ||
|
||
private async buildProvePayload(slashings: string[], withdrawals: string[]): Promise<any> { | ||
// TODO: implement | ||
// this.consensus.getState(...) | ||
return {}; | ||
} | ||
private async sendProves(payload: any): Promise<void> { | ||
// TODO: implement | ||
} | ||
|
||
private async getUnprovenSlashings( | ||
blockRoot: RootHex, | ||
blockInfo: BlockInfoResponse, | ||
keyInfoFn: KeyInfoFn, | ||
): Promise<string[]> { | ||
const slashings = [ | ||
...this.getSlashedProposers(blockInfo, keyInfoFn), | ||
...this.getSlashedAttesters(blockInfo, keyInfoFn), | ||
]; | ||
if (!slashings.length) return []; | ||
const unproven = []; | ||
for (const slashing of slashings) { | ||
// TODO: implement | ||
// const proved = await this.execution.isSlashingProved(slashing); | ||
const proved = false; | ||
if (!proved) unproven.push(slashing); | ||
} | ||
if (!unproven.length) { | ||
this.logger.log(`No slashings to prove. Root [${blockRoot}]`); | ||
return []; | ||
} | ||
this.logger.warn(`🔍 Unproven slashings: ${unproven}`); | ||
return unproven; | ||
} | ||
|
||
private async getUnprovenWithdrawals( | ||
blockRoot: RootHex, | ||
blockInfo: BlockInfoResponse, | ||
keyInfoFn: KeyInfoFn, | ||
): Promise<string[]> { | ||
const withdrawals = this.getFullWithdrawals(blockInfo, keyInfoFn); | ||
if (!withdrawals.length) return []; | ||
const unproven = []; | ||
for (const withdrawal of withdrawals) { | ||
// TODO: implement | ||
// const proved = await this.execution.isSlashingProved(slashing); | ||
const proved = false; | ||
if (!proved) unproven.push(withdrawal); | ||
} | ||
if (!unproven.length) { | ||
this.logger.log(`No full withdrawals to prove. Root [${blockRoot}]`); | ||
return []; | ||
} | ||
this.logger.warn(`🔍 Unproven full withdrawals: ${unproven}`); | ||
return unproven; | ||
} | ||
|
||
private getSlashedAttesters( | ||
blockInfo: BlockInfoResponse, | ||
keyInfoFn: (valIndex: number) => KeyInfo | undefined, | ||
): string[] { | ||
const slashed = []; | ||
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), | ||
); | ||
slashed.push(...accused.filter((item) => keyInfoFn(Number(item)))); | ||
} | ||
return slashed; | ||
} | ||
|
||
private getSlashedProposers( | ||
blockInfo: BlockInfoResponse, | ||
keyInfoFn: (valIndex: number) => KeyInfo | undefined, | ||
): string[] { | ||
const slashed = []; | ||
for (const prop of blockInfo.message.body.proposer_slashings) { | ||
if (keyInfoFn(Number(prop.signed_header_1.proposer_index))) { | ||
slashed.push(prop.signed_header_1.proposer_index); | ||
} | ||
} | ||
return slashed; | ||
} | ||
|
||
private getFullWithdrawals( | ||
blockInfo: BlockInfoResponse, | ||
keyInfoFn: (valIndex: number) => KeyInfo | undefined, | ||
): string[] { | ||
const fullWithdrawals = []; | ||
const blockEpoch = Number(blockInfo.message.slot) / 32; | ||
const withdrawals = blockInfo.message.body.execution_payload.withdrawals; | ||
for (const withdrawal of withdrawals) { | ||
const keyInfo = keyInfoFn(Number(withdrawal.validator_index)); | ||
if (keyInfo && blockEpoch >= keyInfo.withdrawableEpoch) { | ||
fullWithdrawals.push(withdrawal.validator_index); | ||
} | ||
} | ||
return fullWithdrawals; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { LoggerService } from '@nestjs/common'; | ||
import { request } from 'undici'; | ||
import { IncomingHttpHeaders } from 'undici/types/header'; | ||
import BodyReadable from 'undici/types/readable'; | ||
|
||
import { PrometheusService } from '../../prometheus/prometheus.service'; | ||
|
||
export interface RequestPolicy { | ||
timeout: number; | ||
maxRetries: number; | ||
fallbacks: Array<string>; | ||
} | ||
|
||
export interface RequestOptions { | ||
streamed?: boolean; | ||
requestPolicy?: RequestPolicy; | ||
signal?: AbortSignal; | ||
} | ||
|
||
export abstract class BaseRestProvider { | ||
protected readonly mainUrl: string; | ||
protected readonly requestPolicy: RequestPolicy; | ||
|
||
protected constructor( | ||
urls: Array<string>, | ||
responseTimeout: number, | ||
maxRetries: number, | ||
protected readonly logger: LoggerService, | ||
protected readonly prometheus?: PrometheusService, | ||
) { | ||
this.mainUrl = urls[0]; | ||
this.requestPolicy = { | ||
timeout: responseTimeout, | ||
maxRetries: maxRetries, | ||
fallbacks: urls.slice(1), | ||
}; | ||
} | ||
|
||
// TODO: Request should have: | ||
// 1. metrics (if it is daemon mode) | ||
// 2. retries | ||
// 3. fallbacks | ||
|
||
protected async baseGet<T>( | ||
base: string, | ||
endpoint: string, | ||
options?: RequestOptions, | ||
): Promise<T | { body: BodyReadable; headers: IncomingHttpHeaders }> { | ||
options = { | ||
streamed: false, | ||
requestPolicy: this.requestPolicy, | ||
...options, | ||
} as RequestOptions; | ||
const { body, headers, statusCode } = await request(new URL(endpoint, base), { | ||
method: 'GET', | ||
headersTimeout: (options.requestPolicy as RequestPolicy).timeout, | ||
signal: options.signal, | ||
}); | ||
if (statusCode !== 200) { | ||
const hostname = new URL(base).hostname; | ||
throw new Error(`Request failed with status code [${statusCode}] on host [${hostname}]: ${endpoint}`); | ||
} | ||
return options.streamed ? { body: body, headers: headers } : ((await body.json()) as T); | ||
} | ||
|
||
protected async basePost<T>( | ||
base: string, | ||
endpoint: string, | ||
requestBody: any, | ||
options?: RequestOptions, | ||
): Promise<T | { body: BodyReadable; headers: IncomingHttpHeaders }> { | ||
options = { | ||
streamed: false, | ||
requestPolicy: this.requestPolicy, | ||
...options, | ||
} as RequestOptions; | ||
const { body, headers, statusCode } = await request(new URL(endpoint, base), { | ||
method: 'POST', | ||
headersTimeout: (options.requestPolicy as RequestPolicy).timeout, | ||
signal: options.signal, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(requestBody), | ||
}); | ||
if (statusCode !== 200) { | ||
const hostname = new URL(base).hostname; | ||
throw new Error(`Request failed with status code [${statusCode}] on host [${hostname}]: ${endpoint}`); | ||
} | ||
return options.streamed ? { body: body, headers: headers } : ((await body.json()) as T); | ||
} | ||
} |
Oops, something went wrong.