From 26ea9fa05b98bebeed502d0bb6667a869d19ba12 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 29 Aug 2025 12:40:35 +0100 Subject: [PATCH] refactor: optimize event listener count checks (#8293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR optimizes event listener count checks for `routes.events.EventType` emissions, following the principle that **listener count checks should only be added where there's expensive preprocessing work before emit**, since `emit()` is already a no-op when no listeners exist. ## Changes Made ### ✅ Added listener count checks (where expensive preprocessing occurs): **`packages/beacon-node/src/network/processor/gossipHandlers.ts`:** - **`dataColumnSidecar`** emission: Added check to avoid `kzgCommitments.map(toHex)` array mapping when no listeners **`packages/beacon-node/src/chain/prepareNextSlot.ts`:** - **`payloadAttributes`** emission: Added check to avoid `await getPayloadAttributesForSSE()` async function call when no listeners ### ✅ Removed unnecessary listener count checks (where no expensive preprocessing occurs): **`packages/beacon-node/src/network/processor/gossipHandlers.ts`:** - **`blockGossip`** emission: Removed check since it only uses existing variables `{slot, block: blockRootHex}` **`packages/beacon-node/src/api/impl/beacon/blocks/index.ts`:** - **`blockGossip`** emission: Removed check since it's a simple emission with existing variables ### ✅ Kept existing correct checks (for expensive operations): - **`blobSidecar`** emissions: Keep checks for `toHex()` conversions and `kzgCommitmentToVersionedHash()` - **All loop-based emissions** in `importBlock.ts`: Keep checks for `for` loop iterations - **`block`** emission: Keep check for `isOptimisticBlock()` computation ## Performance Benefits 1. **Reduced CPU usage**: Expensive operations like async function calls and array mapping only occur when needed 2. **Better resource utilization**: Memory allocations and computations are avoided when events won't be consumed 3. **Cleaner code**: Removed redundant checks where the emit operation itself is lightweight ## Key Principle Applied **Only add listener count checks where there's expensive preprocessing work before emission:** - ✅ Function calls, array operations, crypto operations - ❌ Simple emissions using existing variables Since `emit()` is already a no-op when no listeners exist, explicit checks are only beneficial when avoiding preprocessing overhead. ## Testing - ✅ Type checks pass - ✅ Build succeeds - ✅ Linting passes Resolves #7996 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude --- .../src/api/impl/beacon/blocks/index.ts | 4 +--- .../beacon-node/src/chain/prepareNextSlot.ts | 5 ++++- .../src/network/processor/gossipHandlers.ts | 18 +++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts index 09b3be0604..a0349d8cec 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -308,9 +308,7 @@ export function getBeaconBlockApi({ } } - if (chain.emitter.listenerCount(routes.events.EventType.blockGossip)) { - chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRoot}); - } + chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRoot}); if (blockForImport.type === BlockInputType.availableData) { if (isForkPostFulu(blockForImport.blockData.fork)) { diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 2b2f31f5fa..cfa2979cff 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -191,7 +191,10 @@ export class PrepareNextSlotScheduler { this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition); // If emitPayloadAttributes is true emit a SSE payloadAttributes event - if (this.chain.opts.emitPayloadAttributes === true) { + if ( + this.chain.opts.emitPayloadAttributes === true && + this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes) + ) { const data = await getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, { prepareState: updatedPrepareState, prepareSlot, diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index ddc152c641..e07bfc2a9d 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -176,9 +176,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand logger.debug("Validated gossip block", {...logCtx, recvToValidation, validationTime}); - if (chain.emitter.listenerCount(routes.events.EventType.blockGossip)) { - chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex}); - } + chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex}); return blockInput; } catch (e) { @@ -311,12 +309,14 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand metrics?.gossipBlob.recvToValidation.observe(recvToValidation); metrics?.gossipBlob.validationTime.observe(validationTime); - chain.emitter.emit(routes.events.EventType.dataColumnSidecar, { - blockRoot: blockRootHex, - slot, - index: dataColumnSidecar.index, - kzgCommitments: dataColumnSidecar.kzgCommitments.map(toHex), - }); + if (chain.emitter.listenerCount(routes.events.EventType.dataColumnSidecar)) { + chain.emitter.emit(routes.events.EventType.dataColumnSidecar, { + blockRoot: blockRootHex, + slot, + index: dataColumnSidecar.index, + kzgCommitments: dataColumnSidecar.kzgCommitments.map(toHex), + }); + } logger.debug("Received gossip dataColumn", { slot: slot,