diff --git a/docker/grafana/provisioning/dashboards/lodestar.json b/docker/grafana/provisioning/dashboards/lodestar.json index 55533761789d..6cf7dd9a80fb 100644 --- a/docker/grafana/provisioning/dashboards/lodestar.json +++ b/docker/grafana/provisioning/dashboards/lodestar.json @@ -12253,6 +12253,402 @@ ], "title": "Precompute Epoch Transition", "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 313, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 3, + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 33, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [ + { + "options": { + "0": { + "index": 0, + "text": "Aborted" + }, + "1": { + "index": 1, + "text": "Pending" + }, + "2": { + "index": 2, + "text": "Syncing" + }, + "3": { + "index": 3, + "text": "Completed" + } + }, + "type": "value" + } + ], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 39 + }, + "id": 315, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": false, + "expr": "lodestar_backfill_sync_status", + "interval": "", + "legendFormat": "status", + "refId": "A" + } + ], + "title": "Backfill Sync Status", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "prev finalized or ws slot for validation" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 39 + }, + "id": 317, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": false, + "expr": "lodestar_backfill_till_slot", + "interval": "", + "legendFormat": "last backfilled slot", + "refId": "A" + }, + { + "exemplar": false, + "expr": "lodestar_backfill_prev_fin_or_ws_slot", + "hide": false, + "interval": "", + "legendFormat": "prev finalized or ws slot for validation", + "refId": "B" + } + ], + "title": "Backfilled Till", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 1, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 64, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 47 + }, + "id": 319, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": false, + "expr": "rate(lodestar_backfill_sync_blocks_total[$__rate_interval])", + "interval": "", + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "title": "Backfill Block Sync Rate", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 47 + }, + "id": 321, + "options": { + "legend": { + "calcs": [], + "displayMode": "hidden", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": false, + "expr": "rate(lodestar_backfill_sync_errors_total[$__rate_interval])", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Backfill Sync Error Rate", + "type": "timeseries" + } + ], + "title": "Backfill Stats", + "type": "row" } ], "refresh": "30s", diff --git a/packages/cli/src/cmds/beacon/handler.ts b/packages/cli/src/cmds/beacon/handler.ts index 9794ce5e0bff..328773e3882b 100644 --- a/packages/cli/src/cmds/beacon/handler.ts +++ b/packages/cli/src/cmds/beacon/handler.ts @@ -72,7 +72,14 @@ export async function beaconHandler(args: IBeaconArgs & IGlobalArgs): Promise, wsState: TreeBacked, wsCheckpoint: Checkpoint -): Promise> { +): Promise<{anchorState: TreeBacked; wsCheckpoint: Checkpoint}> { // Check if the store's state and wsState are compatible if ( store.genesisTime !== wsState.genesisTime || @@ -64,7 +64,10 @@ async function initAndVerifyWeakSubjectivityState( throw new Error("Fetched weak subjectivity checkpoint not within weak subjectivity period."); } - return await initStateFromAnchorState(config, db, logger, anchorState); + anchorState = await initStateFromAnchorState(config, db, logger, anchorState); + + // Return the latest anchorState but still return original wsCheckpoint to validate in backfill + return {anchorState, wsCheckpoint}; } /** @@ -83,7 +86,7 @@ export async function initBeaconState( db: IBeaconDb, logger: ILogger, signal: AbortSignal -): Promise> { +): Promise<{anchorState: TreeBacked; wsCheckpoint?: Checkpoint}> { // fetch the latest state stored in the db // this will be used in all cases, if it exists, either used during verification of a weak subjectivity state, or used directly as the anchor state const lastDbState = await db.stateArchive.lastValue(); @@ -132,16 +135,19 @@ export async function initBeaconState( } else if (lastDbState) { // start the chain from the latest stored state in the db const config = createIBeaconConfig(chainForkConfig, lastDbState.genesisValidatorsRoot); - return await initStateFromAnchorState(config, db, logger, lastDbState); + const anchorState = await initStateFromAnchorState(config, db, logger, lastDbState); + return {anchorState}; } else { const genesisStateFile = args.genesisStateFile || getGenesisFileUrl(args.network || defaultNetwork); if (genesisStateFile && !args.forceGenesis) { const stateBytes = await downloadOrLoadFile(genesisStateFile); - const anchorState = getStateTypeFromBytes(chainForkConfig, stateBytes).createTreeBackedFromBytes(stateBytes); + let anchorState = getStateTypeFromBytes(chainForkConfig, stateBytes).createTreeBackedFromBytes(stateBytes); const config = createIBeaconConfig(chainForkConfig, anchorState.genesisValidatorsRoot); - return await initStateFromAnchorState(config, db, logger, anchorState); + anchorState = await initStateFromAnchorState(config, db, logger, anchorState); + return {anchorState}; } else { - return await initStateFromEth1({config: chainForkConfig, db, logger, opts: options.eth1, signal}); + const anchorState = await initStateFromEth1({config: chainForkConfig, db, logger, opts: options.eth1, signal}); + return {anchorState}; } } } diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index cefcaf113709..9d4ac48e4ced 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -66,6 +66,8 @@ export enum Bucket { lightClient_bestPartialLightClientUpdate = 54, // SyncPeriod -> PartialLightClientUpdate validator_metaData = 41, + + backfilled_ranges = 42, // Backfilled From to To, inclusive of both From, To } export enum Key { diff --git a/packages/lodestar/src/chain/archiver/archiveStates.ts b/packages/lodestar/src/chain/archiver/archiveStates.ts index 2b3794d16b1a..09e9cbd51193 100644 --- a/packages/lodestar/src/chain/archiver/archiveStates.ts +++ b/packages/lodestar/src/chain/archiver/archiveStates.ts @@ -3,6 +3,7 @@ */ import {ILogger} from "@chainsafe/lodestar-utils"; +import {Slot} from "@chainsafe/lodestar-types"; import {computeEpochAtSlot} from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconDb} from "../../db"; import {CheckpointStateCache} from "../stateCache"; @@ -42,10 +43,22 @@ export class StatesArchiver { * epoch - 1024*2 epoch - 1024 epoch - 32 epoch * ``` */ - async maybeArchiveState(finalized: CheckpointWithHex): Promise { + async maybeArchiveState(finalized: CheckpointWithHex, anchorSlot: Slot): Promise { const lastStoredSlot = await this.db.stateArchive.lastKey(); const lastStoredEpoch = computeEpochAtSlot(lastStoredSlot ?? 0); + // Mark the sequence in backfill db from finalized slot till anchor slot as filled + const finalizedState = this.checkpointStateCache.get(finalized); + if (!finalizedState) { + throw Error("No state in cache for finalized checkpoint state epoch #" + finalized.epoch); + } + await this.db.backfilledRanges.put(finalizedState.slot, anchorSlot); + + // Clear previously marked sequence till anchorSlot, without touching backfill sync + // process sequence which are at <=anchorSlot i.e. clear >anchorSlot and < currentSlot + const filteredSeqs = await this.db.backfilledRanges.keys({gt: anchorSlot, lt: finalizedState.slot}); + await this.db.backfilledRanges.batchDelete(filteredSeqs); + if (finalized.epoch - lastStoredEpoch > PERSIST_TEMP_STATE_EVERY_EPOCHS) { await this.archiveState(finalized); diff --git a/packages/lodestar/src/chain/archiver/index.ts b/packages/lodestar/src/chain/archiver/index.ts index 265c03d212e3..d272528dfcb8 100644 --- a/packages/lodestar/src/chain/archiver/index.ts +++ b/packages/lodestar/src/chain/archiver/index.ts @@ -71,7 +71,7 @@ export class Archiver { await archiveBlocks(this.db, this.chain.forkChoice, this.chain.lightClientServer, this.logger, finalized); // should be after ArchiveBlocksTask to handle restart cleanly - await this.statesArchiver.maybeArchiveState(finalized); + await this.statesArchiver.maybeArchiveState(finalized, this.chain.anchorSlot); await Promise.all([ this.chain.checkpointStateCache.pruneFinalized(finalizedEpoch), diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 20997909c07e..eb936f09ed14 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -51,6 +51,7 @@ export class BeaconChain implements IBeaconChain { readonly executionEngine: IExecutionEngine; // Expose config for convenience in modularized functions readonly config: IBeaconConfig; + readonly anchorSlot: Slot; bls: IBlsVerifier; forkChoice: IForkChoice; @@ -109,6 +110,7 @@ export class BeaconChain implements IBeaconChain { this.logger = logger; this.metrics = metrics; this.genesisTime = anchorState.genesisTime; + this.anchorSlot = anchorState.slot; this.genesisValidatorsRoot = anchorState.genesisValidatorsRoot.valueOf() as Uint8Array; this.eth1 = eth1; this.executionEngine = executionEngine; diff --git a/packages/lodestar/src/chain/interface.ts b/packages/lodestar/src/chain/interface.ts index 9119e08171cb..c52cde32c643 100644 --- a/packages/lodestar/src/chain/interface.ts +++ b/packages/lodestar/src/chain/interface.ts @@ -40,6 +40,9 @@ export interface IBeaconChain { // Expose config for convenience in modularized functions readonly config: IBeaconConfig; + /** The initial slot that the chain is started with */ + readonly anchorSlot: Slot; + bls: IBlsVerifier; forkChoice: IForkChoice; clock: IBeaconClock; diff --git a/packages/lodestar/src/constants/network.ts b/packages/lodestar/src/constants/network.ts index 0fd765408fad..d2caeeba6c00 100644 --- a/packages/lodestar/src/constants/network.ts +++ b/packages/lodestar/src/constants/network.ts @@ -44,7 +44,6 @@ export type RpcResponseStatusError = Exclude; export const GOSSIP_MAX_SIZE = 2 ** 20; /** The maximum allowed size of uncompressed req/resp chunked responses. */ export const MAX_CHUNK_SIZE = 2 ** 20; - /** The maximum time to wait for first byte of request response (time-to-first-byte). */ export const TTFB_TIMEOUT = 5 * 1000; // 5 sec /** The maximum time for complete response transfer. */ diff --git a/packages/lodestar/src/db/beacon.ts b/packages/lodestar/src/db/beacon.ts index 13ad05fa941b..33b740a8f1c8 100644 --- a/packages/lodestar/src/db/beacon.ts +++ b/packages/lodestar/src/db/beacon.ts @@ -18,6 +18,7 @@ import { CheckpointHeaderRepository, SyncCommitteeRepository, SyncCommitteeWitnessRepository, + BackfilledRanges, } from "./repositories"; import {PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single"; @@ -44,6 +45,8 @@ export class BeaconDb extends DatabaseService implements IBeaconDb { syncCommittee: SyncCommitteeRepository; syncCommitteeWitness: SyncCommitteeWitnessRepository; + backfilledRanges: BackfilledRanges; + constructor(opts: IDatabaseApiOptions) { super(opts); this.metrics = opts.metrics; @@ -65,6 +68,8 @@ export class BeaconDb extends DatabaseService implements IBeaconDb { this.checkpointHeader = new CheckpointHeaderRepository(this.config, this.db, this.metrics); this.syncCommittee = new SyncCommitteeRepository(this.config, this.db, this.metrics); this.syncCommitteeWitness = new SyncCommitteeWitnessRepository(this.config, this.db, this.metrics); + + this.backfilledRanges = new BackfilledRanges(this.config, this.db, this.metrics); } async stop(): Promise { diff --git a/packages/lodestar/src/db/interface.ts b/packages/lodestar/src/db/interface.ts index 59e11b926016..6eef82f9dc51 100644 --- a/packages/lodestar/src/db/interface.ts +++ b/packages/lodestar/src/db/interface.ts @@ -18,6 +18,7 @@ import { CheckpointHeaderRepository, SyncCommitteeRepository, SyncCommitteeWitnessRepository, + BackfilledRanges, } from "./repositories"; import {PreGenesisState, PreGenesisStateLastProcessedBlock} from "./single"; @@ -58,6 +59,8 @@ export interface IBeaconDb { syncCommittee: SyncCommitteeRepository; syncCommitteeWitness: SyncCommitteeWitnessRepository; + backfilledRanges: BackfilledRanges; + /** * Start the connection to the db instance and open the db store. */ diff --git a/packages/lodestar/src/db/repositories/backfilledRanges.ts b/packages/lodestar/src/db/repositories/backfilledRanges.ts new file mode 100644 index 000000000000..14ee131f2902 --- /dev/null +++ b/packages/lodestar/src/db/repositories/backfilledRanges.ts @@ -0,0 +1,28 @@ +import {IChainForkConfig} from "@chainsafe/lodestar-config"; +import {Slot, ssz} from "@chainsafe/lodestar-types"; +import {IDatabaseController, Bucket, IDbMetrics, Repository} from "@chainsafe/lodestar-db"; +import {bytesToInt} from "@chainsafe/lodestar-utils"; + +/** + * Slot to slot ranges that ensure that block range is fully backfilled + * + * If node starts backfilling at slots 1000, and backfills to 800, there will be an entry + * 1000 -> 800 + * + * When the node is backfilling if it starts at 1200 and backfills to 1000, it will find this sequence and, + * jump directly to 800 and delete the key 1000. + */ +export class BackfilledRanges extends Repository { + constructor(config: IChainForkConfig, db: IDatabaseController, metrics?: IDbMetrics) { + super(config, db, Bucket.backfilled_ranges, ssz.Slot, metrics); + } + + decodeKey(data: Buffer): number { + return bytesToInt((super.decodeKey(data) as unknown) as Uint8Array, "be"); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getId(value: Slot): number { + throw new Error("Cannot get the db key from slot"); + } +} diff --git a/packages/lodestar/src/db/repositories/index.ts b/packages/lodestar/src/db/repositories/index.ts index ab49668dbaf3..7234c896a246 100644 --- a/packages/lodestar/src/db/repositories/index.ts +++ b/packages/lodestar/src/db/repositories/index.ts @@ -14,3 +14,4 @@ export {BestPartialLightClientUpdateRepository} from "./lightclientBestPartialUp export {CheckpointHeaderRepository} from "./lightclientCheckpointHeader"; export {SyncCommitteeRepository} from "./lightclientSyncCommittee"; export {SyncCommitteeWitnessRepository} from "./lightclientSyncCommitteeWitness"; +export {BackfilledRanges} from "./backfilledRanges"; diff --git a/packages/lodestar/src/metrics/metrics/lodestar.ts b/packages/lodestar/src/metrics/metrics/lodestar.ts index dfeef24310d5..b9d6d417958a 100644 --- a/packages/lodestar/src/metrics/metrics/lodestar.ts +++ b/packages/lodestar/src/metrics/metrics/lodestar.ts @@ -431,6 +431,30 @@ export function createLodestarMetrics( }), }, + backfillSync: { + backfilledTillSlot: register.gauge({ + name: "lodestar_backfill_till_slot", + help: "Current lowest backfilled slot", + }), + prevFinOrWsSlot: register.gauge({ + name: "lodestar_backfill_prev_fin_or_ws_slot", + help: "Slot of previous finalized or wsCheckpoint block to be validated", + }), + totalBlocks: register.gauge<"method">({ + name: "lodestar_backfill_sync_blocks_total", + help: "Total amount of backfilled blocks", + labelNames: ["method"], + }), + errors: register.gauge({ + name: "lodestar_backfill_sync_errors_total", + help: "Total number of errors while backfilling", + }), + status: register.gauge({ + name: "lodestar_backfill_sync_status", + help: "Current backfill syncing status: [Aborted, Pending, Syncing, Completed]", + }), + }, + // Validator monitoring validatorMonitor: { diff --git a/packages/lodestar/src/network/interface.ts b/packages/lodestar/src/network/interface.ts index 5d405b0dc0bf..45fc7a06c3c3 100644 --- a/packages/lodestar/src/network/interface.ts +++ b/packages/lodestar/src/network/interface.ts @@ -8,7 +8,7 @@ import PeerId from "peer-id"; import {INetworkEventBus} from "./events"; import {Eth2Gossipsub} from "./gossip"; import {MetadataController} from "./metadata"; -import {IPeerRpcScoreStore, IPeerMetadataStore} from "./peers"; +import {IPeerRpcScoreStore, IPeerMetadataStore, PeerAction} from "./peers"; import {IReqResp} from "./reqresp"; import {IAttnetsService, ISubnetsService, CommitteeSubscription} from "./subnets"; @@ -39,6 +39,7 @@ export interface INetwork { /** Subscribe, search peers, join long-lived syncnets */ prepareSyncCommitteeSubnets(subscriptions: CommitteeSubscription[]): void; reStatusPeers(peers: PeerId[]): void; + reportPeer(peer: PeerId, action: PeerAction, actionName?: string): void; // Gossip handler subscribeGossipCoreTopics(): void; diff --git a/packages/lodestar/src/network/network.ts b/packages/lodestar/src/network/network.ts index 68befbb8dbcc..5f8ce815a6e4 100644 --- a/packages/lodestar/src/network/network.ts +++ b/packages/lodestar/src/network/network.ts @@ -22,7 +22,7 @@ import {MetadataController} from "./metadata"; import {getActiveForks, getCurrentAndNextFork, FORK_EPOCH_LOOKAHEAD} from "./forks"; import {IPeerMetadataStore, Libp2pPeerMetadataStore} from "./peers/metastore"; import {PeerManager} from "./peers/peerManager"; -import {IPeerRpcScoreStore, PeerRpcScoreStore} from "./peers"; +import {IPeerRpcScoreStore, PeerAction, PeerRpcScoreStore} from "./peers"; import {INetworkEventBus, NetworkEventBus} from "./events"; import {AttnetsService, SyncnetsService, CommitteeSubscription} from "./subnets"; @@ -205,6 +205,10 @@ export class Network implements INetwork { this.peerManager.reStatusPeers(peers); } + reportPeer(peer: PeerId, action: PeerAction, actionName?: string): void { + this.peerRpcScores.applyAction(peer, action, actionName); + } + /** * Subscribe to all gossip events. Safe to call multiple times */ diff --git a/packages/lodestar/src/node/nodejs.ts b/packages/lodestar/src/node/nodejs.ts index e3e23050ce58..3f7fe97c758a 100644 --- a/packages/lodestar/src/node/nodejs.ts +++ b/packages/lodestar/src/node/nodejs.ts @@ -8,13 +8,14 @@ import {Registry} from "prom-client"; import {TreeBacked} from "@chainsafe/ssz"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; -import {allForks} from "@chainsafe/lodestar-types"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {ILogger} from "@chainsafe/lodestar-utils"; import {Api} from "@chainsafe/lodestar-api"; import {IBeaconDb} from "../db"; import {INetwork, Network, getReqRespHandlers} from "../network"; import {BeaconSync, IBeaconSync} from "../sync"; +import {BackfillSync} from "../sync/backfill"; import {BeaconChain, IBeaconChain, initBeaconMetrics} from "../chain"; import {createMetrics, IMetrics, HttpMetricsServer} from "../metrics"; import {getApi, RestApi} from "../api"; @@ -34,6 +35,7 @@ export interface IBeaconNodeModules { chain: IBeaconChain; api: Api; sync: IBeaconSync; + backfillSync: BackfillSync; metricsServer?: HttpMetricsServer; restApi?: RestApi; controller?: AbortController; @@ -46,6 +48,7 @@ export interface IBeaconNodeInitModules { logger: ILogger; libp2p: LibP2p; anchorState: TreeBacked; + wsCheckpoint?: phase0.Checkpoint; metricsRegistries?: Registry[]; } @@ -70,6 +73,7 @@ export class BeaconNode { api: Api; restApi?: RestApi; sync: IBeaconSync; + backfillSync: BackfillSync; status: BeaconNodeStatus; private controller?: AbortController; @@ -85,6 +89,7 @@ export class BeaconNode { api, restApi, sync, + backfillSync, controller, }: IBeaconNodeModules) { this.opts = opts; @@ -97,6 +102,7 @@ export class BeaconNode { this.restApi = restApi; this.network = network; this.sync = sync; + this.backfillSync = backfillSync; this.controller = controller; this.status = BeaconNodeStatus.started; @@ -113,6 +119,7 @@ export class BeaconNode { logger, libp2p, anchorState, + wsCheckpoint, metricsRegistries = [], }: IBeaconNodeInitModules): Promise { const controller = new AbortController(); @@ -158,6 +165,18 @@ export class BeaconNode { chain, metrics, network, + wsCheckpoint, + logger: logger.child(opts.logger.sync), + }); + + const backfillSync = await BackfillSync.init({ + config, + db, + chain, + metrics, + network, + wsCheckpoint, + anchorState, logger: logger.child(opts.logger.sync), }); @@ -203,6 +222,7 @@ export class BeaconNode { api, restApi, sync, + backfillSync, controller, }) as T; } @@ -214,6 +234,7 @@ export class BeaconNode { if (this.status === BeaconNodeStatus.started) { this.status = BeaconNodeStatus.closing; this.sync.close(); + this.backfillSync?.close(); await this.network.stop(); if (this.metricsServer) await this.metricsServer.stop(); if (this.restApi) await this.restApi.close(); diff --git a/packages/lodestar/src/node/utils/state.ts b/packages/lodestar/src/node/utils/state.ts index b20365c41d42..513d903541cb 100644 --- a/packages/lodestar/src/node/utils/state.ts +++ b/packages/lodestar/src/node/utils/state.ts @@ -6,6 +6,7 @@ import {mkdirSync, writeFileSync} from "fs"; import {dirname} from "path"; import {IBeaconDb} from "../../db"; import {TreeBacked} from "@chainsafe/ssz"; +import {GENESIS_SLOT} from "../../constants"; export async function initDevState( config: IChainForkConfig, @@ -21,6 +22,9 @@ export async function initDevState( deposits, await db.depositDataRoot.getTreeBacked(validatorCount - 1) ); + const block = config.getForkTypes(GENESIS_SLOT).SignedBeaconBlock.defaultValue(); + block.message.stateRoot = config.getForkTypes(state.slot).BeaconState.hashTreeRoot(state); + await db.blockArchive.add(block); return state; } diff --git a/packages/lodestar/src/sync/backfill/backfill.ts b/packages/lodestar/src/sync/backfill/backfill.ts new file mode 100644 index 000000000000..d9573b6f05cb --- /dev/null +++ b/packages/lodestar/src/sync/backfill/backfill.ts @@ -0,0 +1,853 @@ +import {IMetrics} from "../../metrics/metrics"; +import {EventEmitter} from "events"; +import PeerId from "peer-id"; +import {StrictEventEmitter} from "strict-event-emitter-types"; +import {blockToHeader} from "@chainsafe/lodestar-beacon-state-transition"; +import {IBeaconConfig, IChainForkConfig} from "@chainsafe/lodestar-config"; +import {phase0, Root, Slot, allForks, ssz} from "@chainsafe/lodestar-types"; +import {ErrorAborted, ILogger} from "@chainsafe/lodestar-utils"; +import {List, toHexString} from "@chainsafe/ssz"; +import {IBeaconChain} from "../../chain"; +import {GENESIS_SLOT, ZERO_HASH} from "../../constants"; +import {IBeaconDb} from "../../db"; +import {INetwork, NetworkEvent, PeerAction} from "../../network"; +import {ItTrigger} from "../../util/itTrigger"; +import {PeerSet} from "../../util/peerMap"; +import {shuffleOne} from "../../util/shuffle"; +import {BackfillSyncError, BackfillSyncErrorCode} from "./errors"; +import {verifyBlockProposerSignature, verifyBlockSequence, BackfillBlockHeader, BackfillBlock} from "./verify"; +import {SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; +import {byteArrayEquals} from "../../util/bytes"; +import {TreeBacked} from "@chainsafe/ssz"; +import {computeAnchorCheckpoint} from "../../chain/initState"; + +/** Default batch size. Same as range sync (2 epochs) */ +const BATCH_SIZE = 64; + +export type BackfillSyncModules = { + chain: IBeaconChain; + db: IBeaconDb; + network: INetwork; + config: IBeaconConfig; + logger: ILogger; + metrics: IMetrics | null; + anchorState: TreeBacked; + wsCheckpoint?: phase0.Checkpoint; +}; + +type BackfillModules = BackfillSyncModules & { + syncAnchor: BackFillSyncAnchor; + backfillStartFromSlot: Slot; + prevFinalizedCheckpointBlock: BackfillBlockHeader; + wsCheckpointHeader: BackfillBlockHeader | null; + backfillRangeWrittenSlot: Slot | null; +}; + +export type BackfillSyncOpts = { + batchSize: number; +}; + +export enum BackfillSyncEvent { + completed = "BackfillSync-completed", +} + +export enum BackfillSyncMethod { + database = "database", + backfilled_ranges = "backfilled_ranges", + rangesync = "rangesync", + blockbyroot = "blockbyroot", +} + +export enum BackfillSyncStatus { + pending = "pending", + syncing = "syncing", + completed = "completed", + aborted = "aborted", +} + +/** Map a SyncState to an integer for rendering in Grafana */ +const syncStatus: {[K in BackfillSyncStatus]: number} = { + [BackfillSyncStatus.aborted]: 0, + [BackfillSyncStatus.pending]: 1, + [BackfillSyncStatus.syncing]: 2, + [BackfillSyncStatus.completed]: 3, +}; + +type BackfillSyncEvents = { + [BackfillSyncEvent.completed]: ( + /** Oldest slot synced */ + oldestSlotSynced: Slot + ) => void; +}; + +type BackfillSyncEmitter = StrictEventEmitter; + +/** + * At any given point, we should have + * 1. anchorBlock (with its root anchorBlockRoot at anchorSlot) for next round of sync + * which is the same as the lastBackSyncedBlock + * 2. We know the anchorBlockRoot but don't have its anchorBlock and anchorSlot yet, and its + * parent of lastBackSyncedBlock we synced in a previous successfull round + * 3. We just started with only anchorBlockRoot, but we know (and will validate) its anchorSlot + */ +type BackFillSyncAnchor = + | { + anchorBlock: allForks.SignedBeaconBlock; + anchorBlockRoot: Root; + anchorSlot: Slot; + lastBackSyncedBlock: BackfillBlock; + } + | {anchorBlock: null; anchorBlockRoot: Root; anchorSlot: null; lastBackSyncedBlock: BackfillBlock} + | {anchorBlock: null; anchorBlockRoot: Root; anchorSlot: Slot; lastBackSyncedBlock: null}; + +export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}) { + /** Lowest slot that we have backfilled to */ + syncAnchor: BackFillSyncAnchor; + + private readonly chain: IBeaconChain; + private readonly network: INetwork; + private readonly db: IBeaconDb; + private readonly config: IBeaconConfig; + private readonly logger: ILogger; + private readonly metrics: IMetrics | null; + + /** + * Process in blocks of at max batchSize + */ + private opts: BackfillSyncOpts; + /** + * If wsCheckpoint provided was in past then the (db) state from which beacon node started, + * needs to be validated as per spec. + * + * 1. This could lie in between of the previous backfilled range, in which case it would be + * sufficient to check if its DB, once the linkage to that range has been verified. + * 2. Else if it lies outside the backfilled range, the linkage to this checkpoint in + * backfill needs to be verified. + */ + private wsCheckpointHeader: BackfillBlockHeader | null; + private wsValidated = false; + + /** + * From https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/weak-subjectivity.md + * + * + * If + * 1. The wsCheckpoint provided was ahead of the db's finalized checkpoint or + * 2. There were gaps in the backfill - keys to backfillRanges are always (by construction) + * a) Finalized Checkpoint or b) previous wsCheckpoint + * + * the linkage to the previous finalized/wss checkpoint(s) needs to be verfied. If there is + * no such checkpoint remaining, the linkage to genesis needs to be validated + * + * Initialize with the blockArchive's last block, and on verification update to the next + * preceding backfillRange key's checkpoint. + */ + private prevFinalizedCheckpointBlock: BackfillBlockHeader; + /** Starting point that this specific backfill sync "session" started from */ + private backfillStartFromSlot: Slot; + private backfillRangeWrittenSlot: Slot | null; + + private processor = new ItTrigger(); + private peers = new PeerSet(); + private status: BackfillSyncStatus = BackfillSyncStatus.pending; + + constructor(modules: BackfillModules, opts?: BackfillSyncOpts) { + super(); + + this.syncAnchor = modules.syncAnchor; + this.backfillStartFromSlot = modules.backfillStartFromSlot; + this.backfillRangeWrittenSlot = modules.backfillRangeWrittenSlot; + this.prevFinalizedCheckpointBlock = modules.prevFinalizedCheckpointBlock; + this.wsCheckpointHeader = modules.wsCheckpointHeader; + + this.chain = modules.chain; + this.network = modules.network; + this.db = modules.db; + this.config = modules.config; + this.logger = modules.logger; + this.metrics = modules.metrics; + + this.opts = opts ?? {batchSize: BATCH_SIZE}; + this.network.events.on(NetworkEvent.peerConnected, this.addPeer); + this.network.events.on(NetworkEvent.peerDisconnected, this.removePeer); + + this.sync() + .then((oldestSlotSynced) => { + if (this.status !== BackfillSyncStatus.completed) { + throw new ErrorAborted(`Invalid BackfillSyncStatus at the completion of sync loop status=${this.status}`); + } + this.emit(BackfillSyncEvent.completed, oldestSlotSynced); + this.logger.info("BackfillSync completed", {oldestSlotSynced}); + // Sync completed, unsubscribe listeners and don't run the processor again. + // Backfill is never necessary again until the node shuts down + this.close(); + }) + .catch((e) => { + this.logger.error("BackfillSync processor error", e); + this.status = BackfillSyncStatus.aborted; + this.close(); + }); + + const metrics = this.metrics; + if (metrics) { + metrics.backfillSync.status.addCollect(() => metrics.backfillSync.status.set(syncStatus[this.status])); + metrics.backfillSync.backfilledTillSlot.addCollect(() => + metrics.backfillSync.backfilledTillSlot.set( + this.syncAnchor.lastBackSyncedBlock?.slot ?? this.backfillStartFromSlot + ) + ); + metrics.backfillSync.prevFinOrWsSlot.addCollect(() => + metrics.backfillSync.prevFinOrWsSlot.set(Math.max(this.prevFinalizedCheckpointBlock.slot, GENESIS_SLOT)) + ); + } + } + + /** + * Use the root of the anchorState of the beacon node as the starting point of the + * backfill sync with its expected slot to be anchorState.slot, which will be + * validated once the block is resolved in the backfill sync. + * + * NOTE: init here is quite light involving couple of + * + * 1. db keys lookup in stateArchive/backfilledRanges + * 2. computing root(s) for anchorBlockRoot and prevFinalizedCheckpointBlock + * + * The way we initialize beacon node, wsCheckpoint's slot is always <= anchorSlot + * If: + * the root belonging to wsCheckpoint is in the DB, we need to verify linkage to it + * i.e. it becomes our first prevFinalizedCheckpointBlock + * Else + * we initialize prevFinalizedCheckpointBlock from the last stored db finalized state + * for verification and when we go below its epoch we just check if a correct block + * corresponding to wsCheckpoint root was stored. + * + * and then we continue going back and verifying the next unconnected previous finalized + * or wsCheckpoints identifiable as the keys of backfill sync. + */ + static async init( + modules: BackfillSyncModules, + opts?: BackfillSyncOpts + ): Promise { + const {config, anchorState, db, wsCheckpoint, logger} = modules; + + const {checkpoint: anchorCp} = computeAnchorCheckpoint(config, anchorState); + const syncAnchor = { + anchorBlock: null, + anchorBlockRoot: anchorCp.root, + anchorSlot: anchorState.slot, + lastBackSyncedBlock: null, + }; + const backfillStartFromSlot = anchorState.slot; + modules.logger.info("BackfillSync - initializing from Checkpoint", { + root: toHexString(anchorCp.root), + epoch: anchorCp.epoch, + }); + + // Load the previous written to slot for the key backfillStartFromSlot + // in backfilledRanges + const backfillRangeWrittenSlot = await db.backfilledRanges.get(backfillStartFromSlot); + + // wsCheckpointHeader is where the checkpoint can actually be validated + const wsCheckpointHeader: BackfillBlockHeader | null = wsCheckpoint + ? {root: wsCheckpoint.root, slot: wsCheckpoint.epoch * SLOTS_PER_EPOCH} + : null; + // Load a previous finalized or wsCheckpoint slot from DB below anchorSlot + const prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(config, db, anchorState.slot, logger); + + return new this( + { + syncAnchor, + backfillStartFromSlot, + backfillRangeWrittenSlot, + wsCheckpointHeader, + prevFinalizedCheckpointBlock, + ...modules, + }, + opts + ) as T; + } + + /** Throw / return all AsyncGenerators */ + close(): void { + this.network.events.off(NetworkEvent.peerConnected, this.addPeer); + this.network.events.off(NetworkEvent.peerDisconnected, this.removePeer); + this.processor.end(new ErrorAborted("BackfillSync")); + } + + /** + * @returns Returns oldestSlotSynced + */ + private async sync(): Promise { + this.processor.trigger(); + + for await (const _ of this.processor) { + if (this.status === BackfillSyncStatus.aborted) { + /** Break out of sync loop and throw error */ + break; + } + this.status = BackfillSyncStatus.syncing; + + // 1. We should always have either anchorBlock or anchorBlockRoot, they are the + // anchor points for this round of the sync + // 2. Check and validate if we have reached prevFinalizedCheckpointBlock + // On success Update prevFinalizedCheckpointBlock to check the *next* previous + // 3. Validate Checkpoint as part of DB block tree if we have backfilled + // before the checkpoint + // 4. Exit the sync if backfilled till genesis + // + // 5. Check if we can jump back from available backfill sequence, if found yield and + // recontinue from top making checks + // 7. Check and read batchSize from DB, if found yield and recontinue from top + // 8. If not in DB, and if peer available + // a) Either fetch blockByRoot if only anchorBlockRoot is set, which could be because + // i) its the unavailable root of the very first block to start off sync + // ii) its parent of lastBackSyncedBlock and there was an issue in establishing + // linear sequence in syncRange as there could be one or more + // skipped/orphaned slots + // between the parent we want to fetch and lastBackSyncedBlock + // b) read previous batchSize blocks from network assuming most likely those blocks + // form a linear anchored chain with anchorBlock. If not, try fetching the + // parent of + // the anchorBlock via strategy a) as it could be multiple skipped/orphaned slots + // behind + if (this.syncAnchor.lastBackSyncedBlock != null) { + // If after a previous sync round: + // lastBackSyncedBlock.slot < prevFinalizedCheckpointBlock.slot + // then it means the prevFinalizedCheckpoint block has been missed because in each + // round we backfill new blocks till (if the batchSize allows): + // lastBackSyncedBlock.slot <= prevFinalizedCheckpointBlock.slot + if (this.syncAnchor.lastBackSyncedBlock.slot < this.prevFinalizedCheckpointBlock.slot) { + this.logger.error( + `Backfilled till ${ + this.syncAnchor.lastBackSyncedBlock.slot + } but not found previous saved finalized or wsCheckpoint with root=${toHexString( + this.prevFinalizedCheckpointBlock.root + )}, slot=${this.prevFinalizedCheckpointBlock.slot}` + ); + // Break sync loop and throw error + break; + } + + if (this.syncAnchor.lastBackSyncedBlock.slot === this.prevFinalizedCheckpointBlock.slot) { + // Okay! we backfilled successfully till prevFinalizedCheckpointBlock + if (!byteArrayEquals(this.syncAnchor.lastBackSyncedBlock.root, this.prevFinalizedCheckpointBlock.root)) { + this.logger.error( + `Invalid root synced at a previous finalized or wsCheckpoint, slot=${ + this.prevFinalizedCheckpointBlock.slot + }: expected=${toHexString(this.prevFinalizedCheckpointBlock.root)}, actual=${toHexString( + this.syncAnchor.lastBackSyncedBlock.root + )}` + ); + // Break sync loop and throw error + break; + } + this.logger.verbose("Validated current prevFinalizedCheckpointBlock", { + root: toHexString(this.prevFinalizedCheckpointBlock.root), + slot: this.prevFinalizedCheckpointBlock.slot, + }); + + // 1. If this is not a genesis block save this block in DB as this wasn't saved + // earlier pending validation. Genesis block will be saved with extra validation + // before returning from the sync. + // + // 2. Load another previous saved finalized or wsCheckpoint which has not + // been validated yet. These are the keys of backfill ranges as each + // range denotes + // a validated connected segment having the slots of previous wsCheckpoint + // or finalized as keys + if (this.syncAnchor.lastBackSyncedBlock.slot !== GENESIS_SLOT) { + await this.db.blockArchive.put( + this.syncAnchor.lastBackSyncedBlock.slot, + this.syncAnchor.lastBackSyncedBlock.block + ); + } + this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint( + this.config, + this.db, + this.syncAnchor.lastBackSyncedBlock.slot, + this.logger + ); + } + + if (this.syncAnchor.lastBackSyncedBlock.slot === GENESIS_SLOT) { + if (!byteArrayEquals(this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot, ZERO_HASH)) { + Error( + `Invalid Gensis Block with non zero parentRoot=${toHexString( + this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot + )}` + ); + } + await this.db.blockArchive.put(GENESIS_SLOT, this.syncAnchor.lastBackSyncedBlock.block); + } + + if (this.wsCheckpointHeader && !this.wsValidated) { + await this.checkIfCheckpointSyncedAndValidate(); + } + + if ( + this.backfillRangeWrittenSlot === null || + this.syncAnchor.lastBackSyncedBlock.slot < this.backfillRangeWrittenSlot + ) { + this.backfillRangeWrittenSlot = this.syncAnchor.lastBackSyncedBlock.slot; + await this.db.backfilledRanges.put(this.backfillStartFromSlot, this.backfillRangeWrittenSlot); + this.logger.debug( + `updated the backfill range from=${this.backfillStartFromSlot} till=${this.backfillRangeWrittenSlot}` + ); + } + + if (this.syncAnchor.lastBackSyncedBlock.slot === GENESIS_SLOT) { + this.logger.verbose("BackfillSync - successfully synced to genesis."); + this.status = BackfillSyncStatus.completed; + return GENESIS_SLOT; + } + + const foundValidSeq = await this.checkUpdateFromBackfillSequences(); + if (foundValidSeq) { + // Go back to top and do checks till + this.processor.trigger(); + continue; + } + } + + try { + const foundBlocks = await this.fastBackfillDb(); + if (foundBlocks) { + this.processor.trigger(); + continue; + } + } catch (e) { + this.logger.error("Error while reading from DB", {}, e as Error); + // Break sync loop and throw error + break; + } + + // Try the network if nothing found in DB + const peer = shuffleOne(this.peers.values()); + if (!peer) { + this.status = BackfillSyncStatus.pending; + this.logger.debug("BackfillSync no peers yet"); + continue; + } + + try { + if (!this.syncAnchor.anchorBlock) { + await this.syncBlockByRoot(peer, this.syncAnchor.anchorBlockRoot); + + // Go back and make the checks in case this block could be at or + // behind prevFinalizedCheckpointBlock + } else { + await this.syncRange(peer); + + // Go back and make the checks in case the lastbackSyncedBlock could be at or + // behind prevFinalizedCheckpointBlock + } + } catch (e) { + this.metrics?.backfillSync.errors.inc(); + this.logger.error("BackfillSync sync error", {}, e as Error); + + if (e instanceof BackfillSyncError) { + switch (e.type.code) { + case BackfillSyncErrorCode.INTERNAL_ERROR: + // Break it out of the loop and throw error + this.status = BackfillSyncStatus.aborted; + break; + case BackfillSyncErrorCode.NOT_ANCHORED: + case BackfillSyncErrorCode.NOT_LINEAR: + // Lets try to jump directly to the parent of this anchorBlock as previous + // (segment) of blocks could be orphaned/missed + if (this.syncAnchor.anchorBlock) { + this.syncAnchor = { + anchorBlock: null, + anchorBlockRoot: this.syncAnchor.anchorBlock.message.parentRoot, + anchorSlot: null, + lastBackSyncedBlock: this.syncAnchor.lastBackSyncedBlock, + }; + } + + // falls through + case BackfillSyncErrorCode.INVALID_SIGNATURE: + this.network.reportPeer(peer, PeerAction.LowToleranceError, "BadSyncBlocks"); + } + } + } finally { + if (this.status !== BackfillSyncStatus.aborted) this.processor.trigger(); + } + } + + throw new ErrorAborted("BackfillSync"); + } + + private addPeer = (peerId: PeerId, peerStatus: phase0.Status): void => { + const requiredSlot = this.syncAnchor.lastBackSyncedBlock?.slot ?? this.backfillStartFromSlot; + this.logger.debug("BackfillSync add peer", {peerhead: peerStatus.headSlot, requiredSlot}); + if (peerStatus.headSlot >= requiredSlot) { + this.peers.add(peerId); + this.processor.trigger(); + } + }; + + private removePeer = (peerId: PeerId): void => { + this.peers.delete(peerId); + }; + + /** + * Ensure that any weak subjectivity checkpoint provided in past with respect + * the initialization point is the same block tree as the DB once backfill + */ + private async checkIfCheckpointSyncedAndValidate(): Promise { + if (this.syncAnchor.lastBackSyncedBlock == null) { + throw Error("Invalid lastBackSyncedBlock for checkpoint validation"); + } + if (this.wsCheckpointHeader == null) { + throw Error("Invalid null checkpoint for validation"); + } + if (this.wsValidated) return; + + if (this.wsCheckpointHeader.slot >= this.syncAnchor.lastBackSyncedBlock.slot) { + // Checkpoint root should be in db now! + const wsDbCheckpointBlock = await this.db.blockArchive.getByRoot(this.wsCheckpointHeader.root); + if ( + !wsDbCheckpointBlock || + Math.floor(wsDbCheckpointBlock.message.slot / SLOTS_PER_EPOCH) !== + this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH + ) + // TODO: explode and stop the entire node + throw new Error( + `InvalidWsCheckpoint root=${this.wsCheckpointHeader.root}, epoch=${ + this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH + }, ${ + wsDbCheckpointBlock + ? "found at epoch=" + Math.floor(wsDbCheckpointBlock?.message.slot / SLOTS_PER_EPOCH) + : "not found" + }` + ); + this.logger.info("BackfillSync - wsCheckpoint validated!", { + root: toHexString(this.wsCheckpointHeader.root), + epoch: this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH, + }); + this.wsValidated = true; + } + } + + private async checkUpdateFromBackfillSequences(): Promise { + if (this.syncAnchor.lastBackSyncedBlock === null) { + throw Error("Backfill ranges can only be used once we have a valid lastBackSyncedBlock as a pivot point"); + } + + let validSequence = false; + if (this.syncAnchor.lastBackSyncedBlock.slot === null) return validSequence; + const lastBackSyncedSlot = this.syncAnchor.lastBackSyncedBlock.slot; + + const filteredSeqs = await this.db.backfilledRanges.entries({ + gte: lastBackSyncedSlot, + lte: this.backfillStartFromSlot, + }); + + if (filteredSeqs.length > 0) { + const jumpBackTo = Math.min(...filteredSeqs.map(({value: justToSlot}) => justToSlot)); + + if (jumpBackTo < lastBackSyncedSlot) { + validSequence = true; + const anchorBlock = await this.db.blockArchive.get(jumpBackTo); + if (!anchorBlock) { + validSequence = false; + this.logger.warn( + `Invalid backfill sequence: expected a block at ${jumpBackTo} in blockArchive, ignoring the sequence` + ); + } + if (anchorBlock && validSequence) { + if (this.prevFinalizedCheckpointBlock.slot >= jumpBackTo) { + this.logger.debug( + `found a sequence going back to ${jumpBackTo} before the previous finalized or wsCheckpoint`, + {slot: this.prevFinalizedCheckpointBlock.slot} + ); + + // Everything saved in db between a backfilled range is a connected sequence + // we only need to check if prevFinalizedCheckpointBlock is in db + const prevBackfillCpBlock = await this.db.blockArchive.getByRoot(this.prevFinalizedCheckpointBlock.root); + if ( + prevBackfillCpBlock != null && + this.prevFinalizedCheckpointBlock.slot === prevBackfillCpBlock.message.slot + ) { + this.logger.verbose("Validated current prevFinalizedCheckpointBlock", { + root: toHexString(this.prevFinalizedCheckpointBlock.root), + slot: prevBackfillCpBlock.message.slot, + }); + } else { + validSequence = false; + this.logger.warn( + `Invalid backfill sequence: previous finalized or checkpoint block root=${ + this.prevFinalizedCheckpointBlock.root + }, slot=${this.prevFinalizedCheckpointBlock.slot} ${ + prevBackfillCpBlock ? "found at slot=" + prevBackfillCpBlock.message.slot : "not found" + }, ignoring the sequence` + ); + } + } + } + + if (anchorBlock && validSequence) { + // Update the current sequence in DB as we will be cleaning up previous sequences + await this.db.backfilledRanges.put(this.backfillStartFromSlot, jumpBackTo); + this.backfillRangeWrittenSlot = jumpBackTo; + this.logger.verbose("backfillSync - found previous backfilled sequence in db, jumping back to", { + slot: jumpBackTo, + }); + + const anchorBlockHeader = blockToHeader(this.config, anchorBlock.message); + const anchorBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(anchorBlockHeader); + + this.syncAnchor = { + anchorBlock, + anchorBlockRoot, + anchorSlot: jumpBackTo, + lastBackSyncedBlock: {root: anchorBlockRoot, slot: jumpBackTo, block: anchorBlock}, + }; + if (this.prevFinalizedCheckpointBlock.slot >= jumpBackTo) { + // prevFinalizedCheckpointBlock must have been validated, update to a + // new unverified + // finalized or wsCheckpoint behind the new lastBackSyncedBlock + this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint( + this.config, + this.db, + jumpBackTo, + this.logger + ); + } + + this.metrics?.backfillSync.totalBlocks.inc( + {method: BackfillSyncMethod.backfilled_ranges}, + lastBackSyncedSlot - jumpBackTo + ); + } + } + } + + if (filteredSeqs.length > 0) { + await this.db.backfilledRanges.batchDelete( + filteredSeqs.filter((entry) => entry.key !== this.backfillStartFromSlot).map((entry) => entry.key) + ); + } + + return validSequence; + } + + private async fastBackfillDb(): Promise { + // Block of this anchorBlockRoot can't be behind the prevFinalizedCheckpointBlock + // as prevFinalizedCheckpointBlock can't be skipped + let anchorBlockRoot: Root; + let expectedSlot: Slot | null = null; + if (this.syncAnchor.anchorBlock) { + anchorBlockRoot = this.syncAnchor.anchorBlock.message.parentRoot; + } else { + anchorBlockRoot = this.syncAnchor.anchorBlockRoot; + expectedSlot = this.syncAnchor.anchorSlot; + } + let anchorBlock = await this.db.blockArchive.getByRoot(anchorBlockRoot); + if (!anchorBlock) return false; + + if (expectedSlot !== null && anchorBlock.message.slot !== expectedSlot) + throw Error( + `Invalid slot of anchorBlock read from DB with root=${anchorBlockRoot}, expected=${expectedSlot}, actual=${anchorBlock.message.slot}` + ); + + // If possible, read back till anchorBlock > this.prevFinalizedCheckpointBlock + let parentBlock, + backCount = 1; + + let isPrevFinWsConfirmedAnchorParent = false; + while ( + backCount !== this.opts.batchSize && + (parentBlock = await this.db.blockArchive.getByRoot(anchorBlock.message.parentRoot)) + ) { + // Before moving anchorBlock back, we need check for prevFinalizedCheckpointBlock + if (anchorBlock.message.slot < this.prevFinalizedCheckpointBlock.slot) { + throw Error( + `Skipped a prevFinalizedCheckpointBlock with slot=${this.prevFinalizedCheckpointBlock}, root=${toHexString( + this.prevFinalizedCheckpointBlock.root + )}` + ); + } + if (anchorBlock.message.slot === this.prevFinalizedCheckpointBlock.slot) { + if ( + !isPrevFinWsConfirmedAnchorParent && + !byteArrayEquals(anchorBlockRoot, this.prevFinalizedCheckpointBlock.root) + ) { + throw Error( + `Invalid root for prevFinalizedCheckpointBlock at slot=${ + this.prevFinalizedCheckpointBlock.slot + }, expected=${toHexString(this.prevFinalizedCheckpointBlock.root)}, found=${anchorBlockRoot}` + ); + } + + // If the new parentBlock is just one slot back, we can safely assign + // prevFinalizedCheckpointBlock with the parentBlock and skip root + // validation in next iteration. Else we need to extract + // prevFinalizedCheckpointBlock + if (parentBlock.message.slot === anchorBlock.message.slot - 1) { + this.prevFinalizedCheckpointBlock = {root: anchorBlock.message.parentRoot, slot: parentBlock.message.slot}; + isPrevFinWsConfirmedAnchorParent = true; + } else { + // Extract new prevFinalizedCheckpointBlock below anchorBlock + this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint( + this.config, + this.db, + anchorBlock.message.slot, + this.logger + ); + isPrevFinWsConfirmedAnchorParent = false; + } + } + anchorBlockRoot = anchorBlock.message.parentRoot; + anchorBlock = parentBlock; + backCount++; + } + + this.syncAnchor = { + anchorBlock, + anchorBlockRoot, + anchorSlot: anchorBlock.message.slot, + lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.message.slot, block: anchorBlock}, + }; + this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.database}, backCount); + this.logger.verbose(`BackfillSync - read ${backCount} blocks from DB till `, { + slot: anchorBlock.message.slot, + }); + return true; + } + + private async syncBlockByRoot(peer: PeerId, anchorBlockRoot: Root): Promise { + const [anchorBlock] = await this.network.reqResp.beaconBlocksByRoot(peer, [anchorBlockRoot] as List); + if (anchorBlock == null) throw new Error("InvalidBlockSyncedFromPeer"); + + // GENESIS_SLOT doesn't has valid signature + if (anchorBlock.message.slot === GENESIS_SLOT) return; + await verifyBlockProposerSignature(this.chain.bls, this.chain.getHeadState(), [anchorBlock]); + + // We can write to the disk if this is ahead of prevFinalizedCheckpointBlock otherwise + // we will need to go make checks on the top of sync loop before writing as it might + // override prevFinalizedCheckpointBlock + if (this.prevFinalizedCheckpointBlock.slot < anchorBlock.message.slot) + await this.db.blockArchive.put(anchorBlock.message.slot, anchorBlock); + + this.syncAnchor = { + anchorBlock, + anchorBlockRoot, + anchorSlot: anchorBlock.message.slot, + lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.message.slot, block: anchorBlock}, + }; + + this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.blockbyroot}); + + this.logger.verbose("BackfillSync - fetched new anchorBlock", { + root: toHexString(anchorBlockRoot), + slot: anchorBlock.message.slot, + }); + + return; + } + + private async syncRange(peer: PeerId): Promise { + if (!this.syncAnchor.anchorBlock) { + throw Error("Invalid anchorBlock null for syncRange"); + } + + const toSlot = this.syncAnchor.anchorBlock.message.slot; + const fromSlot = Math.max(toSlot - this.opts.batchSize, this.prevFinalizedCheckpointBlock.slot, GENESIS_SLOT); + const blocks = await this.network.reqResp.beaconBlocksByRange(peer, { + startSlot: fromSlot, + count: toSlot - fromSlot, + step: 1, + }); + + const anchorParentRoot = this.syncAnchor.anchorBlock.message.parentRoot; + + if (blocks.length === 0) { + // Lets just directly try to jump to anchorParentRoot + this.syncAnchor = { + anchorBlock: null, + anchorBlockRoot: anchorParentRoot, + anchorSlot: null, + lastBackSyncedBlock: this.syncAnchor.lastBackSyncedBlock, + }; + return; + } + + const {nextAnchor, verifiedBlocks, error} = verifyBlockSequence(this.config, blocks, anchorParentRoot); + + // If any of the block's proposer signature fail, we can't trust this peer at all + if (verifiedBlocks.length > 0) { + await verifyBlockProposerSignature(this.chain.bls, this.chain.getHeadState(), verifiedBlocks); + + // This is bad, like super bad. Abort the backfill + if (!nextAnchor) + throw new BackfillSyncError({ + code: BackfillSyncErrorCode.INTERNAL_ERROR, + reason: "Invalid verifyBlockSequence result", + }); + + // Verified blocks are in reverse order with the nextAnchor being the smallest slot + // if nextAnchor is on the same slot as prevFinalizedCheckpointBlock, we can't save + // it before returning to top of sync loop for validation + await this.db.blockArchive.batchAdd( + nextAnchor.slot > this.prevFinalizedCheckpointBlock.slot + ? verifiedBlocks + : verifiedBlocks.slice(0, verifiedBlocks.length - 1) + ); + this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.rangesync}, verifiedBlocks.length); + } + + // If nextAnchor provided, found some linear anchored blocks + if (nextAnchor !== null) { + this.syncAnchor = { + anchorBlock: nextAnchor.block, + anchorBlockRoot: nextAnchor.root, + anchorSlot: nextAnchor.slot, + lastBackSyncedBlock: nextAnchor, + }; + this.logger.verbose(`BackfillSync - syncRange discovered ${verifiedBlocks.length} valid blocks`, { + backfilled: this.syncAnchor.lastBackSyncedBlock.slot, + }); + } + if (error) throw new BackfillSyncError({code: error}); + } +} + +async function extractPreviousFinOrWsCheckpoint( + config: IChainForkConfig, + db: IBeaconDb, + belowSlot: Slot, + logger?: ILogger +): Promise { + // Anything below genesis block is just zero hash + if (belowSlot <= GENESIS_SLOT) return {root: ZERO_HASH, slot: belowSlot - 1}; + + // To extract the next prevFinalizedCheckpointBlock, we just need to look back in DB + // Any saved previous finalized or ws checkpoint, will also have a corresponding block + // saved in DB, as we make sure of that + // 1. When we archive new finalized state and blocks + // 2. When we backfill from a wsCheckpoint + const nextPrevFinOrWsBlock = ( + await db.blockArchive.values({ + lt: belowSlot, + reverse: true, + limit: 1, + }) + )[0]; + + let prevFinalizedCheckpointBlock: BackfillBlockHeader; + if (nextPrevFinOrWsBlock != null) { + const header = blockToHeader(config, nextPrevFinOrWsBlock.message); + const root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header); + prevFinalizedCheckpointBlock = {root, slot: nextPrevFinOrWsBlock.message.slot}; + logger?.debug("Extracted new prevFinalizedCheckpointBlock as potential previous finalized or wsCheckpoint", { + root: toHexString(prevFinalizedCheckpointBlock.root), + slot: prevFinalizedCheckpointBlock.slot, + }); + } else { + // GENESIS_SLOT -1 is the placeholder for parentHash of the genesis block + // which should always be ZERO_HASH. + prevFinalizedCheckpointBlock = {root: ZERO_HASH, slot: GENESIS_SLOT - 1}; + } + return prevFinalizedCheckpointBlock; +} diff --git a/packages/lodestar/src/sync/backfill/errors.ts b/packages/lodestar/src/sync/backfill/errors.ts new file mode 100644 index 000000000000..b06ec2c9a340 --- /dev/null +++ b/packages/lodestar/src/sync/backfill/errors.ts @@ -0,0 +1,23 @@ +import {LodestarError} from "@chainsafe/lodestar-utils"; +import PeerId from "peer-id"; +import {Root} from "@chainsafe/lodestar-types"; + +export enum BackfillSyncErrorCode { + /** fetched block doesn't connect to anchor block */ + NOT_ANCHORED = "not_anchored", + /** fetched blocks are not linear */ + NOT_LINEAR = "not_linear", + /** peer doesn't have required block by root */ + MISSING_BLOCK = "missing_blocks", + INVALID_SIGNATURE = "invalid_proposer_signature", + INTERNAL_ERROR = "backfill_internal_error", +} + +export type BackfillSyncErrorType = + | {code: BackfillSyncErrorCode.NOT_ANCHORED} + | {code: BackfillSyncErrorCode.NOT_LINEAR} + | {code: BackfillSyncErrorCode.INVALID_SIGNATURE} + | {code: BackfillSyncErrorCode.MISSING_BLOCK; root: Root; peerId: PeerId} + | {code: BackfillSyncErrorCode.INTERNAL_ERROR; reason: string}; + +export class BackfillSyncError extends LodestarError {} diff --git a/packages/lodestar/src/sync/backfill/index.ts b/packages/lodestar/src/sync/backfill/index.ts new file mode 100644 index 000000000000..2b90657e78bf --- /dev/null +++ b/packages/lodestar/src/sync/backfill/index.ts @@ -0,0 +1 @@ +export * from "./backfill"; diff --git a/packages/lodestar/src/sync/backfill/verify.ts b/packages/lodestar/src/sync/backfill/verify.ts new file mode 100644 index 000000000000..e235f6330c53 --- /dev/null +++ b/packages/lodestar/src/sync/backfill/verify.ts @@ -0,0 +1,58 @@ +import {allForks, CachedBeaconState, ISignatureSet} from "@chainsafe/lodestar-beacon-state-transition"; +import {IBeaconConfig} from "@chainsafe/lodestar-config"; +import {Root, allForks as allForkTypes, ssz, Slot} from "@chainsafe/lodestar-types"; +import {GENESIS_SLOT} from "@chainsafe/lodestar-params"; +import {IBlsVerifier} from "../../chain/bls"; +import {BackfillSyncError, BackfillSyncErrorCode} from "./errors"; + +export type BackfillBlockHeader = { + slot: Slot; + root: Root; +}; + +export type BackfillBlock = BackfillBlockHeader & {block: allForks.SignedBeaconBlock}; + +export function verifyBlockSequence( + config: IBeaconConfig, + blocks: allForkTypes.SignedBeaconBlock[], + anchorRoot: Root +): { + nextAnchor: BackfillBlock | null; + verifiedBlocks: allForkTypes.SignedBeaconBlock[]; + error?: BackfillSyncErrorCode.NOT_LINEAR; +} { + let nextRoot: Root = anchorRoot; + let nextAnchor: BackfillBlock | null = null; + + const verifiedBlocks: allForkTypes.SignedBeaconBlock[] = []; + for (const block of blocks.reverse()) { + const blockRoot = config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message); + if (!ssz.Root.equals(blockRoot, nextRoot)) { + if (ssz.Root.equals(nextRoot, anchorRoot)) { + throw new BackfillSyncError({code: BackfillSyncErrorCode.NOT_ANCHORED}); + } + return {nextAnchor, verifiedBlocks, error: BackfillSyncErrorCode.NOT_LINEAR}; + } + verifiedBlocks.push(block); + nextAnchor = {block, slot: block.message.slot, root: nextRoot}; + nextRoot = block.message.parentRoot; + } + return {nextAnchor, verifiedBlocks}; +} + +export async function verifyBlockProposerSignature( + bls: IBlsVerifier, + state: CachedBeaconState, + blocks: allForkTypes.SignedBeaconBlock[] +): Promise { + if (blocks.length === 1 && blocks[0].message.slot === GENESIS_SLOT) return; + const signatures = blocks.reduce((sigs: ISignatureSet[], block) => { + // genesis block doesn't have valid signature + if (block.message.slot !== GENESIS_SLOT) sigs.push(allForks.getProposerSignatureSet(state, block)); + return sigs; + }, []); + + if (!(await bls.verifySignatureSets(signatures))) { + throw new BackfillSyncError({code: BackfillSyncErrorCode.INVALID_SIGNATURE}); + } +} diff --git a/packages/lodestar/src/sync/interface.ts b/packages/lodestar/src/sync/interface.ts index 4528ef9b8db0..4a71ebb1f5cd 100644 --- a/packages/lodestar/src/sync/interface.ts +++ b/packages/lodestar/src/sync/interface.ts @@ -1,5 +1,5 @@ import {ILogger} from "@chainsafe/lodestar-utils"; -import {allForks, RootHex, Slot} from "@chainsafe/lodestar-types"; +import {allForks, RootHex, Slot, phase0} from "@chainsafe/lodestar-types"; import {IBeaconConfig} from "@chainsafe/lodestar-config"; import {routes} from "@chainsafe/lodestar-api"; import {INetwork} from "../network"; @@ -55,6 +55,7 @@ export interface ISyncModules { metrics: IMetrics | null; logger: ILogger; chain: IBeaconChain; + wsCheckpoint?: phase0.Checkpoint; } export type PendingBlock = { diff --git a/packages/lodestar/src/sync/range/chain.ts b/packages/lodestar/src/sync/range/chain.ts index b543c7bbd5bd..d9b1e2b8af4a 100644 --- a/packages/lodestar/src/sync/range/chain.ts +++ b/packages/lodestar/src/sync/range/chain.ts @@ -201,6 +201,10 @@ export class SyncChain { return toArr(this.batches).map((batch) => batch.getMetadata()); } + get startEpochValue(): Epoch { + return this.startEpoch; + } + get isSyncing(): boolean { return this.status === SyncChainStatus.Syncing; } diff --git a/packages/lodestar/src/sync/range/range.ts b/packages/lodestar/src/sync/range/range.ts index f5ff02772302..1e9d59e91b99 100644 --- a/packages/lodestar/src/sync/range/range.ts +++ b/packages/lodestar/src/sync/range/range.ts @@ -211,7 +211,7 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) { /** Convenience method for `SyncChain` */ private reportPeer: SyncChainFns["reportPeer"] = (peer, action, actionName) => { - this.network.peerRpcScores.applyAction(peer, action, actionName); + this.network.reportPeer(peer, action, actionName); }; /** Convenience method for `SyncChain` */ diff --git a/packages/lodestar/src/util/shuffle.ts b/packages/lodestar/src/util/shuffle.ts index a576fc96aea6..b34b439e963f 100644 --- a/packages/lodestar/src/util/shuffle.ts +++ b/packages/lodestar/src/util/shuffle.ts @@ -10,3 +10,12 @@ export function shuffle(arr: T[]): T[] { } return _arr; } + +/** + * Return one random item from array + */ +export function shuffleOne(arr: T[]): T | undefined { + if (arr.length === 0) return undefined; + + return arr[Math.floor(Math.random() * arr.length)]; +} diff --git a/packages/lodestar/test/e2e/sync/wss.test.ts b/packages/lodestar/test/e2e/sync/wss.test.ts new file mode 100644 index 000000000000..1d2bc1908bc6 --- /dev/null +++ b/packages/lodestar/test/e2e/sync/wss.test.ts @@ -0,0 +1,144 @@ +import {assert} from "chai"; +import {GENESIS_SLOT, SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; +import {phase0, Slot} from "@chainsafe/lodestar-types"; +import {initBLS} from "@chainsafe/lodestar-cli/src/util"; +import {IChainConfig} from "@chainsafe/lodestar-config"; +import {fetchWeakSubjectivityState} from "@chainsafe/lodestar-cli/src/networks"; +import {config} from "@chainsafe/lodestar-config/default"; +import {getDevBeaconNode} from "../../utils/node/beacon"; +import {waitForEvent} from "../../utils/events/resolver"; +import {getAndInitDevValidators} from "../../utils/node/validator"; +import {ChainEvent} from "../../../src/chain"; +import {RestApiOptions} from "../../../src/api/rest"; +import {testLogger, TestLoggerOpts} from "../../utils/logger"; +import {connect} from "../../utils/network"; +import {Network} from "../../../src/network"; +import {BackfillSyncEvent} from "../../../src/sync/backfill"; +import {TimestampFormatCode} from "@chainsafe/lodestar-utils"; +import {computeEpochAtSlot, allForks} from "@chainsafe/lodestar-beacon-state-transition"; + +/* eslint-disable @typescript-eslint/naming-convention */ +describe("Start from WSS", function () { + const testParams: Pick = { + SECONDS_PER_SLOT: 2, + }; + + before(async function () { + await initBLS(); + }); + + const afterEachCallbacks: (() => Promise | void)[] = []; + afterEach(async () => { + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); + + it("using another node", async function () { + // Should reach justification in 3 epochs max, and finalization in 4 epochs max + const expectedEpochsToFinish = 4; + // 1 epoch of margin of error + const epochsOfMargin = 1; + const timeoutSetupMargin = 5 * 1000; // Give extra 5 seconds of margin + + // delay a bit so regular sync sees it's up to date and sync is completed from the beginning + const genesisSlotsDelay = 3; + + const timeout = + ((epochsOfMargin + expectedEpochsToFinish) * SLOTS_PER_EPOCH + genesisSlotsDelay) * + testParams.SECONDS_PER_SLOT * + 1000; + + this.timeout(timeout + 2 * timeoutSetupMargin); + + const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; + + const testLoggerOpts: TestLoggerOpts = { + timestampFormat: { + format: TimestampFormatCode.EpochSlot, + genesisTime: genesisTime, + slotsPerEpoch: SLOTS_PER_EPOCH, + secondsPerSlot: testParams.SECONDS_PER_SLOT, + }, + }; + const loggerNodeA = testLogger("Node-A", testLoggerOpts); + const loggerNodeB = testLogger("Node-B", testLoggerOpts); + + const bn = await getDevBeaconNode({ + params: {...testParams, ALTAIR_FORK_EPOCH: Infinity}, + options: { + api: { + rest: {enabled: true, api: ["debug"]} as RestApiOptions, + }, + sync: {isSingleNode: true}, + }, + validatorCount: 32, + logger: loggerNodeA, + genesisTime, + }); + afterEachCallbacks.push(() => bn.close()); + + const finalizedEventistener = waitForEvent(bn.chain.emitter, ChainEvent.finalized, timeout); + const validators = await getAndInitDevValidators({ + node: bn, + validatorsPerClient: 32, + validatorClientCount: 1, + startIndex: 0, + // At least one sim test must use the REST API for beacon <-> validator comms + useRestApi: true, + testLoggerOpts, + }); + + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.stop()))); + await Promise.all(validators.map((v) => v.start())); + + try { + await finalizedEventistener; + await waitForEvent(bn.chain.emitter, ChainEvent.finalized, timeout); + loggerNodeA.important("\n\nNode A finalized\n\n"); + } catch (e) { + (e as Error).message = `Node A failed to finalize: ${(e as Error).message}`; + throw e; + } + + const url = "http://127.0.0.1:9596/eth/v1/debug/beacon/states/finalized"; + + loggerNodeB.important("Fetching weak subjectivity state from " + url); + const wsState = await fetchWeakSubjectivityState(config, url); + const wsCheckpoint = { + epoch: computeEpochAtSlot(wsState.latestBlockHeader.slot), + root: allForks.getLatestBlockRoot(config, wsState), + }; + loggerNodeB.important("Fetched wss state"); + + const bnStartingFromWSS = await getDevBeaconNode({ + params: {...testParams, ALTAIR_FORK_EPOCH: Infinity}, + options: {api: {rest: {enabled: true, port: 9587} as RestApiOptions}, sync: {isSingleNode: true}}, + validatorCount: 32, + logger: loggerNodeB, + genesisTime, + anchorState: wsState, + wsCheckpoint, + }); + afterEachCallbacks.push(() => bnStartingFromWSS.close()); + + const head = bn.chain.forkChoice.getHead(); + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (!head) throw Error("First beacon node has no head block"); + const waitForSynced = waitForEvent( + bnStartingFromWSS.backfillSync, + BackfillSyncEvent.completed, + 100000, + (slot) => slot == GENESIS_SLOT + ); + + await connect(bnStartingFromWSS.network as Network, bn.network.peerId, bn.network.localMultiaddrs); + + try { + await waitForSynced; + } catch (e) { + assert.fail("Failed to backfill sync to other node in time"); + } + }); +}); diff --git a/packages/lodestar/test/unit/sync/backfill/blocks.json b/packages/lodestar/test/unit/sync/backfill/blocks.json new file mode 100644 index 000000000000..898c63b3d038 --- /dev/null +++ b/packages/lodestar/test/unit/sync/backfill/blocks.json @@ -0,0 +1,862 @@ +[ + { + "message": { + "slot": "1", + "proposer_index": "19026", + "parent_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "state_root": "0x71ddf39322af21eeabda04f605992ab5027533eb8e9ab915075d432a886c6e51", + "body": { + "randao_reveal": "0xb741ebc01d9fe20dace1e59d5f23907d8099e7584dee8929a08d0d058cc232a645427c61ebad281f589bc8a17aed7bfc08040a0cf5d20f5f679643ffddd010edf30f4a042bbae85b02599aa7b7f043582386f891d991a006f481783ea6102b07", + "eth1_data": { + "deposit_root": "0xcf4ace744728226f928b4e20d578ca07d873d7e6b49b2874cf909c879fa8ded3", + "deposit_count": "27252", + "block_hash": "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" + }, + "graffiti": "0x4d72204620776173206865726500000000000000000000000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [], + "deposits": [], + "voluntary_exits": [] + } + }, + "signature": "0xb591bd4ca7d745b6e027879645d7c014fecb8c58631af070f7607acc0c1c948a5102a33267f0e4ba41a85b254b07df91185274375b2e6436e37e81d2fd46cb3751f5a6c86efb7499c1796c0c17e122a54ac067bb0f5ff41f3241659cceb0c21c" + }, + { + "message": { + "slot": "2", + "proposer_index": "11516", + "parent_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "state_root": "0x850bbbc29a57458784f92d2cabe51a6dd3e514a691b4849f888040770f37afee", + "body": { + "randao_reveal": "0x90eed25eb2211e68bfaad0a1617b5400ec970542840f91fcd76914fb0d40874d95ff28e71ff87dd06431c88d1bc1ed7510cb37be2bc914b1873e4e8716f12d2d95233f372998850dcc9b02ecfbe38210ebb844ea89666cb34caefbe168106d09", + "eth1_data": { + "deposit_root": "0xcf4ace744728226f928b4e20d578ca07d873d7e6b49b2874cf909c879fa8ded3", + "deposit_count": "27252", + "block_hash": "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" + }, + "graffiti": "0x42544353205a75672076616c696461746f720000000000000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0x0000000000000000000000000000400010", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x856a859b955a4204164ed1b69c0695e7e5dbaa94bacade7b4095aacdc73a069a73e74ee04b38f3cab59e193c733f4c8819f9c82c7d8d1e496e3d85fccd508cfa84686feee414c1c5c7e9bfd43b9b0f30fada80d6aa87faed3a8b174bf05c6eed" + }, + { + "aggregation_bits": "0x0000001000000000000080000000000008", + "data": { + "slot": "1", + "index": "3", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x93ff997397e402f158f3568d7738709c402e28b60001ee57cf15a8c2c2b96e068bd8a71a889f328843125e329cb04ab807e84f3197a7c855cc67d2547f9ecb245f4be84268be4677fcc7bf06b7231ef505e88949eac9dc340c6c5f6924b58f5b" + }, + { + "aggregation_bits": "0x0002000040400000000000000000000009", + "data": { + "slot": "1", + "index": "0", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x9856ff677c0563544a37b31589aa5b6045d5ce27c682591bc04246946870f28de8a6d0aa7e4439b5d5a023d0c68b78c603b7b47d5fd66444613d5012679019b7cd318fcd733d0625127ab07d5f3ebf216355580c06d762055c0f0a808404a3a4" + }, + { + "aggregation_bits": "0x7d7eff3eebfbff75a95bdfff3fce3c771d", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x9538205ee54473636b3c6ed61b220c1f3a4220bb6eb36e7340d3419c8a56284ceff4982e32278f109316dce763b407ac16f7c447ecea522a92e53f37af1ad38f2d5d8b0bf3de41c4e4cfa4e5f448161e866c9c662952fc850b90eeab10000ed6" + }, + { + "aggregation_bits": "0x7d7eff3eebfbdf75a95bcbff7fce3c771d", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa43e07aae66343d409304fa751cb10240861ddff07119cae64f58a368c62d9013177677862091f01ee32c62c91ad253d14e9dc0e317f720b962ec06b7b15c5c278535cff1eb367d27d8e9cb8208332247606b838fbd25ded815a304adcd74a15" + }, + { + "aggregation_bits": "0x0200000000000000000000000002000010", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x8990ff4a007eabb76f2e4892c41c725287d2b4c97e07ec2ae88fdff8593c2b73d8bd0b46927ab5c77cc6a765a37829dd0843304f6e2390518ac07f6af55075b6d9a75b6a07a2b82aaf981c1cf88268e6fcc903b13c2ceba50bd5e795a78122af" + }, + { + "aggregation_bits": "0x7e55daaf2abeada7dbefafefb9a3df9c0e", + "data": { + "slot": "1", + "index": "0", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x8a2ad22843a8a3180e8801ecb8bedf0ac5c1f65aea455c35d6d2acfa241c0c2ff08dd95db1f6cde5e86cc054d68f1f9d09135ee60b72fa8ba1ada65294cce46e66457e6977f489cd1d9c02688d174d85d8669adf871b2c7b16023ebc7a96bdb2" + }, + { + "aggregation_bits": "0x000004098800115c200000000200800008", + "data": { + "slot": "1", + "index": "0", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa313d5826641f84be1f89324d5603cae2da14ad847e32220b32f894f622ab7dbce2ee1173e9d316a24c4bea6b9c820c2158e9cc07da9ea3e0bdf5ebf4ede584f679e261b2f81fb395ca15d00ecdf5fb99eef0a94f90f8af8a238966f594c1ea3" + }, + { + "aggregation_bits": "0xfbf5e6f777edfff7f97dfffcf5dfefbf1f", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb04afb45252dcce977476df9ab718a81bdf63539cd7e3cc8e64979a1817ca64cc55411c35a5ecb5fca2508cbf9b75b0e0a09c01cc8c30550fc1ad041adafa56b185fb8b9afa0e021c03c7785cf838663d984ef16fcc7108356941d8ccefe6d7c" + }, + { + "aggregation_bits": "0xfe3ea9abffd73fffbeb97836bfefbafd0e", + "data": { + "slot": "1", + "index": "3", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa74ebac74907c9ebbeb2ecf415fe0a1ada57748e805f1426893f903d10a555cf37a78784899b6e67f1a6cf58c17525ad16cee0377f09e63851ffce558315e736e3fa9894d1432258af91b404c51d338db469a07c6f96cdd5491c8b2b5f7a0741" + }, + { + "aggregation_bits": "0xbc0404ede11c35729798509227d438f90e", + "data": { + "slot": "1", + "index": "3", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x98287d506e3bd24879e1e1d2e4c94d00afd3dd92fe7459a4475835f248f5c0563cd8026798a3fa110c65e46c220fb774068652fe92ade6dc14ee68c3103473365d7542790f6f09ce6ca1f11389dd493cfc8f7ccd3d3ca1a0c044dcfe02f5acf5" + }, + { + "aggregation_bits": "0x0052380b761400705630681235c4028108", + "data": { + "slot": "1", + "index": "3", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa9838cd5777c35c5410074589d63b679b45a9d6ee43638535a1129597fa755af1b92fb10c857d09d44b5d74a9ae7211e09c6136e6cdcdab1aff3d079666c46c49f26eeee6d328dc9228e7a9f775d9b51430434b30f408c3ab4d2a2f8ebc12f12" + }, + { + "aggregation_bits": "0xeffebf47fff3beffef8fe7d6fbfb5dee1f", + "data": { + "slot": "1", + "index": "4", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xaed0d2195b85f7009fc641b4dff2e38ca680abb3801045cd7184681d010cbce18d128b204f03ac023402ba9cc22c9c0a0b1fdc3d5c4541523d272224deaab26f69214b8a6f2f70250c4dc632d78c099d8cccdbb1a47d8c90bef8bf9f000bcb40" + }, + { + "aggregation_bits": "0x0000110040010030852480404012048018", + "data": { + "slot": "1", + "index": "4", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x899c3b66f068b7a85415bda23374c93af71ce78c23aa5e523ee5ceefc8a9c0d18b875ef4357bd4ee72fef7ce560dc8df02953e19e6abbc065d4c22d3d8b99111983ba9e1567f74e159e14afce585143993aac386909199fb5a5054a6cd08eb96" + }, + { + "aggregation_bits": "0x0084040010000080100800100001000108", + "data": { + "slot": "0", + "index": "2", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x92e4d60e46e01b21399b710e8f691faf03301a5ffd216c6aed570beb8f8d82dddfde2c385cdd1ea52d54f241564151e21018338bbb909d50e55f60add5d1e3179fba5faf17a262e498df5b9b18fc6dd8f3d0d062e83ab7cb33aabbcf09bbea40" + }, + { + "aggregation_bits": "0x0000000020000080000010000000001010", + "data": { + "slot": "0", + "index": "4", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb0f44edb265f2c54e590f75e284bab6fb7fe8e5008a40c7107c36de48f23bb8faaffddf83df73c4ff07eca41475fcaa5193a6570ed2e645e48fc202b90180873b477e8c94b43bbf9c51c5c32c744790b5608b0424027b7e00a22939a8c2bd827" + }, + { + "aggregation_bits": "0x0000102004000020000100521804280008", + "data": { + "slot": "0", + "index": "0", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x86759bf159cba4cf7d67c01f027f9c2b659c31195c660c0a1dd6d078f23e16588b980916917c18b50dd4e56a89c9d22d06a920124716d8395cfd6e0aa346d076e2ee5d6d3062dbe2472b269303b859b50cc502aeb03c5ff70418b015275ba9a9" + }, + { + "aggregation_bits": "0x0000000840000041000001000118080010", + "data": { + "slot": "0", + "index": "3", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x90d7dc9f338eae41e77183e72790bb818955009b88e2e3738fc457d5ab3b13459103513f0b68447430c9be911be2202f04aace8ce78453b1125103234f519590d42181967b3f1d0a2adcff371a59beebe4ac282c8940957cc493b0ae9a9de218" + }, + { + "aggregation_bits": "0x0008000000000000000000020001100010", + "data": { + "slot": "0", + "index": "1", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x8dd997bdb6cc4d7b004fa00afad8964f2811528f7f92a48729c9293154c1bc9e6233f89c993b9656e53c7e900aaae480037eaf494bd5a2040efe2885753fdc1466ec2ab13a1dd89accea23801ca0f9a0486913c4bb684677b333e8f0bcfc3796" + } + ], + "deposits": [], + "voluntary_exits": [] + } + }, + "signature": "0x99dfe11b6c8b306d2c72eb891926d37922d226ea8e1e7484d6c30fab746494f192b0daa3e40c13f1e335b35238f3362c113455a329b1fab0bc500bc47f643786f49e151d5b5052afb51af57ba5aa34a6051dc90ee4de83a26eb54a895061d89a" + }, + { + "message": { + "slot": "3", + "proposer_index": "20640", + "parent_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "state_root": "0xea80b8dfdde8331107e3021390b16544b60021e31b9c7d4192a28e1f661c1e85", + "body": { + "randao_reveal": "0x8fc07f3eb4bb88cdad6e72f1450c54e959535fb0570f6676cb936b5ce129fb2b8d474d919b3ed4c292b417ae51c73e1d0cff7a3637a547563e4c12c382ff61149df768a740cde6a003b972eff07617b707819b45dd9ee2dc72134b03fb72d2e9", + "eth1_data": { + "deposit_root": "0xcf4ace744728226f928b4e20d578ca07d873d7e6b49b2874cf909c879fa8ded3", + "deposit_count": "27252", + "block_hash": "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" + }, + "graffiti": "0x0000000000000000000000000000000000000000000000000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0x7efff7ff6ffdffd7f7fffbfbbfdddeff1d", + "data": { + "slot": "2", + "index": "0", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x9555ad23a2dfbd5aabd5b829d79c6246ce3145dd848ba3cc49744c4a7ddf51a59f8ffad60d4724f39a21327c02500b0e00d5c08ff6bf6f16b978f3df4ef5f4a971090efedbfc587dfc0e1738f5f5a962b4f66396fa7577e48373e6873e50dd85" + }, + { + "aggregation_bits": "0x6abffffeff3fdbf7afa7fffff7ffffff1e", + "data": { + "slot": "2", + "index": "3", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb43b001d28d66937b233c687b7d7e64ed8ef984225a625ae6c3e8a99c49137b9eb38786b3af4ff678eb18ad3bd8bbc1814ca709f77bb2679597d76d14161206adfab48ef611b26eb9f0061022f0ce5c2ade1f0deee255095f8e870546b870dc5" + }, + { + "aggregation_bits": "0xf7ffaffeefb7f8df577dfffbcfffcfff0f", + "data": { + "slot": "2", + "index": "4", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb37a31922e260c8868ea4cb799c432848bbfa1a862e0cf29e73f7b1c2cbc6fe5067d19268f1607f80b4f6fce1089847516db6a15840eb6bbda2f5704ff8bca051bad217e36d54861a4dfb0ebc8a17e671dc9ce8d2512a8f308b4e529a2dbb5b0" + }, + { + "aggregation_bits": "0x5bbffff9f6ff1eeef73effd776fff7ff1f", + "data": { + "slot": "2", + "index": "2", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x89511ea98d6c5fd0964908d62a75d6ddf657a853abdcb7fc8caed0eb68d074a1401ae298e6311598732e5a72c27eb8db01d1201fec9fdb7190b0b107555dd9f9aeabde168f46fe93a4ebd176df062c34bb43703f3aa7e4bc0b7d1d62b09ff042" + }, + { + "aggregation_bits": "0xfffefdeeff5eb3f6777d67bff96b9fff0d", + "data": { + "slot": "2", + "index": "1", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x98dd2ef2d2dde46e24a43c1cfd58e7fd19c0d8d45ac565e38bb4ee0f3e5c37dd70b778be00eff8a11f84dd3370feaa6c0422b04353682c05b47285cafc2969ed63cb093edebb1bd4f6559c5dac108f1573d3fd0bd1399840a569c22308073216" + }, + { + "aggregation_bits": "0x0000000000004000000000000000000008", + "data": { + "slot": "2", + "index": "4", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa84a9fc14f1d616ddc6d4f1f790d6cdef05508b9b4eb1c6b7c7fb0fd1baadfa5a9ececb82b599f2d7f77210a9c2105cb0d1ac5487a8b5c2924875ec701767d44726d78067681afcc3200a1fa55bae3d94515c7bd3d5f0be42e7b581d1ec52c67" + }, + { + "aggregation_bits": "0x0100000000000000000000000000000010", + "data": { + "slot": "2", + "index": "0", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa37c24c1c44233459d4aa0601ed2ea2ab26de6feb05d3b4c3a05f5636970c0a7cfac90e50450a85045a2528dddbc690802d2d8efb7c40e8a5af98a1bccecdd897bf5f183fb69e7d42b5f84f4c5e5dbbe27c3bc65c927e2b9a1deb843889dab7c" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000011", + "data": { + "slot": "2", + "index": "2", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb1011c1e6913849c6197db0139c6c4ef9feab8f2d7f7e783af58a19c6c719e0a606404d94845429a124e298aa7a240ea073f636afea7748d0b286c3794ee2b0a741ef7281589be0afda967b868e4dcc43749c361fbd8898c8708501fbb30ab98" + }, + { + "aggregation_bits": "0x0008180880000000000000010a20100010", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x986f5be6fa4c539e717a15af2657cd2235487c6f67c40f407213429c16adeace23de9c9ccfa6a73b19e03d699e00b51813ee09312212dd94c6c51a320e0af80ceb0e58b3e654949205dc87f450b3ac07b1d3aa3e5d2a2c9d855b0987e0ef0430" + }, + { + "aggregation_bits": "0x0000000000200000000005084000000208", + "data": { + "slot": "1", + "index": "3", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xabfe02b5c53e3244c40a114775004e48c52304132714beb3b11e31a1b8c6ba7316617b55dc88edc611fad19ea7b4adec145c119bc07330e36380db2c1dd30b36d665577578416eadae1ff3f8d4514a16aaa9fb94ce6a1d2cfacc6c89b8e6dce4" + }, + { + "aggregation_bits": "0x0080001000000000000000000004000108", + "data": { + "slot": "1", + "index": "0", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xae563396ffc0ba524593dcfa6d571f4781ae11ab918b4ee368cb442f070c12d676a149bd40fbcf5db19b610f74b3f22d074ef67ad9fa60fd96c04cdef22e5fcda472cecb61e2b2eb7a28e341760bfdba5c91eea1f789d912dcf19f5e1d9beeed" + }, + { + "aggregation_bits": "0x0001000000040000000008000000800010", + "data": { + "slot": "1", + "index": "4", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x8a92ce25ad66880ccea6605983a38057bc906c37772627139423f0113ef6e263f8c9be1c442d773a79c783b4da25c6311242e7c328f18aa09e238226c3a900e63786d5100b31e22627b86ba070332abb0523977947bd1372ce2ae3eda037ab1d" + }, + { + "aggregation_bits": "0x0000000000000000100000000000808010", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0xbacd20f09da907734434f052bd4c9503aa16bab1960e89ea20610d08d064481c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb4ec39c13adf7521324d001310fa87cf82c2602a0a1554e7f9c69e4a122d27f073b614393d4af2a473517502c06971c9077c8b14d9fc4a86da9e83def24f9e0cb30c87bd411d05e90ac17ae305b1b9253238323aa5dd964eb40f64ff1b157dff" + }, + { + "aggregation_bits": "0x0000000000080000004010000000000010", + "data": { + "slot": "1", + "index": "4", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xaad68980fec865f56e46e94bd8f8ee6cd40a04f54b4146a88da733ab754e1eed13ba3a25c2b0ae1034bc03fa81359110044a77a21ceca5111eeb5c67c3eda48ac650877f1e0d13a01d012a5e073ff26a8851425244846e7ed144a1aa05eb09fc" + }, + { + "aggregation_bits": "0x0002000000000000000000020000000010", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x94ce610dc7d2b046c35dd0a64997380e2fd0f70708c009757fc261121bd79cf07aae844dd9321aa523299fde1696c39203472118cc4a2baf7e40930654974497072eff0152f176858bb04ff424744598f40243250f826f273e90e6ba41b9ea54" + }, + { + "aggregation_bits": "0x0080000010000000000000000000000010", + "data": { + "slot": "1", + "index": "2", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa954d40dfc2a6cfc49220ca97c608f147cc88199424b2acd3ef193f021ce76718e710759bd251a50d641f075f076e6c107a1d703e4ace0a2323a5413514fcd0d450b446c9a492f0a0249b82fcb6cccd9b3febab430e6b6352a1133f1228d4f52" + }, + { + "aggregation_bits": "0x0002100000000000000040000000000010", + "data": { + "slot": "0", + "index": "4", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x8d8805bea2f58c2828c56c47a906e73bb04e5e64f62153ea1fa695374546fd4d65330d5a0871905831d9f266fcf4fc28115227707409938fbf609885a2865ada4a3a869c08fee12658852cbb5ca522b9fc50d202bea9e938d4c4a99ab4b152a0" + }, + { + "aggregation_bits": "0x0000000000200000000020000000000010", + "data": { + "slot": "0", + "index": "1", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x9047b6e0b26c385309bf01f848582249fc38a0bf0fc1ae27c010c9d19f38cc0ef6efe1045aa73c4331292290f31433ec09b4e6668ddb2828652f9d4b5c19f05b7a0fc7450fd4c07d3852e4bdd08deaaa5714b31b9dbe79b7bc9478a9a3001a53" + } + ], + "deposits": [], + "voluntary_exits": [] + } + }, + "signature": "0xa68eccda0ad17e3b8fa74b65bf77c4612fca83f7ff852568f08bdbb94726862a1dc1076969db610b09f7908ae14436b01901e10ddefe3de03089f0dc85ae196dbad3013ebd194cc4aeb1be0bf803af0271f55e1860bdc86844af3d245311a2c2" + }, + { + "message": { + "slot": "4", + "proposer_index": "11308", + "parent_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "state_root": "0xf87def8c708f47cb1ed5e02bd92f28af0e209e52d6c2769e04ba1d84d78f9207", + "body": { + "randao_reveal": "0x8f2390cd052f2e60de5e029cf5fe064cca7fe75b9ec653b9ce248cc30fdc914626607da0d9d385f6296ba655df6bf6f406f4e9c2600098ac111e8b8c5cdf1e01ede79ec393b8a86c9ff657367a9909dc6dcac47d3a52714debf8f6a9d2a86cc6", + "eth1_data": { + "deposit_root": "0xcf4ace744728226f928b4e20d578ca07d873d7e6b49b2874cf909c879fa8ded3", + "deposit_count": "27252", + "block_hash": "0x8d3f027beef5cbd4f8b29fc831aba67a5d74768edca529f5596f07fd207865e1" + }, + "graffiti": "0xf09f90a057656c636f6d6520746f20746865204e657720426567696e6e696e67", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0xf7fffefefffffebfdafff7fffdf9ffff1f", + "data": { + "slot": "3", + "index": "0", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa4f5032a93c703951b6b79e30322f418880ccfad654ccc2dfa9f4e6ef1a2d58985aacaa4e55159617b458ed7ea463aca0869a456ae724282e0492652e278c7f14218220d56ff5775ae45c5f8cd2134b4f6fbc8d0bbab5b38533952e46f2154aa" + }, + { + "aggregation_bits": "0xf7bfffefefffbd9d7fff9ffffff7ffde0f", + "data": { + "slot": "3", + "index": "1", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa86dcfab597d0fee351ac1285f6e3a723f588ea11cf343017ab993473d77ca45ae61c19799d74a8de55fd3cbff1e1ff60c0d5a1caba17044a068a4342eaf1a34b7be566358225385cc4b910978c981516c61872740f1f4c936a179db1910c6a1" + }, + { + "aggregation_bits": "0xfff3df6ffffffcf9ffffefff7fdfedf70f", + "data": { + "slot": "3", + "index": "4", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa2f8b6bf375d7b9d4e3d19226d30bce9caeb34f38a5a62d9d0e599a951cc5b8592f0c518a54322028c7c01ccdc3fd0e3053184a09344dba7585169423510a06d7d40f9366f345184d88d76f987e84774245e81511bb4661650064c9b1e03ce45" + }, + { + "aggregation_bits": "0xfbcafff8ffff7effe7f5bbf7bedfffff17", + "data": { + "slot": "3", + "index": "2", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x87177d8f20209e89d7041fa14395524189c0db738da8f22d6f45a4cc944c4785ce9e7c5642e96b2612fab6043bcb23360e1a0bd8cda42512fd0fdef8c979cd199958c65b355e916d1ecab8b5e493a1627e1563564ab92d4fcf2beb9468a6dabf" + }, + { + "aggregation_bits": "0xffe7db739f5fbebfb7affffff7edeffe1e", + "data": { + "slot": "3", + "index": "3", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xb7181b5fd05f29b06cebfe780e9bf77caa918076fd6e7eb91806b56730b2cd0dd7643d6df5f548ac717ee8c36fa4747e0888c865aaa2a8e35349aa7e1c20292848c821177230a1326271d9c819d045b0614d9e3e743343e52bde765cc03ee731" + }, + { + "aggregation_bits": "0xffb3df6efbffbc79ffffefff7fffedf70f", + "data": { + "slot": "3", + "index": "4", + "beacon_block_root": "0xb6c2a16aa85959604baba5344e869cabe1d096bb179d57e61fdca933f5ec7bf5", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0xa57e16fa2cf88fd78db8525b25d63042ffe607fe7bd7117fdf091d10ddab0c5bcffead0cea23297abf959853356762880d92c1d3838c9091fe9c893b92372688803a9b20fbaefc1ddda7f4511563a3159a21fe3d2ec59e35b90f7b1c67d2170e" + }, + { + "aggregation_bits": "0x0000000000008000000000000000000010", + "data": { + "slot": "3", + "index": "2", + "beacon_block_root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x888f05cedf7afab65c604d9c89c03996d69874461d35d6c4ceefbdc95b679494b1c14e889e80e1740aa20bb2032dbd1116440dea015b748d82f30a4e54980da9e490baee2dbfc60db91f967349a7716e4bb2aa15c2c61793bb550d13351c2fe6" + }, + { + "aggregation_bits": "0x0000040000000000000000000000000010", + "data": { + "slot": "3", + "index": "3", + "beacon_block_root": "0x2757f6fd8590925cd000a86a3e543f98a93eae23781783a33e34504729a8ad0c", + "source": { + "epoch": "0", + "root": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "target": { + "epoch": "0", + "root": "0x4d611d5b93fdab69013a7f0a2f961caca0c853f87cfe9595fe50038163079360" + } + }, + "signature": "0x87ef3bb886fe1fa29507531449342b12f578c36d5b73b205f38ce3547a25b1b4ab0fa2d60f6fdef7b048fb757b9616330f3aef185cdaff7c037779c97b162615e1ca5d1ab8b0eaf88f87d9bf8f6bc554fa818f6b7cafd9dd6a746df344177dc5" + } + ], + "deposits": [], + "voluntary_exits": [] + } + }, + "signature": "0x82ec2f973c1e311ed56ad16332fe0282ede81dc8e232c158d27bf384664f260dc20d5391f3b4af70a04a704ca1364c320d6bff478e5347a5564f69722da18fa0ae1a2b4951031b3354fcf48df308703a08282801687420bf800941b8a868bb61" + } +] \ No newline at end of file diff --git a/packages/lodestar/test/unit/sync/backfill/verify.test.ts b/packages/lodestar/test/unit/sync/backfill/verify.test.ts new file mode 100644 index 000000000000..1fbcc2b0a907 --- /dev/null +++ b/packages/lodestar/test/unit/sync/backfill/verify.test.ts @@ -0,0 +1,53 @@ +import {BackfillSyncErrorCode, BackfillSyncError} from "./../../../../src/sync/backfill/errors"; +import {Json} from "@chainsafe/ssz"; +import {createIBeaconConfig} from "@chainsafe/lodestar-config"; +import {config} from "@chainsafe/lodestar-config/default"; +import {phase0, ssz} from "@chainsafe/lodestar-types"; +import {expect} from "chai"; +import {readFileSync} from "fs"; +import {verifyBlockSequence} from "../../../../src/sync/backfill/verify"; +import path from "path"; + +describe("backfill sync - verify block sequence", function () { + //mainnet validators root + const beaconConfig = createIBeaconConfig( + config, + ssz.Root.fromJson("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95") + ); + + it("should verify valid chain of blocks", function () { + const blocks = getBlocks(); + + expect(() => verifyBlockSequence(beaconConfig, blocks.slice(0, 2), blocks[2].message.parentRoot)).to.not.throw; + }); + + it("should fail with sequence not anchored", function () { + const blocks = getBlocks(); + + const wrongAncorRoot = ssz.Root.defaultValue(); + expect(() => verifyBlockSequence(beaconConfig, blocks, wrongAncorRoot)).to.throw( + BackfillSyncErrorCode.NOT_ANCHORED + ); + }); + + it("should fail with sequence not linear", function () { + const blocks = getBlocks(); + expect(() => { + const {error} = verifyBlockSequence( + beaconConfig, + // remove middle block + blocks.filter((b) => b.message.slot !== 2).slice(0, blocks.length - 2), + blocks[blocks.length - 1].message.parentRoot + ); + if (error) throw new BackfillSyncError({code: error}); + }).to.throw(BackfillSyncErrorCode.NOT_LINEAR); + }); + + //first 4 mainnet blocks + function getBlocks(): phase0.SignedBeaconBlock[] { + const json = JSON.parse(readFileSync(path.join(__dirname, "./blocks.json"), "utf-8")) as Json[]; + return json.map((b) => { + return ssz.phase0.SignedBeaconBlock.fromJson(b, {case: "snake"}); + }); + } +}); diff --git a/packages/lodestar/test/unit/util/itTrigger.test.ts b/packages/lodestar/test/unit/util/itTrigger.test.ts index e01b59f1b855..1cf46e8329f6 100644 --- a/packages/lodestar/test/unit/util/itTrigger.test.ts +++ b/packages/lodestar/test/unit/util/itTrigger.test.ts @@ -48,4 +48,21 @@ describe("util / itTrigger", () => { await expect(all(itTrigger)).to.be.rejectedWith(testError); }); + + it("ItTrigger as a single thread processor", async () => { + const processor = new ItTrigger(); + + for (let i = 0; i <= 4; i++) { + setTimeout(() => { + processor.trigger(); + }, i * 5); + } + + let counter = 0; + for await (const _ of processor) { + if (counter++ >= 3) { + break; + } + } + }); }); diff --git a/packages/lodestar/test/utils/mocks/chain/chain.ts b/packages/lodestar/test/utils/mocks/chain/chain.ts index 24c3f987a18e..d9b0e94fe4ca 100644 --- a/packages/lodestar/test/utils/mocks/chain/chain.ts +++ b/packages/lodestar/test/utils/mocks/chain/chain.ts @@ -52,6 +52,7 @@ export class MockBeaconChain implements IBeaconChain { readonly eth1 = new Eth1ForBlockProductionDisabled(); readonly executionEngine = new ExecutionEngineDisabled(); readonly config: IBeaconConfig; + readonly anchorSlot: Slot; readonly bls: IBlsVerifier; forkChoice: IForkChoice; @@ -89,6 +90,7 @@ export class MockBeaconChain implements IBeaconChain { this.chainId = chainId || 0; this.networkId = networkId || BigInt(0); this.state = state; + this.anchorSlot = state.slot; this.config = config; this.emitter = new ChainEventEmitter(); this.abortController = new AbortController(); diff --git a/packages/lodestar/test/utils/node/beacon.ts b/packages/lodestar/test/utils/node/beacon.ts index 725740c3ab35..2702473d0d3a 100644 --- a/packages/lodestar/test/utils/node/beacon.ts +++ b/packages/lodestar/test/utils/node/beacon.ts @@ -16,6 +16,8 @@ import {defaultOptions} from "../../../src/node/options"; import {BeaconDb} from "../../../src/db"; import {testLogger} from "../logger"; import {InteropStateOpts} from "../../../src/node/utils/interop/state"; +import {TreeBacked} from "@chainsafe/ssz"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; export async function getDevBeaconNode( opts: { @@ -25,6 +27,8 @@ export async function getDevBeaconNode( logger?: ILogger; peerId?: PeerId; peerStoreDir?: string; + anchorState?: TreeBacked; + wsCheckpoint?: phase0.Checkpoint; } & InteropStateOpts ): Promise { const {params, validatorCount = 8, peerStoreDir} = opts; @@ -67,14 +71,15 @@ export async function getDevBeaconNode( ) ); - const anchorState = await initDevState(config, db, validatorCount, opts); - const beaconConfig = createIBeaconConfig(config, anchorState.genesisValidatorsRoot); + const state = opts.anchorState || (await initDevState(config, db, validatorCount, opts)); + const beaconConfig = createIBeaconConfig(config, state.genesisValidatorsRoot); return await BeaconNode.init({ opts: options as IBeaconNodeOptions, config: beaconConfig, db, logger, libp2p, - anchorState, + anchorState: state, + wsCheckpoint: opts.wsCheckpoint, }); }