Skip to content

Commit

Permalink
Merge eded1c0 into aa1af04
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion committed Sep 17, 2022
2 parents aa1af04 + eded1c0 commit 887eedd
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 50 deletions.
29 changes: 3 additions & 26 deletions packages/beacon-node/src/api/impl/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {computeEpochAtSlot, computeStartSlotAtEpoch, getBlockRootAtSlot} from "@lodestar/state-transition";
import {computeEpochAtSlot} from "@lodestar/state-transition";
import {routes} from "@lodestar/api";
import {toHexString} from "@chainsafe/ssz";
import {ApiModules, IS_OPTIMISTIC_TEMP} from "../types.js";
import {ChainEvent, IChainEvents} from "../../../chain/index.js";
import {ApiError} from "../errors.js";
import {isOptimsticBlock} from "../../../util/forkChoice.js";

/**
* Mapping of internal `ChainEvents` to API spec events
*/
const chainEventMap = {
[routes.events.EventType.head]: ChainEvent.forkChoiceHead as const,
[routes.events.EventType.head]: ChainEvent.head as const,
[routes.events.EventType.block]: ChainEvent.block as const,
[routes.events.EventType.attestation]: ChainEvent.attestation as const,
[routes.events.EventType.voluntaryExit]: ChainEvent.block as const,
Expand All @@ -30,29 +29,7 @@ export function getEventsApi({chain, config}: Pick<ApiModules, "chain" | "config
...args: Parameters<IChainEvents[typeof chainEventMap[K]]>
) => routes.events.EventData[K][];
} = {
[routes.events.EventType.head]: (head) => {
const state = chain.stateCache.get(head.stateRoot);
if (!state) {
throw Error("cannot get state for head " + head.stateRoot);
}

const currentEpoch = state.epochCtx.epoch;
const [previousDutyDependentRoot, currentDutyDependentRoot] = [currentEpoch - 1, currentEpoch].map((epoch) =>
toHexString(getBlockRootAtSlot(state, Math.max(computeStartSlotAtEpoch(epoch) - 1, 0)))
);

return [
{
block: head.blockRoot,
epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot,
slot: head.slot,
state: head.stateRoot,
previousDutyDependentRoot,
currentDutyDependentRoot,
executionOptimistic: isOptimsticBlock(head),
},
];
},
[routes.events.EventType.head]: (data) => [data],
[routes.events.EventType.block]: (block) => [
{
block: toHexString(config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)),
Expand Down
22 changes: 19 additions & 3 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {altair, ssz} from "@lodestar/types";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {toHexString} from "@chainsafe/ssz";
import {CachedBeaconStateAltair, computeEpochAtSlot, RootCache} from "@lodestar/state-transition";
import {ForkChoiceError, ForkChoiceErrorCode} from "@lodestar/fork-choice";
import {
CachedBeaconStateAltair,
computeEpochAtSlot,
computeStartSlotAtEpoch,
RootCache,
} from "@lodestar/state-transition";
import {ForkChoiceError, ForkChoiceErrorCode, EpochDifference} from "@lodestar/fork-choice";
import {ZERO_HASH_HEX} from "../../constants/index.js";
import {toCheckpointHex} from "../stateCache/index.js";
import {isOptimsticBlock} from "../../util/forkChoice.js";
import {ChainEvent} from "../emitter.js";
import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC} from "../reprocess.js";
import type {BeaconChain} from "../chain.js";
Expand Down Expand Up @@ -180,7 +186,17 @@ export async function importBlock(

if (newHead.blockRoot !== oldHead.blockRoot) {
// new head
pendingEvents.push(ChainEvent.forkChoiceHead, newHead);

pendingEvents.push(ChainEvent.head, {
block: newHead.blockRoot,
epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(newHead.slot)) === newHead.slot,
slot: newHead.slot,
state: newHead.stateRoot,
previousDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.previous),
currentDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.current),
executionOptimistic: isOptimsticBlock(newHead),
});

this.metrics?.forkChoice.changedHead.inc();

const distance = this.forkChoice.getCommonAncestorDistance(oldHead, newHead);
Expand Down
9 changes: 4 additions & 5 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {IBeaconConfig} from "@lodestar/config";
import {allForks, bellatrix, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex} from "@lodestar/types";
import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {ProcessShutdownCallback} from "@lodestar/validator";

