Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fork choice update for 0.9.1 - Spec PR 1465 #573

Merged
merged 8 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eth2.0-params/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface IBeaconParams {

// Initial values
GENESIS_SLOT: number;
SAFE_SLOTS_TO_UPDATE_JUSTIFIED: number;
GENESIS_EPOCH: number;
BLS_WITHDRAWAL_PREFIX_BYTE: Buffer;
GENESIS_FORK_VERSION: Buffer;
Expand Down
1 change: 1 addition & 0 deletions packages/eth2.0-params/src/presets/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const EFFECTIVE_BALANCE_INCREMENT = new BN(2 ** 0 * 1e9); // 1,000,000,00

// Initial values
export const GENESIS_SLOT = 0;
export const SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8;
export const GENESIS_EPOCH = 0;
export const BLS_WITHDRAWAL_PREFIX_BYTE = Buffer.alloc(1, 0);
export const GENESIS_FORK_VERSION = Buffer.alloc(4, 0);
Expand Down
1 change: 1 addition & 0 deletions packages/eth2.0-params/src/presets/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const EFFECTIVE_BALANCE_INCREMENT = new BN("1000000000"); // 1,000,000,00

// Initial values
export const GENESIS_SLOT = 0;
export const SAFE_SLOTS_TO_UPDATE_JUSTIFIED = 8;
export const GENESIS_EPOCH = 0;
export const BLS_WITHDRAWAL_PREFIX_BYTE = Buffer.alloc(1);
export const GENESIS_FORK_VERSION = Buffer.alloc(4);
Expand Down
8 changes: 0 additions & 8 deletions packages/eth2.0-state-transition/src/util/genesis.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/eth2.0-state-transition/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ export * from "./proposer";
export * from "./balance";
export * from "./validatorStatus";
export * from "./duties";
export * from "./genesis";
export * from "./slot";
14 changes: 14 additions & 0 deletions packages/eth2.0-state-transition/src/util/slot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {IBeaconConfig} from "@chainsafe/eth2.0-config";
import {number64, Slot, Epoch} from "@chainsafe/eth2.0-types";
import {intDiv} from "@chainsafe/eth2.0-utils";
import {computeStartSlotAtEpoch, computeEpochAtSlot} from ".";

export function getCurrentSlot(config: IBeaconConfig, genesisTime: number64): Slot {
const diffInSeconds = (Date.now() / 1000) - genesisTime;
return intDiv(diffInSeconds, config.params.SECONDS_PER_SLOT);
}

export function computeSlotsSinceEpochStart(config: IBeaconConfig, slot: Slot, epoch?: Epoch): number {
const computeEpoch = epoch? epoch : computeEpochAtSlot(config, slot);
return slot - computeStartSlotAtEpoch(config, computeEpoch);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {assert} from "chai";

import {config} from "@chainsafe/eth2.0-config/lib/presets/mainnet";import { computeSlotsSinceEpochStart } from "../../../../src/util";
import {Slot} from "@chainsafe/eth2.0-types";


describe("computeSlotsSinceEpochStart", () => {
const pairs = [
{test: 0, expected: 0},
{test: 5, expected: 5},
{test: 40, expected: 8},
{test: 50, expected: 18},
];

for (const pair of pairs) {
it(`Slot ${pair.test} is ${pair.expected} from current Epoch start`, () => {
const result: Slot = computeSlotsSinceEpochStart(config, pair.test);
assert.equal(result, pair.expected);
});
}

it("should compute slot correctly since a specified epoch", () => {
const epoch = 1;
const slot = 70;
const result = computeSlotsSinceEpochStart(config, slot, epoch);
// 70 - NUM_SLOT_PER_EPOCH
assert.equal(result, 38);
});
});
14 changes: 7 additions & 7 deletions packages/lodestar/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })
this.opPool = opPool;
this.logger = logger;
this.metrics = metrics;
this.forkChoice = new StatefulDagLMDGHOST();
this.forkChoice = new StatefulDagLMDGHOST(config);
this.chainId = 0; // TODO make this real
this.networkId = new BN(0); // TODO make this real
this.attestationProcessingQueue = queue(async (task: Function) => {
Expand All @@ -87,6 +87,7 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })

