Skip to content

Commit 911a3f5

Browse files
authoredOct 11, 2024··
feat: use rust shuffle (#7120)
* feat: add temp-deps to test on feat group * feat: add build_temp_deps.sh process * feat: use async for shuffling in epoch transition * feat: use v0.0.1 instead of temp-deps * chore: lint and check-types * fix: log error context * refactor: use toHex * fix: address computeEpochShuffling refactor comments * test: remove perf tests that were moved to swp-or-not-shuffle package * test: add strict equal check for sync/async computeEpochShuffling impls * Revert "refactor: use toHex" This reverts commit 9d64b67. * fix: EpochShuffling ssz type issue * feat: upgrade swap-or-not to 0.0.2 * refactor: buildCommitteesFromShuffling * docs: add TODO about removing state from shuffling computation * docs: add TODO about removing state from shuffling computation
1 parent 105a388 commit 911a3f5

File tree

12 files changed

+161
-399
lines changed

12 files changed

+161
-399
lines changed
 

‎packages/beacon-node/src/chain/shufflingCache.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import {
44
IShufflingCache,
55
ShufflingBuildProps,
66
computeEpochShuffling,
7+
computeEpochShufflingAsync,
78
} from "@lodestar/state-transition";
89
import {Epoch, RootHex} from "@lodestar/types";
910
import {LodestarError, Logger, MapDef, pruneSetToMax} from "@lodestar/utils";
1011
import {Metrics} from "../metrics/metrics.js";
11-
import {callInNextEventLoop} from "../util/eventLoop.js";
1212

1313
/**
1414
* Same value to CheckpointBalancesCache, with the assumption that we don't have to use it for old epochs. In the worse case:
@@ -178,14 +178,19 @@ export class ShufflingCache implements IShufflingCache {
178178
this.insertPromise(epoch, decisionRoot);
179179
/**
180180
* TODO: (@matthewkeil) This will get replaced by a proper build queue and a worker to do calculations
181-
* on a NICE thread with a rust implementation
181+
* on a NICE thread
182182
*/
183-
callInNextEventLoop(() => {
184-
const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "build"});
185-
const shuffling = computeEpochShuffling(state, activeIndices, epoch);
186-
timer?.();
187-
this.set(shuffling, decisionRoot);
188-
});
183+
const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "build"});
184+
computeEpochShufflingAsync(state, activeIndices, epoch)
185+
.then((shuffling) => {
186+
this.set(shuffling, decisionRoot);
187+
})
188+
.catch((err) =>
189+
this.logger?.error(`error building shuffling for epoch ${epoch} at decisionRoot ${decisionRoot}`, {}, err)
190+
)
191+
.finally(() => {
192+
timer?.();
193+
});
189194
}
190195