import {ILogger, toHex} from "@lodestar/utils";
import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
Expand All @@ -31,7 +30,7 @@ import {ensureDir, writeIfNotExist} from "../util/file.js";
import {CheckpointStateCache, StateContextCache} from "./stateCache/index.js";
import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
import {IBeaconClock, LocalClock} from "./clock/index.js";
import {ChainEventEmitter, ChainEvent} from "./emitter.js";
import {ChainEventEmitter, ChainEvent, HeadEventData} from "./emitter.js";
import {IBeaconChain, ProposerPreparationData} from "./interface.js";
import {IChainOptions} from "./options.js";
import {IStateRegenerator, QueuedStateRegenerator, RegenCaller} from "./regen/index.js";
Expand Down Expand Up @@ -271,7 +270,7 @@ export class BeaconChain implements IBeaconChain {
this.emitter.addListener(ChainEvent.clockEpoch, this.onClockEpoch.bind(this));
this.emitter.addListener(ChainEvent.forkChoiceFinalized, this.onForkChoiceFinalized.bind(this));
this.emitter.addListener(ChainEvent.forkChoiceJustified, this.onForkChoiceJustified.bind(this));
this.emitter.addListener(ChainEvent.forkChoiceHead, this.onForkChoiceHead.bind(this));
this.emitter.addListener(ChainEvent.head, this.onNewHead.bind(this));
}

async close(): Promise<void> {
Expand Down Expand Up @@ -610,11 +609,11 @@ export class BeaconChain implements IBeaconChain {
}
}

