From c73e0c6838af8934265830f883316989301ba7e9 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Fri, 10 Sep 2021 12:46:21 +0200 Subject: [PATCH 1/3] Add op pool for remaining operations --- .../allForks/block/processVoluntaryExit.ts | 44 ++-- packages/lodestar/src/chain/opPools/opPool.ts | 198 ++++++++++++++++++ 2 files changed, 217 insertions(+), 25 deletions(-) create mode 100644 packages/lodestar/src/chain/opPools/opPool.ts diff --git a/packages/beacon-state-transition/src/allForks/block/processVoluntaryExit.ts b/packages/beacon-state-transition/src/allForks/block/processVoluntaryExit.ts index 4ea4fbed94fe..af399eb088a0 100644 --- a/packages/beacon-state-transition/src/allForks/block/processVoluntaryExit.ts +++ b/packages/beacon-state-transition/src/allForks/block/processVoluntaryExit.ts @@ -17,41 +17,35 @@ export function processVoluntaryExitAllForks( blockProcess: BlockProcess, verifySignature = true ): void { - assertValidVoluntaryExit(state as CachedBeaconState, signedVoluntaryExit, verifySignature); + if (!isValidVoluntaryExit(state as CachedBeaconState, signedVoluntaryExit, verifySignature)) { + throw Error("Invalid voluntary exit"); + } + const validator = state.validators[signedVoluntaryExit.message.validatorIndex]; initiateValidatorExit(state as CachedBeaconState, validator); } -export function assertValidVoluntaryExit( +export function isValidVoluntaryExit( state: CachedBeaconState, signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true -): void { +): boolean { const {config, epochCtx} = state; const voluntaryExit = signedVoluntaryExit.message; const validator = state.validators[voluntaryExit.validatorIndex]; const currentEpoch = epochCtx.currentShuffling.epoch; - // verify the validator is active - if (!isActiveValidator(validator, currentEpoch)) { - throw new Error("VoluntaryExit validator is not active"); - } - // verify exit has not been initiated - if (validator.exitEpoch !== FAR_FUTURE_EPOCH) { - throw new Error(`VoluntaryExit validator exit has already been initiated: exitEpoch=${validator.exitEpoch}`); - } - // exits must specify an epoch when they become valid; they are not valid before then - if (!(currentEpoch >= voluntaryExit.epoch)) { - throw new Error(`VoluntaryExit epoch is not yet valid: epoch=${voluntaryExit.epoch} currentEpoch=${currentEpoch}`); - } - // verify the validator had been active long enough - if (!(currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD)) { - throw new Error("VoluntaryExit validator has not been active for long enough"); - } - // verify signature - if (verifySignature) { - if (!verifyVoluntaryExitSignature(state as CachedBeaconState, signedVoluntaryExit)) { - throw new Error("VoluntaryExit has an invalid signature"); - } - } + return ( + // verify the validator is active + isActiveValidator(validator, currentEpoch) && + // verify exit has not been initiated + validator.exitEpoch === FAR_FUTURE_EPOCH && + // exits must specify an epoch when they become valid; they are not valid before then + currentEpoch >= voluntaryExit.epoch && + // verify the validator had been active long enough + currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD && + // verify signature + (!verifySignature || + verifyVoluntaryExitSignature(state as CachedBeaconState, signedVoluntaryExit)) + ); } diff --git a/packages/lodestar/src/chain/opPools/opPool.ts b/packages/lodestar/src/chain/opPools/opPool.ts new file mode 100644 index 000000000000..f4a8d878a783 --- /dev/null +++ b/packages/lodestar/src/chain/opPools/opPool.ts @@ -0,0 +1,198 @@ +import {CachedBeaconState, computeEpochAtSlot, allForks} from "@chainsafe/lodestar-beacon-state-transition"; +import {MAX_PROPOSER_SLASHINGS, MAX_VOLUNTARY_EXITS} from "@chainsafe/lodestar-params"; +import {Epoch, phase0, ssz, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {IBeaconDb} from "../../db"; + +type HexRoot = string; +type AttesterSlashingCached = { + attesterSlashing: phase0.AttesterSlashing; + intersectingIndices: number[]; +}; + +export class OpPool { + /** Map of uniqueId(AttesterSlashing) -> AttesterSlashing */ + private readonly attesterSlashings = new Map(); + /** Map of to slash validator index -> ProposerSlashing */ + private readonly proposerSlashings = new Map(); + /** Map of to exit validator index -> SignedVoluntaryExit */ + private readonly voluntaryExits = new Map(); + + static async fromPersisted(db: IBeaconDb): Promise { + const opPool = new OpPool(); + + const [attesterSlashings, proposerSlashings, voluntaryExits] = await Promise.all([ + db.attesterSlashing.entries(), + db.proposerSlashing.values(), + db.voluntaryExit.values(), + ]); + + for (const attesterSlashing of attesterSlashings) { + opPool.insertAttesterSlashing(attesterSlashing.value, attesterSlashing.key); + } + for (const proposerSlashing of proposerSlashings) { + opPool.insertProposerSlashing(proposerSlashing); + } + for (const voluntaryExit of voluntaryExits) { + opPool.insertVoluntaryExit(voluntaryExit); + } + + return opPool; + } + + async toPersisted(db: IBeaconDb): Promise { + // TODO: Only write new content + await Promise.all([ + db.attesterSlashing.batchPut( + Array.from(this.attesterSlashings.entries()).map(([key, value]) => ({ + key: fromHexString(key), + value: value.attesterSlashing, + })) + ), + db.proposerSlashing.batchPut(Array.from(this.proposerSlashings.entries()).map(([key, value]) => ({key, value}))), + db.voluntaryExit.batchPut(Array.from(this.voluntaryExits.entries()).map(([key, value]) => ({key, value}))), + ]); + } + + /// Insert an attester slashing into the pool. + insertAttesterSlashing(attesterSlashing: phase0.AttesterSlashing, rootHash?: Uint8Array): void { + if (!rootHash) rootHash = ssz.phase0.AttesterSlashing.hashTreeRoot(attesterSlashing); + const intersectingIndices = getAttesterSlashableIndices(attesterSlashing); + this.attesterSlashings.set(toHexString(rootHash), { + attesterSlashing, + intersectingIndices, + }); + } + + insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing): void { + this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex, proposerSlashing); + } + + /** Must be validated beforehand */ + insertVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): void { + this.voluntaryExits.set(voluntaryExit.message.validatorIndex, voluntaryExit); + } + + /** + * Get proposer and attester slashings for inclusion in a block. + * + * This function computes both types of slashings together, because attester slashings may be invalidated by + * proposer slashings included earlier in the block. + */ + getSlashings(state: phase0.BeaconState): [phase0.AttesterSlashing[], phase0.ProposerSlashing[]] { + const stateEpoch = computeEpochAtSlot(state.slot); + const toBeSlashedIndices: ValidatorIndex[] = []; + const proposerSlashings: phase0.ProposerSlashing[] = []; + + for (const proposerSlashing of this.proposerSlashings.values()) { + const index = proposerSlashing.signedHeader1.message.proposerIndex; + const validator = state.validators[index]; + if (!validator.slashed && validator.activationEpoch <= stateEpoch && stateEpoch < validator.withdrawableEpoch) { + proposerSlashings.push(proposerSlashing); + // Set of validators to be slashed, so we don't attempt to construct invalid attester slashings. + toBeSlashedIndices.push(index); + if (proposerSlashings.length >= MAX_PROPOSER_SLASHINGS) { + break; + } + } + } + + const attesterSlashings: phase0.AttesterSlashing[] = []; + attesterSlashing: for (const attesterSlashing of this.attesterSlashings.values()) { + for (let i = 0; i < attesterSlashing.intersectingIndices.length; i++) { + const index = attesterSlashing.intersectingIndices[i]; + const validator = state.validators[index]; + if (isSlashableAtEpoch(validator, stateEpoch)) { + // At least one validator is slashable, include. TODO: Optimize including the biggest attester slashings + attesterSlashings.push(attesterSlashing.attesterSlashing); + continue attesterSlashing; + } + } + } + + return [attesterSlashings, proposerSlashings]; + } + + /** Get a list of voluntary exits for inclusion in a block */ + getVoluntaryExits(state: CachedBeaconState): phase0.SignedVoluntaryExit[] { + const voluntaryExits: phase0.SignedVoluntaryExit[] = []; + for (const voluntaryExit of this.voluntaryExits.values()) { + if (allForks.isValidVoluntaryExit(state, voluntaryExit, false)) { + voluntaryExits.push(voluntaryExit); + if (voluntaryExits.length >= MAX_VOLUNTARY_EXITS) { + break; + } + } + } + return voluntaryExits; + } + + /** + * Prune attester slashings for all slashed or withdrawn validators. + */ + pruneAttesterSlashings(headState: phase0.BeaconState): void { + const finalizedEpoch = headState.finalizedCheckpoint.epoch; + attesterSlashing: for (const attesterSlashing of this.attesterSlashings.values()) { + // Slashings that don't slash any validators can be dropped + for (let i = 0; i < attesterSlashing.intersectingIndices.length; i++) { + const index = attesterSlashing.intersectingIndices[i]; + + // Declare that a validator is still slashable if they have not exited prior + // to the finalized epoch. + // + // We cannot check the `slashed` field since the `head` is not finalized and + // a fork could un-slash someone. + if (headState.validators[index].exitEpoch > finalizedEpoch) { + continue attesterSlashing; + } + } + + // All intersecting indices are not slashable + // PRUNE + } + } + + /** + * Prune proposer slashings for validators which are exited in the finalized epoch. + */ + pruneProposerSlashings(headState: phase0.BeaconState): void { + const finalizedEpoch = headState.finalizedCheckpoint.epoch; + for (const proposerSlashing of this.proposerSlashings.values()) { + const index = proposerSlashing.signedHeader1.message.proposerIndex; + if (headState.validators[index].exitEpoch <= finalizedEpoch) { + // PRUNE + } + } + } + + /** + * Call after finalizing + * Prune if validator has already exited at or before the finalized checkpoint of the head. + */ + pruneVoluntaryExits(headState: phase0.BeaconState): void { + const finalizedEpoch = headState.finalizedCheckpoint.epoch; + for (const voluntaryExit of this.voluntaryExits.values()) { + // TODO: Improve this simplistic condition + if (voluntaryExit.message.epoch <= finalizedEpoch) { + // PRUNE + } + } + } +} + +function isSlashableAtEpoch(validator: phase0.Validator, epoch: Epoch): boolean { + return !validator.slashed && validator.activationEpoch <= epoch && epoch < validator.withdrawableEpoch; +} + +function getAttesterSlashableIndices(attesterSlashing: phase0.AttesterSlashing): ValidatorIndex[] { + const indices: ValidatorIndex[] = []; + const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices); + const attArr2 = attesterSlashing.attestation2.attestingIndices; + for (let i = 0, len = attArr2.length; i < len; i++) { + const index = attArr2[i]; + if (attSet1.has(index)) { + indices.push(index); + } + } + return indices; +} From 06f3da51ff1419c31c0854838cae57fcb67dfb14 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 14 Sep 2021 13:53:57 +0200 Subject: [PATCH 2/3] Refactor operation pools --- .../allForks/block/processAttesterSlashing.ts | 18 ++--- .../src/util/attestation.ts | 15 +++- .../src/api/impl/beacon/pool/index.ts | 30 +++---- .../lodestar/src/api/impl/validator/index.ts | 3 +- packages/lodestar/src/chain/chain.ts | 17 +++- .../src/chain/errors/voluntaryExitError.ts | 4 +- .../lodestar/src/chain/factory/block/body.ts | 29 ++----- .../lodestar/src/chain/factory/block/index.ts | 6 +- packages/lodestar/src/chain/interface.ts | 6 +- .../opPools/aggregatedAttestationPool.ts | 2 +- packages/lodestar/src/chain/opPools/index.ts | 9 ++- packages/lodestar/src/chain/opPools/opPool.ts | 78 +++++++++++++------ .../src/chain/validation/attesterSlashing.ts | 20 ++--- .../src/chain/validation/proposerSlashing.ts | 7 +- .../src/chain/validation/voluntaryExit.ts | 17 ++-- .../src/network/gossip/handlers/index.ts | 38 +++++---- packages/lodestar/src/network/network.ts | 6 +- packages/lodestar/src/node/nodejs.ts | 5 +- packages/lodestar/src/util/objects.ts | 16 ---- .../unit/api/impl/beacon/pool/pool.test.ts | 22 ++---- .../chain/factory/block/blockAssembly.test.ts | 6 +- .../unit/chain/factory/block/body.test.ts | 57 ++++---------- .../opPools/syncCommitteeContribution.test.ts | 2 +- .../chain/validation/attesterSlashing.test.ts | 24 +++--- .../chain/validation/proposerSlashing.test.ts | 23 +++--- .../chain/validation/voluntaryExit.test.ts | 19 ++--- .../lodestar/test/unit/util/objects.test.ts | 9 +-- .../lodestar/test/utils/mocks/chain/chain.ts | 3 + 28 files changed, 232 insertions(+), 259 deletions(-) diff --git a/packages/beacon-state-transition/src/allForks/block/processAttesterSlashing.ts b/packages/beacon-state-transition/src/allForks/block/processAttesterSlashing.ts index fa1d4014d64a..265fa51df7f0 100644 --- a/packages/beacon-state-transition/src/allForks/block/processAttesterSlashing.ts +++ b/packages/beacon-state-transition/src/allForks/block/processAttesterSlashing.ts @@ -1,7 +1,7 @@ -import {allForks, phase0, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {allForks, phase0} from "@chainsafe/lodestar-types"; import {ForkName} from "@chainsafe/lodestar-params"; -import {isSlashableValidator, isSlashableAttestationData} from "../../util"; +import {isSlashableValidator, isSlashableAttestationData, getAttesterSlashableIndices} from "../../util"; import {CachedBeaconState} from "../util"; import {isValidIndexedAttestation} from "./isValidIndexedAttestation"; import {slashValidatorAllForks} from "./slashValidator"; @@ -22,20 +22,12 @@ export function processAttesterSlashing( ): void { assertValidAttesterSlashing(state as CachedBeaconState, attesterSlashing, verifySignatures); - // TODO: Is there a more performant intersection algorythm? This should be a utility function: intersect() - const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices); - const attSet2 = new Set(attesterSlashing.attestation2.attestingIndices); - const indices: ValidatorIndex[] = []; - for (const i of attSet1.values()) { - if (attSet2.has(i)) { - indices.push(i); - } - } + const intersectingIndices = getAttesterSlashableIndices(attesterSlashing); let slashedAny = false; const validators = state.validators; // Get the validators sub tree once for all indices - // TODO: Why do we need to sort()? If it necessary add a comment with why - for (const index of indices.sort((a, b) => a - b)) { + // Spec requires to sort indexes beforehand + for (const index of intersectingIndices.sort((a, b) => a - b)) { if (isSlashableValidator(validators[index], state.epochCtx.currentShuffling.epoch)) { slashValidatorAllForks(fork, state, index, blockProcess); slashedAny = true; diff --git a/packages/beacon-state-transition/src/util/attestation.ts b/packages/beacon-state-transition/src/util/attestation.ts index 436c71731f87..44f2fe7e6c40 100644 --- a/packages/beacon-state-transition/src/util/attestation.ts +++ b/packages/beacon-state-transition/src/util/attestation.ts @@ -3,7 +3,7 @@ */ import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; -import {phase0, Slot, ssz} from "@chainsafe/lodestar-types"; +import {phase0, Slot, ssz, ValidatorIndex} from "@chainsafe/lodestar-types"; /** * Check if [[data1]] and [[data2]] are slashable according to Casper FFG rules. @@ -22,3 +22,16 @@ export function isValidAttestationSlot(attestationSlot: Slot, currentSlot: Slot) attestationSlot + MIN_ATTESTATION_INCLUSION_DELAY <= currentSlot && currentSlot <= attestationSlot + SLOTS_PER_EPOCH ); } + +export function getAttesterSlashableIndices(attesterSlashing: phase0.AttesterSlashing): ValidatorIndex[] { + const indices: ValidatorIndex[] = []; + const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices); + const attArr2 = attesterSlashing.attestation2.attestingIndices; + for (let i = 0, len = attArr2.length; i < len; i++) { + const index = attArr2[i]; + if (attSet1.has(index)) { + indices.push(index); + } + } + return indices; +} diff --git a/packages/lodestar/src/api/impl/beacon/pool/index.ts b/packages/lodestar/src/api/impl/beacon/pool/index.ts index 2796f54b16b8..fee2b92fb487 100644 --- a/packages/lodestar/src/api/impl/beacon/pool/index.ts +++ b/packages/lodestar/src/api/impl/beacon/pool/index.ts @@ -18,8 +18,7 @@ export function getBeaconPoolApi({ logger, metrics, network, - db, -}: Pick): IBeaconPoolApi { +}: Pick): IBeaconPoolApi { return { async getPoolAttestations(filters) { // Already filtered by slot @@ -33,15 +32,15 @@ export function getBeaconPoolApi({ }, async getPoolAttesterSlashings() { - return {data: await db.attesterSlashing.values()}; + return {data: chain.opPool.getAllAttesterSlashings()}; }, async getPoolProposerSlashings() { - return {data: await db.proposerSlashing.values()}; + return {data: chain.opPool.getAllProposerSlashings()}; }, async getPoolVoluntaryExits() { - return {data: await db.voluntaryExit.values()}; + return {data: chain.opPool.getAllVoluntaryExits()}; }, async submitPoolAttestations(attestations) { @@ -85,19 +84,22 @@ export function getBeaconPoolApi({ } }, - async submitPoolAttesterSlashing(slashing) { - await validateGossipAttesterSlashing(chain, db, slashing); - await Promise.all([network.gossip.publishAttesterSlashing(slashing), db.attesterSlashing.add(slashing)]); + async submitPoolAttesterSlashing(attesterSlashing) { + await validateGossipAttesterSlashing(chain, attesterSlashing); + chain.opPool.insertAttesterSlashing(attesterSlashing); + await network.gossip.publishAttesterSlashing(attesterSlashing); }, - async submitPoolProposerSlashing(slashing) { - await validateGossipProposerSlashing(chain, db, slashing); - await Promise.all([network.gossip.publishProposerSlashing(slashing), db.proposerSlashing.add(slashing)]); + async submitPoolProposerSlashing(proposerSlashing) { + await validateGossipProposerSlashing(chain, proposerSlashing); + chain.opPool.insertProposerSlashing(proposerSlashing); + await network.gossip.publishProposerSlashing(proposerSlashing); }, - async submitPoolVoluntaryExit(exit) { - await validateGossipVoluntaryExit(chain, db, exit); - await Promise.all([network.gossip.publishVoluntaryExit(exit), db.voluntaryExit.add(exit)]); + async submitPoolVoluntaryExit(voluntaryExit) { + await validateGossipVoluntaryExit(chain, voluntaryExit); + chain.opPool.insertVoluntaryExit(voluntaryExit); + await network.gossip.publishVoluntaryExit(voluntaryExit); }, /** diff --git a/packages/lodestar/src/api/impl/validator/index.ts b/packages/lodestar/src/api/impl/validator/index.ts index 3bdbff4a6910..238100df1a29 100644 --- a/packages/lodestar/src/api/impl/validator/index.ts +++ b/packages/lodestar/src/api/impl/validator/index.ts @@ -52,7 +52,6 @@ const SYNC_TOLERANCE_EPOCHS = 8; export function getValidatorApi({ chain, config, - db, eth1, logger, metrics, @@ -145,7 +144,7 @@ export function getValidatorApi({ timer = metrics?.blockProductionTime.startTimer(); const block = await assembleBlock( - {config, chain, db, eth1, metrics}, + {config, chain, eth1, metrics}, slot, randaoReveal, toGraffitiBuffer(graffiti || "") diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 203443893953..b7a2b239a154 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -33,10 +33,15 @@ import { SeenSyncCommitteeMessages, SeenContributionAndProof, } from "./seenCache"; -import {AttestationPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools"; +import { + AggregatedAttestationPool, + AttestationPool, + SyncCommitteeMessagePool, + SyncContributionAndProofPool, + OpPool, +} from "./opPools"; import {ForkDigestContext, IForkDigestContext} from "../util/forkDigestContext"; import {LightClientIniter} from "./lightClient"; -import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool"; import {Archiver} from "./archiver"; export interface IBeaconChainModules { @@ -68,6 +73,7 @@ export class BeaconChain implements IBeaconChain { readonly aggregatedAttestationPool = new AggregatedAttestationPool(); readonly syncCommitteeMessagePool = new SyncCommitteeMessagePool(); readonly syncContributionAndProofPool = new SyncContributionAndProofPool(); + readonly opPool = new OpPool(); // Gossip seen cache readonly seenAttesters = new SeenAttesters(); @@ -156,8 +162,15 @@ export class BeaconChain implements IBeaconChain { this.checkpointStateCache.clear(); } + /** Populate in-memory caches with persisted data. Call at least once on startup */ + async loadFromDisk(): Promise { + await this.opPool.fromPersisted(this.db); + } + + /** Persist in-memory data to the DB. Call at least once before stopping the process */ async persistToDisk(): Promise { await this.archiver.persistToDisk(); + await this.opPool.toPersisted(this.db); } getGenesisTime(): Number64 { diff --git a/packages/lodestar/src/chain/errors/voluntaryExitError.ts b/packages/lodestar/src/chain/errors/voluntaryExitError.ts index a53a047c603a..6b13507a9c9c 100644 --- a/packages/lodestar/src/chain/errors/voluntaryExitError.ts +++ b/packages/lodestar/src/chain/errors/voluntaryExitError.ts @@ -3,9 +3,11 @@ import {GossipActionError} from "./gossipValidation"; export enum VoluntaryExitErrorCode { ALREADY_EXISTS = "VOLUNTARY_EXIT_ERROR_ALREADY_EXISTS", INVALID = "VOLUNTARY_EXIT_ERROR_INVALID", + INVALID_SIGNATURE = "VOLUNTARY_EXIT_ERROR_INVALID_SIGNATURE", } export type VoluntaryExitErrorType = | {code: VoluntaryExitErrorCode.ALREADY_EXISTS} - | {code: VoluntaryExitErrorCode.INVALID; error: Error}; + | {code: VoluntaryExitErrorCode.INVALID} + | {code: VoluntaryExitErrorCode.INVALID_SIGNATURE}; export class VoluntaryExitError extends GossipActionError {} diff --git a/packages/lodestar/src/chain/factory/block/body.ts b/packages/lodestar/src/chain/factory/block/body.ts index 4c1ca5210e47..60696d1f5282 100644 --- a/packages/lodestar/src/chain/factory/block/body.ts +++ b/packages/lodestar/src/chain/factory/block/body.ts @@ -3,28 +3,16 @@ */ import {List} from "@chainsafe/ssz"; -import { - ForkName, - MAX_ATTESTATIONS, - MAX_ATTESTER_SLASHINGS, - MAX_PROPOSER_SLASHINGS, - MAX_VOLUNTARY_EXITS, -} from "@chainsafe/lodestar-params"; +import {ForkName} from "@chainsafe/lodestar-params"; import {Bytes96, Bytes32, phase0, allForks, altair, Root, Slot} from "@chainsafe/lodestar-types"; import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {CachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition"; -import {IBeaconDb} from "../../../db"; import {IEth1ForBlockProduction} from "../../../eth1"; import {IBeaconChain} from "../../interface"; export async function assembleBody( - { - chain, - config, - db, - eth1, - }: {chain: IBeaconChain; config: IChainForkConfig; db: IBeaconDb; eth1: IEth1ForBlockProduction}, + {chain, config, eth1}: {chain: IBeaconChain; config: IChainForkConfig; eth1: IEth1ForBlockProduction}, currentState: CachedBeaconState, randaoReveal: Bytes96, graffiti: Bytes32, @@ -43,13 +31,12 @@ export async function assembleBody( // } // } - const [proposerSlashings, attesterSlashings, attestations, voluntaryExits, {eth1Data, deposits}] = await Promise.all([ - db.proposerSlashing.values({limit: MAX_PROPOSER_SLASHINGS}), - db.attesterSlashing.values({limit: MAX_ATTESTER_SLASHINGS}), - chain.aggregatedAttestationPool.getAttestationsForBlock(currentState).slice(0, MAX_ATTESTATIONS), - db.voluntaryExit.values({limit: MAX_VOLUNTARY_EXITS}), - eth1.getEth1DataAndDeposits(currentState as CachedBeaconState), - ]); + const [attesterSlashings, proposerSlashings] = chain.opPool.getSlashings(currentState); + const voluntaryExits = chain.opPool.getVoluntaryExits(currentState); + const attestations = chain.aggregatedAttestationPool.getAttestationsForBlock(currentState); + const {eth1Data, deposits} = await eth1.getEth1DataAndDeposits( + currentState as CachedBeaconState + ); const blockBodyPhase0: phase0.BeaconBlockBody = { randaoReveal, diff --git a/packages/lodestar/src/chain/factory/block/index.ts b/packages/lodestar/src/chain/factory/block/index.ts index b7051f06ad34..f883e20cc6df 100644 --- a/packages/lodestar/src/chain/factory/block/index.ts +++ b/packages/lodestar/src/chain/factory/block/index.ts @@ -6,7 +6,6 @@ import {CachedBeaconState, allForks} from "@chainsafe/lodestar-beacon-state-tran import {IChainForkConfig} from "@chainsafe/lodestar-config"; import {Bytes96, Root, Slot} from "@chainsafe/lodestar-types"; import {ZERO_HASH} from "../../../constants"; -import {IBeaconDb} from "../../../db"; import {IEth1ForBlockProduction} from "../../../eth1"; import {IMetrics} from "../../../metrics"; import {IBeaconChain} from "../../interface"; @@ -16,13 +15,12 @@ import {RegenCaller} from "../../regen"; type AssembleBlockModules = { config: IChainForkConfig; chain: IBeaconChain; - db: IBeaconDb; eth1: IEth1ForBlockProduction; metrics: IMetrics | null; }; export async function assembleBlock( - {config, chain, db, eth1, metrics}: AssembleBlockModules, + {config, chain, eth1, metrics}: AssembleBlockModules, slot: Slot, randaoReveal: Bytes96, graffiti = ZERO_HASH @@ -35,7 +33,7 @@ export async function assembleBlock( proposerIndex: state.getBeaconProposer(slot), parentRoot: head.blockRoot, stateRoot: ZERO_HASH, - body: await assembleBody({config, chain, db, eth1}, state, randaoReveal, graffiti, slot, { + body: await assembleBody({config, chain, eth1}, state, randaoReveal, graffiti, slot, { parentSlot: slot - 1, parentBlockRoot: head.blockRoot, }), diff --git a/packages/lodestar/src/chain/interface.ts b/packages/lodestar/src/chain/interface.ts index 26c40e7f9737..ab7a57f95c0d 100644 --- a/packages/lodestar/src/chain/interface.ts +++ b/packages/lodestar/src/chain/interface.ts @@ -17,7 +17,7 @@ import { SeenSyncCommitteeMessages, SeenContributionAndProof, } from "./seenCache"; -import {AttestationPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools"; +import {AttestationPool, OpPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools"; import {IForkDigestContext} from "../util/forkDigestContext"; import {LightClientIniter} from "./lightClient"; import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool"; @@ -75,6 +75,7 @@ export interface IBeaconChain { readonly aggregatedAttestationPool: AggregatedAttestationPool; readonly syncCommitteeMessagePool: SyncCommitteeMessagePool; readonly syncContributionAndProofPool: SyncContributionAndProofPool; + readonly opPool: OpPool; // Gossip seen cache readonly seenAttesters: SeenAttesters; @@ -85,6 +86,9 @@ export interface IBeaconChain { /** Stop beacon chain processing */ close(): void; + /** Populate in-memory caches with persisted data. Call at least once on startup */ + loadFromDisk(): Promise; + /** Persist in-memory data to the DB. Call at least once before stopping the process */ persistToDisk(): Promise; getGenesisTime(): Number64; diff --git a/packages/lodestar/src/chain/opPools/aggregatedAttestationPool.ts b/packages/lodestar/src/chain/opPools/aggregatedAttestationPool.ts index 736c5bcc21de..573a2082edeb 100644 --- a/packages/lodestar/src/chain/opPools/aggregatedAttestationPool.ts +++ b/packages/lodestar/src/chain/opPools/aggregatedAttestationPool.ts @@ -76,7 +76,7 @@ export class AggregatedAttestationPool { } /** - * Get attestations to be included in a block. + * Get attestations to be included in a block. Returns $MAX_ATTESTATIONS items */ getAttestationsForBlock(state: CachedBeaconState): phase0.Attestation[] { const stateSlot = state.slot; diff --git a/packages/lodestar/src/chain/opPools/index.ts b/packages/lodestar/src/chain/opPools/index.ts index c7892d7f93b4..f860b302b0da 100644 --- a/packages/lodestar/src/chain/opPools/index.ts +++ b/packages/lodestar/src/chain/opPools/index.ts @@ -1,4 +1,5 @@ -export * from "./attestationPool"; -export * from "./aggregatedAttestationPool"; -export * from "./syncCommitteeMessagePool"; -export * from "./syncContributionAndProofPool"; +export {AggregatedAttestationPool} from "./aggregatedAttestationPool"; +export {AttestationPool} from "./attestationPool"; +export {SyncCommitteeMessagePool} from "./syncCommitteeMessagePool"; +export {SyncContributionAndProofPool} from "./syncContributionAndProofPool"; +export {OpPool} from "./opPool"; diff --git a/packages/lodestar/src/chain/opPools/opPool.ts b/packages/lodestar/src/chain/opPools/opPool.ts index f4a8d878a783..39ec556b7924 100644 --- a/packages/lodestar/src/chain/opPools/opPool.ts +++ b/packages/lodestar/src/chain/opPools/opPool.ts @@ -1,4 +1,9 @@ -import {CachedBeaconState, computeEpochAtSlot, allForks} from "@chainsafe/lodestar-beacon-state-transition"; +import { + CachedBeaconState, + computeEpochAtSlot, + allForks, + getAttesterSlashableIndices, +} from "@chainsafe/lodestar-beacon-state-transition"; import {MAX_PROPOSER_SLASHINGS, MAX_VOLUNTARY_EXITS} from "@chainsafe/lodestar-params"; import {Epoch, phase0, ssz, ValidatorIndex} from "@chainsafe/lodestar-types"; import {fromHexString, toHexString} from "@chainsafe/ssz"; @@ -17,10 +22,10 @@ export class OpPool { private readonly proposerSlashings = new Map(); /** Map of to exit validator index -> SignedVoluntaryExit */ private readonly voluntaryExits = new Map(); + /** Set of seen attester slashing indexes. No need to prune */ + private readonly attesterSlashingIndexes = new Set(); - static async fromPersisted(db: IBeaconDb): Promise { - const opPool = new OpPool(); - + async fromPersisted(db: IBeaconDb): Promise { const [attesterSlashings, proposerSlashings, voluntaryExits] = await Promise.all([ db.attesterSlashing.entries(), db.proposerSlashing.values(), @@ -28,16 +33,14 @@ export class OpPool { ]); for (const attesterSlashing of attesterSlashings) { - opPool.insertAttesterSlashing(attesterSlashing.value, attesterSlashing.key); + this.insertAttesterSlashing(attesterSlashing.value, attesterSlashing.key); } for (const proposerSlashing of proposerSlashings) { - opPool.insertProposerSlashing(proposerSlashing); + this.insertProposerSlashing(proposerSlashing); } for (const voluntaryExit of voluntaryExits) { - opPool.insertVoluntaryExit(voluntaryExit); + this.insertVoluntaryExit(voluntaryExit); } - - return opPool; } async toPersisted(db: IBeaconDb): Promise { @@ -54,16 +57,41 @@ export class OpPool { ]); } - /// Insert an attester slashing into the pool. + // Use the opPool as seen cache for gossip validation + + /** Returns false if at least one intersecting index has not been seen yet */ + hasSeenAttesterSlashing(intersectingIndices: ValidatorIndex[]): boolean { + for (const validatorIndex of intersectingIndices) { + if (!this.attesterSlashingIndexes.has(validatorIndex)) { + return false; + } + } + return true; + } + + hasSeenVoluntaryExit(validatorIndex: ValidatorIndex): boolean { + return this.voluntaryExits.has(validatorIndex); + } + + hasSeenProposerSlashing(validatorIndex: ValidatorIndex): boolean { + return this.proposerSlashings.has(validatorIndex); + } + + /** Must be validated beforehand */ insertAttesterSlashing(attesterSlashing: phase0.AttesterSlashing, rootHash?: Uint8Array): void { if (!rootHash) rootHash = ssz.phase0.AttesterSlashing.hashTreeRoot(attesterSlashing); + // TODO: Do once and cache attached to the AttesterSlashing object const intersectingIndices = getAttesterSlashableIndices(attesterSlashing); this.attesterSlashings.set(toHexString(rootHash), { attesterSlashing, intersectingIndices, }); + for (const index of intersectingIndices) { + this.attesterSlashingIndexes.add(index); + } } + /** Must be validated beforehand */ insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing): void { this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex, proposerSlashing); } @@ -79,7 +107,7 @@ export class OpPool { * This function computes both types of slashings together, because attester slashings may be invalidated by * proposer slashings included earlier in the block. */ - getSlashings(state: phase0.BeaconState): [phase0.AttesterSlashing[], phase0.ProposerSlashing[]] { + getSlashings(state: CachedBeaconState): [phase0.AttesterSlashing[], phase0.ProposerSlashing[]] { const stateEpoch = computeEpochAtSlot(state.slot); const toBeSlashedIndices: ValidatorIndex[] = []; const proposerSlashings: phase0.ProposerSlashing[] = []; @@ -178,21 +206,23 @@ export class OpPool { } } } + + /** For beacon pool API */ + getAllAttesterSlashings(): phase0.AttesterSlashing[] { + return Array.from(this.attesterSlashings.values()).map((attesterSlashings) => attesterSlashings.attesterSlashing); + } + + /** For beacon pool API */ + getAllProposerSlashings(): phase0.ProposerSlashing[] { + return Array.from(this.proposerSlashings.values()); + } + + /** For beacon pool API */ + getAllVoluntaryExits(): phase0.SignedVoluntaryExit[] { + return Array.from(this.voluntaryExits.values()); + } } function isSlashableAtEpoch(validator: phase0.Validator, epoch: Epoch): boolean { return !validator.slashed && validator.activationEpoch <= epoch && epoch < validator.withdrawableEpoch; } - -function getAttesterSlashableIndices(attesterSlashing: phase0.AttesterSlashing): ValidatorIndex[] { - const indices: ValidatorIndex[] = []; - const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices); - const attArr2 = attesterSlashing.attestation2.attestingIndices; - for (let i = 0, len = attArr2.length; i < len; i++) { - const index = attArr2[i]; - if (attSet1.has(index)) { - indices.push(index); - } - } - return indices; -} diff --git a/packages/lodestar/src/chain/validation/attesterSlashing.ts b/packages/lodestar/src/chain/validation/attesterSlashing.ts index b826e2743cbf..a0065ee909ec 100644 --- a/packages/lodestar/src/chain/validation/attesterSlashing.ts +++ b/packages/lodestar/src/chain/validation/attesterSlashing.ts @@ -1,22 +1,17 @@ -import {phase0, allForks} from "@chainsafe/lodestar-beacon-state-transition"; -import {ssz, ValidatorIndex} from "@chainsafe/lodestar-types"; +import {phase0, allForks, getAttesterSlashableIndices} from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconChain} from ".."; import {AttesterSlashingError, AttesterSlashingErrorCode, GossipAction} from "../errors"; -import {IBeaconDb} from "../../db"; -import {arrayIntersection, sszEqualPredicate} from "../../util/objects"; export async function validateGossipAttesterSlashing( chain: IBeaconChain, - db: IBeaconDb, attesterSlashing: phase0.AttesterSlashing ): Promise { - const attesterSlashedIndices = arrayIntersection( - attesterSlashing.attestation1.attestingIndices.valueOf() as ValidatorIndex[], - attesterSlashing.attestation2.attestingIndices.valueOf() as ValidatorIndex[], - sszEqualPredicate(ssz.ValidatorIndex) - ); - - if (await db.attesterSlashing.hasAll(attesterSlashedIndices)) { + // [IGNORE] At least one index in the intersection of the attesting indices of each attestation has not yet been seen + // in any prior attester_slashing (i.e. + // attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices + // ), verify if any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))). + const intersectingIndices = getAttesterSlashableIndices(attesterSlashing); + if (chain.opPool.hasSeenAttesterSlashing(intersectingIndices)) { throw new AttesterSlashingError(GossipAction.IGNORE, { code: AttesterSlashingErrorCode.ALREADY_EXISTS, }); @@ -24,6 +19,7 @@ export async function validateGossipAttesterSlashing( const state = chain.getHeadState(); + // [REJECT] All of the conditions within process_attester_slashing pass validation. try { // verifySignature = false, verified in batch below allForks.assertValidAttesterSlashing(state, attesterSlashing, false); diff --git a/packages/lodestar/src/chain/validation/proposerSlashing.ts b/packages/lodestar/src/chain/validation/proposerSlashing.ts index 4ffa1ca994f0..670eab2d92a5 100644 --- a/packages/lodestar/src/chain/validation/proposerSlashing.ts +++ b/packages/lodestar/src/chain/validation/proposerSlashing.ts @@ -1,14 +1,14 @@ import {phase0, allForks} from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconChain} from ".."; import {ProposerSlashingError, ProposerSlashingErrorCode, GossipAction} from "../errors"; -import {IBeaconDb} from "../../db"; export async function validateGossipProposerSlashing( chain: IBeaconChain, - db: IBeaconDb, proposerSlashing: phase0.ProposerSlashing ): Promise { - if (await db.proposerSlashing.has(proposerSlashing.signedHeader1.message.proposerIndex)) { + // [IGNORE] The proposer slashing is the first valid proposer slashing received for the proposer with index + // proposer_slashing.signed_header_1.message.proposer_index. + if (chain.opPool.hasSeenProposerSlashing(proposerSlashing.signedHeader1.message.proposerIndex)) { throw new ProposerSlashingError(GossipAction.IGNORE, { code: ProposerSlashingErrorCode.ALREADY_EXISTS, }); @@ -16,6 +16,7 @@ export async function validateGossipProposerSlashing( const state = chain.getHeadState(); + // [REJECT] All of the conditions within process_proposer_slashing pass validation. try { // verifySignature = false, verified in batch below allForks.assertValidProposerSlashing(state, proposerSlashing, false); diff --git a/packages/lodestar/src/chain/validation/voluntaryExit.ts b/packages/lodestar/src/chain/validation/voluntaryExit.ts index c199c9caca3c..7fab5ec863a9 100644 --- a/packages/lodestar/src/chain/validation/voluntaryExit.ts +++ b/packages/lodestar/src/chain/validation/voluntaryExit.ts @@ -1,14 +1,14 @@ import {phase0, allForks} from "@chainsafe/lodestar-beacon-state-transition"; import {IBeaconChain} from ".."; import {VoluntaryExitError, VoluntaryExitErrorCode, GossipAction} from "../errors"; -import {IBeaconDb} from "../../db"; export async function validateGossipVoluntaryExit( chain: IBeaconChain, - db: IBeaconDb, voluntaryExit: phase0.SignedVoluntaryExit ): Promise { - if (await db.voluntaryExit.has(voluntaryExit.message.validatorIndex)) { + // [IGNORE] The voluntary exit is the first valid voluntary exit received for the validator with index + // signed_voluntary_exit.message.validator_index. + if (chain.opPool.hasSeenVoluntaryExit(voluntaryExit.message.validatorIndex)) { throw new VoluntaryExitError(GossipAction.IGNORE, { code: VoluntaryExitErrorCode.ALREADY_EXISTS, }); @@ -23,21 +23,18 @@ export async function validateGossipVoluntaryExit( // relevant on periods of many skipped slots. const state = await chain.getHeadStateAtCurrentEpoch(); - try { - // verifySignature = false, verified in batch below - allForks.assertValidVoluntaryExit(state, voluntaryExit, false); - } catch (e) { + // [REJECT] All of the conditions within process_voluntary_exit pass validation. + // verifySignature = false, verified in batch below + if (!allForks.isValidVoluntaryExit(state, voluntaryExit, false)) { throw new VoluntaryExitError(GossipAction.REJECT, { code: VoluntaryExitErrorCode.INVALID, - error: e as Error, }); } const signatureSet = allForks.getVoluntaryExitSignatureSet(state, voluntaryExit); if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true}))) { throw new VoluntaryExitError(GossipAction.REJECT, { - code: VoluntaryExitErrorCode.INVALID, - error: Error("Invalid signature"), + code: VoluntaryExitErrorCode.INVALID_SIGNATURE, }); } } diff --git a/packages/lodestar/src/network/gossip/handlers/index.ts b/packages/lodestar/src/network/gossip/handlers/index.ts index ca7fe061dfb0..80e72fa91899 100644 --- a/packages/lodestar/src/network/gossip/handlers/index.ts +++ b/packages/lodestar/src/network/gossip/handlers/index.ts @@ -4,7 +4,6 @@ import {phase0, ssz, ValidatorIndex} from "@chainsafe/lodestar-types"; import {ILogger, prettyBytes} from "@chainsafe/lodestar-utils"; import {IMetrics} from "../../../metrics"; import {OpSource} from "../../../metrics/validatorMonitor"; -import {IBeaconDb} from "../../../db"; import {IBeaconChain} from "../../../chain"; import { AttestationError, @@ -34,7 +33,6 @@ export type GossipHandlers = { type ValidatorFnsModules = { chain: IBeaconChain; config: IBeaconConfig; - db: IBeaconDb; logger: ILogger; network: INetwork; metrics: IMetrics | null; @@ -55,7 +53,7 @@ type ValidatorFnsModules = { * - Eth2.0 gossipsub protocol strictly defined a single topic for message */ export function getGossipHandlers(modules: ValidatorFnsModules): GossipHandlers { - const {chain, db, config, metrics, network, logger} = modules; + const {chain, config, metrics, network, logger} = modules; return { [GossipType.beacon_block]: async (signedBlock) => { @@ -187,34 +185,40 @@ export function getGossipHandlers(modules: ValidatorFnsModules): GossipHandlers } }, - [GossipType.voluntary_exit]: async (voluntaryExit) => { - await validateGossipVoluntaryExit(chain, db, voluntaryExit); + [GossipType.attester_slashing]: async (attesterSlashing) => { + await validateGossipAttesterSlashing(chain, attesterSlashing); // Handler - db.voluntaryExit.add(voluntaryExit).catch((e: Error) => { - logger.error("Error adding attesterSlashing to pool", {}, e); - }); + try { + chain.opPool.insertAttesterSlashing(attesterSlashing); + } catch (e) { + logger.error("Error adding attesterSlashing to pool", {}, e as Error); + } }, [GossipType.proposer_slashing]: async (proposerSlashing) => { - await validateGossipProposerSlashing(chain, db, proposerSlashing); + await validateGossipProposerSlashing(chain, proposerSlashing); // Handler - db.proposerSlashing.add(proposerSlashing).catch((e: Error) => { - logger.error("Error adding attesterSlashing to pool", {}, e); - }); + try { + chain.opPool.insertProposerSlashing(proposerSlashing); + } catch (e) { + logger.error("Error adding attesterSlashing to pool", {}, e as Error); + } }, - [GossipType.attester_slashing]: async (attesterSlashing) => { - await validateGossipAttesterSlashing(chain, db, attesterSlashing); + [GossipType.voluntary_exit]: async (voluntaryExit) => { + await validateGossipVoluntaryExit(chain, voluntaryExit); // Handler - db.attesterSlashing.add(attesterSlashing).catch((e: Error) => { - logger.error("Error adding attesterSlashing to pool", {}, e); - }); + try { + chain.opPool.insertVoluntaryExit(voluntaryExit); + } catch (e) { + logger.error("Error adding attesterSlashing to pool", {}, e as Error); + } }, [GossipType.sync_committee_contribution_and_proof]: async (contributionAndProof) => { diff --git a/packages/lodestar/src/network/network.ts b/packages/lodestar/src/network/network.ts index 6b690d3b7df0..99920ef2e971 100644 --- a/packages/lodestar/src/network/network.ts +++ b/packages/lodestar/src/network/network.ts @@ -14,7 +14,6 @@ import {computeEpochAtSlot} from "@chainsafe/lodestar-beacon-state-transition"; import {Epoch} from "@chainsafe/lodestar-types"; import {IMetrics} from "../metrics"; import {ChainEvent, IBeaconChain, IBeaconClock} from "../chain"; -import {IBeaconDb} from "../db"; import {INetworkOptions} from "./options"; import {INetwork} from "./interface"; import {ReqResp, IReqResp, IReqRespOptions, ReqRespHandlers} from "./reqresp"; @@ -33,7 +32,6 @@ interface INetworkModules { logger: ILogger; metrics: IMetrics | null; chain: IBeaconChain; - db: IBeaconDb; reqRespHandlers: ReqRespHandlers; signal: AbortSignal; // Optionally pass custom GossipHandlers, for testing @@ -60,7 +58,7 @@ export class Network implements INetwork { private subscribedForks = new Set(); constructor(private readonly opts: INetworkOptions & IReqRespOptions, modules: INetworkModules) { - const {config, db, libp2p, logger, metrics, chain, reqRespHandlers, gossipHandlers, signal} = modules; + const {config, libp2p, logger, metrics, chain, reqRespHandlers, gossipHandlers, signal} = modules; this.libp2p = libp2p; this.logger = logger; this.config = config; @@ -96,7 +94,7 @@ export class Network implements INetwork { logger, metrics, signal, - gossipHandlers: gossipHandlers ?? getGossipHandlers({chain, config, db, logger, network: this, metrics}), + gossipHandlers: gossipHandlers ?? getGossipHandlers({chain, config, logger, network: this, metrics}), forkDigestContext: chain.forkDigestContext, }); diff --git a/packages/lodestar/src/node/nodejs.ts b/packages/lodestar/src/node/nodejs.ts index 808992314488..ce288c77c55e 100644 --- a/packages/lodestar/src/node/nodejs.ts +++ b/packages/lodestar/src/node/nodejs.ts @@ -132,13 +132,16 @@ export class BeaconNode { metrics, anchorState, }); + + // Load persisted data from disk to in-memory caches + await chain.loadFromDisk(); + const network = new Network(opts.network, { config, libp2p, logger: logger.child(opts.logger.network), metrics, chain, - db, reqRespHandlers: getReqRespHandlers({db, chain}), signal, }); diff --git a/packages/lodestar/src/util/objects.ts b/packages/lodestar/src/util/objects.ts index d35586568033..2a6023a769a3 100644 --- a/packages/lodestar/src/util/objects.ts +++ b/packages/lodestar/src/util/objects.ts @@ -35,19 +35,3 @@ export function mostFrequent(type: Type, array: T[]): T[] { } return results; } - -export function sszEqualPredicate(type: Type): (a: T, b: T) => boolean { - return (a: T, b: T) => { - return type.equals(a, b); - }; -} - -export function arrayIntersection(arr1: T[], arr2: T[], predicate: (a: T, b: T) => boolean): T[] { - return arr1.filter((item1) => { - return ( - arr2.findIndex((item2) => { - return predicate(item1, item2); - }) !== -1 - ); - }); -} diff --git a/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts b/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts index d72cf1c138b1..a9113e59a168 100644 --- a/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts +++ b/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts @@ -7,7 +7,6 @@ import { generateAttestationData, generateEmptySignedVoluntaryExit, } from "../../../../../utils/attestation"; -import {StubbedBeaconDb} from "../../../../../utils/stub"; import {SinonStubbedInstance} from "sinon"; import {IBeaconChain} from "../../../../../../src/chain"; import * as attesterSlashingValidation from "../../../../../../src/chain/validation/attesterSlashing"; @@ -26,7 +25,6 @@ import {AggregatedAttestationPool} from "../../../../../../src/chain/opPools"; describe("beacon pool api impl", function () { const logger = testLogger(); let poolApi: ReturnType; - let dbStub: StubbedBeaconDb; let chainStub: SinonStubbedInstance; let networkStub: SinonStubbedInstance; let gossipStub: SinonStubbedInstance; @@ -37,7 +35,6 @@ describe("beacon pool api impl", function () { beforeEach(function () { const server = setupApiImplTestServer(); - dbStub = server.dbStub; chainStub = server.chainStub; aggregatedAttestationPool = sinon.createStubInstance(AggregatedAttestationPool); ((chainStub as unknown) as { @@ -50,7 +47,6 @@ describe("beacon pool api impl", function () { networkStub = server.networkStub; networkStub.gossip = (gossipStub as unknown) as Eth2Gossipsub; poolApi = getBeaconPoolApi({ - db: server.dbStub, logger, network: networkStub, chain: chainStub, @@ -97,18 +93,16 @@ describe("beacon pool api impl", function () { }, }; - it("should broadcast and persist to db", async function () { + it("should broadcast", async function () { validateGossipAttesterSlashing.resolves(); await poolApi.submitPoolAttesterSlashing(atterterSlashing); expect(gossipStub.publishAttesterSlashing.calledOnceWithExactly(atterterSlashing)).to.be.true; - expect(dbStub.attesterSlashing.add.calledOnceWithExactly(atterterSlashing)).to.be.true; }); - it("should not broadcast or persist to db", async function () { + it("should not broadcast", async function () { validateGossipAttesterSlashing.throws(new Error("unit test error")); await poolApi.submitPoolAttesterSlashing(atterterSlashing).catch(() => ({})); expect(gossipStub.publishAttesterSlashing.calledOnce).to.be.false; - expect(dbStub.attesterSlashing.add.calledOnce).to.be.false; }); }); @@ -118,36 +112,32 @@ describe("beacon pool api impl", function () { signedHeader2: generateEmptySignedBlockHeader(), }; - it("should broadcast and persist to db", async function () { + it("should broadcast", async function () { validateGossipProposerSlashing.resolves(); await poolApi.submitPoolProposerSlashing(proposerSlashing); expect(gossipStub.publishProposerSlashing.calledOnceWithExactly(proposerSlashing)).to.be.true; - expect(dbStub.proposerSlashing.add.calledOnceWithExactly(proposerSlashing)).to.be.true; }); - it("should not broadcast or persist to db", async function () { + it("should not broadcast", async function () { validateGossipProposerSlashing.throws(new Error("unit test error")); await poolApi.submitPoolProposerSlashing(proposerSlashing).catch(() => ({})); expect(gossipStub.publishProposerSlashing.calledOnce).to.be.false; - expect(dbStub.proposerSlashing.add.calledOnce).to.be.false; }); }); describe("submitPoolVoluntaryExit", function () { const voluntaryExit = generateEmptySignedVoluntaryExit(); - it("should broadcast and persist to db", async function () { + it("should broadcast", async function () { validateVoluntaryExit.resolves(); await poolApi.submitPoolVoluntaryExit(voluntaryExit); expect(gossipStub.publishVoluntaryExit.calledOnceWithExactly(voluntaryExit)).to.be.true; - expect(dbStub.voluntaryExit.add.calledOnceWithExactly(voluntaryExit)).to.be.true; }); - it("should not broadcast or persist to db", async function () { + it("should not broadcast", async function () { validateVoluntaryExit.throws(new Error("unit test error")); await poolApi.submitPoolVoluntaryExit(voluntaryExit).catch(() => ({})); expect(gossipStub.publishVoluntaryExit.calledOnce).to.be.false; - expect(dbStub.voluntaryExit.add.calledOnce).to.be.false; }); }); }); diff --git a/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts b/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts index a5c889b4749a..1eb408f60241 100644 --- a/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts +++ b/packages/lodestar/test/unit/chain/factory/block/blockAssembly.test.ts @@ -56,11 +56,7 @@ describe("block assembly", function () { const eth1 = sandbox.createStubInstance(Eth1ForBlockProduction); eth1.getEth1DataAndDeposits.resolves({eth1Data: state.eth1Data, deposits: []}); - const result = await assembleBlock( - {config, chain: chainStub, db: beaconDB, eth1, metrics: null}, - 1, - Buffer.alloc(96, 0) - ); + const result = await assembleBlock({config, chain: chainStub, eth1, metrics: null}, 1, Buffer.alloc(96, 0)); expect(result).to.not.be.null; expect(result.slot).to.equal(1); expect(result.proposerIndex).to.equal(2); diff --git a/packages/lodestar/test/unit/chain/factory/block/body.test.ts b/packages/lodestar/test/unit/chain/factory/block/body.test.ts index 438754dd8d95..9cf8cf168169 100644 --- a/packages/lodestar/test/unit/chain/factory/block/body.test.ts +++ b/packages/lodestar/test/unit/chain/factory/block/body.test.ts @@ -1,13 +1,6 @@ import sinon, {SinonStubbedInstance} from "sinon"; import {expect} from "chai"; - import {ssz} from "@chainsafe/lodestar-types"; -import { - MAX_ATTESTATIONS, - MAX_ATTESTER_SLASHINGS, - MAX_PROPOSER_SLASHINGS, - MAX_VOLUNTARY_EXITS, -} from "@chainsafe/lodestar-params"; import {config} from "@chainsafe/lodestar-config/default"; import {assembleBody} from "../../../../../src/chain/factory/block/body"; import {generateCachedState} from "../../../../utils/state"; @@ -17,7 +10,7 @@ import {generateDeposit} from "../../../../utils/deposit"; import {StubbedBeaconDb} from "../../../../utils/stub"; import {Eth1ForBlockProduction} from "../../../../../src/eth1/"; import {BeaconChain} from "../../../../../src/chain"; -import {AggregatedAttestationPool} from "../../../../../src/chain/opPools"; +import {AggregatedAttestationPool, OpPool} from "../../../../../src/chain/opPools"; describe("blockAssembly - body", function () { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type @@ -27,23 +20,29 @@ describe("blockAssembly - body", function () { const eth1 = sandbox.createStubInstance(Eth1ForBlockProduction); eth1.getEth1DataAndDeposits.resolves({eth1Data: state.eth1Data, deposits: [generateDeposit()]}); const chain = sandbox.createStubInstance(BeaconChain); + const opPool = sandbox.createStubInstance(OpPool); const aggregatedAttestationPool = sinon.createStubInstance(AggregatedAttestationPool); ((chain as unknown) as { aggregatedAttestationPool: SinonStubbedInstance; }).aggregatedAttestationPool = aggregatedAttestationPool; - return {chain, aggregatedAttestationPool, dbStub: new StubbedBeaconDb(sandbox), eth1}; + ((chain as unknown) as { + opPool: SinonStubbedInstance; + }).opPool = opPool; + return {chain, aggregatedAttestationPool, dbStub: new StubbedBeaconDb(sandbox), eth1, opPool}; } it("should generate block body", async function () { - const {chain, dbStub, eth1, aggregatedAttestationPool} = getStubs(); - dbStub.proposerSlashing.values.resolves([ssz.phase0.ProposerSlashing.defaultValue()]); - dbStub.attesterSlashing.values.resolves([ssz.phase0.AttesterSlashing.defaultValue()]); + const {chain, opPool, dbStub, eth1, aggregatedAttestationPool} = getStubs(); + opPool.getSlashings.returns([ + [ssz.phase0.AttesterSlashing.defaultValue()], + [ssz.phase0.ProposerSlashing.defaultValue()], + ]); aggregatedAttestationPool.getAttestationsForBlock.returns([generateEmptyAttestation()]); - dbStub.voluntaryExit.values.resolves([generateEmptySignedVoluntaryExit()]); + opPool.getVoluntaryExits.returns([generateEmptySignedVoluntaryExit()]); dbStub.depositDataRoot.getTreeBacked.resolves(ssz.phase0.DepositDataRootList.defaultTreeBacked()); const result = await assembleBody( - {chain, config, db: dbStub, eth1}, + {chain, config, eth1}, generateCachedState(), Buffer.alloc(96, 0), Buffer.alloc(32, 0), @@ -58,34 +57,4 @@ describe("blockAssembly - body", function () { expect(result.proposerSlashings.length).to.be.equal(1); expect(result.deposits.length).to.be.equal(1); }); - - it("should generate block body with max respective field lengths", async function () { - const {chain, dbStub, eth1, aggregatedAttestationPool} = getStubs(); - dbStub.proposerSlashing.values.resolves( - Array.from({length: MAX_PROPOSER_SLASHINGS}, () => ssz.phase0.ProposerSlashing.defaultValue()) - ); - dbStub.attesterSlashing.values.resolves( - Array.from({length: MAX_ATTESTER_SLASHINGS}, () => ssz.phase0.AttesterSlashing.defaultValue()) - ); - aggregatedAttestationPool.getAttestationsForBlock.returns( - Array.from({length: MAX_ATTESTATIONS}, generateEmptyAttestation) - ); - dbStub.voluntaryExit.values.resolves(Array.from({length: MAX_VOLUNTARY_EXITS}, generateEmptySignedVoluntaryExit)); - - const result = await assembleBody( - {chain, config, db: dbStub, eth1}, - generateCachedState(), - Buffer.alloc(96, 0), - Buffer.alloc(32, 0), - 1, - {parentSlot: 0, parentBlockRoot: Buffer.alloc(32, 0)} - ); - expect(result).to.not.be.null; - expect(result.randaoReveal.length).to.be.equal(96); - expect(result.attestations.length).to.be.equal(MAX_ATTESTATIONS); - expect(result.attesterSlashings.length).to.be.equal(MAX_ATTESTER_SLASHINGS); - expect(result.voluntaryExits.length).to.be.equal(MAX_VOLUNTARY_EXITS); - expect(result.proposerSlashings.length).to.be.equal(MAX_PROPOSER_SLASHINGS); - expect(result.deposits.length).to.be.equal(1); - }); }); diff --git a/packages/lodestar/test/unit/chain/opPools/syncCommitteeContribution.test.ts b/packages/lodestar/test/unit/chain/opPools/syncCommitteeContribution.test.ts index 6c963c24863c..ed1bf9a78cba 100644 --- a/packages/lodestar/test/unit/chain/opPools/syncCommitteeContribution.test.ts +++ b/packages/lodestar/test/unit/chain/opPools/syncCommitteeContribution.test.ts @@ -10,7 +10,7 @@ import { replaceIfBetter, SyncContributionAndProofPool, SyncContributionFast, -} from "../../../../src/chain/opPools"; +} from "../../../../src/chain/opPools/syncContributionAndProofPool"; import {generateContributionAndProof, generateEmptyContribution} from "../../../utils/contributionAndProof"; import {InsertOutcome} from "../../../../src/chain/opPools/types"; import bls, {SecretKey, Signature} from "@chainsafe/bls"; diff --git a/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts b/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts index 401311f73279..5c39b5036958 100644 --- a/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts +++ b/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts @@ -1,36 +1,34 @@ -import sinon from "sinon"; +import sinon, {SinonStubbedInstance} from "sinon"; import {phase0} from "@chainsafe/lodestar-beacon-state-transition"; import {ForkChoice} from "@chainsafe/lodestar-fork-choice"; import {ssz} from "@chainsafe/lodestar-types"; import {BeaconChain} from "../../../../src/chain"; -import {StubbedBeaconDb, StubbedChain} from "../../../utils/stub"; +import {StubbedChain} from "../../../utils/stub"; import {generateCachedState} from "../../../utils/state"; import {validateGossipAttesterSlashing} from "../../../../src/chain/validation/attesterSlashing"; import {AttesterSlashingErrorCode} from "../../../../src/chain/errors/attesterSlashingError"; +import {OpPool} from "../../../../src/chain/opPools"; import {expectRejectedWithLodestarError} from "../../../utils/errors"; import {List} from "@chainsafe/ssz"; describe("GossipMessageValidator", () => { const sandbox = sinon.createSandbox(); - let dbStub: StubbedBeaconDb, chainStub: StubbedChain; + let chainStub: StubbedChain; + let opPool: OpPool & SinonStubbedInstance; before(() => { chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain; chainStub.forkChoice = sandbox.createStubInstance(ForkChoice); chainStub.bls = {verifySignatureSets: async () => true}; - dbStub = new StubbedBeaconDb(sandbox); - dbStub.attesterSlashing.hasAll.resolves(false); + opPool = sandbox.createStubInstance(OpPool) as OpPool & SinonStubbedInstance; + (chainStub as {opPool: OpPool}).opPool = opPool; const state = generateCachedState(); chainStub.getHeadState.returns(state); }); - afterEach(() => { - dbStub.attesterSlashing.hasAll.resolves(false); - }); - after(() => { sandbox.restore(); }); @@ -38,10 +36,10 @@ describe("GossipMessageValidator", () => { describe("validate attester slashing", () => { it("should return invalid attester slashing - already exisits", async () => { const attesterSlashing = ssz.phase0.AttesterSlashing.defaultValue(); - dbStub.attesterSlashing.hasAll.resolves(true); + opPool.hasSeenAttesterSlashing.returns(true); await expectRejectedWithLodestarError( - validateGossipAttesterSlashing(chainStub, dbStub, attesterSlashing), + validateGossipAttesterSlashing(chainStub, attesterSlashing), AttesterSlashingErrorCode.ALREADY_EXISTS ); }); @@ -50,7 +48,7 @@ describe("GossipMessageValidator", () => { const attesterSlashing = ssz.phase0.AttesterSlashing.defaultValue(); await expectRejectedWithLodestarError( - validateGossipAttesterSlashing(chainStub, dbStub, attesterSlashing), + validateGossipAttesterSlashing(chainStub, attesterSlashing), AttesterSlashingErrorCode.INVALID ); }); @@ -70,7 +68,7 @@ describe("GossipMessageValidator", () => { }, }; - await validateGossipAttesterSlashing(chainStub, dbStub, attesterSlashing); + await validateGossipAttesterSlashing(chainStub, attesterSlashing); }); }); }); diff --git a/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts b/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts index cc3eccc2fbc0..239786c3a2c5 100644 --- a/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts +++ b/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts @@ -1,44 +1,43 @@ -import sinon from "sinon"; +import sinon, {SinonStubbedInstance} from "sinon"; import {phase0} from "@chainsafe/lodestar-beacon-state-transition"; import {ForkChoice} from "@chainsafe/lodestar-fork-choice"; import {ssz} from "@chainsafe/lodestar-types"; import {BeaconChain} from "../../../../src/chain"; -import {StubbedBeaconDb, StubbedChain} from "../../../utils/stub"; +import {StubbedChain} from "../../../utils/stub"; import {generateCachedState} from "../../../utils/state"; import {ProposerSlashingErrorCode} from "../../../../src/chain/errors/proposerSlashingError"; import {validateGossipProposerSlashing} from "../../../../src/chain/validation/proposerSlashing"; +import {OpPool} from "../../../../src/chain/opPools"; import {expectRejectedWithLodestarError} from "../../../utils/errors"; describe("validate proposer slashing", () => { const sandbox = sinon.createSandbox(); - let dbStub: StubbedBeaconDb, chainStub: StubbedChain; + let chainStub: StubbedChain; + let opPool: OpPool & SinonStubbedInstance; before(() => { chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain; chainStub.forkChoice = sandbox.createStubInstance(ForkChoice); chainStub.bls = {verifySignatureSets: async () => true}; - dbStub = new StubbedBeaconDb(sandbox); + opPool = sandbox.createStubInstance(OpPool) as OpPool & SinonStubbedInstance; + (chainStub as {opPool: OpPool}).opPool = opPool; const state = generateCachedState(); chainStub.getHeadState.returns(state); }); - afterEach(() => { - dbStub.proposerSlashing.has.resolves(false); - }); - after(() => { sandbox.restore(); }); it("should return invalid proposer slashing - existing", async () => { const proposerSlashing = ssz.phase0.ProposerSlashing.defaultValue(); - dbStub.proposerSlashing.has.resolves(true); + opPool.hasSeenProposerSlashing.returns(true); await expectRejectedWithLodestarError( - validateGossipProposerSlashing(chainStub, dbStub, proposerSlashing), + validateGossipProposerSlashing(chainStub, proposerSlashing), ProposerSlashingErrorCode.ALREADY_EXISTS ); }); @@ -50,7 +49,7 @@ describe("validate proposer slashing", () => { proposerSlashing.signedHeader2.message.slot = 0; await expectRejectedWithLodestarError( - validateGossipProposerSlashing(chainStub, dbStub, proposerSlashing), + validateGossipProposerSlashing(chainStub, proposerSlashing), ProposerSlashingErrorCode.INVALID ); }); @@ -66,6 +65,6 @@ describe("validate proposer slashing", () => { signedHeader2: signedHeader2, }; - await validateGossipProposerSlashing(chainStub, dbStub, proposerSlashing); + await validateGossipProposerSlashing(chainStub, proposerSlashing); }); }); diff --git a/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts b/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts index 6743cb9a1431..384e90681cd5 100644 --- a/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts +++ b/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts @@ -1,4 +1,4 @@ -import sinon from "sinon"; +import sinon, {SinonStubbedInstance} from "sinon"; import {config} from "@chainsafe/lodestar-config/default"; import { @@ -13,10 +13,11 @@ import {ForkChoice} from "@chainsafe/lodestar-fork-choice"; import {allForks, ssz} from "@chainsafe/lodestar-types"; import {BeaconChain} from "../../../../src/chain"; -import {StubbedBeaconDb, StubbedChain} from "../../../utils/stub"; +import {StubbedChain} from "../../../utils/stub"; import {generateState} from "../../../utils/state"; import {validateGossipVoluntaryExit} from "../../../../src/chain/validation/voluntaryExit"; import {VoluntaryExitErrorCode} from "../../../../src/chain/errors/voluntaryExitError"; +import {OpPool} from "../../../../src/chain/opPools"; import {expectRejectedWithLodestarError} from "../../../utils/errors"; import {DOMAIN_VOLUNTARY_EXIT, FAR_FUTURE_EPOCH, SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params"; import {PointFormat, SecretKey} from "@chainsafe/bls"; @@ -24,10 +25,10 @@ import {createIBeaconConfig} from "@chainsafe/lodestar-config"; describe("validate voluntary exit", () => { const sandbox = sinon.createSandbox(); - let dbStub: StubbedBeaconDb; let chainStub: StubbedChain; let state: CachedBeaconState; let signedVoluntaryExit: phase0.SignedVoluntaryExit; + let opPool: OpPool & SinonStubbedInstance; before(() => { const sk = SecretKey.fromKeygen(); @@ -68,11 +69,11 @@ describe("validate voluntary exit", () => { beforeEach(() => { chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain; chainStub.forkChoice = sandbox.createStubInstance(ForkChoice); + opPool = sandbox.createStubInstance(OpPool) as OpPool & SinonStubbedInstance; + (chainStub as {opPool: OpPool}).opPool = opPool; chainStub.getHeadStateAtCurrentEpoch.resolves(state); // TODO: Use actual BLS verification chainStub.bls = {verifySignatureSets: async () => true}; - dbStub = new StubbedBeaconDb(sandbox); - dbStub.voluntaryExit.has.resolves(false); }); afterEach(() => { @@ -86,10 +87,10 @@ describe("validate voluntary exit", () => { }; // Return SignedVoluntaryExit known - dbStub.voluntaryExit.has.resolves(true); + opPool.hasSeenProposerSlashing.returns(true); await expectRejectedWithLodestarError( - validateGossipVoluntaryExit(chainStub, dbStub, signedVoluntaryExitInvalidSig), + validateGossipVoluntaryExit(chainStub, signedVoluntaryExitInvalidSig), VoluntaryExitErrorCode.ALREADY_EXISTS ); }); @@ -105,12 +106,12 @@ describe("validate voluntary exit", () => { }; await expectRejectedWithLodestarError( - validateGossipVoluntaryExit(chainStub, dbStub, signedVoluntaryExitInvalid), + validateGossipVoluntaryExit(chainStub, signedVoluntaryExitInvalid), VoluntaryExitErrorCode.INVALID ); }); it("should return valid Voluntary Exit", async () => { - await validateGossipVoluntaryExit(chainStub, dbStub, signedVoluntaryExit); + await validateGossipVoluntaryExit(chainStub, signedVoluntaryExit); }); }); diff --git a/packages/lodestar/test/unit/util/objects.test.ts b/packages/lodestar/test/unit/util/objects.test.ts index ff91457b6521..5f00ac4819df 100644 --- a/packages/lodestar/test/unit/util/objects.test.ts +++ b/packages/lodestar/test/unit/util/objects.test.ts @@ -1,6 +1,6 @@ import {expect} from "chai"; import {ssz, Uint64} from "@chainsafe/lodestar-types"; -import {arrayIntersection, mostFrequent, sszEqualPredicate} from "../../../src/util/objects"; +import {mostFrequent} from "../../../src/util/objects"; describe("Objects helper", () => { it("return most frequent objects", () => { @@ -18,11 +18,4 @@ describe("Objects helper", () => { const result = mostFrequent(ssz.Uint64, array); expect(result).to.be.deep.equal([obj1, obj3]); }); - - it("should return array intersection", function () { - const array1 = [2, 5, 7, 8]; - const array2 = [1, 5, 7, 9]; - const result = arrayIntersection(array1, array2, sszEqualPredicate(ssz.Number64)); - expect(result).to.be.deep.equal([5, 7]); - }); }); diff --git a/packages/lodestar/test/utils/mocks/chain/chain.ts b/packages/lodestar/test/utils/mocks/chain/chain.ts index 3120af205972..f8eb05c8f32f 100644 --- a/packages/lodestar/test/utils/mocks/chain/chain.ts +++ b/packages/lodestar/test/utils/mocks/chain/chain.ts @@ -34,6 +34,7 @@ import { SyncCommitteeMessagePool, SyncContributionAndProofPool, AggregatedAttestationPool, + OpPool, } from "../../../../src/chain/opPools"; import {LightClientIniter} from "../../../../src/chain/lightClient"; @@ -69,6 +70,7 @@ export class MockBeaconChain implements IBeaconChain { readonly aggregatedAttestationPool = new AggregatedAttestationPool(); readonly syncCommitteeMessagePool = new SyncCommitteeMessagePool(); readonly syncContributionAndProofPool = new SyncContributionAndProofPool(); + readonly opPool = new OpPool(); // Gossip seen cache readonly seenAttesters = new SeenAttesters(); @@ -177,6 +179,7 @@ export class MockBeaconChain implements IBeaconChain { } async persistToDisk(): Promise {} + async loadFromDisk(): Promise {} getStatus(): phase0.Status { return { From 62279f7fda5967481bb0de2bd1e32f9522ee0d9b Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:09:24 +0200 Subject: [PATCH 3/3] Fix unit tests --- .../lodestar/test/unit/api/impl/beacon/pool/pool.test.ts | 7 ++++++- .../test/unit/chain/validation/attesterSlashing.test.ts | 2 +- .../test/unit/chain/validation/proposerSlashing.test.ts | 2 +- .../test/unit/chain/validation/voluntaryExit.test.ts | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts b/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts index a9113e59a168..fdef6b926ff1 100644 --- a/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts +++ b/packages/lodestar/test/unit/api/impl/beacon/pool/pool.test.ts @@ -20,12 +20,13 @@ import {generateEmptySignedBlockHeader} from "../../../../../utils/block"; import {setupApiImplTestServer} from "../../index.test"; import {SinonStubFn} from "../../../../../utils/types"; import {testLogger} from "../../../../../utils/logger"; -import {AggregatedAttestationPool} from "../../../../../../src/chain/opPools"; +import {AggregatedAttestationPool, OpPool} from "../../../../../../src/chain/opPools"; describe("beacon pool api impl", function () { const logger = testLogger(); let poolApi: ReturnType; let chainStub: SinonStubbedInstance; + let opPool: SinonStubbedInstance; let networkStub: SinonStubbedInstance; let gossipStub: SinonStubbedInstance; let validateGossipAttesterSlashing: SinonStubFn; @@ -40,6 +41,10 @@ describe("beacon pool api impl", function () { ((chainStub as unknown) as { aggregatedAttestationPool: SinonStubbedInstance; }).aggregatedAttestationPool = aggregatedAttestationPool; + opPool = sinon.createStubInstance(OpPool); + ((chainStub as unknown) as { + opPool: SinonStubbedInstance; + }).opPool = opPool; gossipStub = sinon.createStubInstance(Eth2Gossipsub); gossipStub.publishAttesterSlashing = sinon.stub(); gossipStub.publishProposerSlashing = sinon.stub(); diff --git a/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts b/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts index 5c39b5036958..321cc301864e 100644 --- a/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts +++ b/packages/lodestar/test/unit/chain/validation/attesterSlashing.test.ts @@ -18,7 +18,7 @@ describe("GossipMessageValidator", () => { let chainStub: StubbedChain; let opPool: OpPool & SinonStubbedInstance; - before(() => { + beforeEach(() => { chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain; chainStub.forkChoice = sandbox.createStubInstance(ForkChoice); chainStub.bls = {verifySignatureSets: async () => true}; diff --git a/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts b/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts index 239786c3a2c5..50521a55d2f1 100644 --- a/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts +++ b/packages/lodestar/test/unit/chain/validation/proposerSlashing.test.ts @@ -17,7 +17,7 @@ describe("validate proposer slashing", () => { let chainStub: StubbedChain; let opPool: OpPool & SinonStubbedInstance; - before(() => { + beforeEach(() => { chainStub = sandbox.createStubInstance(BeaconChain) as StubbedChain; chainStub.forkChoice = sandbox.createStubInstance(ForkChoice); chainStub.bls = {verifySignatureSets: async () => true}; diff --git a/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts b/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts index 384e90681cd5..8c27a8f7ba7f 100644 --- a/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts +++ b/packages/lodestar/test/unit/chain/validation/voluntaryExit.test.ts @@ -87,7 +87,7 @@ describe("validate voluntary exit", () => { }; // Return SignedVoluntaryExit known - opPool.hasSeenProposerSlashing.returns(true); + opPool.hasSeenVoluntaryExit.returns(true); await expectRejectedWithLodestarError( validateGossipVoluntaryExit(chainStub, signedVoluntaryExitInvalidSig),