Skip to content

Commit

Permalink
Merge pull request #42 from lidofinance/develop
Browse files Browse the repository at this point in the history
develop to main
  • Loading branch information
vgorkavenko authored Oct 16, 2024
2 parents 93da5e2 + fa14744 commit e35d4ca
Show file tree
Hide file tree
Showing 13 changed files with 1,688 additions and 1,457 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
CHAIN_ID=1
EL_RPC_URLS=https://mainnet.infura.io/v3/...
CL_API_URLS=https://quiknode.pro/...
CSM_ADDRESS=0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320
VERIFIER_ADDRESS=0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6321
CSM_ADDRESS=0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F
VERIFIER_ADDRESS=0x3Dfc50f22aCA652a0a6F28a0F892ab62074b5583
TX_SIGNER_PRIVATE_KEY=deadbeef...

# only for daemon mode
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ RUN mkdir -p ./storage/ && chown -R node:node ./storage/

USER node

HEALTHCHECK --interval=120s --timeout=60s --retries=3 \
CMD curl -f http://localhost:$HTTP_PORT/health || exit 1
HEALTHCHECK --interval=360s --timeout=120s --retries=3 \
CMD curl -f http://localhost:$HTTP_PORT/health || exit 1

CMD ["yarn", "start:prod"]
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ So, according to the algorithm, there are the following statements:
| TX_MAX_GAS_PRIORITY_FEES | Maximum gas priority fees for the transaction | no | 10_000_000_000 (10 gwei) |
| TX_GAS_PRIORITY_FEE_PERCENTILE | Gas priority fee percentile for the transaction | no | 25 |
| TX_GAS_FEE_HISTORY_DAYS | Days of gas fee history for analyzing gas | no | 1 |
| TX_GAS_FEE_HISTORY_PERCENTILE | Gas fee percentile for analyzing gas | no | 20 |
| TX_GAS_FEE_HISTORY_PERCENTILE | Gas fee percentile for analyzing gas | no | 50 |
| TX_GAS_LIMIT | Gas limit for the transaction | no | 1_000_000 |
| TX_MINING_WAITING_TIMEOUT_MS | Timeout for waiting for the transaction mining | no | 3_600_000 (1 hour) |
| TX_CONFIRMATIONS | Number of confirmations for the transaction | no | 1 |
| KEYS_INDEXER_RUNNING_PERIOD_MS | Period of running keys indexer in milliseconds | no | 3 * 3_600_000 (3 hours) |
| KEYS_INDEXER_KEYAPI_FRESHNESS_PERIOD_MS | Period of keys indexer freshness in milliseconds | no | 8 * 3_600_000 (8 hours) |
| HTTP_PORT | Port for the HTTP server | no | 8080 |
Expand Down
10 changes: 9 additions & 1 deletion src/common/config/env.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,20 @@ export class EnvironmentVariables {
@IsNumber()
@Max(100)
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
public TX_GAS_FEE_HISTORY_PERCENTILE = 20;
public TX_GAS_FEE_HISTORY_PERCENTILE = 50;

@IsNumber()
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
public TX_GAS_LIMIT = 1_000_000;

@IsNumber()
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
public TX_MINING_WAITING_TIMEOUT_MS = HOUR;

@IsNumber()
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
public TX_CONFIRMATIONS = 1;

@IsNumber()
@Min(30 * MINUTE)
@Transform(({ value }) => parseInt(value, 10), { toClassOnly: true })
Expand Down
2 changes: 1 addition & 1 deletion src/common/prover/duties/slashings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class SlashingsService {
slashings: InvolvedKeys,
): Generator<SlashingProofPayload> {
for (const [valIndex, keyInfo] of Object.entries(slashings)) {
const validator = stateView.validators.get(Number(valIndex));
const validator = stateView.validators.getReadonly(Number(valIndex));
this.logger.log(`Generating validator [${valIndex}] proof`);
const validatorProof = generateValidatorProof(stateView, Number(valIndex));
this.logger.log('Verifying validator proof locally');
Expand Down
10 changes: 5 additions & 5 deletions src/common/prover/duties/withdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export class WithdrawalsService {
): Generator<WithdrawalsProofPayload> {
const epoch = this.consensus.slotToEpoch(Number(currentHeader.header.message.slot));
for (const [valIndex, keyWithWithdrawalInfo] of Object.entries(withdrawals)) {
const validator = stateView.validators.get(Number(valIndex));
const validator = stateView.validators.getReadonly(Number(valIndex));
if (epoch < validator.withdrawableEpoch) {
this.logger.warn(`Validator ${valIndex} is not full withdrawn. Just huge amount of ETH. Skipped`);
continue;
Expand All @@ -184,7 +184,7 @@ export class WithdrawalsService {
(
currentBlockView as ContainerTreeViewType<typeof ssz.capella.BeaconBlock.fields>
).body.executionPayload.withdrawals
.get(keyWithWithdrawalInfo.withdrawal.offset)
.getReadonly(keyWithWithdrawalInfo.withdrawal.offset)
.hashTreeRoot(),
);
yield {
Expand Down Expand Up @@ -233,7 +233,7 @@ export class WithdrawalsService {
): Generator<HistoricalWithdrawalsProofPayload> {
const epoch = this.consensus.slotToEpoch(Number(headerWithWds.header.message.slot));
for (const [valIndex, keyWithWithdrawalInfo] of Object.entries(withdrawals)) {
const validator = stateWithWdsView.validators.get(Number(valIndex));
const validator = stateWithWdsView.validators.getReadonly(Number(valIndex));
if (epoch < validator.withdrawableEpoch) {
this.logger.warn(`Validator ${valIndex} is not full withdrawn. Just huge amount of ETH. Skipped`);
continue;
Expand Down Expand Up @@ -268,15 +268,15 @@ export class WithdrawalsService {
(
blockWithWdsView as ContainerTreeViewType<typeof ssz.capella.BeaconBlock.fields>
).body.executionPayload.withdrawals
.get(keyWithWithdrawalInfo.withdrawal.offset)
.getReadonly(keyWithWithdrawalInfo.withdrawal.offset)
.hashTreeRoot(),
);
this.logger.log('Verifying historical state proof locally');
verifyProof(
finalizedStateView.hashTreeRoot(),
historicalStateProof.gindex,
historicalStateProof.witnesses,
(summaryStateView as ContainerTreeViewType<typeof ssz.capella.BeaconState.fields>).blockRoots.get(
(summaryStateView as ContainerTreeViewType<typeof ssz.capella.BeaconState.fields>).blockRoots.getReadonly(
rootIndexInSummary,
),
);
Expand Down
6 changes: 3 additions & 3 deletions src/common/prover/helpers/proofs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHash } from 'node:crypto';

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

let ssz: typeof import('@lodestar/types').ssz;
Expand All @@ -20,7 +20,7 @@ export function generateWithdrawalProof(
withdrawalOffset: number,
): SingleProof {
// NOTE: ugly hack to replace root with the value to make a proof
const patchedTree = (stateView as any).tree.clone();
const patchedTree = new Tree(stateView.node);
const stateWdGindex = stateView.type.getPathInfo(['latestExecutionPayloadHeader', 'withdrawalsRoot']).gindex;
patchedTree.setNode(
stateWdGindex,
Expand All @@ -43,7 +43,7 @@ export function generateHistoricalStateProof(
rootIndex: number,
): SingleProof {
// NOTE: ugly hack to replace root with the value to make a proof
const patchedTree = (finalizedStateView as any).tree.clone();
const patchedTree = new Tree(finalizedStateView.node);
const blockSummaryRootGI = finalizedStateView.type.getPathInfo([
'historicalSummaries',
summaryIndex,
Expand Down
2 changes: 1 addition & 1 deletion src/common/prover/prover.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export class ProverService {
finalizedHeader: BlockHeaderResponse,
keyInfoFn: KeyInfoFn,
): Promise<void> {
await this.handleWithdrawalsInBlock(blockRoot, blockInfo, finalizedHeader, keyInfoFn);
await this.handleSlashingsInBlock(blockInfo, finalizedHeader, keyInfoFn);
await this.handleWithdrawalsInBlock(blockRoot, blockInfo, finalizedHeader, keyInfoFn);
}

public async handleWithdrawalsInBlock(
Expand Down
2 changes: 1 addition & 1 deletion src/common/providers/consensus/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export class Consensus extends BaseRestProvider implements OnModuleInit {
const { body, headers } = await requestPromise;
this.progress?.show('State downloading', { body, headers });
const forkName = headers['eth-consensus-version'] as keyof typeof ForkName;
const bodyBytes = new Uint8Array(await body.arrayBuffer());
const bodyBytes = await body.bytes();
return { bodyBytes, forkName };
}

Expand Down
9 changes: 7 additions & 2 deletions src/common/providers/execution/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,12 @@ export class Execution {
}
submitted = await submittedPromise;
this.logger.log(`Transaction sent to mempool. Hash: ${submitted.hash}`);
const waitingPromise = submitted.wait();
msg = `Waiting until the transaction has been mined`;
const waitingPromise = this.provider.waitForTransaction(
submitted.hash,
this.config.get('TX_CONFIRMATIONS'),
this.config.get('TX_MINING_WAITING_TIMEOUT_MS'),
);
msg = `Waiting until the transaction has been mined and confirmed ${this.config.get('TX_CONFIRMATIONS')} times`;
if (this.isCLI()) {
spinnerFor(waitingPromise, { text: msg });
} else {
Expand Down Expand Up @@ -205,6 +209,7 @@ export class Execution {
}

private async calcPriorityFee(): Promise<{ maxFeePerGas: bigint; maxPriorityFeePerGas: bigint }> {
this.logger.log('🔄 Calculating priority fee');
const { baseFeePerGas } = await this.provider.getBlock('pending');
const { reward } = await this.provider.getFeeHistory(1, 'latest', [
this.config.get('TX_GAS_PRIORITY_FEE_PERCENTILE'),
Expand Down
7 changes: 5 additions & 2 deletions src/daemon/daemon.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Inject, Injectable, LoggerService, OnModuleInit } from '@nestjs/common'

import * as buildInfo from 'build-info';

import { KeysIndexer } from './services/keys-indexer';
import { KeysIndexer, ModuleNotFoundError } from './services/keys-indexer';
import { RootsProcessor } from './services/roots-processor';
import { RootsProvider } from './services/roots-provider';
import sleep from './utils/sleep';
Expand Down Expand Up @@ -37,10 +37,13 @@ export class DaemonService implements OnModuleInit {
public async loop() {
while (true) {
try {
if (!this.keysIndexer.isInitialized()) await this.keysIndexer.initOrReadServiceData();
await this.baseRun();
} catch (e) {
this.logger.error(e);
await sleep(1000);
e instanceof ModuleNotFoundError
? await sleep(this.keysIndexer.MODULE_NOT_FOUND_NEXT_TRY_MS)
: await sleep(1000);
}
}
}
Expand Down
36 changes: 13 additions & 23 deletions src/daemon/services/keys-indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ListCompositeTreeView } from '@chainsafe/ssz/lib/view/listComposite';
import { Low } from '@huanshiwushuang/lowdb';
import { JSONFile } from '@huanshiwushuang/lowdb/node';
import { LOGGER_PROVIDER } from '@lido-nestjs/logger';
import { Inject, Injectable, LoggerService, OnApplicationBootstrap, OnModuleInit } from '@nestjs/common';
import { Inject, Injectable, LoggerService, OnApplicationBootstrap } from '@nestjs/common';

import { ConfigService } from '../../common/config/config.service';
import {
Expand All @@ -16,7 +16,6 @@ import {
} from '../../common/prometheus';
import { toHex } from '../../common/prover/helpers/proofs';
import { KeyInfo } from '../../common/prover/types';
import { sleep } from '../../common/providers/base/utils/func';
import { Consensus } from '../../common/providers/consensus/consensus';
import { BlockHeaderResponse, RootHex, Slot } from '../../common/providers/consensus/response.interface';
import { Keysapi } from '../../common/providers/keysapi/keysapi';
Expand Down Expand Up @@ -59,13 +58,14 @@ function Single(target: any, propertyKey: string, descriptor: PropertyDescriptor
return descriptor;
}

class ModuleNotFoundError extends Error {}
export class ModuleNotFoundError extends Error {}

@Injectable()
export class KeysIndexer implements OnModuleInit, OnApplicationBootstrap {
private startedAt: number = 0;
export class KeysIndexer implements OnApplicationBootstrap {
public MODULE_NOT_FOUND_NEXT_TRY_MS = 60000;

private MODULE_NOT_FOUND_NEXT_TRY_MS = 60000;
// it actually used by `Single` decorator
private startedAt: number = 0;

private info: Low<KeysIndexerServiceInfo>;
private storage: Low<KeysIndexerServiceStorage>;
Expand All @@ -78,22 +78,6 @@ export class KeysIndexer implements OnModuleInit, OnApplicationBootstrap {
protected readonly keysapi: Keysapi,
) {}

public async onModuleInit(): Promise<void> {
while (true) {
try {
await this.initOrReadServiceData();
return;
} catch (e) {
if (e instanceof ModuleNotFoundError) {
this.logger.error(e);
await sleep(this.MODULE_NOT_FOUND_NEXT_TRY_MS);
continue;
}
throw e;
}
}
}

public async onApplicationBootstrap(): Promise<void> {
this.setMetrics();
}
Expand Down Expand Up @@ -191,7 +175,13 @@ export class KeysIndexer implements OnModuleInit, OnApplicationBootstrap {
return slotNumber - this.info.data.storageStateSlot <= safeDelay; // ~27 hours
}

private async initOrReadServiceData() {
public isInitialized(): boolean {
return Boolean(
this.info?.data?.moduleId && this.info?.data?.storageStateSlot && this.info?.data?.lastValidatorsCount,
);
}

public async initOrReadServiceData() {
const defaultInfo: KeysIndexerServiceInfo = {
moduleAddress: this.config.get('CSM_ADDRESS'),
moduleId: 0,
Expand Down
Loading

0 comments on commit e35d4ca

Please sign in to comment.