public async start(): Promise<void> {
const state = this.latestState || await this.db.state.getLatest();
await this.forkChoice.start(state.genesisTime);
// if state doesn't exist in the db, the chain maybe hasn't started
if(!state) {
// check every block if genesis
Expand Down Expand Up @@ -220,9 +221,9 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })
this.db.chain.setFinalizedStateRoot(stateRoot),
this.db.merkleTree.set(genesisState.eth1DepositIndex, merkleTree.toObject())
]);
this.forkChoice.addBlock(genesisBlock.slot, blockRoot, Buffer.alloc(32));
this.forkChoice.setJustified(blockRoot);
this.forkChoice.setFinalized(blockRoot);
const justifiedFinalizedCheckpoint = {root: blockRoot, epoch: computeEpochAtSlot(this.config, genesisBlock.slot)};
this.forkChoice.addBlock(genesisBlock.slot, blockRoot, Buffer.alloc(32),
justifiedFinalizedCheckpoint, justifiedFinalizedCheckpoint);
this.logger.info("Beacon chain initialized");
}

Expand Down Expand Up @@ -334,7 +335,8 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })
this.db.state.set(block.stateRoot, newState),
]);
await this.db.setChainHeadRoots(blockRoot, block.stateRoot);
this.forkChoice.addBlock(block.slot, blockRoot, block.parentRoot);
this.forkChoice.addBlock(block.slot, blockRoot, block.parentRoot, newState.currentJustifiedCheckpoint,
newState.finalizedCheckpoint);
// await this.applyForkChoiceRule();
await this.updateDepositMerkleTree(newState);
// update metrics
Expand All @@ -353,7 +355,6 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })
this.db.chain.setJustifiedStateRoot(justifiedBlock.stateRoot),
this.db.chain.setJustifiedBlockRoot(justifiedBlockRoot),
]);
this.forkChoice.setJustified(justifiedBlockRoot);
this.emit("justifiedCheckpoint", newState.currentJustifiedCheckpoint);
}
// Newly finalized epoch
Expand All @@ -365,7 +366,6 @@ export class BeaconChain extends (EventEmitter as { new(): ChainEventEmitter })
this.db.chain.setFinalizedStateRoot(finalizedBlock.stateRoot),
this.db.chain.setFinalizedBlockRoot(finalizedBlockRoot),
]);
this.forkChoice.setFinalized(finalizedBlockRoot);
this.emit("finalizedCheckpoint", newState.finalizedCheckpoint);
}
this.metrics.previousJustifiedEpoch.set(newState.previousJustifiedCheckpoint.epoch);
Expand Down
8 changes: 4 additions & 4 deletions packages/lodestar/src/chain/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* @module chain/forkChoice
*/

import {Gwei, Hash, Slot, ValidatorIndex,} from "@chainsafe/eth2.0-types";
import {Gwei, Hash, Slot, ValidatorIndex, Checkpoint,} from "@chainsafe/eth2.0-types";


export interface ILMDGHOST {
addBlock(slot: Slot, blockRootBuf: Hash, parentRootBuf: Hash): void;
start(genesisTime: number): void;
addBlock(slot: Slot, blockRootBuf: Hash, parentRootBuf: Hash, justifiedCheckpoint: Checkpoint,
finalizedCheckpoint: Checkpoint): void;
addAttestation(blockRootBuf: Hash, attester: ValidatorIndex, weight: Gwei): void;
setFinalized(blockRoot: Hash): void;
setJustified(blockRoot: Hash): void;
head(): Hash;
}
99 changes: 77 additions & 22 deletions packages/lodestar/src/chain/forkChoice/statefulDag/lmdGhost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import assert from "assert";
import BN from "bn.js";

import {Gwei, Hash, Slot, ValidatorIndex,} from "@chainsafe/eth2.0-types";
import {Gwei, Hash, Slot, ValidatorIndex, number64, Checkpoint, Epoch,} from "@chainsafe/eth2.0-types";

import {ILMDGHOST} from "../interface";

import {AttestationAggregator, Root,} from "./attestationAggregator";
import {IBeaconConfig} from "@chainsafe/eth2.0-config";
import {computeSlotsSinceEpochStart, getCurrentSlot} from "@chainsafe/eth2.0-state-transition";


