From 2f5b4ada4452fc6952be0c05509d72f20fba2731 Mon Sep 17 00:00:00 2001 From: Tuyen Date: Fri, 13 Dec 2019 08:30:58 +0700 Subject: [PATCH 1/6] Handle block forks --- .../api/impl/validator/produceAttestation.ts | 9 ++-- packages/lodestar/src/chain/chain.ts | 42 +++++++------------ .../src/chain/forkChoice/interface.ts | 1 + .../chain/forkChoice/statefulDag/lmdGhost.ts | 7 ++++ packages/lodestar/src/chain/interface.ts | 2 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/packages/lodestar/src/api/impl/validator/produceAttestation.ts b/packages/lodestar/src/api/impl/validator/produceAttestation.ts index c9cb3c36e59a..e8c4a5cc2387 100644 --- a/packages/lodestar/src/api/impl/validator/produceAttestation.ts +++ b/packages/lodestar/src/api/impl/validator/produceAttestation.ts @@ -1,9 +1,9 @@ import {assembleAttestation} from "../../../chain/factory/attestation"; -import {Attestation, BeaconState, BLSPubkey, CommitteeIndex, Slot} from "@chainsafe/eth2.0-types"; +import {Attestation, BLSPubkey, CommitteeIndex, Slot} from "@chainsafe/eth2.0-types"; import {IBeaconDb} from "../../../db/api"; import {IBeaconChain} from "../../../chain"; import {IBeaconConfig} from "@chainsafe/eth2.0-config"; -import {clone} from "@chainsafe/ssz"; +import { processSlots } from "@chainsafe/eth2.0-state-transition"; export async function produceAttestation( {config, db, chain}: {config: IBeaconConfig; db: IBeaconDb; chain: IBeaconChain}, @@ -12,11 +12,12 @@ export async function produceAttestation( slot: Slot ): Promise { try { - const [headState, headBlock, validatorIndex] = await Promise.all([ - clone(chain.latestState, config.types.BeaconState) as BeaconState, + const [headBlock, validatorIndex] = await Promise.all([ db.block.get(chain.forkChoice.head()), db.getValidatorIndex(validatorPubKey) ]); + const headState = await db.state.get(headBlock.stateRoot); + await processSlots(config, headState, slot); return await assembleAttestation({config, db}, headState, headBlock, validatorIndex, index, slot); } catch (e) { throw e; diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 9bc9ca0a9f24..12d81110f2e9 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -118,10 +118,9 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) public async receiveAttestation(attestation: Attestation): Promise { const attestationHash = hashTreeRoot(attestation, this.config.types.Attestation); this.logger.info(`Received attestation ${attestationHash.toString("hex")}`); - const latestState = this.latestState; try { const attestationSlot: Slot = attestation.data.slot; - if(attestationSlot + this.config.params.SLOTS_PER_EPOCH < latestState.slot) { + if(attestationSlot + this.config.params.SLOTS_PER_EPOCH < this.latestState.slot) { this.logger.verbose(`Attestation ${attestationHash.toString("hex")} is too old. Ignored.`); return; } @@ -129,7 +128,7 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return; } this.attestationProcessingQueue.push(async () => { - return this.processAttestation(latestState, attestation, attestationHash); + return this.processAttestation(attestation, attestationHash); }); } @@ -142,6 +141,8 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) if(!await this.db.block.has(block.parentRoot)) { this.emit("unknownBlockRoot", block.parentRoot); + this.blockProcessingQueue.add(block); + return; } if(block.slot <= this.latestState.slot) { @@ -152,25 +153,11 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return; } - if(block.slot > this.latestState.slot) { - //either block came too early or we are suppose to skip some slots - const latestBlock = await this.db.block.getChainHead(); - if(!block.parentRoot.equals(signingRoot(latestBlock, this.config.types.BeaconBlock))){ - //block processed too early - this.logger.warn(`Block ${blockHash.toString("hex")} tried to be processed too early. Requeue...`); - //wait a bit to give new block a chance - await sleep(500); - // add to priority queue - this.blockProcessingQueue.add(block); - return; - } - } await this.processBlock(block, blockHash); const nextBlockInQueue = this.blockProcessingQueue.peek(); while (nextBlockInQueue) { - const latestBlock = await this.db.block.getChainHead(); - if (nextBlockInQueue.parentRoot.equals(signingRoot(latestBlock, this.config.types.BeaconBlock))) { + if (await this.db.block.has(nextBlockInQueue.parentRoot)) { await this.processBlock(nextBlockInQueue, signingRoot(nextBlockInQueue, this.config.types.BeaconBlock)); this.blockProcessingQueue.poll(); } else{ @@ -243,16 +230,19 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return Math.floor(Date.now() / 1000) >= stateSlotTime; } - private processAttestation = async (latestState: BeaconState, attestation: Attestation, attestationHash: Hash) => { - const currentSlot = getCurrentSlot(this.config, latestState.genesisTime); + private processAttestation = async (attestation: Attestation, attestationHash: Hash) => { + const justifiedCheckpoint = this.forkChoice.getJustified(); + const justifiedBlock = await this.db.block.get(justifiedCheckpoint.root); + const checkpointState = await this.db.state.get(justifiedBlock.stateRoot); + const currentSlot = getCurrentSlot(this.config, checkpointState.genesisTime); const currentEpoch = computeEpochAtSlot(this.config, currentSlot); const previousEpoch = currentEpoch > GENESIS_EPOCH ? currentEpoch - 1 : GENESIS_EPOCH; const epoch = attestation.data.target.epoch; assert([currentEpoch, previousEpoch].includes(epoch)); const validators = getAttestingIndices( - this.config, latestState, attestation.data, attestation.aggregationBits); - const balances = validators.map((index) => latestState.balances[index]); + this.config, checkpointState, attestation.data, attestation.aggregationBits); + const balances = validators.map((index) => checkpointState.balances[index]); for (let i = 0; i < validators.length; i++) { this.forkChoice.addAttestation(attestation.data.beaconBlockRoot, validators[i], balances[i]); } @@ -261,12 +251,12 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) }; private processBlock = async (block: BeaconBlock, blockHash: Hash) => { - - const isValidBlock = await this.isValidBlock(this.latestState, block); + const parentBlock = await this.db.block.get(block.parentRoot); + const pre = await this.db.state.get(parentBlock.stateRoot); + const isValidBlock = await this.isValidBlock(pre, block); assert(isValidBlock); this.logger.info(`0x${blockHash.toString("hex")} is valid, running state transition...`); - const pre = this.latestState; // process current slot const post = await this.runStateTransition(block, pre); @@ -338,7 +328,7 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) await this.db.setChainHeadRoots(blockRoot, block.stateRoot); this.forkChoice.addBlock(block.slot, blockRoot, block.parentRoot, newState.currentJustifiedCheckpoint, newState.finalizedCheckpoint); - // await this.applyForkChoiceRule(); + await this.applyForkChoiceRule(); await this.updateDepositMerkleTree(newState); // update metrics this.metrics.currentSlot.set(block.slot); diff --git a/packages/lodestar/src/chain/forkChoice/interface.ts b/packages/lodestar/src/chain/forkChoice/interface.ts index fda90d8c8784..d3cb768155ce 100644 --- a/packages/lodestar/src/chain/forkChoice/interface.ts +++ b/packages/lodestar/src/chain/forkChoice/interface.ts @@ -12,4 +12,5 @@ export interface ILMDGHOST { finalizedCheckpoint: Checkpoint): void; addAttestation(blockRootBuf: Hash, attester: ValidatorIndex, weight: Gwei): void; head(): Hash; + getJustified(): Checkpoint; } diff --git a/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts b/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts index a3b85ab92b48..45bc4061e43a 100644 --- a/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts +++ b/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts @@ -299,6 +299,13 @@ export class StatefulDagLMDGHOST implements ILMDGHOST { return true; } + public getJustified(): Checkpoint { + if (!this.justified) { + return null; + } + return {root: Buffer.from(this.justified.node.blockRoot, "hex"), epoch: this.justified.epoch}; + } + private setFinalized(checkpoint: Checkpoint): void { this.synced = false; const rootHex = checkpoint.root.toString("hex"); diff --git a/packages/lodestar/src/chain/interface.ts b/packages/lodestar/src/chain/interface.ts index 5bc31365ad2e..587a41f9f602 100644 --- a/packages/lodestar/src/chain/interface.ts +++ b/packages/lodestar/src/chain/interface.ts @@ -38,7 +38,7 @@ export interface IBeaconChain extends ChainEventEmitter { /** * Add attestation to the fork-choice rule */ - receiveAttestation(attestation: Attestation): Promise; + receiveAttestation(attestation: Attestation, beaconState?: BeaconState): Promise; /** * Pre-process and run the per slot state transition function From 20ef9339054b4d8ec5b348f4008a5fdc4b64c694 Mon Sep 17 00:00:00 2001 From: Tuyen Date: Fri, 13 Dec 2019 16:35:41 +0700 Subject: [PATCH 2/6] Fix linting --- packages/lodestar/src/api/impl/validator/produceAttestation.ts | 2 +- packages/lodestar/src/chain/chain.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/lodestar/src/api/impl/validator/produceAttestation.ts b/packages/lodestar/src/api/impl/validator/produceAttestation.ts index e8c4a5cc2387..d95f65bb3c07 100644 --- a/packages/lodestar/src/api/impl/validator/produceAttestation.ts +++ b/packages/lodestar/src/api/impl/validator/produceAttestation.ts @@ -3,7 +3,7 @@ import {Attestation, BLSPubkey, CommitteeIndex, Slot} from "@chainsafe/eth2.0-ty import {IBeaconDb} from "../../../db/api"; import {IBeaconChain} from "../../../chain"; import {IBeaconConfig} from "@chainsafe/eth2.0-config"; -import { processSlots } from "@chainsafe/eth2.0-state-transition"; +import {processSlots} from "@chainsafe/eth2.0-state-transition"; export async function produceAttestation( {config, db, chain}: {config: IBeaconConfig; db: IBeaconDb; chain: IBeaconChain}, diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 12d81110f2e9..72a187519b6a 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -30,7 +30,6 @@ import {IChainOptions} from "./options"; import {OpPool} from "../opPool"; import {Block} from "ethers/providers"; import fs from "fs"; -import {sleep} from "../util/sleep"; import {AsyncQueue, queue} from "async"; import FastPriorityQueue from "fastpriorityqueue"; From f10adfa8fcf88d664bebdbc758bcb3bf8e09aaf2 Mon Sep 17 00:00:00 2001 From: Tuyen Date: Fri, 13 Dec 2019 16:42:23 +0700 Subject: [PATCH 3/6] No need to change chain interface --- packages/lodestar/src/chain/interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lodestar/src/chain/interface.ts b/packages/lodestar/src/chain/interface.ts index 587a41f9f602..5bc31365ad2e 100644 --- a/packages/lodestar/src/chain/interface.ts +++ b/packages/lodestar/src/chain/interface.ts @@ -38,7 +38,7 @@ export interface IBeaconChain extends ChainEventEmitter { /** * Add attestation to the fork-choice rule */ - receiveAttestation(attestation: Attestation, beaconState?: BeaconState): Promise; + receiveAttestation(attestation: Attestation): Promise; /** * Pre-process and run the per slot state transition function From 66fdce7e7a09964265f301c26ca5eea8cf3fbade Mon Sep 17 00:00:00 2001 From: Tuyen Date: Sat, 14 Dec 2019 16:24:58 +0700 Subject: [PATCH 4/6] Address PR comments --- packages/lodestar/src/chain/chain.ts | 4 +++- packages/lodestar/src/chain/factory/attestation/index.ts | 4 ---- .../test/unit/chain/factory/attestation/index.test.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 72a187519b6a..9973a27adaa6 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -119,7 +119,9 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) this.logger.info(`Received attestation ${attestationHash.toString("hex")}`); try { const attestationSlot: Slot = attestation.data.slot; - if(attestationSlot + this.config.params.SLOTS_PER_EPOCH < this.latestState.slot) { + const headBlock = await this.db.block.get(this.forkChoice.head()); + const state = await this.db.state.get(headBlock.stateRoot); + if(attestationSlot + this.config.params.SLOTS_PER_EPOCH < state.slot) { this.logger.verbose(`Attestation ${attestationHash.toString("hex")} is too old. Ignored.`); return; } diff --git a/packages/lodestar/src/chain/factory/attestation/index.ts b/packages/lodestar/src/chain/factory/attestation/index.ts index de1455ee55dd..f07f85accd23 100644 --- a/packages/lodestar/src/chain/factory/attestation/index.ts +++ b/packages/lodestar/src/chain/factory/attestation/index.ts @@ -14,10 +14,6 @@ export async function assembleAttestation( validatorIndex: ValidatorIndex, index: CommitteeIndex, slot: Slot): Promise { - while(state.slot < slot) { - state.slot++; - } - const committee = getBeaconCommittee(config, state, computeEpochAtSlot(config, slot), index); const aggregationBits = getAggregationBits(committee, validatorIndex); try { diff --git a/packages/lodestar/test/unit/chain/factory/attestation/index.test.ts b/packages/lodestar/test/unit/chain/factory/attestation/index.test.ts index b82eff6257c0..e6127a4e5d55 100644 --- a/packages/lodestar/test/unit/chain/factory/attestation/index.test.ts +++ b/packages/lodestar/test/unit/chain/factory/attestation/index.test.ts @@ -33,7 +33,7 @@ describe("assemble attestation", function () { //TODO: try to test if validator bit is correctly set expect(result).to.not.be.null; expect(result.data).to.be.equal(attestationData); - expect(state.slot).to.be.equal(2); + expect(state.slot).to.be.equal(1); expect(assembleAttestationDataStub.calledOnceWith(dbStub, state, generateEmptyBlock(), 2)); }); From dee73c965db509b4f102f7cf68b0579e3cf70a85 Mon Sep 17 00:00:00 2001 From: Tuyen Date: Thu, 19 Dec 2019 08:51:02 +0700 Subject: [PATCH 5/6] Correct the check when receiving a block, remove redundant setChainHeadRoots --- packages/lodestar/src/chain/chain.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 9973a27adaa6..51606196fe50 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -146,7 +146,14 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return; } - if(block.slot <= this.latestState.slot) { + if(await this.db.block.has(blockHash)) { + this.logger.warn(`Block ${blockHash} existed already, no need to process it.`) + return; + } + + const headBlockRoot = this.forkChoice.head(); + const headBlock = await this.db.block.get(headBlockRoot); + if(block.slot <= headBlock.slot) { this.logger.warn( `Block ${blockHash.toString("hex")} is in past. ` + "Probably fork choice/double propose/processed block. Ignored for now." @@ -154,7 +161,6 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return; } - await this.processBlock(block, blockHash); const nextBlockInQueue = this.blockProcessingQueue.peek(); while (nextBlockInQueue) { @@ -326,7 +332,6 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) this.db.block.set(blockRoot, block), this.db.state.set(block.stateRoot, newState), ]); - await this.db.setChainHeadRoots(blockRoot, block.stateRoot); this.forkChoice.addBlock(block.slot, blockRoot, block.parentRoot, newState.currentJustifiedCheckpoint, newState.finalizedCheckpoint); await this.applyForkChoiceRule(); From 1af3cc318c587bf9594a00553101fdc3b8dcaa85 Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 19 Dec 2019 09:36:42 -0600 Subject: [PATCH 6/6] Update old block check --- packages/lodestar/src/chain/chain.ts | 10 +++++----- packages/lodestar/src/chain/forkChoice/interface.ts | 1 + .../src/chain/forkChoice/statefulDag/lmdGhost.ts | 7 +++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/lodestar/src/chain/chain.ts b/packages/lodestar/src/chain/chain.ts index 51606196fe50..15b737b6f68a 100644 --- a/packages/lodestar/src/chain/chain.ts +++ b/packages/lodestar/src/chain/chain.ts @@ -19,6 +19,7 @@ import {getEmptyBlock, initializeBeaconStateFromEth1, isValidGenesisState} from import {processSlots, stateTransition, computeEpochAtSlot, + computeStartSlotAtEpoch, getAttestingIndices, isActiveValidator ,getCurrentSlot} from "@chainsafe/eth2.0-state-transition"; @@ -151,12 +152,11 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter }) return; } - const headBlockRoot = this.forkChoice.head(); - const headBlock = await this.db.block.get(headBlockRoot); - if(block.slot <= headBlock.slot) { + const finalizedCheckpoint = this.forkChoice.getFinalized(); + if(block.slot < computeStartSlotAtEpoch(finalizedCheckpoint.epoch + 1)) { this.logger.warn( - `Block ${blockHash.toString("hex")} is in past. ` + - "Probably fork choice/double propose/processed block. Ignored for now." + `Block ${blockHash.toString("hex")} is not after ` + + `finalized checkpoint ${finalizedCheckpoint.root.toString("hex")}.` ); return; } diff --git a/packages/lodestar/src/chain/forkChoice/interface.ts b/packages/lodestar/src/chain/forkChoice/interface.ts index d3cb768155ce..ce7152f74fa9 100644 --- a/packages/lodestar/src/chain/forkChoice/interface.ts +++ b/packages/lodestar/src/chain/forkChoice/interface.ts @@ -13,4 +13,5 @@ export interface ILMDGHOST { addAttestation(blockRootBuf: Hash, attester: ValidatorIndex, weight: Gwei): void; head(): Hash; getJustified(): Checkpoint; + getFinalized(): Checkpoint; } diff --git a/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts b/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts index 45bc4061e43a..d66f5d3b1fca 100644 --- a/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts +++ b/packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts @@ -306,6 +306,13 @@ export class StatefulDagLMDGHOST implements ILMDGHOST { return {root: Buffer.from(this.justified.node.blockRoot, "hex"), epoch: this.justified.epoch}; } + public getFinalized(): Checkpoint { + if (!this.finalized) { + return null; + } + return {root: Buffer.from(this.finalized.node.blockRoot, "hex"), epoch: this.finalized.epoch}; + } + private setFinalized(checkpoint: Checkpoint): void { this.synced = false; const rootHex = checkpoint.root.toString("hex");