From 0282fff31353a2bca4903faf481bebec0f6f891c Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Mon, 5 Feb 2024 11:32:45 +0100 Subject: [PATCH 1/3] feat: emit slashing events --- packages/api/src/beacon/routes/events.ts | 10 +++ .../api/test/unit/beacon/testData/events.ts | 62 +++++++++++++++++++ .../src/chain/blocks/importBlock.ts | 10 +++ .../src/network/processor/gossipHandlers.ts | 4 ++ 4 files changed, 86 insertions(+) diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index 41a8e2e9cad9..1db61bafafbd 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -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", /** The node has received a valid blsToExecutionChange (from P2P or API) */ blsToExecutionChange = "bls_to_execution_change", /** Finalized checkpoint has been updated */ @@ -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, @@ -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; @@ -174,6 +182,8 @@ export function getTypeByEvent(): {[K in EventType]: TypeJson} { [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( diff --git a/packages/api/test/unit/beacon/testData/events.ts b/packages/api/test/unit/beacon/testData/events.ts index 7bfac9a59a88..08dd27e09d76 100644 --- a/packages/api/test/unit/beacon/testData/events.ts +++ b/packages/api/test/unit/beacon/testData/events.ts @@ -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", diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 89ed52b66750..c8fc62eaa622 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -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 diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 9073a204b785..bdd5f1917dc5 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -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 ({ @@ -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) => { From b22e7ad4370a124448ea939ee60c9417abdea31d Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Mon, 5 Feb 2024 14:49:47 +0100 Subject: [PATCH 2/3] chore: added tests --- .../test/e2e/network/gossipsub.test.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/beacon-node/test/e2e/network/gossipsub.test.ts b/packages/beacon-node/test/e2e/network/gossipsub.test.ts index c7b3dbefbe77..fd1794bf549d 100644 --- a/packages/beacon-node/test/e2e/network/gossipsub.test.ts +++ b/packages/beacon-node/test/e2e/network/gossipsub.test.ts @@ -138,6 +138,70 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); + it("Publish and receive an attesterSlashing", async function () { + let onAttesterSlashingChange: (payload: Uint8Array) => void; + const onAttesterSlashingChangePromise = new Promise((resolve) => (onAttesterSlashingChange = resolve)); + + const {netA, netB} = await mockModules({ + [GossipType.attester_slashing]: async ({gossipData}: GossipHandlerParamGeneric) => { + 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((resolve) => (onProposerSlashingChange = resolve)); + + const {netA, netB} = await mockModules({ + [GossipType.proposer_slashing]: async ({gossipData}: GossipHandlerParamGeneric) => { + 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( From 0fc928eb82a3fc23994eb7c8d3c336d16cd7da72 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 8 Feb 2024 00:42:42 -0800 Subject: [PATCH 3/3] chore: wording Co-authored-by: Nico Flaig --- packages/api/src/beacon/routes/events.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index 1db61bafafbd..ddcc57104523 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -35,9 +35,9 @@ 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 */ + /** The node has received a valid proposer slashing (from P2P or API) */ proposerSlashing = "proposer_slashing", - /** The node has received an AttesterSlashing (from P2P or API) that passes validation rules of the `attester_slashing` topic */ + /** The node has received a valid attester slashing (from P2P or API) */ attesterSlashing = "attester_slashing", /** The node has received a valid blsToExecutionChange (from P2P or API) */ blsToExecutionChange = "bls_to_execution_change",