Skip to content

Commit

Permalink
moved next epoch proposers from the chain to the state
Browse files Browse the repository at this point in the history
  • Loading branch information
dadepo committed Mar 7, 2022
1 parent 4f34fbd commit ce0bcb5
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 56 deletions.
22 changes: 22 additions & 0 deletions packages/beacon-state-transition/src/cache/epochContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ export class EpochContext {
* 32 x Number
*/
proposers: ValidatorIndex[];

/**
* Indexes of the block proposers for the next current epoch.
*
* We allow requesting proposal duties only one epoch in the future
* Note: There is a small probability that returned validators differs
* than what is returned when the epoch is reached.
*
* 32 x Number
*/
nextEpochProposers: ValidatorIndex[];
/**
* Shuffling of validator indexes. Immutable through the epoch, then it's replaced entirely.
* Note: Per spec definition, shuffling will always be defined. They are never called before loadState()
Expand Down Expand Up @@ -170,6 +181,7 @@ export class EpochContext {
pubkey2index: PubkeyIndexMap;
index2pubkey: Index2PubkeyCache;
proposers: number[];
nextEpochProposers: number[];
previousShuffling: IEpochShuffling;
currentShuffling: IEpochShuffling;
nextShuffling: IEpochShuffling;
Expand All @@ -190,6 +202,7 @@ export class EpochContext {
this.pubkey2index = data.pubkey2index;
this.index2pubkey = data.index2pubkey;
this.proposers = data.proposers;
this.nextEpochProposers = data.nextEpochProposers;
this.previousShuffling = data.previousShuffling;
this.currentShuffling = data.currentShuffling;
this.nextShuffling = data.nextShuffling;
Expand Down Expand Up @@ -283,6 +296,8 @@ export class EpochContext {
// Allow to create CachedBeaconState for empty states
const proposers =
state.validators.length > 0 ? computeProposers(state, currentShuffling, effectiveBalanceIncrements) : [];
const nextEpochProposers =
state.validators.length > 0 ? computeProposers(state, nextShuffling, effectiveBalanceIncrements) : [];

// Only after altair, compute the indices of the current sync committee
const afterAltairFork = currentEpoch >= config.ALTAIR_FORK_EPOCH;
Expand Down Expand Up @@ -330,6 +345,7 @@ export class EpochContext {
pubkey2index,
index2pubkey,
proposers,
nextEpochProposers,
previousShuffling,
currentShuffling,
nextShuffling,
Expand Down Expand Up @@ -362,6 +378,7 @@ export class EpochContext {
index2pubkey: this.index2pubkey,
// Immutable data
proposers: this.proposers,
nextEpochProposers: this.nextEpochProposers,
previousShuffling: this.previousShuffling,
currentShuffling: this.currentShuffling,
nextShuffling: this.nextShuffling,
Expand Down Expand Up @@ -400,6 +417,7 @@ export class EpochContext {
const nextEpoch = currEpoch + 1;
this.nextShuffling = computeEpochShuffling(state, epochProcess.nextEpochShufflingActiveValidatorIndices, nextEpoch);
this.proposers = computeProposers(state, this.currentShuffling, this.effectiveBalanceIncrements);
this.nextEpochProposers = computeProposers(state, this.nextShuffling, this.effectiveBalanceIncrements);

// TODO: DEDUPLICATE from createEpochContext
//
Expand Down Expand Up @@ -477,6 +495,10 @@ export class EpochContext {
return this.proposers[slot % SLOTS_PER_EPOCH];
}

async getNextEpochBeaconProposer(): Promise<ValidatorIndex[]> {
return this.nextEpochProposers;
}

/**
* Return the indexed attestation corresponding to ``attestation``.
*/
Expand Down
9 changes: 1 addition & 8 deletions packages/lodestar/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export function getValidatorApi({chain, config, logger, metrics, network, sync}:
// Note: There is a small probability that returned validators differs
// than what is returned when the epoch is reached.
if (epoch === nextEpoch) {
indexes.push(...(await chain.getNextEpochProposerDuty()));
indexes.push(...(await state.getNextEpochBeaconProposer()));
} else {
// Gather indexes to get pubkeys in batch (performance optimization)
for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
Expand All @@ -295,13 +295,6 @@ export function getValidatorApi({chain, config, logger, metrics, network, sync}:
const validatorIndex = state.getBeaconProposer(startSlot + i);
indexes.push(validatorIndex);
}

for (const cachedEpoch of chain.nextEpochProposerDutyCache.keys()) {
// Do not keep past cached future proposal duties.
if (cachedEpoch <= epoch) {
chain.nextEpochProposerDutyCache.delete(cachedEpoch);
}
}
}

// NOTE: this is the fastest way of getting compressed pubkeys.
Expand Down
33 changes: 2 additions & 31 deletions packages/lodestar/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
*/

import fs from "node:fs";
import {
CachedBeaconStateAllForks,
computeProposers,
computeStartSlotAtEpoch,
} from "@chainsafe/lodestar-beacon-state-transition";
import {CachedBeaconStateAllForks, computeStartSlotAtEpoch} from "@chainsafe/lodestar-beacon-state-transition";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {IForkChoice} from "@chainsafe/lodestar-fork-choice";
import {allForks, Number64, Root, phase0, Slot, RootHex, ValidatorIndex, Epoch} from "@chainsafe/lodestar-types";
import {allForks, Number64, Root, phase0, Slot, RootHex} from "@chainsafe/lodestar-types";
import {ILogger} from "@chainsafe/lodestar-utils";
import {fromHexString, TreeBacked} from "@chainsafe/ssz";
import {AbortController} from "@chainsafe/abort-controller";
Expand Down Expand Up @@ -82,9 +78,6 @@ export class BeaconChain implements IBeaconChain {
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
readonly seenContributionAndProof = new SeenContributionAndProof();

// caches computed proposers for epoch N+1 in the future
readonly nextEpochProposerDutyCache = new Map<Epoch, ValidatorIndex[]>();

protected readonly blockProcessor: BlockProcessor;
protected readonly db: IBeaconDb;
protected readonly logger: ILogger;
Expand Down Expand Up @@ -226,28 +219,6 @@ export class BeaconChain implements IBeaconChain {
return headState;
}

async getNextEpochProposerDuty(): Promise<ValidatorIndex[]> {
const nextEpoch = this.clock.currentEpoch + 1;
const cachedDutiesForEpoch = this.nextEpochProposerDutyCache.get(nextEpoch);

if (cachedDutiesForEpoch) {
return cachedDutiesForEpoch;
} else {
const state = await this.getHeadStateAtCurrentEpoch();
const futureProposers = computeProposers(
state,
state.getShufflingAtEpoch(nextEpoch),
state.effectiveBalanceIncrements
);

// Do not keep previous future proposal duties, so clear cache
// before setting a new value.
this.nextEpochProposerDutyCache.clear();
this.nextEpochProposerDutyCache.set(nextEpoch, futureProposers);
return futureProposers;
}
}

async getHeadStateAtCurrentEpoch(): Promise<CachedBeaconStateAllForks> {
const currentEpochStartSlot = computeStartSlotAtEpoch(this.clock.currentEpoch);
const head = this.forkChoice.getHead();
Expand Down
10 changes: 1 addition & 9 deletions packages/lodestar/src/chain/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {allForks, Number64, Root, phase0, Slot, RootHex, ValidatorIndex, Epoch} from "@chainsafe/lodestar-types";
import {allForks, Number64, Root, phase0, Slot, RootHex} from "@chainsafe/lodestar-types";
import {CachedBeaconStateAllForks} from "@chainsafe/lodestar-beacon-state-transition";
import {IForkChoice} from "@chainsafe/lodestar-fork-choice";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
Expand Down Expand Up @@ -68,9 +68,6 @@ export interface IBeaconChain {
readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
readonly seenContributionAndProof: SeenContributionAndProof;

// caches computed proposers for epoch N+1
readonly nextEpochProposerDutyCache: Map<Epoch, ValidatorIndex[]>;

/** Stop beacon chain processing */
close(): void;
/** Populate in-memory caches with persisted data. Call at least once on startup */
Expand All @@ -82,11 +79,6 @@ export interface IBeaconChain {
getHeadState(): CachedBeaconStateAllForks;
getHeadStateAtCurrentEpoch(): Promise<CachedBeaconStateAllForks>;

/** We allow requesting proposal duties only one epoch in the future
Note: There is a small probability that returned validators differs
than what is returned when the epoch is reached.**/
getNextEpochProposerDuty(): Promise<ValidatorIndex[]>;

/**
* Since we can have multiple parallel chains,
* this methods returns blocks in current chain head according to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,39 @@ describe("get proposers api impl", function () {
);
const cachedState = createCachedBeaconState(config, state);
chainStub.getHeadStateAtCurrentEpoch.resolves(cachedState);
chainStub.getNextEpochProposerDuty.resolves(new Array(SLOTS_PER_EPOCH));
const stubGetBeaconProposer = sinon.stub(cachedState.epochCtx, "getBeaconProposer");
stubGetBeaconProposer.returns(1);
const {data: result} = await api.getProposerDuties(1);
expect(result.length).to.be.equal(SLOTS_PER_EPOCH, "result should be equals to slots per epoch");
expect(stubGetBeaconProposer.called, "stubGetBeaconProposer function should not have been called").to.be.false;
});

it("should have different proposer for current and next epoch", async function () {
syncStub.isSynced.returns(true);
server.sandbox.stub(chainStub.clock, "currentEpoch").get(() => 0);
server.sandbox.stub(chainStub.clock, "currentSlot").get(() => 0);
dbStub.block.get.resolves({message: {stateRoot: Buffer.alloc(32)}} as any);
const state = generateState(
{
slot: 0,
validators: generateValidators(25, {
effectiveBalance: MAX_EFFECTIVE_BALANCE,
activationEpoch: 0,
exitEpoch: FAR_FUTURE_EPOCH,
}),
balances: generateInitialMaxBalances(config, 25),
},
config
);
const cachedState = createCachedBeaconState(config, state);
chainStub.getHeadStateAtCurrentEpoch.resolves(cachedState);
const stubGetBeaconProposer = sinon.stub(cachedState.epochCtx, "getBeaconProposer");
stubGetBeaconProposer.returns(1);
const {data: currentProposers} = await api.getProposerDuties(0);
const {data: nextProposers} = await api.getProposerDuties(1);
expect(currentProposers).to.not.deep.equal(nextProposers, "current proposer and next proposer should be different");
});

it("should not get proposers for more than one epoch in the future", async function () {
syncStub.isSynced.returns(true);
server.sandbox.stub(chainStub.clock, "currentEpoch").get(() => 0);
Expand Down
8 changes: 1 addition & 7 deletions packages/lodestar/test/utils/mocks/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {AbortController} from "@chainsafe/abort-controller";
import sinon from "sinon";

import {toHexString, TreeBacked} from "@chainsafe/ssz";
import {allForks, Epoch, Number64, Root, Slot, ssz, Uint16, Uint64, ValidatorIndex} from "@chainsafe/lodestar-types";
import {allForks, Number64, Root, Slot, ssz, Uint16, Uint64} from "@chainsafe/lodestar-types";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {CachedBeaconStateAllForks, createCachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition";
import {phase0} from "@chainsafe/lodestar-beacon-state-transition";
Expand Down Expand Up @@ -81,8 +81,6 @@ export class MockBeaconChain implements IBeaconChain {
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
readonly seenContributionAndProof = new SeenContributionAndProof();

readonly nextEpochProposerDutyCache = new Map<Epoch, ValidatorIndex[]>();

private state: TreeBacked<allForks.BeaconState>;
private abortController: AbortController;

Expand Down Expand Up @@ -178,10 +176,6 @@ export class MockBeaconChain implements IBeaconChain {
persistInvalidSszObject(): string | null {
return null;
}

getNextEpochProposerDuty(): Promise<ValidatorIndex[]> {
return Promise.resolve([]);
}
}

function mockForkChoice(): IForkChoice {
Expand Down

0 comments on commit ce0bcb5

Please sign in to comment.