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

fix: correct slot and index in getEpochCommittees response #5634

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 11 additions & 7 deletions packages/beacon-node/src/api/impl/beacon/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
BeaconStateAllForks,
CachedBeaconStateAltair,
computeEpochAtSlot,
computeStartSlotAtEpoch,
getCurrentEpoch,
} from "@lodestar/state-transition";
import {ApiError} from "../../errors.js";
Expand Down Expand Up @@ -171,14 +172,17 @@ export function getBeaconStateApi({
throw new ApiError(400, `No cached state available for stateId: ${stateId}`);
}

const shuffling = stateCached.epochCtx.getShufflingAtEpoch(filters?.epoch ?? computeEpochAtSlot(state.slot));
const committes = shuffling.committees;
const committesFlat = committes.flatMap((slotCommittees, committeeIndex) => {
if (filters?.index !== undefined && filters.index !== committeeIndex) {
const epoch = filters?.epoch ?? computeEpochAtSlot(state.slot);
const startSlot = computeStartSlotAtEpoch(epoch);
const shuffling = stateCached.epochCtx.getShufflingAtEpoch(epoch);
const committees = shuffling.committees;
const committeesFlat = committees.flatMap((slotCommittees, slotInEpoch) => {
const slot = startSlot + slotInEpoch;
if (filters?.slot !== undefined && filters.slot !== slot) {
return [];
}
return slotCommittees.flatMap((committee, slot) => {
if (filters?.slot !== undefined && filters.slot !== slot) {
return slotCommittees.flatMap((committee, committeeIndex) => {
if (filters?.index !== undefined && filters.index !== committeeIndex) {
return [];
}
return [
Expand All @@ -193,7 +197,7 @@ export function getBeaconStateApi({

return {
executionOptimistic,
data: committesFlat,
data: committeesFlat,
};
},

Expand Down
101 changes: 101 additions & 0 deletions packages/beacon-node/test/e2e/api/impl/beacon/state/endpoint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {expect} from "chai";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {Api, ApiError, getClient} from "@lodestar/api";
import {computeCommitteeCount} from "@lodestar/state-transition";
import {LogLevel, testLogger} from "../../../../../utils/logger.js";
import {getDevBeaconNode} from "../../../../../utils/node/beacon.js";
import {BeaconNode} from "../../../../../../src/node/nodejs.js";

describe("beacon state api", function () {
this.timeout("10 min");
nflaig marked this conversation as resolved.
Show resolved Hide resolved
nflaig marked this conversation as resolved.
Show resolved Hide resolved

const restPort = 9596;
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
const validatorCount = 512;
const committeesPerSlot = computeCommitteeCount(validatorCount);
const committeeCount = committeesPerSlot * SLOTS_PER_EPOCH;
const validatorsPerCommittee = validatorCount / committeeCount;

let bn: BeaconNode;
let client: Api["beacon"];

before(async () => {
bn = await getDevBeaconNode({
params: chainConfigDef,
options: {
sync: {isSingleNode: true},
network: {allowPublishToZeroPeers: true},
api: {
rest: {
enabled: true,
port: restPort,
},
},
chain: {blsVerifyAllMainThread: true},
},
validatorCount,
logger: testLogger("Node-A", {level: LogLevel.info}),
});
client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon;
});

after(async () => {
await bn.close();
});

describe("getEpochCommittees", async () => {
it("should return all committees for the given state", async () => {
const res = await client.getEpochCommittees("head");
ApiError.assert(res);
const epochCommittees = res.response.data;

expect(epochCommittees.length).to.be.equal(committeeCount, "Incorrect committee count");

const slotCount: Record<string, number> = {};
const indexCount: Record<string, number> = {};

for (const committee of epochCommittees) {
expect(committee.index).to.be.within(0, committeeCount - 1, "Committee index out of range");
expect(committee.slot).to.be.within(0, SLOTS_PER_EPOCH - 1, "Committee slot out of range");
expect(committee.validators.length).to.be.equal(
validatorsPerCommittee,
"Incorrect number of validators in committee"
);
slotCount[committee.slot] = (slotCount[committee.slot] || 0) + 1;
indexCount[committee.index] = (indexCount[committee.index] || 0) + 1;
}

for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
expect(slotCount[i]).to.be.equal(committeesPerSlot, `Incorrect number of committees with slot ${i}`);
}

for (let i = 0; i < committeesPerSlot; i++) {
expect(indexCount[i]).to.be.equal(SLOTS_PER_EPOCH, `Incorrect number of committees with index ${i}`);
}
});

it("should restrict returned committees to those matching the supplied index", async () => {
const index = committeesPerSlot / 2;
const res = await client.getEpochCommittees("head", {index});
ApiError.assert(res);
const epochCommittees = res.response.data;
expect(epochCommittees.length).to.be.equal(SLOTS_PER_EPOCH, `Incorrect committee count for index ${index}`);
for (const committee of epochCommittees) {
expect(committee.index).to.equal(index, "Committee index does not match supplied index");
}
});

it("should restrict returned committees to those matching the supplied slot", async () => {
const slot = SLOTS_PER_EPOCH / 2;
const res = await client.getEpochCommittees("head", {slot});
ApiError.assert(res);
const epochCommittees = res.response.data;
expect(epochCommittees.length).to.be.equal(committeesPerSlot, `Incorrect committee count for slot ${slot}`);
for (const committee of epochCommittees) {
expect(committee.slot).to.equal(slot, "Committee slot does not match supplied slot");
}
});
});
});