Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
Binary file added chainsafe-swap-or-not-shuffle-1.2.1-local.tgz
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/state-transition/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"@chainsafe/persistent-ts": "^1.0.0",
"@chainsafe/pubkey-index-map": "^3.0.0",
"@chainsafe/ssz": "^1.4.0",
"@chainsafe/swap-or-not-shuffle": "^1.2.1",
"@chainsafe/swap-or-not-shuffle": "file:../../chainsafe-swap-or-not-shuffle-1.2.1-local.tgz",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of a local tarball dependency (file:../../...) is problematic for shared repositories as it breaks builds for other developers and CI environments that do not have the file at that specific relative path. Furthermore, the pnpm-lock.yaml changes indicate that several supported platforms (e.g., darwin-x64, linux-x64-gnu) have been removed and versions for native bindings have been downgraded to 0.0.2. This dependency should be properly published to a registry or integrated via workspace references to maintain repository integrity and cross-platform support.

"@lodestar/config": "workspace:^",
"@lodestar/params": "workspace:^",
"@lodestar/types": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/state-transition/src/epoch/processPtcWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function processPtcWindow(state: CachedBeaconStateGloas, cache: EpochTran
const newNextPayloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch(
state,
nextEpoch,
nextEpochShuffling.committees,
nextEpochShuffling,
state.epochCtx.effectiveBalanceIncrements
);

Expand Down
2 changes: 1 addition & 1 deletion packages/state-transition/src/util/gloas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[]
...computePayloadTimelinessCommitteesForEpoch(
state,
epoch,
shuffling.committees,
shuffling,
state.epochCtx.effectiveBalanceIncrements
)
);
Expand Down
86 changes: 81 additions & 5 deletions packages/state-transition/src/util/seed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {digest} from "@chainsafe/as-sha256";
import {
computePtcIndices,
computePtcIndicesForEpoch,
computeProposerIndex as nativeComputeProposerIndex,
computeSyncCommitteeIndices as nativeComputeSyncCommitteeIndices,
} from "@chainsafe/swap-or-not-shuffle";
Expand All @@ -23,6 +25,7 @@ import {assert, bytesToBigInt, bytesToInt, intToBytes} from "@lodestar/utils";
import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js";
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js";
import {EpochShuffling} from "./epochShuffling.js";

/**
* Compute proposer indices for an epoch
Expand Down Expand Up @@ -269,9 +272,12 @@ export function getNextSyncCommitteeIndices(
}

/**
* Compute PTC for all slots in an epoch eagerly.
* Naive JS version of `computePayloadTimelinessCommitteesForEpoch`.
* Used to verify the optimized Rust-backed version. Not for production use.
*
* SLOW CODE - 🐢
*/
export function computePayloadTimelinessCommitteesForEpoch(
export function naiveComputePayloadTimelinessCommitteesForEpoch(
state: BeaconStateAllForks,
epoch: number,
committees: Uint32Array[][],
Expand All @@ -293,15 +299,60 @@ export function computePayloadTimelinessCommitteesForEpoch(
slotSeedView.setUint32(epochSeed.length + 4, 0, true);
const slotSeed = digest(slotSeedInput);

result[i] = computePayloadTimelinessCommitteeForSlot(slotSeed, committees[i], effectiveBalanceIncrements);
result[i] = naiveComputePayloadTimelinessCommitteeForSlot(slotSeed, committees[i], effectiveBalanceIncrements);
}
return result;
}

/**
* Compute PTC for a single slot.
* Compute PTC for all slots in an epoch eagerly.
*/
export function computePayloadTimelinessCommitteeForSlot(
export function computePayloadTimelinessCommitteesForEpoch(
state: BeaconStateAllForks,
epoch: number,
epochShuffling: EpochShuffling,
effectiveBalanceIncrements: EffectiveBalanceIncrements
): Uint32Array[] {
const epochSeed = getSeed(state, epoch, DOMAIN_PTC_ATTESTER);
const startSlot = epoch * SLOTS_PER_EPOCH;
const {committees, shuffling} = epochShuffling;

const slotOffsets = new Uint32Array(SLOTS_PER_EPOCH + 1);
for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
let slotLen = 0;
for (const c of committees[i]) slotLen += c.length;
slotOffsets[i + 1] = slotOffsets[i] + slotLen;
}
if (shuffling.length === 0) {
throw Error("Validator indices must not be empty");
}
Comment on lines +326 to +328
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the assert utility for consistency with other validation checks in this file and the Lodestar codebase.

  assert(shuffling.length > 0, "Validator indices must not be empty");


const flat = computePtcIndicesForEpoch(
epochSeed,
startSlot,
SLOTS_PER_EPOCH,
shuffling,
slotOffsets,
effectiveBalanceIncrements,
PTC_SIZE,
MAX_EFFECTIVE_BALANCE_ELECTRA,
EFFECTIVE_BALANCE_INCREMENT
);

const result = new Array<Uint32Array>(SLOTS_PER_EPOCH);
for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
result[i] = flat.subarray(i * PTC_SIZE, (i + 1) * PTC_SIZE);
}
return result;
}

