Skip to content

Commit

Permalink
Merge b2e5529 into bfeb48a
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarhussain authored Sep 23, 2022
2 parents bfeb48a + b2e5529 commit c501b00
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 1 deletion.
5 changes: 5 additions & 0 deletions packages/cli/test/simulation/simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
attestationPerSlotAssertions,
finalityAssertions,
headsAssertions,
syncCommitteeAssertions,
} from "../utils/simulation/assertions.js";

chai.use(chaiAsPromised);
Expand Down Expand Up @@ -128,6 +129,10 @@ for (const {beaconNodes, validatorClients, validatorsPerClient} of nodeCases) {
describe("attestation participation", () => {
attestationParticipationAssertions(env, epoch);
});

describe("sync committee participation", () => {
syncCommitteeAssertions(env, epoch);
});
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class SimulationEnvironment {
readonly clock: EpochClock;
readonly acceptableParticipationRate = 1;
readonly acceptableMaxInclusionDelay = 1;
readonly acceptableMinSyncParticipation = 1;
readonly tracker: SimulationTracker;
readonly emitter: EventEmitter;
readonly controller: AbortController;
Expand Down
31 changes: 30 additions & 1 deletion packages/cli/test/utils/simulation/SimulationTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import {altair, Epoch, Slot} from "@lodestar/types";
import {toHexString} from "@lodestar/utils";
import {EpochClock} from "./EpochClock.js";
import {BeaconNodeProcess, SimulationParams} from "./types.js";
import {computeAttestation, computeAttestationParticipation, computeInclusionDelay, getForkName} from "./utils.js";
import {
avg,
computeAttestation,
computeAttestationParticipation,
computeInclusionDelay,
computeSyncCommitteeParticipation,
getForkName,
} from "./utils.js";

const participationHeading = (id: string): string => `${id}-P-H/S/T`;
const missedBlocksHeading = (id: string): string => `${id}-M`;
// const nodeHeadHeading = (id: string): string => `${id}-H`;
const finalizedHeading = (id: string): string => `${id}-F`;
const syncCommitteeHeading = (id: string): string => `${id}-SC`;

export class SimulationTracker {
readonly producedBlocks: Map<string, Map<Slot, boolean>>;
Expand All @@ -19,6 +27,7 @@ export class SimulationTracker {
private lastSeenSlot: Map<string, Slot>;
readonly headPerSlot: Map<string, Map<Slot, string>>;
readonly finalizedPerSlot: Map<string, Map<Slot, Slot>>;
readonly syncCommitteeParticipation: Map<string, Map<Slot, number>>;

readonly emitter = new EventEmitter();

Expand All @@ -40,6 +49,7 @@ export class SimulationTracker {
this.lastSeenSlot = new Map();
this.headPerSlot = new Map();
this.finalizedPerSlot = new Map();
this.syncCommitteeParticipation = new Map();

for (let i = 0; i < nodes.length; i += 1) {
this.producedBlocks.set(nodes[i].id, new Map());
Expand All @@ -49,9 +59,11 @@ export class SimulationTracker {
this.lastSeenSlot.set(nodes[i].id, 0);
this.headPerSlot.set(nodes[i].id, new Map());
this.finalizedPerSlot.set(nodes[i].id, new Map());
this.syncCommitteeParticipation.set(nodes[i].id, new Map());

// Set finalized slot to genesis
this.finalizedPerSlot.get(nodes[i].id)?.set(0, 0);
this.syncCommitteeParticipation.get(nodes[i].id)?.set(0, params.altairEpoch === 0 ? 1 : 0);
}
}

Expand Down Expand Up @@ -107,6 +119,7 @@ export class SimulationTracker {
const slot = event.slot;
const lastSeenSlot = this.lastSeenSlot.get(node.id);
const blockAttestations = await node.api.beacon.getBlockAttestations(slot);
const block = await node.api.beacon.getBlockV2(slot);

if (lastSeenSlot !== undefined && slot > lastSeenSlot) {
this.lastSeenSlot.set(node.id, slot);
Expand All @@ -115,6 +128,9 @@ export class SimulationTracker {
this.producedBlocks.get(node.id)?.set(slot, true);
this.attestationsPerSlot.get(node.id)?.set(slot, computeAttestation(blockAttestations.data));
this.inclusionDelayPerBlock.get(node.id)?.set(slot, computeInclusionDelay(blockAttestations.data, slot));
this.syncCommitteeParticipation
.get(node.id)
?.set(slot, computeSyncCommitteeParticipation(block.version, block.data as altair.SignedBeaconBlock));

const head = await node.api.beacon.getBlockHeader("head");
this.headPerSlot.get(node.id)?.set(slot, toHexString(head.data.root));
Expand Down Expand Up @@ -181,10 +197,17 @@ export class SimulationTracker {
}`;
}

for (const node of this.nodes) {
record[syncCommitteeHeading(node.id)] = this.syncCommitteeParticipation.get(node.id)?.get(slot)?.toFixed(2);
}

records.push(record);

if (this.clock.isLastSlotOfEpoch(slot)) {
const epoch = this.clock.getEpochForSlot(slot);
const firstSlot = this.clock.getFirstSlotOfEpoch(epoch);
const lastSlot = this.clock.getLastSlotOfEpoch(epoch);

const record: Record<string, unknown> = {
F: getForkName(epoch, this.params),
Eph: epoch,
Expand All @@ -204,6 +227,12 @@ export class SimulationTracker {
)}`
: "";
record[participationHeading(node.id)] = participationStr;

const syncParticipation: number[] = [];
for (let i = firstSlot; i <= lastSlot; i++) {
syncParticipation.push(this.syncCommitteeParticipation.get(node.id)?.get(i) ?? 0);
}
record[syncCommitteeHeading(node.id)] = avg(syncParticipation).toFixed(2);
}
records.push(record);
}
Expand Down
31 changes: 31 additions & 0 deletions packages/cli/test/utils/simulation/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,34 @@ export function headsAssertions(env: SimulationEnvironment, epoch: Epoch): void
});
}
}

export function syncCommitteeAssertions(env: SimulationEnvironment, epoch: Epoch): void {
if (epoch < env.params.altairEpoch) {
return;
}

for (const node of env.nodes) {
describe(node.id, () => {
const startSlot = env.clock.getFirstSlotOfEpoch(epoch);
const endSlot = env.clock.getLastSlotOfEpoch(epoch);
const altairStartSlot = env.clock.getFirstSlotOfEpoch(env.params.altairEpoch);

for (let slot = startSlot; slot <= endSlot; slot++) {
// Sync committee is not available before until 2 slots for altair epoch
if (slot === altairStartSlot || slot === altairStartSlot + 1) {
continue;
}

it(`should have have higher participation for slot "${slot}"`, () => {
const participation = env.tracker.syncCommitteeParticipation.get(env.nodes[0].id)?.get(slot);
const acceptableMinSyncParticipation = env.acceptableMinSyncParticipation;

expect(participation).to.gte(
acceptableMinSyncParticipation,
`node "${node.id}" low sync committee participation slot: ${slot}, participation: ${participation}, acceptableMinSyncParticipation: ${acceptableMinSyncParticipation}`
);
});
}
});
}
}
9 changes: 9 additions & 0 deletions packages/cli/test/utils/simulation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ export const computeInclusionDelay = (attestations: phase0.Attestation[], slot:
return avg(Array.from(attestations).map((att) => slot - att.data.slot));
};

export const computeSyncCommitteeParticipation = (version: ForkName, block: altair.SignedBeaconBlock): number => {
if (version === ForkName.phase0) {
return 0;
}

const {syncCommitteeBits} = block.message.body.syncAggregate;
return syncCommitteeBits.getTrueBitIndexes().length / syncCommitteeBits.bitLen;
};

export const avg = (arr: number[]): number => {
return arr.length === 0 ? 0 : arr.reduce((p, c) => p + c, 0) / arr.length;
};
Expand Down

0 comments on commit c501b00

Please sign in to comment.