private onForkChoiceHead(head: ProtoBlock): void {
private onNewHead(head: HeadEventData): void {
const delaySec = this.clock.secFromSlot(head.slot);
this.logger.verbose("New chain head", {
headSlot: head.slot,
headRoot: head.blockRoot,
headRoot: head.block,
delaySec,
});
this.syncContributionAndProofPool.prune(head.slot);
Expand Down
6 changes: 4 additions & 2 deletions packages/beacon-node/src/chain/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export enum ChainEvent {
*
* This event is guaranteed to be emitted after every sucessfully processed block, if that block updates the head.
*/
forkChoiceHead = "forkChoice:head",
head = "forkChoice:head",
/**
* This event signals that the fork choice has been updated to a new head that is not a descendant of the previous head.
*
Expand Down Expand Up @@ -95,6 +95,8 @@ export enum ChainEvent {
lightclientFinalizedUpdate = "lightclient:finalized_update",
}

export type HeadEventData = routes.events.EventData[routes.events.EventType.head];

export interface IChainEvents {
[ChainEvent.attestation]: (attestation: phase0.Attestation) => void;
[ChainEvent.contributionAndProof]: (contributionAndProof: altair.SignedContributionAndProof) => void;
Expand All @@ -107,7 +109,7 @@ export interface IChainEvents {
[ChainEvent.clockSlot]: (slot: Slot) => void;
[ChainEvent.clockEpoch]: (epoch: Epoch) => void;

[ChainEvent.forkChoiceHead]: (head: ProtoBlock) => void;
[ChainEvent.head]: (data: HeadEventData) => void;
[ChainEvent.forkChoiceReorg]: (head: ProtoBlock, oldHead: ProtoBlock, depth: number) => void;
[ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithHex) => void;
[ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithHex) => void;
Expand Down
11 changes: 5 additions & 6 deletions packages/beacon-node/test/e2e/chain/lightclient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import {fromHexString, toHexString} from "@chainsafe/ssz";
import {TimestampFormatCode} from "@lodestar/utils";
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params";
import {Lightclient} from "@lodestar/light-client";
import {ProtoBlock} from "@lodestar/fork-choice";
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {testLogger, LogLevel, TestLoggerOpts} from "../../utils/logger.js";
import {getDevBeaconNode} from "../../utils/node/beacon.js";
import {getAndInitDevValidators} from "../../utils/node/validator.js";
import {ChainEvent} from "../../../src/chain/index.js";
import {ChainEvent, HeadEventData} from "../../../src/chain/index.js";

describe("chain / lightclient", function () {
/**
Expand Down Expand Up @@ -104,8 +103,8 @@ describe("chain / lightclient", function () {
// 4. On every new beacon node head, check that the lightclient is following closely
// - If too far behind error the test
// - If beacon node reaches the finality slot, resolve test
const promiseUntilHead = new Promise<ProtoBlock>((resolve) => {
bn.chain.emitter.on(ChainEvent.forkChoiceHead, async (head) => {
const promiseUntilHead = new Promise<HeadEventData>((resolve) => {
bn.chain.emitter.on(ChainEvent.head, async (head) => {
// Wait for the second slot so syncCommitteeWitness is available
if (head.slot > 2) {
resolve(head);
Expand All @@ -123,7 +122,7 @@ describe("chain / lightclient", function () {
genesisTime: bn.chain.genesisTime,
genesisValidatorsRoot: bn.chain.genesisValidatorsRoot as Uint8Array,
},
checkpointRoot: fromHexString(head.blockRoot),
checkpointRoot: fromHexString(head.block),
});

afterEachCallbacks.push(async () => {
Expand All @@ -134,7 +133,7 @@ describe("chain / lightclient", function () {
lightclient.start();

return new Promise<void>((resolve, reject) => {
bn.chain.emitter.on(ChainEvent.forkChoiceHead, async (head) => {
bn.chain.emitter.on(ChainEvent.head, async (head) => {
try {
// Test fetching proofs
const {proof, header} = await lightclient.getHeadStateProof([["latestBlockHeader", "bodyRoot"]]);
Expand Down
17 changes: 14 additions & 3 deletions packages/beacon-node/test/unit/api/impl/events/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import {expect} from "chai";
import sinon, {SinonStubbedInstance} from "sinon";
import {routes} from "@lodestar/api";
import {config} from "@lodestar/config/default";
import {BeaconChain, ChainEvent, ChainEventEmitter} from "../../../../../src/chain/index.js";
import {BeaconChain, ChainEvent, ChainEventEmitter, HeadEventData} from "../../../../../src/chain/index.js";
import {getEventsApi} from "../../../../../src/api/impl/events/index.js";
import {generateProtoBlock, generateEmptySignedBlock, generateSignedBlock} from "../../../../utils/block.js";
import {generateAttestation, generateEmptySignedVoluntaryExit} from "../../../../utils/attestation.js";
import {generateCachedState} from "../../../../utils/state.js";
import {StateContextCache} from "../../../../../src/chain/stateCache/index.js";
import {StubbedChainMutable} from "../../../../utils/stub/index.js";
import {ZERO_HASH_HEX} from "../../../../../src/constants/constants.js";

describe("Events api impl", function () {
describe("beacon event stream", function () {
Expand Down Expand Up @@ -38,13 +39,23 @@ describe("Events api impl", function () {
return events;
}

const headEventData: HeadEventData = {
slot: 0,
block: ZERO_HASH_HEX,
state: ZERO_HASH_HEX,
epochTransition: false,
previousDutyDependentRoot: ZERO_HASH_HEX,
currentDutyDependentRoot: ZERO_HASH_HEX,
executionOptimistic: false,
};

it("should ignore not sent topics", async function () {
const events = getEvents([routes.events.EventType.head]);

const headBlock = generateProtoBlock();
stateCacheStub.get.withArgs(headBlock.stateRoot).returns(generateCachedState({slot: 1000}));
chainEventEmmitter.emit(ChainEvent.forkChoiceReorg, headBlock, headBlock, 2);
chainEventEmmitter.emit(ChainEvent.forkChoiceHead, headBlock);
chainEventEmmitter.emit(ChainEvent.head, headEventData);

expect(events).to.have.length(1, "Wrong num of received events");
expect(events[0].type).to.equal(routes.events.EventType.head);
Expand All @@ -56,7 +67,7 @@ describe("Events api impl", function () {

const headBlock = generateProtoBlock();
stateCacheStub.get.withArgs(headBlock.stateRoot).returns(generateCachedState({slot: 1000}));
chainEventEmmitter.emit(ChainEvent.forkChoiceHead, headBlock);
chainEventEmmitter.emit(ChainEvent.head, headEventData);

expect(events).to.have.length(1, "Wrong num of received events");
expect(events[0].type).to.equal(routes.events.EventType.head);
Expand Down
9 changes: 4 additions & 5 deletions packages/beacon-node/test/utils/node/simTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import {
beforeProcessEpoch,
} from "@lodestar/state-transition";
import {IBeaconConfig} from "@lodestar/config";
import {ProtoBlock} from "@lodestar/fork-choice";
import {SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {allForks, Epoch, Slot} from "@lodestar/types";
import {Checkpoint} from "@lodestar/types/phase0";
import {ILogger, mapValues} from "@lodestar/utils";
import {toHexString} from "@chainsafe/ssz";
import {BeaconNode} from "../../../src/index.js";
import {ChainEvent} from "../../../src/chain/index.js";
import {ChainEvent, HeadEventData} from "../../../src/chain/index.js";
import {linspace} from "../../../src/util/numpy.js";
import {RegenCaller} from "../../../src/chain/regen/index.js";

Expand All @@ -26,7 +25,7 @@ export function simTestInfoTracker(bn: BeaconNode, logger: ILogger): () => void
const prevParticipationPerEpoch = new Map<Epoch, number>();
const currParticipationPerEpoch = new Map<Epoch, number>();

async function onHead(head: ProtoBlock): Promise<void> {
async function onHead(head: HeadEventData): Promise<void> {
const slot = head.slot;

// For each block
Expand Down Expand Up @@ -73,11 +72,11 @@ export function simTestInfoTracker(bn: BeaconNode, logger: ILogger): () => void
logParticipation(lastState);
}

bn.chain.emitter.on(ChainEvent.forkChoiceHead, onHead);
bn.chain.emitter.on(ChainEvent.head, onHead);
bn.chain.emitter.on(ChainEvent.checkpoint, onCheckpoint);

return function stop() {
bn.chain.emitter.off(ChainEvent.forkChoiceHead, onHead);
bn.chain.emitter.off(ChainEvent.head, onHead);
bn.chain.emitter.off(ChainEvent.checkpoint, onCheckpoint);

// Write report
Expand Down

0 comments on commit 887eedd

Please sign in to comment.