/**
* Naive JS version of `computePayloadTimelinessCommitteeForSlot`.
* Used to verify the optimized Rust-backed version. Not for production use.
*
* SLOW CODE - 🐢
*/
export function naiveComputePayloadTimelinessCommitteeForSlot(
slotSeed: Uint8Array,
slotCommittees: Uint32Array[],
effectiveBalanceIncrements: EffectiveBalanceIncrements
Expand All @@ -317,6 +368,31 @@ export function computePayloadTimelinessCommitteeForSlot(
return computePayloadTimelinessCommitteeIndices(effectiveBalanceIncrements, allIndices, slotSeed);
}

/**
* Compute PTC for a single slot.
*/
export function computePayloadTimelinessCommitteeForSlot(
slotSeed: Uint8Array,
slotCommittees: Uint32Array[],
effectiveBalanceIncrements: EffectiveBalanceIncrements
): Uint32Array {
const totalLen = slotCommittees.reduce((sum, c) => sum + c.length, 0);
const allIndices = new Uint32Array(totalLen);
let offset = 0;
for (const c of slotCommittees) {
allIndices.set(c, offset);
offset += c.length;
}
return computePtcIndices(
slotSeed,
allIndices,
effectiveBalanceIncrements,
PTC_SIZE,
MAX_EFFECTIVE_BALANCE_ELECTRA,
EFFECTIVE_BALANCE_INCREMENT
);
}

/**
* Optimized version of PTC indices computation.
* Avoids BigInt conversions and uses DataView for efficient byte reading.
Expand Down
92 changes: 91 additions & 1 deletion packages/state-transition/test/perf/util/seed.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {bench, describe} from "@chainsafe/benchmark";
import {ForkSeq} from "@lodestar/params";
import {fromHex} from "@lodestar/utils";
import {generatePerfTestCachedStateAltair} from "../../../src/testUtils/util.js";
import {generatePerfTestCachedStateAltair, generatePerfTestCachedStateElectra} from "../../../src/testUtils/util.js";
import {
computePayloadTimelinessCommitteeForSlot,
computePayloadTimelinessCommitteesForEpoch,
computeProposerIndex,
computeShuffledIndex,
getNextSyncCommitteeIndices,
naiveComputePayloadTimelinessCommitteeForSlot,
naiveComputePayloadTimelinessCommitteesForEpoch,
naiveComputeProposerIndex,
naiveGetNextSyncCommitteeIndices,
} from "../../../src/util/seed.js";
Expand Down Expand Up @@ -89,3 +93,89 @@ describe("computeShuffledIndex", () => {
});
}
});

describe("computePayloadTimelinessCommitteeForSlot - pure TS vs Rust magic (250k-1M validators)", () => {
for (const vc of [250_000, 1_000_000]) {
const seed = new Uint8Array(32).fill(1);
const indices = new Uint32Array(Array.from({length: vc}, (_, i) => i));
const effectiveBalanceIncrements = new Uint16Array(vc).fill(32);
const slotCommittees = [indices]; // single committee spanning all validators

bench({
id: `naive TS - naiveComputePayloadTimelinessCommitteeForSlot - ${vc} validators`,
fn: () => {
naiveComputePayloadTimelinessCommitteeForSlot(seed, slotCommittees, effectiveBalanceIncrements);
},
});

bench({
id: `(uses native Rust) -computePayloadTimelinessCommitteeForSlot - ${vc} validators`,
fn: () => {
computePayloadTimelinessCommitteeForSlot(seed, slotCommittees, effectiveBalanceIncrements);
},
});
}
});

describe("computePayloadTimelinessCommitteesForEpoch - pure TS vs Rust magic (250k -1M validators)", () => {
for (const vc of [250_000, 1_000_000]) {
const cachedState = generatePerfTestCachedStateElectra({goBackOneSlot: false, vc});
const {epochCtx} = cachedState;
const epoch = epochCtx.epoch;
const {effectiveBalanceIncrements} = epochCtx;

// eslint-disable-next-line no-console
console.log(`[vc=${vc}] effectiveBalanceIncrements[0]=${effectiveBalanceIncrements[0]}`);
Comment on lines +127 to +128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Remove debug console.log and the associated eslint disable comment before merging.


const naiveResult = naiveComputePayloadTimelinessCommitteesForEpoch(
cachedState,
epoch,
epochCtx.currentShuffling.committees,
effectiveBalanceIncrements
);
const rustResult = computePayloadTimelinessCommitteesForEpoch(
cachedState,
epoch,
epochCtx.currentShuffling,
effectiveBalanceIncrements
);
for (let i = 0; i < naiveResult.length; i++) {
const naive = naiveResult[i];
const rust = rustResult[i];
if (naive.length !== rust.length) {
throw new Error(`PTC length mismatch at slot ${i} (vc=${vc}): naive=${naive.length} rust=${rust.length}`);
}
for (let j = 0; j < naive.length; j++) {
if (naive[j] !== rust[j]) {
throw new Error(
`PTC index mismatch at slot ${i} position ${j} (vc=${vc}): naive=${naive[j]} rust=${rust[j]}`
Comment on lines +128 to +151
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed, added this just to check the values from rust and native values

);
}
}
}

bench({
id: `naive TS - naiveComputePayloadTimelinessCommitteesForEpoch - ${vc} validators`,
fn: () => {
naiveComputePayloadTimelinessCommitteesForEpoch(
cachedState,
epoch,
epochCtx.currentShuffling.committees,
effectiveBalanceIncrements
);
},
});

bench({
id: `(uses native Rust) -computePayloadTimelinessCommitteesForEpoch - ${vc} validators`,
fn: () => {
computePayloadTimelinessCommitteesForEpoch(
cachedState,
epoch,
epochCtx.currentShuffling,
effectiveBalanceIncrements
);
},
});
}
});
Loading