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: emit slashing events #6391

Merged
merged 3 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/api/src/beacon/routes/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export enum EventType {
attestation = "attestation",
/** The node has received a valid voluntary exit (from P2P or API) */
voluntaryExit = "voluntary_exit",
/** The node has received a ProposerSlashing (from P2P or API) that passes validation rules of the `proposer_slashing` topic */
proposerSlashing = "proposer_slashing",
/** The node has received an AttesterSlashing (from P2P or API) that passes validation rules of the `attester_slashing` topic */
attesterSlashing = "attester_slashing",
jeluard marked this conversation as resolved.
Show resolved Hide resolved
/** The node has received a valid blsToExecutionChange (from P2P or API) */
blsToExecutionChange = "bls_to_execution_change",
/** Finalized checkpoint has been updated */
Expand All @@ -58,6 +62,8 @@ export const eventTypes: {[K in EventType]: K} = {
[EventType.block]: EventType.block,
[EventType.attestation]: EventType.attestation,
[EventType.voluntaryExit]: EventType.voluntaryExit,
[EventType.proposerSlashing]: EventType.proposerSlashing,
[EventType.attesterSlashing]: EventType.attesterSlashing,
[EventType.blsToExecutionChange]: EventType.blsToExecutionChange,
[EventType.finalizedCheckpoint]: EventType.finalizedCheckpoint,
[EventType.chainReorg]: EventType.chainReorg,
Expand Down Expand Up @@ -85,6 +91,8 @@ export type EventData = {
};
[EventType.attestation]: phase0.Attestation;
[EventType.voluntaryExit]: phase0.SignedVoluntaryExit;
[EventType.proposerSlashing]: phase0.ProposerSlashing;
[EventType.attesterSlashing]: phase0.AttesterSlashing;
[EventType.blsToExecutionChange]: capella.SignedBLSToExecutionChange;
[EventType.finalizedCheckpoint]: {
block: RootHex;
Expand Down Expand Up @@ -174,6 +182,8 @@ export function getTypeByEvent(): {[K in EventType]: TypeJson<EventData[K]>} {

[EventType.attestation]: ssz.phase0.Attestation,
[EventType.voluntaryExit]: ssz.phase0.SignedVoluntaryExit,
[EventType.proposerSlashing]: ssz.phase0.ProposerSlashing,
[EventType.attesterSlashing]: ssz.phase0.AttesterSlashing,
[EventType.blsToExecutionChange]: ssz.capella.SignedBLSToExecutionChange,

[EventType.finalizedCheckpoint]: new ContainerType(
Expand Down
62 changes: 62 additions & 0 deletions packages/api/test/unit/beacon/testData/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,68 @@ export const eventTestData: EventData = {
signature:
"0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
}),
[EventType.proposerSlashing]: ssz.phase0.ProposerSlashing.fromJson({
signed_header_1: {
message: {
slot: "0",
proposer_index: "0",
parent_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
state_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
body_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
signature:
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
signed_header_2: {
message: {
slot: "0",
proposer_index: "0",
parent_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
state_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
body_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
signature:
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
}),
[EventType.attesterSlashing]: ssz.phase0.AttesterSlashing.fromJson({
attestation_1: {
attesting_indices: ["0", "1"],
data: {
slot: "0",
index: "0",
beacon_block_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
source: {
epoch: "0",
root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
target: {
epoch: "0",
root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
},
signature:
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
attestation_2: {
attesting_indices: ["0", "1"],
data: {
slot: "0",
index: "0",
beacon_block_root: "0x0000000000000000000000000000000000000000000000000000000000000000",
source: {
epoch: "0",
root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
target: {
epoch: "0",
root: "0x0000000000000000000000000000000000000000000000000000000000000000",
},
},
signature:
"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
}),
[EventType.blsToExecutionChange]: ssz.capella.SignedBLSToExecutionChange.fromJson({
message: {
validator_index: "1",
Expand Down
10 changes: 10 additions & 0 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@ export async function importBlock(
this.emitter.emit(routes.events.EventType.attestation, attestation);
}
}
if (this.emitter.listenerCount(routes.events.EventType.attesterSlashing)) {
for (const attesterSlashing of block.message.body.attesterSlashings) {
this.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
}
}
if (this.emitter.listenerCount(routes.events.EventType.proposerSlashing)) {
for (const proposerSlashing of block.message.body.proposerSlashings) {
this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
}
}
}

// Register stat metrics about the block after importing it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler
} catch (e) {
logger.error("Error adding attesterSlashing to pool", {}, e as Error);
}

chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
},

[GossipType.proposer_slashing]: async ({
Expand All @@ -470,6 +472,8 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler
} catch (e) {
logger.error("Error adding attesterSlashing to pool", {}, e as Error);
}

chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
},

[GossipType.voluntary_exit]: async ({gossipData, topic}: GossipHandlerParamGeneric<GossipType.voluntary_exit>) => {
Expand Down
64 changes: 64 additions & 0 deletions packages/beacon-node/test/e2e/network/gossipsub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,70 @@ function runTests({useWorker}: {useWorker: boolean}): void {
);
});

it("Publish and receive an attesterSlashing", async function () {
Copy link
Member

Choose a reason for hiding this comment

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

The amount of copy pasted code is quite high in this file, maybe something that could be improved in this PR (or later one) to make it easier to add new events in the future

Copy link
Member Author

Choose a reason for hiding this comment

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

I would suggest to do that in a different PR for extra clarity.

let onAttesterSlashingChange: (payload: Uint8Array) => void;
const onAttesterSlashingChangePromise = new Promise<Uint8Array>((resolve) => (onAttesterSlashingChange = resolve));

const {netA, netB} = await mockModules({
[GossipType.attester_slashing]: async ({gossipData}: GossipHandlerParamGeneric<GossipType.attester_slashing>) => {
onAttesterSlashingChange(gossipData.serializedData);
},
});

await Promise.all([onPeerConnect(netA), onPeerConnect(netB), connect(netA, netB)]);
expect(netA.getConnectedPeerCount()).toBe(1);
expect(netB.getConnectedPeerCount()).toBe(1);

await netA.subscribeGossipCoreTopics();
await netB.subscribeGossipCoreTopics();

// Wait to have a peer connected to a topic
while (!netA.closed) {
await sleep(500);
if (await hasSomeMeshPeer(netA)) {
break;
}
}

const attesterSlashing = ssz.phase0.AttesterSlashing.defaultValue();
await netA.publishAttesterSlashing(attesterSlashing);

const received = await onAttesterSlashingChangePromise;
expect(Buffer.from(received)).toEqual(Buffer.from(ssz.phase0.AttesterSlashing.serialize(attesterSlashing)));
});

it("Publish and receive a proposerSlashing", async function () {
let onProposerSlashingChange: (payload: Uint8Array) => void;
const onProposerSlashingChangePromise = new Promise<Uint8Array>((resolve) => (onProposerSlashingChange = resolve));

const {netA, netB} = await mockModules({
[GossipType.proposer_slashing]: async ({gossipData}: GossipHandlerParamGeneric<GossipType.proposer_slashing>) => {
onProposerSlashingChange(gossipData.serializedData);
},
});

await Promise.all([onPeerConnect(netA), onPeerConnect(netB), connect(netA, netB)]);
expect(netA.getConnectedPeerCount()).toBe(1);
expect(netB.getConnectedPeerCount()).toBe(1);

await netA.subscribeGossipCoreTopics();
await netB.subscribeGossipCoreTopics();

// Wait to have a peer connected to a topic
while (!netA.closed) {
await sleep(500);
if (await hasSomeMeshPeer(netA)) {
break;
}
}

const proposerSlashing = ssz.phase0.ProposerSlashing.defaultValue();
await netA.publishProposerSlashing(proposerSlashing);

const received = await onProposerSlashingChangePromise;
expect(Buffer.from(received)).toEqual(Buffer.from(ssz.phase0.ProposerSlashing.serialize(proposerSlashing)));
});

it("Publish and receive a LightClientOptimisticUpdate", async function () {
let onLightClientOptimisticUpdate: (ou: Uint8Array) => void;
const onLightClientOptimisticUpdatePromise = new Promise<Uint8Array>(
Expand Down