191196
/**

‎packages/beacon-node/test/spec/presets/shuffling.test.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import path from "node:path";
2-
import {unshuffleList} from "@lodestar/state-transition";
2+
import {unshuffleList} from "@chainsafe/swap-or-not-shuffle";
33
import {InputType} from "@lodestar/spec-test-util";
44
import {bnToNum, fromHex} from "@lodestar/utils";
5-
import {ACTIVE_PRESET} from "@lodestar/params";
5+
import {ACTIVE_PRESET, SHUFFLE_ROUND_COUNT} from "@lodestar/params";
66
import {RunnerType, TestRunnerFn} from "../utils/types.js";
77
import {ethereumConsensusSpecsTests} from "../specTestVersioning.js";
88
import {specTestIterator} from "../utils/specTestIterator.js";
99

10-
const shuffling: TestRunnerFn<ShufflingTestCase, number[]> = () => {
10+
const shuffling: TestRunnerFn<ShufflingTestCase, string> = () => {
1111
return {
1212
testFunction: (testcase) => {
1313
const seed = fromHex(testcase.mapping.seed);
14-
const output = Array.from({length: bnToNum(testcase.mapping.count)}, (_, i) => i);
15-
unshuffleList(output, seed);
16-
return output;
14+
const output = unshuffleList(
15+
Uint32Array.from(Array.from({length: bnToNum(testcase.mapping.count)}, (_, i) => i)),
16+
seed,
17+
SHUFFLE_ROUND_COUNT
18+
);
19+
return Buffer.from(output).toString("hex");
1720
},
1821
options: {
1922
inputTypes: {mapping: InputType.YAML},
2023
timeout: 10000,
21-
getExpected: (testCase) => testCase.mapping.mapping.map((value) => bnToNum(value)),
24+
getExpected: (testCase) => Buffer.from(testCase.mapping.mapping.map((value) => bnToNum(value))).toString("hex"),
2225
// Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts
2326
},
2427
};

‎packages/state-transition/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@chainsafe/persistent-merkle-tree": "^0.8.0",
6464
"@chainsafe/persistent-ts": "^0.19.1",
6565
"@chainsafe/ssz": "^0.17.1",
66+
"@chainsafe/swap-or-not-shuffle": "^0.0.2",
6667
"@lodestar/config": "^1.22.0",
6768
"@lodestar/params": "^1.22.0",
6869
"@lodestar/types": "^1.22.0",

‎packages/state-transition/src/util/epochShuffling.ts

+41-18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {asyncUnshuffleList, unshuffleList} from "@chainsafe/swap-or-not-shuffle";
12
import {Epoch, RootHex, ssz, ValidatorIndex} from "@lodestar/types";
23
import {GaugeExtra, intDiv, Logger, NoLabels, toRootHex} from "@lodestar/utils";
34
import {
@@ -6,11 +7,11 @@ import {
67
MAX_COMMITTEES_PER_SLOT,
78
SLOTS_PER_EPOCH,
89
TARGET_COMMITTEE_SIZE,
10+
SHUFFLE_ROUND_COUNT,
911
} from "@lodestar/params";
1012
import {BeaconConfig} from "@lodestar/config";
1113
import {BeaconStateAllForks} from "../types.js";
1214
import {getSeed} from "./seed.js";
13-
import {unshuffleList} from "./shuffle.js";
1415
import {computeStartSlotAtEpoch} from "./epoch.js";
1516
import {getBlockRootAtSlot} from "./blockRoot.js";
1617
import {computeAnchorCheckpoint} from "./computeAnchorCheckpoint.js";
@@ -102,42 +103,64 @@ export function computeCommitteeCount(activeValidatorCount: number): number {
102103
return Math.max(1, Math.min(MAX_COMMITTEES_PER_SLOT, committeesPerSlot));
103104
}
104105

105-
export function computeEpochShuffling(
106-
state: BeaconStateAllForks,
107-
activeIndices: Uint32Array,
108-
epoch: Epoch
109-
): EpochShuffling {
110-
const activeValidatorCount = activeIndices.length;
111-
112-
const shuffling = activeIndices.slice();
113-
const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER);
114-
unshuffleList(shuffling, seed);
115-
106+
function buildCommitteesFromShuffling(shuffling: Uint32Array): Uint32Array[][] {
107+
const activeValidatorCount = shuffling.length;
116108
const committeesPerSlot = computeCommitteeCount(activeValidatorCount);
117-
118109
const committeeCount = committeesPerSlot * SLOTS_PER_EPOCH;
119110

120-
const committees: Uint32Array[][] = [];
111+
const committees = new Array<Uint32Array[]>(SLOTS_PER_EPOCH);
121112
for (let slot = 0; slot < SLOTS_PER_EPOCH; slot++) {
122-
const slotCommittees: Uint32Array[] = [];
113+
const slotCommittees = new Array<Uint32Array>(committeesPerSlot);
114+
123115
for (let committeeIndex = 0; committeeIndex < committeesPerSlot; committeeIndex++) {
124116
const index = slot * committeesPerSlot + committeeIndex;
125117
const startOffset = Math.floor((activeValidatorCount * index) / committeeCount);
126118
const endOffset = Math.floor((activeValidatorCount * (index + 1)) / committeeCount);
127119
if (!(startOffset <= endOffset)) {
128120
throw new Error(`Invalid offsets: start ${startOffset} must be less than or equal end ${endOffset}`);
129121
}
130-
slotCommittees.push(shuffling.subarray(startOffset, endOffset));
122+
slotCommittees[committeeIndex] = shuffling.subarray(startOffset, endOffset);
131123
}
132-
committees.push(slotCommittees);
124+
125+
committees[slot] = slotCommittees;
133126
}
134127

128+
return committees;
129+
}
130+
131+
export function computeEpochShuffling(
132+
// TODO: (@matthewkeil) remove state/epoch and pass in seed to clean this up
133+
state: BeaconStateAllForks,
134+
activeIndices: Uint32Array,
135+
epoch: Epoch
136+
): EpochShuffling {
137+
const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER);
138+
const shuffling = unshuffleList(activeIndices, seed, SHUFFLE_ROUND_COUNT);
139+
const committees = buildCommitteesFromShuffling(shuffling);
140+
return {
141+
epoch,
142+
activeIndices,
143+
shuffling,
144+
committees,
145+
committeesPerSlot: committees[0].length,
146+
};
147+
}
148+
149+
export async function computeEpochShufflingAsync(
150+
// TODO: (@matthewkeil) remove state/epoch and pass in seed to clean this up
151+
state: BeaconStateAllForks,
152+
activeIndices: Uint32Array,
153+
epoch: Epoch
154+
): Promise<EpochShuffling> {
155+
const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER);
156+
const shuffling = await asyncUnshuffleList(activeIndices, seed, SHUFFLE_ROUND_COUNT);
157+
const committees = buildCommitteesFromShuffling(shuffling);
135158
return {
136159
epoch,
137160
activeIndices,
138161
shuffling,
139162
committees,
140-
committeesPerSlot,
163+
committeesPerSlot: committees[0].length,
141164
};
142165
}
143166

‎packages/state-transition/src/util/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export * from "./genesis.js";
1717
export * from "./interop.js";
1818
export * from "./rootCache.js";
1919
export * from "./seed.js";
20-
export * from "./shuffle.js";
2120
export * from "./shufflingDecisionRoot.js";
2221
export * from "./signatureSets.js";
2322
export * from "./signingRoot.js";

0 commit comments

Comments
 (0)
Please sign in to comment.