/**
Expand All @@ -18,7 +20,7 @@ import {AttestationAggregator, Root,} from "./attestationAggregator";
*/
class Node {
// block data
public slot: number;
public slot: Slot;
public blockRoot: Root;

/**
Expand Down Expand Up @@ -148,6 +150,9 @@ class Node {
* See https://github.com/protolambda/lmd-ghost#state-ful-dag
*/
export class StatefulDagLMDGHOST implements ILMDGHOST {
private readonly config: IBeaconConfig;
private genesisTime: number64;

/**
* Aggregated attestations
*/
Expand All @@ -161,24 +166,30 @@ export class StatefulDagLMDGHOST implements ILMDGHOST {
/**
* Last finalized block
*/
private finalized: Node|null;
private finalized: {node: Node; epoch: Epoch} | null;

/**
* Last justified block
*/
private justified: Node|null;
private justified: {node: Node; epoch: Epoch} | null;
private synced: boolean;

public constructor() {
public constructor(config: IBeaconConfig) {
this.aggregator =
new AttestationAggregator((hex: string) => this.nodes[hex] ? this.nodes[hex].slot : null);
this.nodes = {};
this.finalized = null;
this.justified = null;
this.synced = true;
this.config = config;
}

public start(genesisTime: number): void {
this.genesisTime = genesisTime;
}

public addBlock(slot: Slot, blockRootBuf: Hash, parentRootBuf: Hash): void {
public addBlock(slot: Slot, blockRootBuf: Hash, parentRootBuf: Hash,
justifiedCheckpoint?: Checkpoint, finalizedCheckpoint?: Checkpoint): void {
this.synced = false;
const blockRoot = blockRootBuf.toString("hex");
const parentRoot = parentRootBuf.toString("hex");
Expand All @@ -196,6 +207,12 @@ export class StatefulDagLMDGHOST implements ILMDGHOST {
if (this.nodes[parentRoot]) {
this.nodes[parentRoot].addChild(node);
}
if (justifiedCheckpoint && (!this.justified || justifiedCheckpoint.epoch > this.justified.epoch)) {
this.setJustified(justifiedCheckpoint);
}
if (finalizedCheckpoint && (!this.finalized || finalizedCheckpoint.epoch > this.finalized.epoch)) {
this.setFinalized(finalizedCheckpoint);
}
}

public addAttestation(blockRootBuf: Hash, attester: ValidatorIndex, weight: Gwei): void {
Expand All @@ -207,19 +224,6 @@ export class StatefulDagLMDGHOST implements ILMDGHOST {
});
}

public setFinalized(blockRoot: Hash): void {
this.synced = false;
const rootHex = blockRoot.toString("hex");
this.finalized = this.nodes[rootHex];
this.prune();
this.aggregator.prune();
}

public setJustified(blockRoot: Hash): void {
const rootHex = blockRoot.toString("hex");
this.justified = this.nodes[rootHex];
}

public syncChanges(): void {
Object.values(this.aggregator.latestAggregates).forEach((agg) => {
if (!agg.prevWeight.eq(agg.weight)) {
Expand All @@ -239,17 +243,68 @@ export class StatefulDagLMDGHOST implements ILMDGHOST {
this.syncChanges();
}
//@ts-ignore
return Buffer.from(this.justified.bestTarget.blockRoot, "hex");
return Buffer.from(this.justified.node.bestTarget.blockRoot, "hex");
}

// To address the bouncing attack, only update conflicting justified
// checkpoints in the fork choice if in the early slots of the epoch.
public shouldUpdateJustifiedCheckpoint(blockRoot: Hash): boolean {
if(!this.justified) {
return true;
}
if(computeSlotsSinceEpochStart(this.config, getCurrentSlot(this.config, this.genesisTime)) <
this.config.params.SAFE_SLOTS_TO_UPDATE_JUSTIFIED) {
return true;
}
const newJustifiedBlock = this.nodes[blockRoot.toString("hex")];
if (newJustifiedBlock.slot <= this.justified.node.slot) {
return false;
}
if (this.getAncestor(blockRoot.toString("hex"), this.justified.node.slot) !== this.justified.node.blockRoot) {
return false;
}

return true;
}

private setFinalized(checkpoint: Checkpoint): void {
this.synced = false;
const rootHex = checkpoint.root.toString("hex");
this.finalized = {node: this.nodes[rootHex], epoch: checkpoint.epoch};
this.prune();
this.aggregator.prune();
}

private setJustified(checkpoint: Checkpoint): void {
const {root: blockRoot, epoch} = checkpoint;
if (this.shouldUpdateJustifiedCheckpoint(blockRoot)) {
const rootHex = blockRoot.toString("hex");
this.justified = {node: this.nodes[rootHex], epoch};
}
}

private getAncestor(root: Root, slot: Slot): Root | null {
const node = this.nodes[root];
if (!node) {
return null;
}
if (node.slot > slot) {
return this.getAncestor(node.parent.blockRoot, slot);
} else if (node.slot === slot) {
return node.blockRoot;
} else {
return null;
}
}

private prune(): void {
if (this.finalized) {
Object.values(this.nodes).forEach((n) => {
if (n.slot < this.finalized.slot) {
if (n.slot < this.finalized.node.slot) {
delete this.nodes[n.blockRoot];
}
});
this.finalized.parent = null;
this.finalized.node.parent = null;
}
}
}
Loading