diff --git a/beacon-light-client/solidity/tasks/deploy.ts b/beacon-light-client/solidity/tasks/deploy.ts index d0df33ae6..cb4cd1cdd 100644 --- a/beacon-light-client/solidity/tasks/deploy.ts +++ b/beacon-light-client/solidity/tasks/deploy.ts @@ -22,7 +22,7 @@ task('deploy', 'Deploy the beacon light client contract') console.log('Deploying contracts with the account:', deployer.address); console.log('Account balance:', (await deployer.getBalance()).toString()); - const beaconApi = new BeaconApi(currentConfig.BEACON_REST_API); + const beaconApi = new BeaconApi([currentConfig.BEACON_REST_API]); const beaconLightClient = await ( await ethers.getContractFactory('BeaconLightClient') diff --git a/beacon-light-client/solidity/tasks/start-publishing.ts b/beacon-light-client/solidity/tasks/start-publishing.ts index d80bb1b51..80aaadd60 100644 --- a/beacon-light-client/solidity/tasks/start-publishing.ts +++ b/beacon-light-client/solidity/tasks/start-publishing.ts @@ -81,7 +81,7 @@ task('start-publishing', 'Run relayer') } const redis = new Redis(config.REDIS_HOST!, config.REDIS_PORT); - const beaconApi = new BeaconApi(currentConfig.BEACON_REST_API); + const beaconApi = new BeaconApi([currentConfig.BEACON_REST_API]); const contract = new SolidityContract( lightClientContract, (network.config as any).url, diff --git a/beacon-light-client/solidity/tasks/verify-contracts.ts b/beacon-light-client/solidity/tasks/verify-contracts.ts index c5033d214..1536c3c56 100644 --- a/beacon-light-client/solidity/tasks/verify-contracts.ts +++ b/beacon-light-client/solidity/tasks/verify-contracts.ts @@ -17,7 +17,7 @@ task('verify-contracts', 'Verify') const currentConfig = networkConfig[args.follownetwork] as Config; - const beaconApi = new BeaconApi(currentConfig.BEACON_REST_API!); + const beaconApi = new BeaconApi([currentConfig.BEACON_REST_API!]); await run('verify:verify', { address: args.lightclient, diff --git a/relay/implementations/beacon-api.ts b/relay/implementations/beacon-api.ts index fd5d760e6..a5de6bf22 100644 --- a/relay/implementations/beacon-api.ts +++ b/relay/implementations/beacon-api.ts @@ -13,10 +13,12 @@ import { computeSyncCommitteePeriodAt } from '../../libs/typescript/ts-utils/ssz import path from 'path'; export class BeaconApi implements IBeaconApi { - private beaconRestApi: string; + private beaconRestApis: string[]; + private currentApiIndex: number; - constructor(beaconRestApi: string) { - this.beaconRestApi = beaconRestApi; + constructor(beaconRestApis: string[]) { + this.beaconRestApis = beaconRestApis; + this.currentApiIndex = 0; } async getHashiAdapterInfo(slot: number): Promise<{ @@ -26,7 +28,7 @@ export class BeaconApi implements IBeaconApi { blockHashProof: string[]; }> { const currentBlock = await ( - await fetch(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) ).json(); const { ssz } = await import('@lodestar/types'); @@ -78,7 +80,7 @@ export class BeaconApi implements IBeaconApi { async getCurrentHeadSlot(): Promise { const currentHead = await ( - await fetch(this.concatUrl('/eth/v1/beacon/headers/head')) + await this.fetchWithFallback(this.concatUrl('/eth/v1/beacon/headers/head')) ).json(); return Number(currentHead.data.header.message.slot); @@ -86,7 +88,7 @@ export class BeaconApi implements IBeaconApi { async getBlockSlot(blockHash: string): Promise { const headResult = await ( - await fetch(this.concatUrl(`/eth/v1/beacon/headers/${blockHash}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v1/beacon/headers/${blockHash}`)) ).json(); return Number(headResult.data.header.message.slot); @@ -104,7 +106,7 @@ export class BeaconApi implements IBeaconApi { const { ssz } = await import('@lodestar/types'); const headResult = await ( - await fetch(this.concatUrl(`/eth/v1/beacon/headers/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v1/beacon/headers/${slot}`)) ).json(); return ssz.phase0.BeaconBlockHeader.fromJson( @@ -130,7 +132,7 @@ export class BeaconApi implements IBeaconApi { while (slot <= limitSlot) { blockHeaderResult = await ( - await fetch(this.concatUrl(`/eth/v1/beacon/headers/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v1/beacon/headers/${slot}`)) ).json(); if (blockHeaderResult.code !== 404) { @@ -155,7 +157,7 @@ export class BeaconApi implements IBeaconApi { while (slot <= limitSlot) { blockHeaderBodyResult = await ( - await fetch(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) ).json(); if (blockHeaderBodyResult.code !== 404) { @@ -195,7 +197,7 @@ export class BeaconApi implements IBeaconApi { await this.getBeaconState(prevSlot); const prevFinalizedHeaderResult = await ( - await fetch( + await this.fetchWithFallback( this.concatUrl( `/eth/v1/beacon/headers/${ '0x' + bytesToHex(prevBeaconSate.finalizedCheckpoint.root) @@ -275,7 +277,7 @@ export class BeaconApi implements IBeaconApi { const { beaconState, stateTree } = await this.getBeaconState(slot); const finalizedHeaderResult = await ( - await fetch( + await this.fetchWithFallback( this.concatUrl( `/eth/v1/beacon/headers/${ '0x' + bytesToHex(beaconState.finalizedCheckpoint.root) @@ -305,7 +307,7 @@ export class BeaconApi implements IBeaconApi { const { ssz } = await import('@lodestar/types'); const finalizedBlockBodyResult = await ( - await fetch(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) ).json(); const finalizedBlockBody = ssz.capella.BeaconBlockBody.fromJson( @@ -345,13 +347,13 @@ export class BeaconApi implements IBeaconApi { const { ssz } = await import('@lodestar/types'); const finality_checkpoints = await ( - await fetch( + await this.fetchWithFallback( this.concatUrl(`/eth/v1/beacon/states/${slot}/finality_checkpoints`), ) ).json(); const finalizedHeadResult = await ( - await fetch( + await this.fetchWithFallback( this.concatUrl( `/eth/v1/beacon/headers/${finality_checkpoints.data.finalized.root}`, ), @@ -365,7 +367,7 @@ export class BeaconApi implements IBeaconApi { async getExecutionStateRoot(slot: number): Promise { const block = await ( - await fetch(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) + await this.fetchWithFallback(this.concatUrl(`/eth/v2/beacon/blocks/${slot}`)) ).json(); return block.data.message.body.execution_payload.state_root; @@ -374,7 +376,7 @@ export class BeaconApi implements IBeaconApi { private async getBeaconState(slot: number) { const { ssz } = await import('@lodestar/types'); - const beaconStateSZZ = await fetch( + const beaconStateSZZ = await this.fetchWithFallback( this.concatUrl(`/eth/v2/debug/beacon/states/${slot}`), { headers: { @@ -392,8 +394,34 @@ export class BeaconApi implements IBeaconApi { return { beaconState, stateTree }; } + private nextApi(): void { + this.currentApiIndex = + (this.currentApiIndex + 1) % this.beaconRestApis.length; + } + + private getCurrentApi(): string { + return this.beaconRestApis[this.currentApiIndex]; + } + + private async fetchWithFallback(input: RequestInfo | URL, init?: RequestInit): Promise { + let retries = 0; + while (true) { + try { + return await fetch(input, init); + } catch (error) { + if (retries >= this.beaconRestApis.length) { + throw error; + } + + retries++; + + this.nextApi(); + } + } + } + private concatUrl(urlPath: string): string { - const url = new URL(this.beaconRestApi); + const url = new URL(this.getCurrentApi()); url.pathname = path.join(url.pathname, urlPath); return url.href; diff --git a/relay/workers/poll-updates/poll-updates-worker.ts b/relay/workers/poll-updates/poll-updates-worker.ts index 6d38d6074..94cb100f4 100644 --- a/relay/workers/poll-updates/poll-updates-worker.ts +++ b/relay/workers/poll-updates/poll-updates-worker.ts @@ -53,7 +53,7 @@ import { checkConfig } from '../../../libs/typescript/ts-utils/common-utils'; doUpdate( redis, - new BeaconApi(job.data.beaconRestApi), + new BeaconApi([job.data.beaconRestApi]), proofGenertorQueue, job.data.lastDownloadedUpdateKey, job.data.slotsJump,