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

feat: async shuffling refactor #6938

Merged
merged 81 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
46b6b3f
feat: add ShufflingCache to EpochCache
matthewkeil Jul 3, 2024
99269ce
fix: implementation in state-transition for EpochCache with Shuffling…
matthewkeil Jul 3, 2024
52187c9
feat: remove shufflingCache.processState
matthewkeil Jul 3, 2024
b6b2f20
feat: implement ShufflingCache changes in beacon-node
matthewkeil Jul 4, 2024
4110dc0
feat: pass shufflingCache when loading cached state from db
matthewkeil Jul 4, 2024
04e4a7b
test: fix state-transition tests for EpochCache changes
matthewkeil Jul 4, 2024
ff2d520
feat: Pass shufflingCache to EpochCache at startup
matthewkeil Jul 4, 2024
53ed72e
test: fix slot off by one for decision root in perf test
matthewkeil Jul 9, 2024
8faee3f
chore: use ?. syntax
wemeetagain Jul 9, 2024
d69c8b1
chore: refactoring
wemeetagain Jul 9, 2024
cfa97ad
feat: add comments and clean up afterProcessEpoch
matthewkeil Jul 11, 2024
78a60a2
fix: perf test slot incrementing
matthewkeil Jul 11, 2024
9b0ffe2
fix: remove MockShufflingCache
matthewkeil Jul 11, 2024
13f5996
Revert "chore: refactoring"
matthewkeil Jul 11, 2024
d8d69c9
refactor: shufflingCache getters
matthewkeil Jul 11, 2024
89798a5
refactor: shufflingCache setters
matthewkeil Jul 11, 2024
81449e4
refactor: build and getOrBuild
matthewkeil Jul 11, 2024
5799676
docs: add comments to ShufflingCache methods
matthewkeil Jul 11, 2024
9a0ca70
chore: lint issues
matthewkeil Jul 11, 2024
10dd105
test: update tests in beacon-node
matthewkeil Jul 11, 2024
bb3fd1c
chore: lint
matthewkeil Jul 11, 2024
59f6d3f
feat: get shufflings from cache for API
matthewkeil Jul 12, 2024
bae40cc
feat: minTimeDelayToBuildShuffling cli flag
matthewkeil Jul 12, 2024
b8269ef
test: fix shufflingCache promise insertion test
matthewkeil Jul 12, 2024
a0e7e60
fix: rebase conflicts
matthewkeil Aug 12, 2024
ff7df85
fix: changes from debugging sim tests
matthewkeil Aug 12, 2024
7468419
refactor: minimize changes in afterProcessEpoch
matthewkeil Aug 13, 2024
9e5e4b0
chore: fix lint
matthewkeil Aug 13, 2024
1988568
chore: fix check-types
matthewkeil Aug 13, 2024
955c66c
chore: fix check-types
matthewkeil Aug 13, 2024
389be49
feat: add diff utility
matthewkeil Aug 15, 2024
529d85c
fix: bug in spec tests from invalid nextActiveIndices
matthewkeil Aug 15, 2024
dbf8ff1
refactor: add/remove comments
matthewkeil Aug 15, 2024
dea45b2
Merge branch 'unstable' into mkeil/shuffling-refactor
matthewkeil Aug 15, 2024
5540ce1
refactor: remove this.activeIndicesLength from EpochCache
matthewkeil Aug 16, 2024
eb30b75
refactor: simplify shufflingCache.getSync
matthewkeil Aug 16, 2024
6388a15
refactor: remove unnecessary undefined's
matthewkeil Aug 16, 2024
b2a7aa2
refactor: clean up ShufflingCache unit test
matthewkeil Aug 16, 2024
a8c0a6d
feat: add metrics for ShufflingCache
matthewkeil Aug 16, 2024
40bb572
feat: add shufflingCache metrics to state-transition
matthewkeil Aug 16, 2024
dbe25c9
chore: lint
matthewkeil Aug 16, 2024
70dc683
fix: metric name clash
matthewkeil Aug 16, 2024
27081b2
refactor: add comment about not having ShufflingCache in EpochCache
matthewkeil Aug 21, 2024
600acae
refactor: rename shuffling decision root functions
matthewkeil Aug 21, 2024
8945e03
refactor: remove unused comment
matthewkeil Aug 21, 2024
635d74c
feat: async add nextShuffling to EpochCache after its built
matthewkeil Aug 21, 2024
a2ad1ed
feat: make ShufflingCache.set private
matthewkeil Aug 21, 2024
db86883
feat: chance metrics to nextShufflingNotOnEpochCache instead of posit…
matthewkeil Aug 21, 2024
7114aa5
refactor: move diff to separate PR
matthewkeil Aug 21, 2024
4555604
chore: fix tests using shufflingCache.set method
matthewkeil Aug 21, 2024
d49bfee
feat: remove minTimeDelayToBuild
matthewkeil Aug 21, 2024
923addb
feat: return promise from insertPromise and then through build
matthewkeil Aug 22, 2024
1613d8f
fix: update metrics names and help field
matthewkeil Aug 22, 2024
0a64e36
feat: move build of shuffling to beforeProcessEpoch
matthewkeil Aug 22, 2024
ed850ee
feat: allow calc of pivot slot before slot increment
matthewkeil Aug 22, 2024
5e65f7e
fix: calc of pivot slot before slot increment
matthewkeil Aug 22, 2024
2c1100c
Revert "fix: calc of pivot slot before slot increment"
matthewkeil Aug 22, 2024
d5317ec
Revert "feat: allow calc of pivot slot before slot increment"
matthewkeil Aug 22, 2024
82ac512
feat: allow getting current block root for shuffling calculation
matthewkeil Aug 22, 2024
aa9ab9c
fix: get nextShufflingDecisionRoot directly from state.blockRoots
matthewkeil Aug 22, 2024
ca8445a
fix: convert toRootHex
matthewkeil Aug 22, 2024
19ae447
docs: add comment about pulling decisionRoot directly from state
matthewkeil Aug 22, 2024
ff6694d
feat: add back metrics for regen attestation cache hit/miss
matthewkeil Aug 23, 2024
1a7ab44
docs: fix docstring on shufflingCache.build
matthewkeil Aug 28, 2024
85a9ae1
refactor: change validatorIndices to Uint32Array
matthewkeil Aug 28, 2024
ee1256e
refactor: remove comment and change variable name
matthewkeil Aug 28, 2024
eff10c4
fix: use toRootHex instead of toHexString
matthewkeil Aug 28, 2024
4d06deb
refactor: deduplicate moved function computeAnchorCheckpoint
matthewkeil Aug 28, 2024
1ab811c
fix: touch up metrics per PR comments
matthewkeil Aug 28, 2024
0e8eeb7
Merge branch 'unstable' into mkeil/shuffling-refactor
matthewkeil Sep 3, 2024
fd8196e
fix: merge conflict
matthewkeil Sep 3, 2024
4f777c8
chore: lint
matthewkeil Sep 3, 2024
222908c
refactor: add scope around activeIndices to GC arrays
matthewkeil Sep 6, 2024
ffbbfea
feat: directly use Uint32Array instead of transcribing number array t…
matthewkeil Sep 6, 2024
7f6d420
refactor: activeIndices per tuyen comment
matthewkeil Sep 6, 2024
afc03f2
refactor: rename to epochAfterNext
matthewkeil Sep 9, 2024
d1d0e22
chore: review PR
twoeths Sep 16, 2024
c5c5772
feat: update no shuffling ApiError to 500 status
matthewkeil Sep 19, 2024
88d3105
fix: add back unnecessary eslint directive. to be remove under separa…
matthewkeil Sep 19, 2024
b857d6e
feat: update no shuffling ApiError to 500 status
matthewkeil Sep 20, 2024
af5c989
docs: add comment about upcomingEpoch
matthewkeil Sep 20, 2024
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
9 changes: 8 additions & 1 deletion packages/beacon-node/src/api/impl/beacon/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,14 @@ export function getBeaconStateApi({

const epoch = filters.epoch ?? computeEpochAtSlot(state.slot);
const startSlot = computeStartSlotAtEpoch(epoch);
const shuffling = stateCached.epochCtx.getShufflingAtEpoch(epoch);
const decisionRoot = stateCached.epochCtx.getShufflingDecisionRoot(epoch);
const shuffling = await chain.shufflingCache.get(epoch, decisionRoot);
if (!shuffling) {
throw new ApiError(
500,
`No shuffling found to calculate committees for epoch: ${epoch} and decisionRoot: ${decisionRoot}`
);
}
const committees = shuffling.committees;
const committeesFlat = committees.flatMap((slotCommittees, slotInEpoch) => {
const slot = startSlot + slotInEpoch;
Expand Down
11 changes: 10 additions & 1 deletion packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {ApplicationMethods} from "@lodestar/api/server";
import {
CachedBeaconStateAllForks,
computeStartSlotAtEpoch,
calculateCommitteeAssignments,
proposerShufflingDecisionRoot,
attesterShufflingDecisionRoot,
getBlockRootAtSlot,
Expand Down Expand Up @@ -995,7 +996,15 @@ export function getValidatorApi(

// Check that all validatorIndex belong to the state before calling getCommitteeAssignments()
const pubkeys = getPubkeysForIndices(state.validators, indices);
const committeeAssignments = state.epochCtx.getCommitteeAssignments(epoch, indices);
const decisionRoot = state.epochCtx.getShufflingDecisionRoot(epoch);
const shuffling = await chain.shufflingCache.get(epoch, decisionRoot);
if (!shuffling) {
throw new ApiError(
500,
`No shuffling found to calculate committee assignments for epoch: ${epoch} and decisionRoot: ${decisionRoot}`
);
}
const committeeAssignments = calculateCommitteeAssignments(shuffling, indices);
const duties: routes.validator.AttesterDuty[] = [];
for (let i = 0, len = indices.length; i < len; i++) {
const validatorIndex = indices[i];
Expand Down
7 changes: 0 additions & 7 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export async function importBlock(
const blockRootHex = toRootHex(blockRoot);
const currentEpoch = computeEpochAtSlot(this.forkChoice.getTime());
const blockEpoch = computeEpochAtSlot(blockSlot);
const parentEpoch = computeEpochAtSlot(parentBlockSlot);
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
const blockDelaySec = (fullyVerifiedBlock.seenTimestampSec - postState.genesisTime) % this.config.SECONDS_PER_SLOT;
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
Expand Down Expand Up @@ -336,12 +335,6 @@ export async function importBlock(
this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postState.slot});
}

if (parentEpoch < blockEpoch) {
matthewkeil marked this conversation as resolved.
Show resolved Hide resolved
// current epoch and previous epoch are likely cached in previous states
this.shufflingCache.processState(postState, postState.epochCtx.nextShuffling.epoch);
this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot});
}

if (blockSlot % SLOTS_PER_EPOCH === 0) {
// Cache state to preserve epoch transition work
const checkpointState = postState;
Expand Down
25 changes: 18 additions & 7 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PubkeyIndexMap,
EpochShuffling,
computeEndSlotAtEpoch,
computeAnchorCheckpoint,
} from "@lodestar/state-transition";
import {BeaconConfig} from "@lodestar/config";
import {
Expand Down Expand Up @@ -60,7 +61,6 @@ import {
import {IChainOptions} from "./options.js";
import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js";
import {initializeForkChoice} from "./forkChoice/index.js";
import {computeAnchorCheckpoint} from "./initState.js";
import {IBlsVerifier, BlsSingleThreadVerifier, BlsMultiThreadWorkerPool} from "./bls/index.js";
import {
SeenAttesters,
Expand Down Expand Up @@ -245,7 +245,6 @@ export class BeaconChain implements IBeaconChain {

this.beaconProposerCache = new BeaconProposerCache(opts);
this.checkpointBalancesCache = new CheckpointBalancesCache();
this.shufflingCache = new ShufflingCache(metrics, this.opts);

// Restore state caches
// anchorState may already by a CachedBeaconState. If so, don't create the cache again, since deserializing all
Expand All @@ -260,9 +259,21 @@ export class BeaconChain implements IBeaconChain {
pubkey2index: new PubkeyIndexMap(),
index2pubkey: [],
});
this.shufflingCache.processState(cachedState, cachedState.epochCtx.previousShuffling.epoch);
this.shufflingCache.processState(cachedState, cachedState.epochCtx.currentShuffling.epoch);
this.shufflingCache.processState(cachedState, cachedState.epochCtx.nextShuffling.epoch);

this.shufflingCache = cachedState.epochCtx.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [
{
shuffling: cachedState.epochCtx.previousShuffling,
decisionRoot: cachedState.epochCtx.previousDecisionRoot,
},
{
shuffling: cachedState.epochCtx.currentShuffling,
decisionRoot: cachedState.epochCtx.currentDecisionRoot,
},
{
shuffling: cachedState.epochCtx.nextShuffling,
decisionRoot: cachedState.epochCtx.nextDecisionRoot,
},
]);

// Persist single global instance of state caches
this.pubkey2index = cachedState.epochCtx.pubkey2index;
Expand Down Expand Up @@ -898,8 +909,8 @@ export class BeaconChain implements IBeaconChain {
state = await this.regen.getState(attHeadBlock.stateRoot, regenCaller);
}

// resolve the promise to unblock other calls of the same epoch and dependent root
return this.shufflingCache.processState(state, attEpoch);
// should always be the current epoch of the active context so no need to await a result from the ShufflingCache
return state.epochCtx.getShufflingAtEpoch(attEpoch);
matthewkeil marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {
getEffectiveBalanceIncrementsZeroInactive,
isExecutionStateType,
isMergeTransitionComplete,
computeAnchorCheckpoint,
} from "@lodestar/state-transition";

import {Logger, toRootHex} from "@lodestar/utils";
import {computeAnchorCheckpoint} from "../initState.js";
import {ChainEventEmitter} from "../emitter.js";
import {ChainEvent} from "../emitter.js";
import {GENESIS_SLOT} from "../../constants/index.js";
Expand Down
38 changes: 2 additions & 36 deletions packages/beacon-node/src/chain/initState.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
blockToHeader,
computeEpochAtSlot,
BeaconStateAllForks,
CachedBeaconStateAllForks,
computeCheckpointEpochAtStateSlot,
computeStartSlotAtEpoch,
} from "@lodestar/state-transition";
import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types";
import {SignedBeaconBlock} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {Logger, toHex, toRootHex} from "@lodestar/utils";
import {GENESIS_SLOT, ZERO_HASH} from "../constants/index.js";
import {GENESIS_SLOT} from "../constants/index.js";
import {IBeaconDb} from "../db/index.js";
import {Eth1Provider} from "../eth1/index.js";
import {Metrics} from "../metrics/index.js";
Expand Down Expand Up @@ -202,35 +200,3 @@ export function initBeaconMetrics(metrics: Metrics, state: BeaconStateAllForks):
metrics.currentJustifiedEpoch.set(state.currentJustifiedCheckpoint.epoch);
metrics.finalizedEpoch.set(state.finalizedCheckpoint.epoch);
}

export function computeAnchorCheckpoint(
config: ChainForkConfig,
anchorState: BeaconStateAllForks
): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} {
let blockHeader;
let root;
const blockTypes = config.getForkTypes(anchorState.latestBlockHeader.slot);

if (anchorState.latestBlockHeader.slot === GENESIS_SLOT) {
const block = blockTypes.BeaconBlock.defaultValue();
block.stateRoot = anchorState.hashTreeRoot();
blockHeader = blockToHeader(config, block);
root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader);
} else {
blockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader);
if (ssz.Root.equals(blockHeader.stateRoot, ZERO_HASH)) {
blockHeader.stateRoot = anchorState.hashTreeRoot();
}
root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader);
}

return {
checkpoint: {
root,
// the checkpoint epoch = computeEpochAtSlot(anchorState.slot) + 1 if slot is not at epoch boundary
// this is similar to a process_slots() call
epoch: computeCheckpointEpochAtStateSlot(anchorState.slot),
},
blockHeader,
};
}
Loading
Loading