From 44b215667efb60366f28f7b11c97c7c092a3dffc Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 14 Aug 2024 03:39:04 +0700 Subject: [PATCH] fix: reuse Buffer instance (#7016) * fix: improve message id to string conversion * fix: use shared Buffers for sszBytes util * feat: implement toRootHex() * fix: lint and check-types * Fix type checks * Add comment to vitest config * Update packages/utils/src/bytes.ts --------- Co-authored-by: Nico Flaig Co-authored-by: Cayman --- .../beacon-node/src/chain/balancesCache.ts | 4 +- .../src/chain/blocks/importBlock.ts | 10 ++-- .../chain/blocks/verifyBlocksSanityChecks.ts | 4 +- packages/beacon-node/src/chain/chain.ts | 18 ++++---- .../src/chain/errors/attestationError.ts | 4 +- .../src/chain/errors/blockError.ts | 7 ++- .../beacon-node/src/chain/forkChoice/index.ts | 16 +++---- packages/beacon-node/src/chain/initState.ts | 13 +++--- .../src/chain/lightClient/index.ts | 20 ++++---- .../opPools/aggregatedAttestationPool.ts | 5 +- .../beacon-node/src/chain/opPools/opPool.ts | 3 +- .../chain/opPools/syncCommitteeMessagePool.ts | 8 ++-- .../opPools/syncContributionAndProofPool.ts | 8 ++-- .../beacon-node/src/chain/regen/queued.ts | 7 ++- packages/beacon-node/src/chain/regen/regen.ts | 8 ++-- .../seenCache/seenCommitteeContribution.ts | 5 +- .../chain/seenCache/seenGossipBlockInput.ts | 9 ++-- .../beacon-node/src/chain/shufflingCache.ts | 5 +- .../chain/stateCache/blockStateCacheImpl.ts | 8 ++-- .../chain/stateCache/fifoBlockStateCache.ts | 6 +-- .../stateCache/inMemoryCheckpointsCache.ts | 9 ++-- .../stateCache/persistentCheckpointsCache.ts | 16 +++---- .../src/chain/validation/aggregateAndProof.ts | 4 +- .../src/chain/validation/attestation.ts | 12 ++--- .../beacon-node/src/chain/validation/block.ts | 7 ++- .../src/chain/validation/syncCommittee.ts | 4 +- .../beacon-node/src/eth1/utils/deposits.ts | 6 +-- .../src/network/gossip/encoding.ts | 9 +++- .../src/network/processor/gossipHandlers.ts | 9 ++-- .../reqresp/handlers/beaconBlocksByRoot.ts | 4 +- .../beacon-node/src/sync/backfill/backfill.ts | 25 +++++----- packages/beacon-node/src/sync/range/chain.ts | 5 +- packages/beacon-node/src/sync/unknownBlock.ts | 26 +++++------ packages/beacon-node/src/util/sszBytes.ts | 28 +++++++---- .../test/perf/network/gossip/encoding.test.ts | 46 +++++++++++++++++++ .../fork-choice/src/forkChoice/forkChoice.ts | 24 +++++----- packages/fork-choice/src/forkChoice/store.ts | 4 +- packages/utils/src/bytes.ts | 11 +++++ packages/utils/test/perf/bytes.test.ts | 27 +++++++++++ packages/utils/vitest.config.ts | 5 ++ 40 files changed, 268 insertions(+), 181 deletions(-) create mode 100644 packages/beacon-node/test/perf/network/gossip/encoding.test.ts create mode 100644 packages/utils/test/perf/bytes.test.ts diff --git a/packages/beacon-node/src/chain/balancesCache.ts b/packages/beacon-node/src/chain/balancesCache.ts index 5f4cf218c341..50a86b31b6c8 100644 --- a/packages/beacon-node/src/chain/balancesCache.ts +++ b/packages/beacon-node/src/chain/balancesCache.ts @@ -7,7 +7,7 @@ import { } from "@lodestar/state-transition"; import {CheckpointWithHex} from "@lodestar/fork-choice"; import {Epoch, RootHex} from "@lodestar/types"; -import {toHexString} from "@lodestar/utils"; +import {toRootHex} from "@lodestar/utils"; /** The number of validator balance sets that are cached within `CheckpointBalancesCache`. */ const MAX_BALANCE_CACHE_SIZE = 4; @@ -33,7 +33,7 @@ export class CheckpointBalancesCache { const epoch = state.epochCtx.epoch; const epochBoundarySlot = computeStartSlotAtEpoch(epoch); const epochBoundaryRoot = - epochBoundarySlot === state.slot ? blockRootHex : toHexString(getBlockRootAtSlot(state, epochBoundarySlot)); + epochBoundarySlot === state.slot ? blockRootHex : toRootHex(getBlockRootAtSlot(state, epochBoundarySlot)); const index = this.items.findIndex((item) => item.epoch === epoch && item.rootHex == epochBoundaryRoot); if (index === -1) { diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 9d46410a8638..360a3f8f5db9 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -10,7 +10,7 @@ import { } from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; import {ForkChoiceError, ForkChoiceErrorCode, EpochDifference, AncestorStatus} from "@lodestar/fork-choice"; -import {isErrorAborted} from "@lodestar/utils"; +import {isErrorAborted, toRootHex} from "@lodestar/utils"; import {ZERO_HASH_HEX} from "../../constants/index.js"; import {toCheckpointHex} from "../stateCache/index.js"; import {isOptimisticBlock} from "../../util/forkChoice.js"; @@ -62,7 +62,7 @@ export async function importBlock( const {block, source} = blockInput; const {slot: blockSlot} = block.message; const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message); - const blockRootHex = toHexString(blockRoot); + const blockRootHex = toRootHex(blockRoot); const currentEpoch = computeEpochAtSlot(this.forkChoice.getTime()); const blockEpoch = computeEpochAtSlot(blockSlot); const parentEpoch = computeEpochAtSlot(parentBlockSlot); @@ -123,7 +123,7 @@ export async function importBlock( const indexedAttestation = postState.epochCtx.getIndexedAttestation(attestation); const {target, beaconBlockRoot} = attestation.data; - const attDataRoot = toHexString(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data)); + const attDataRoot = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data)); this.seenAggregatedAttestations.add( target.epoch, attDataRoot, @@ -371,9 +371,9 @@ export async function importBlock( const preFinalizedEpoch = parentBlockSummary.finalizedEpoch; if (finalizedEpoch > preFinalizedEpoch) { this.emitter.emit(routes.events.EventType.finalizedCheckpoint, { - block: toHexString(finalizedCheckpoint.root), + block: toRootHex(finalizedCheckpoint.root), epoch: finalizedCheckpoint.epoch, - state: toHexString(checkpointState.hashTreeRoot()), + state: toRootHex(checkpointState.hashTreeRoot()), executionOptimistic: false, }); this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint)); diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts index e62355a4889d..fcbcfea05d6e 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts @@ -2,7 +2,7 @@ import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {Slot} from "@lodestar/types"; -import {toHexString} from "@lodestar/utils"; +import {toHexString, toRootHex} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockInput, ImportBlockOpts} from "./types.js"; @@ -67,7 +67,7 @@ export function verifyBlocksSanityChecks( parentBlockSlot = relevantBlocks[relevantBlocks.length - 1].block.message.slot; } else { // When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice. - const parentRoot = toHexString(block.message.parentRoot); + const parentRoot = toRootHex(block.message.parentRoot); parentBlock = chain.forkChoice.getBlockHex(parentRoot); if (!parentBlock) { throw new BlockError(block, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index a12ee4a21f64..43f72d81e882 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {CompositeTypeAny, fromHexString, TreeView, Type, toHexString} from "@chainsafe/ssz"; +import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz"; import { BeaconStateAllForks, CachedBeaconStateAllForks, @@ -35,7 +35,7 @@ import { } from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; -import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils"; +import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex, toRootHex} from "@lodestar/utils"; import {ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; @@ -592,7 +592,7 @@ export class BeaconChain implements IBeaconChain { async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise { const {slot, parentBlockRoot} = blockAttributes; const state = await this.regen.getBlockSlotState( - toHexString(parentBlockRoot), + toRootHex(parentBlockRoot), slot, {dontTransferCache: true}, RegenCaller.produceBlock @@ -641,7 +641,7 @@ export class BeaconChain implements IBeaconChain { shouldOverrideBuilder?: boolean; }> { const state = await this.regen.getBlockSlotState( - toHexString(parentBlockRoot), + toRootHex(parentBlockRoot), slot, {dontTransferCache: true}, RegenCaller.produceBlock @@ -673,7 +673,7 @@ export class BeaconChain implements IBeaconChain { : this.config.getExecutionForkTypes(slot).BlindedBeaconBlockBody.hashTreeRoot(body as BlindedBeaconBlockBody); this.logger.debug("Computing block post state from the produced body", { slot, - bodyRoot: toHexString(bodyRoot), + bodyRoot: toRootHex(bodyRoot), blockType, }); @@ -1152,10 +1152,10 @@ export class BeaconChain implements IBeaconChain { const preState = this.regen.getPreStateSync(block); if (preState === null) { - throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); + throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`); } - const postState = this.regen.getStateSync(toHexString(block.stateRoot)) ?? undefined; + const postState = this.regen.getStateSync(toRootHex(block.stateRoot)) ?? undefined; return computeBlockRewards(block, preState.clone(), postState?.clone()); } @@ -1173,7 +1173,7 @@ export class BeaconChain implements IBeaconChain { } const {executionOptimistic, finalized} = stateResult; - const stateRoot = toHexString(stateResult.state.hashTreeRoot()); + const stateRoot = toRootHex(stateResult.state.hashTreeRoot()); const cachedState = this.regen.getStateSync(stateRoot); @@ -1193,7 +1193,7 @@ export class BeaconChain implements IBeaconChain { const preState = this.regen.getPreStateSync(block); if (preState === null) { - throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); + throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`); } return computeSyncCommitteeRewards(block, preState.clone(), validatorIds); diff --git a/packages/beacon-node/src/chain/errors/attestationError.ts b/packages/beacon-node/src/chain/errors/attestationError.ts index 8e0dc925f32e..fa0635bc0115 100644 --- a/packages/beacon-node/src/chain/errors/attestationError.ts +++ b/packages/beacon-node/src/chain/errors/attestationError.ts @@ -1,5 +1,5 @@ -import {toHexString} from "@chainsafe/ssz"; import {Epoch, Slot, ValidatorIndex, RootHex} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; import {GossipActionError} from "./gossipValidation.js"; export enum AttestationErrorCode { @@ -167,7 +167,7 @@ export class AttestationError extends GossipActionError { const type = this.type; switch (type.code) { case AttestationErrorCode.UNKNOWN_TARGET_ROOT: - return {code: type.code, root: toHexString(type.root)}; + return {code: type.code, root: toRootHex(type.root)}; case AttestationErrorCode.MISSING_STATE_TO_VERIFY_ATTESTATION: // TODO: The stack trace gets lost here return {code: type.code, error: type.error.message}; diff --git a/packages/beacon-node/src/chain/errors/blockError.ts b/packages/beacon-node/src/chain/errors/blockError.ts index 5f12bd939342..6280533c7a68 100644 --- a/packages/beacon-node/src/chain/errors/blockError.ts +++ b/packages/beacon-node/src/chain/errors/blockError.ts @@ -1,6 +1,5 @@ -import {toHexString} from "@chainsafe/ssz"; import {RootHex, SignedBeaconBlock, Slot, ValidatorIndex} from "@lodestar/types"; -import {LodestarError} from "@lodestar/utils"; +import {LodestarError, toRootHex} from "@lodestar/utils"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {ExecutionPayloadStatus} from "../../execution/engine/interface.js"; import {QueueErrorCode} from "../../util/queue/index.js"; @@ -151,8 +150,8 @@ export function renderBlockErrorType(type: BlockErrorType): Record { this.metrics?.lightclientServer.onSyncAggregate.inc({event: "processed"}); - const signedBlockRootHex = toHexString(signedBlockRoot); + const signedBlockRootHex = toRootHex(signedBlockRoot); const attestedData = this.prevHeadData.get(signedBlockRootHex); if (!attestedData) { // Log cacheSize since at start this.prevHeadData will be empty @@ -574,7 +574,7 @@ export class LightClientServer { } catch (e) { this.logger.error( "Error updating best LightClientUpdate", - {syncPeriod, slot: attestedHeader.beacon.slot, blockRoot: toHexString(attestedData.blockRoot)}, + {syncPeriod, slot: attestedHeader.beacon.slot, blockRoot: toRootHex(attestedData.blockRoot)}, e as Error ); } @@ -619,7 +619,7 @@ export class LightClientServer { const syncCommitteeWitness = await this.db.syncCommitteeWitness.get(attestedData.blockRoot); if (!syncCommitteeWitness) { - throw Error(`syncCommitteeWitness not available at ${toHexString(attestedData.blockRoot)}`); + throw Error(`syncCommitteeWitness not available at ${toRootHex(attestedData.blockRoot)}`); } const nextSyncCommittee = await this.db.syncCommittee.get(syncCommitteeWitness.nextSyncCommitteeRoot); if (!nextSyncCommittee) { @@ -697,7 +697,7 @@ export class LightClientServer { * Get finalized header from db. Keeps a small in-memory cache to speed up most of the lookups */ private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise { - const finalizedBlockRootHex = toHexString(finalizedBlockRoot); + const finalizedBlockRootHex = toRootHex(finalizedBlockRoot); const cachedFinalizedHeader = this.checkpointHeaders.get(finalizedBlockRootHex); if (cachedFinalizedHeader) { return cachedFinalizedHeader; diff --git a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts index 556e1f397d60..d896c9d3cc35 100644 --- a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {aggregateSignatures} from "@chainsafe/blst"; import {ForkName, ForkSeq, MAX_ATTESTATIONS, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; import {phase0, Epoch, Slot, ssz, ValidatorIndex, RootHex} from "@lodestar/types"; @@ -11,7 +10,7 @@ import { getBlockRootAtSlot, } from "@lodestar/state-transition"; import {IForkChoice, EpochDifference} from "@lodestar/fork-choice"; -import {toHex, MapDef} from "@lodestar/utils"; +import {toHex, MapDef, toRootHex} from "@lodestar/utils"; import {intersectUint8Arrays, IntersectResult} from "../../util/bitArray.js"; import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js"; import {InsertOutcome} from "./types.js"; @@ -573,7 +572,7 @@ function isValidShuffling( // Otherwise the shuffling is determined by the block at the end of the target epoch // minus the shuffling lookahead (usually 2). We call this the "pivot". const pivotSlot = computeStartSlotAtEpoch(targetEpoch - 1) - 1; - const stateDependentRoot = toHexString(getBlockRootAtSlot(state, pivotSlot)); + const stateDependentRoot = toRootHex(getBlockRootAtSlot(state, pivotSlot)); // Use fork choice's view of the block DAG to quickly evaluate whether the attestation's // pivot block is the same as the current state's pivot block. If it is, then the diff --git a/packages/beacon-node/src/chain/opPools/opPool.ts b/packages/beacon-node/src/chain/opPools/opPool.ts index 69c331f6fd39..a991a6e8630a 100644 --- a/packages/beacon-node/src/chain/opPools/opPool.ts +++ b/packages/beacon-node/src/chain/opPools/opPool.ts @@ -16,6 +16,7 @@ import { ForkSeq, } from "@lodestar/params"; import {Epoch, phase0, capella, ssz, ValidatorIndex, SignedBeaconBlock} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; import {IBeaconDb} from "../../db/index.js"; import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js"; import {BlockType} from "../interface.js"; @@ -135,7 +136,7 @@ export class OpPool { if (!rootHash) rootHash = ssz.phase0.AttesterSlashing.hashTreeRoot(attesterSlashing); // TODO: Do once and cache attached to the AttesterSlashing object const intersectingIndices = getAttesterSlashableIndices(attesterSlashing); - this.attesterSlashings.set(toHexString(rootHash), { + this.attesterSlashings.set(toRootHex(rootHash), { attesterSlashing, intersectingIndices, }); diff --git a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts index 90a310841f01..bbaba1835dce 100644 --- a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts +++ b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts @@ -1,8 +1,8 @@ -import {BitArray, toHexString} from "@chainsafe/ssz"; +import {BitArray} from "@chainsafe/ssz"; import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {altair, Root, Slot, SubcommitteeIndex} from "@lodestar/types"; -import {MapDef} from "@lodestar/utils"; +import {MapDef, toRootHex} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js"; import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js"; @@ -64,7 +64,7 @@ export class SyncCommitteeMessagePool { // TODO: indexInSubcommittee: number should be indicesInSyncCommittee add(subnet: Subnet, signature: altair.SyncCommitteeMessage, indexInSubcommittee: number): InsertOutcome { const {slot, beaconBlockRoot} = signature; - const rootHex = toHexString(beaconBlockRoot); + const rootHex = toRootHex(beaconBlockRoot); const lowestPermissibleSlot = this.lowestPermissibleSlot; // Reject if too old. @@ -99,7 +99,7 @@ export class SyncCommitteeMessagePool { * This is for the aggregator to produce ContributionAndProof. */ getContribution(subnet: SubcommitteeIndex, slot: Slot, prevBlockRoot: Root): altair.SyncCommitteeContribution | null { - const contribution = this.contributionsByRootBySubnetBySlot.get(slot)?.get(subnet)?.get(toHexString(prevBlockRoot)); + const contribution = this.contributionsByRootBySubnetBySlot.get(slot)?.get(subnet)?.get(toRootHex(prevBlockRoot)); if (!contribution) { return null; } diff --git a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts index 7834ae534501..ff0feea891e1 100644 --- a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts +++ b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts @@ -1,9 +1,9 @@ -import {BitArray, toHexString} from "@chainsafe/ssz"; +import {BitArray} from "@chainsafe/ssz"; import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params"; import {altair, Slot, Root, ssz} from "@lodestar/types"; import {G2_POINT_AT_INFINITY} from "@lodestar/state-transition"; -import {MapDef} from "@lodestar/utils"; +import {MapDef, toRootHex} from "@lodestar/utils"; import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js"; import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js"; @@ -72,7 +72,7 @@ export class SyncContributionAndProofPool { add(contributionAndProof: altair.ContributionAndProof, syncCommitteeParticipants: number): InsertOutcome { const {contribution} = contributionAndProof; const {slot, beaconBlockRoot} = contribution; - const rootHex = toHexString(beaconBlockRoot); + const rootHex = toRootHex(beaconBlockRoot); // Reject if too old. if (slot < this.lowestPermissibleSlot) { @@ -100,7 +100,7 @@ export class SyncContributionAndProofPool { * This is for the block factory, the same to process_sync_committee_contributions in the spec. */ getAggregate(slot: Slot, prevBlockRoot: Root): altair.SyncAggregate { - const bestContributionBySubnet = this.bestContributionBySubnetRootBySlot.get(slot)?.get(toHexString(prevBlockRoot)); + const bestContributionBySubnet = this.bestContributionBySubnetRootBySlot.get(slot)?.get(toRootHex(prevBlockRoot)); if (!bestContributionBySubnet || bestContributionBySubnet.size === 0) { // TODO: Add metric for missing SyncAggregate // Must return signature as G2_POINT_AT_INFINITY when participating bits are empty diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index ad673b334bd1..082ffbe271e4 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -1,8 +1,7 @@ -import {toHexString} from "@chainsafe/ssz"; import {phase0, Slot, RootHex, Epoch, BeaconBlock} from "@lodestar/types"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; -import {Logger} from "@lodestar/utils"; +import {Logger, toRootHex} from "@lodestar/utils"; import {routes} from "@lodestar/api"; import {CheckpointHex, toCheckpointHex} from "../stateCache/index.js"; import {Metrics} from "../../metrics/index.js"; @@ -89,7 +88,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { block: BeaconBlock, opts: StateCloneOpts = {dontTransferCache: true} ): CachedBeaconStateAllForks | null { - const parentRoot = toHexString(block.parentRoot); + const parentRoot = toRootHex(block.parentRoot); const parentBlock = this.forkChoice.getBlockHex(parentRoot); if (!parentBlock) { throw new RegenError({ @@ -167,7 +166,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { updateHeadState(newHead: ProtoBlock, maybeHeadState: CachedBeaconStateAllForks): void { const {stateRoot: newHeadStateRoot, blockRoot: newHeadBlockRoot, slot: newHeadSlot} = newHead; - const maybeHeadStateRoot = toHexString(maybeHeadState.hashTreeRoot()); + const maybeHeadStateRoot = toRootHex(maybeHeadState.hashTreeRoot()); const logCtx = { newHeadSlot, newHeadBlockRoot, diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 2b6fc835cf7c..409c12c77b21 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -1,4 +1,4 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {fromHexString} from "@chainsafe/ssz"; import {phase0, Slot, RootHex, BeaconBlock} from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -10,7 +10,7 @@ import { stateTransition, } from "@lodestar/state-transition"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; -import {Logger} from "@lodestar/utils"; +import {Logger, toRootHex} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; import {Metrics} from "../../metrics/index.js"; @@ -89,7 +89,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { allowDiskReload = false ): Promise { const checkpointStartSlot = computeStartSlotAtEpoch(cp.epoch); - return this.getBlockSlotState(toHexString(cp.root), checkpointStartSlot, opts, regenCaller, allowDiskReload); + return this.getBlockSlotState(toRootHex(cp.root), checkpointStartSlot, opts, regenCaller, allowDiskReload); } /** @@ -224,7 +224,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { this.modules.metrics ); - const stateRoot = toHexString(state.hashTreeRoot()); + const stateRoot = toRootHex(state.hashTreeRoot()); if (b.stateRoot !== stateRoot) { throw new RegenError({ slot: b.slot, diff --git a/packages/beacon-node/src/chain/seenCache/seenCommitteeContribution.ts b/packages/beacon-node/src/chain/seenCache/seenCommitteeContribution.ts index fedaff8225d6..86ade618b1d1 100644 --- a/packages/beacon-node/src/chain/seenCache/seenCommitteeContribution.ts +++ b/packages/beacon-node/src/chain/seenCache/seenCommitteeContribution.ts @@ -1,7 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {Slot, ValidatorIndex} from "@lodestar/types"; import {ContributionAndProof, SyncCommitteeContribution} from "@lodestar/types/altair"; -import {MapDef} from "@lodestar/utils"; +import {MapDef, toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {isSuperSetOrEqual} from "../../util/bitArray.js"; import {AggregationInfo, insertDesc} from "./seenAggregateAndProof.js"; @@ -101,5 +100,5 @@ function seenAggregatorKey(subcommitteeIndex: number, aggregatorIndex: Validator function toContributionDataKey(contribution: SyncCommitteeContribution): ContributionDataKey { const {slot, beaconBlockRoot, subcommitteeIndex} = contribution; - return `${slot} - ${toHexString(beaconBlockRoot)} - ${subcommitteeIndex}`; + return `${slot} - ${toRootHex(beaconBlockRoot)} - ${subcommitteeIndex}`; } diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 6b51332353f2..3806668436d8 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -1,7 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {deneb, RootHex, SignedBeaconBlock, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; -import {pruneSetToMax} from "@lodestar/utils"; +import {pruneSetToMax, toRootHex} from "@lodestar/utils"; import {BLOBSIDECAR_FIXED_SIZE, isForkBlobs, ForkName} from "@lodestar/params"; import { @@ -81,9 +80,7 @@ export class SeenGossipBlockInput { const {signedBlock, blockBytes} = gossipedInput; fork = config.getForkName(signedBlock.message.slot); - blockHex = toHexString( - config.getForkTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message) - ); + blockHex = toRootHex(config.getForkTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message)); blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); blockCache.block = signedBlock; @@ -93,7 +90,7 @@ export class SeenGossipBlockInput { const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobSidecar.signedBlockHeader.message); fork = config.getForkName(blobSidecar.signedBlockHeader.message.slot); - blockHex = toHexString(blockRoot); + blockHex = toRootHex(blockRoot); blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); // TODO: freetheblobs check if its the same blob or a duplicate and throw/take actions diff --git a/packages/beacon-node/src/chain/shufflingCache.ts b/packages/beacon-node/src/chain/shufflingCache.ts index 23177142d846..12dd1bf3e9ae 100644 --- a/packages/beacon-node/src/chain/shufflingCache.ts +++ b/packages/beacon-node/src/chain/shufflingCache.ts @@ -1,7 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {CachedBeaconStateAllForks, EpochShuffling, getShufflingDecisionBlock} from "@lodestar/state-transition"; import {Epoch, RootHex, ssz} from "@lodestar/types"; -import {MapDef, pruneSetToMax} from "@lodestar/utils"; +import {MapDef, pruneSetToMax, toRootHex} from "@lodestar/utils"; import {GENESIS_SLOT} from "@lodestar/params"; import {Metrics} from "../metrics/metrics.js"; import {computeAnchorCheckpoint} from "./initState.js"; @@ -206,5 +205,5 @@ function isPromiseCacheItem(item: CacheItem): item is PromiseCacheItem { function getDecisionBlock(state: CachedBeaconStateAllForks, epoch: Epoch): RootHex { return state.slot > GENESIS_SLOT ? getShufflingDecisionBlock(state, epoch) - : toHexString(ssz.phase0.BeaconBlockHeader.hashTreeRoot(computeAnchorCheckpoint(state.config, state).blockHeader)); + : toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(computeAnchorCheckpoint(state.config, state).blockHeader)); } diff --git a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts index fdeb3ed5a659..05073d0e515f 100644 --- a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts +++ b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts @@ -1,7 +1,7 @@ -import {toHexString} from "@chainsafe/ssz"; import {Epoch, RootHex} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; +import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {StateCloneOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; @@ -53,7 +53,7 @@ export class BlockStateCacheImpl implements BlockStateCache { } add(item: CachedBeaconStateAllForks): void { - const key = toHexString(item.hashTreeRoot()); + const key = toRootHex(item.hashTreeRoot()); if (this.cache.get(key)) { return; } @@ -70,7 +70,7 @@ export class BlockStateCacheImpl implements BlockStateCache { setHeadState(item: CachedBeaconStateAllForks | null): void { if (item) { - const key = toHexString(item.hashTreeRoot()); + const key = toRootHex(item.hashTreeRoot()); this.head = {state: item, stateRoot: key}; } else { this.head = null; @@ -130,7 +130,7 @@ export class BlockStateCacheImpl implements BlockStateCache { dumpSummary(): routes.lodestar.StateCacheItem[] { return Array.from(this.cache.entries()).map(([key, state]) => ({ slot: state.slot, - root: toHexString(state.hashTreeRoot()), + root: toRootHex(state.hashTreeRoot()), reads: this.cache.readCount.get(key) ?? 0, lastRead: this.cache.lastRead.get(key) ?? 0, checkpointState: false, diff --git a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts index 93b581633c05..602b0abaee8d 100644 --- a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts +++ b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts @@ -1,7 +1,7 @@ -import {toHexString} from "@chainsafe/ssz"; import {RootHex} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; +import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {LinkedList} from "../../util/array.js"; import {StateCloneOpts} from "../regen/interface.js"; @@ -107,7 +107,7 @@ export class FIFOBlockStateCache implements BlockStateCache { * In importBlock() steps, normally it'll call add() with isHead = false first. Then call setHeadState() to set the head. */ add(item: CachedBeaconStateAllForks, isHead = false): void { - const key = toHexString(item.hashTreeRoot()); + const key = toRootHex(item.hashTreeRoot()); if (this.cache.get(key) != null) { if (!this.keyOrder.has(key)) { throw Error(`State exists but key not found in keyOrder: ${key}`); @@ -183,7 +183,7 @@ export class FIFOBlockStateCache implements BlockStateCache { dumpSummary(): routes.lodestar.StateCacheItem[] { return Array.from(this.cache.entries()).map(([key, state]) => ({ slot: state.slot, - root: toHexString(state.hashTreeRoot()), + root: toRootHex(state.hashTreeRoot()), reads: this.cache.readCount.get(key) ?? 0, lastRead: this.cache.lastRead.get(key) ?? 0, checkpointState: false, diff --git a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts index 37c67c0d86b4..03cdc84de166 100644 --- a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts @@ -1,7 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {phase0, Epoch, RootHex} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; -import {MapDef} from "@lodestar/utils"; +import {MapDef, toRootHex} from "@lodestar/utils"; import {routes} from "@lodestar/api"; import {Metrics} from "../../metrics/index.js"; import {StateCloneOpts} from "../regen/interface.js"; @@ -144,7 +143,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { delete(cp: phase0.Checkpoint): void { this.cache.delete(toCheckpointKey(toCheckpointHex(cp))); - const epochKey = toHexString(cp.root); + const epochKey = toRootHex(cp.root); const value = this.epochIndex.get(cp.epoch); if (value) { value.delete(epochKey); @@ -170,7 +169,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { dumpSummary(): routes.lodestar.StateCacheItem[] { return Array.from(this.cache.entries()).map(([key, state]) => ({ slot: state.slot, - root: toHexString(state.hashTreeRoot()), + root: toRootHex(state.hashTreeRoot()), reads: this.cache.readCount.get(key) ?? 0, lastRead: this.cache.lastRead.get(key) ?? 0, checkpointState: true, @@ -186,7 +185,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex { return { epoch: checkpoint.epoch, - rootHex: toHexString(checkpoint.root), + rootHex: toRootHex(checkpoint.root), }; } diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 58aeca061bc0..5c5901583ad8 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -1,7 +1,7 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {phase0, Epoch, RootHex} from "@lodestar/types"; import {CachedBeaconStateAllForks, computeStartSlotAtEpoch, getBlockRootAtSlot} from "@lodestar/state-transition"; -import {Logger, MapDef, sleep} from "@lodestar/utils"; +import {Logger, MapDef, sleep, toRootHex} from "@lodestar/utils"; import {routes} from "@lodestar/api"; import {loadCachedBeaconState} from "@lodestar/state-transition"; import {INTERVALS_PER_SLOT} from "@lodestar/params"; @@ -171,7 +171,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { for (const persistedKey of persistedKeys) { const cp = datastoreKeyToCheckpoint(persistedKey); this.cache.set(toCacheKey(cp), {type: CacheItemType.persisted, value: persistedKey}); - this.epochIndex.getOrDefault(cp.epoch).add(toHexString(cp.root)); + this.epochIndex.getOrDefault(cp.epoch).add(toRootHex(cp.root)); } this.logger.info("Loaded persisted checkpoint states from the last run", { count: persistedKeys.length, @@ -227,7 +227,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { validatorsBytes ); newCachedState.commit(); - const stateRoot = toHexString(newCachedState.hashTreeRoot()); + const stateRoot = toRootHex(newCachedState.hashTreeRoot()); timer?.(); this.logger.debug("Reload: cached state load successful", { ...logMeta, @@ -561,7 +561,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { // amongst states of the same epoch, choose the one with the same view of reloadedCp if ( reloadedCpSlot < state.slot && - toHexString(getBlockRootAtSlot(state, reloadedCpSlot)) === reloadedCp.rootHex + toRootHex(getBlockRootAtSlot(state, reloadedCpSlot)) === reloadedCp.rootHex ) { return state; } @@ -645,8 +645,8 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { const epochBoundarySlot = computeStartSlotAtEpoch(epoch); const epochBoundaryRoot = epochBoundarySlot === state.slot ? fromHexString(blockRootHex) : getBlockRootAtSlot(state, epochBoundarySlot); - const epochBoundaryHex = toHexString(epochBoundaryRoot); - const prevEpochRoot = toHexString(getBlockRootAtSlot(state, epochBoundarySlot - 1)); + const epochBoundaryHex = toRootHex(epochBoundaryRoot); + const prevEpochRoot = toRootHex(getBlockRootAtSlot(state, epochBoundarySlot - 1)); // for each epoch, usually there are 2 rootHexes respective to the 2 checkpoint states: Previous Root Checkpoint State and Current Root Checkpoint State const cpRootHexes = this.epochIndex.get(epoch) ?? []; @@ -804,7 +804,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex { return { epoch: checkpoint.epoch, - rootHex: toHexString(checkpoint.root), + rootHex: toRootHex(checkpoint.root), }; } @@ -812,7 +812,7 @@ function toCacheKey(cp: CheckpointHex | phase0.Checkpoint): CacheKey { if (isCheckpointHex(cp)) { return `${cp.rootHex}_${cp.epoch}`; } - return `${toHexString(cp.root)}_${cp.epoch}`; + return `${toRootHex(cp.root)}_${cp.epoch}`; } function fromCacheKey(key: CacheKey): CheckpointHex { diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index 430464683493..3c32ffe1a9ec 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; import {phase0, RootHex, ssz} from "@lodestar/types"; import { @@ -6,6 +5,7 @@ import { isAggregatorFromCommitteeLength, createAggregateSignatureSetFromComponents, } from "@lodestar/state-transition"; +import {toRootHex} from "@lodestar/utils"; import {IBeaconChain} from ".."; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; import {RegenCaller} from "../regen/index.js"; @@ -111,7 +111,7 @@ async function validateAggregateAndProof( // is a non-strict superset has _not_ already been seen. const attDataRootHex = cachedAttData ? cachedAttData.attDataRootHex - : toHexString(ssz.phase0.AttestationData.hashTreeRoot(attData)); + : toRootHex(ssz.phase0.AttestationData.hashTreeRoot(attData)); if ( !skipValidationKnownAttesters && chain.seenAggregatedAttestations.isKnown(targetEpoch, attDataRootHex, aggregationBits) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index fc39534b45e6..1116e87e1d25 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {phase0, Epoch, Root, Slot, RootHex, ssz} from "@lodestar/types"; import {ProtoBlock} from "@lodestar/fork-choice"; import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, ForkName, ForkSeq, DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; @@ -13,6 +12,7 @@ import { computeSigningRoot, } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; +import {toRootHex} from "@lodestar/utils"; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC} from "../../constants/index.js"; import {RegenCaller} from "../regen/index.js"; @@ -437,7 +437,7 @@ async function validateGossipAttestationNoSignatureCheck( ); // add cached attestation data before verifying signature - attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(attData)); + attDataRootHex = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(attData)); if (attDataBase64) { chain.seenAttestationDatas.add(attSlot, attDataBase64, { committeeIndices, @@ -643,7 +643,7 @@ function verifyHeadBlockIsKnown(chain: IBeaconChain, beaconBlockRoot: Root): Pro if (headBlock === null) { throw new AttestationError(GossipAction.IGNORE, { code: AttestationErrorCode.UNKNOWN_OR_PREFINALIZED_BEACON_BLOCK_ROOT, - root: toHexString(beaconBlockRoot), + root: toRootHex(beaconBlockRoot), }); } @@ -671,7 +671,7 @@ function verifyAttestationTargetRoot(headBlock: ProtoBlock, targetRoot: Root, at // https://github.com/ethereum/consensus-specs/pull/2001#issuecomment-699246659 throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_TARGET_ROOT, - targetRoot: toHexString(targetRoot), + targetRoot: toRootHex(targetRoot), expected: null, }); } else { @@ -687,11 +687,11 @@ function verifyAttestationTargetRoot(headBlock: ProtoBlock, targetRoot: Root, at headBlock.blockRoot; // TODO: Do a fast comparision to convert and compare byte by byte - if (expectedTargetRoot !== toHexString(targetRoot)) { + if (expectedTargetRoot !== toRootHex(targetRoot)) { // Reject any attestation with an invalid target root. throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_TARGET_ROOT, - targetRoot: toHexString(targetRoot), + targetRoot: toRootHex(targetRoot), expected: expectedTargetRoot, }); } diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index 214eeaf0ab4e..aabc1b14958a 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import { computeStartSlotAtEpoch, @@ -8,7 +7,7 @@ import { isExecutionEnabled, getBlockProposerSignatureSet, } from "@lodestar/state-transition"; -import {sleep} from "@lodestar/utils"; +import {sleep, toRootHex} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {SignedBeaconBlock} from "@lodestar/types"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; @@ -55,7 +54,7 @@ export async function validateGossipBlock( // reboot if the `observed_block_producers` cache is empty. In that case, without this // check, we will load the parent and state from disk only to find out later that we // already know this block. - const blockRoot = toHexString(config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block)); + const blockRoot = toRootHex(config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block)); if (chain.forkChoice.getBlockHex(blockRoot) !== null) { throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.ALREADY_KNOWN, root: blockRoot}); } @@ -71,7 +70,7 @@ export async function validateGossipBlock( // [REJECT] The current finalized_checkpoint is an ancestor of block -- i.e. // get_ancestor(store, block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) == store.finalized_checkpoint.root - const parentRoot = toHexString(block.parentRoot); + const parentRoot = toRootHex(block.parentRoot); const parentBlock = chain.forkChoice.getBlockHex(parentRoot); if (parentBlock === null) { // If fork choice does *not* consider the parent to be a descendant of the finalized block, diff --git a/packages/beacon-node/src/chain/validation/syncCommittee.ts b/packages/beacon-node/src/chain/validation/syncCommittee.ts index 43a4c95c59da..f47aa53a314e 100644 --- a/packages/beacon-node/src/chain/validation/syncCommittee.ts +++ b/packages/beacon-node/src/chain/validation/syncCommittee.ts @@ -1,7 +1,7 @@ -import {toHexString} from "@chainsafe/ssz"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {SYNC_COMMITTEE_SUBNET_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {altair} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; import {GossipAction, SyncCommitteeError, SyncCommitteeErrorCode} from "../errors/index.js"; import {IBeaconChain} from "../interface.js"; import {getSyncCommitteeSignatureSet} from "./signatureSets/index.js"; @@ -17,7 +17,7 @@ export async function validateGossipSyncCommittee( subnet: number ): Promise<{indexInSubcommittee: IndexInSubcommittee}> { const {slot, validatorIndex, beaconBlockRoot} = syncCommittee; - const messageRoot = toHexString(beaconBlockRoot); + const messageRoot = toRootHex(beaconBlockRoot); const headState = chain.getHeadState(); const indexInSubcommittee = validateGossipSyncCommitteeExceptSig(chain, headState, subnet, syncCommittee); diff --git a/packages/beacon-node/src/eth1/utils/deposits.ts b/packages/beacon-node/src/eth1/utils/deposits.ts index 19544917ffdc..24916264e6d8 100644 --- a/packages/beacon-node/src/eth1/utils/deposits.ts +++ b/packages/beacon-node/src/eth1/utils/deposits.ts @@ -1,9 +1,9 @@ import {toGindex, Tree} from "@chainsafe/persistent-merkle-tree"; -import {toHexString} from "@chainsafe/ssz"; import {MAX_DEPOSITS} from "@lodestar/params"; import {BeaconStateAllForks} from "@lodestar/state-transition"; import {phase0, ssz} from "@lodestar/types"; import {FilterOptions} from "@lodestar/db"; +import {toRootHex} from "@lodestar/utils"; import {Eth1Error, Eth1ErrorCode} from "../errors.js"; import {DepositTree} from "../../db/repositories/depositDataRoot.js"; @@ -51,8 +51,8 @@ export function getDepositsWithProofs( if (!ssz.Root.equals(depositRoot, eth1Data.depositRoot)) { throw new Eth1Error({ code: Eth1ErrorCode.WRONG_DEPOSIT_ROOT, - root: toHexString(depositRoot), - expectedRoot: toHexString(eth1Data.depositRoot), + root: toRootHex(depositRoot), + expectedRoot: toRootHex(eth1Data.depositRoot), }); } diff --git a/packages/beacon-node/src/network/gossip/encoding.ts b/packages/beacon-node/src/network/gossip/encoding.ts index 53847f56df7d..02c0df07b2f1 100644 --- a/packages/beacon-node/src/network/gossip/encoding.ts +++ b/packages/beacon-node/src/network/gossip/encoding.ts @@ -4,7 +4,7 @@ import {Message} from "@libp2p/interface"; import {digest} from "@chainsafe/as-sha256"; import {RPC} from "@chainsafe/libp2p-gossipsub/message"; import {DataTransform} from "@chainsafe/libp2p-gossipsub/types"; -import {intToBytes, toHex} from "@lodestar/utils"; +import {intToBytes} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js"; import {getGossipSSZType, GossipTopicCache} from "./topic.js"; @@ -15,6 +15,9 @@ const xxhash = await xxhashFactory(); // Use salt to prevent msgId from being mined for collisions const h64Seed = BigInt(Math.floor(Math.random() * 1e9)); +// Shared buffer to convert msgId to string +const sharedMsgIdBuf = Buffer.alloc(20); + /** * The function used to generate a gossipsub message id * We use the first 8 bytes of SHA256(data) for content addressing @@ -28,7 +31,9 @@ export function fastMsgIdFn(rpcMsg: RPC.Message): string { } export function msgIdToStrFn(msgId: Uint8Array): string { - return toHex(msgId); + // this is the same logic to `toHex(msgId)` with better performance + sharedMsgIdBuf.set(msgId); + return `0x${sharedMsgIdBuf.toString("hex")}`; } /** diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 82fe7d8db358..6f64148b87e2 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -1,6 +1,5 @@ -import {toHexString} from "@chainsafe/ssz"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; -import {LogLevel, Logger, prettyBytes} from "@lodestar/utils"; +import {LogLevel, Logger, prettyBytes, toRootHex} from "@lodestar/utils"; import {Root, Slot, ssz, deneb, UintNum64, SignedBeaconBlock} from "@lodestar/types"; import {ForkName, ForkSeq} from "@lodestar/params"; import {routes} from "@lodestar/api"; @@ -298,7 +297,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler case BlockErrorCode.DATA_UNAVAILABLE: { const slot = signedBlock.message.slot; const forkTypes = config.getForkTypes(slot); - const rootHex = toHexString(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message)); + const rootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message)); events.emit(NetworkEvent.unknownBlock, {rootHex, peer: peerIdStr}); @@ -747,12 +746,12 @@ export async function validateGossipFnRetryUnknownRoot( ) { if (unknownBlockRootRetries === 0) { // Trigger unknown block root search here - const rootHex = toHexString(blockRoot); + const rootHex = toRootHex(blockRoot); network.searchUnknownSlotRoot({slot, root: rootHex}); } if (unknownBlockRootRetries++ < MAX_UNKNOWN_BLOCK_ROOT_RETRIES) { - const foundBlock = await chain.waitForBlock(slot, toHexString(blockRoot)); + const foundBlock = await chain.waitForBlock(slot, toRootHex(blockRoot)); // Returns true if the block was found on time. In that case, try to get it from the fork-choice again. // Otherwise, throw the error below. if (foundBlock) { diff --git a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRoot.ts b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRoot.ts index 0ed0e6a2d185..36d90256276e 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRoot.ts @@ -1,6 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {ResponseOutgoing} from "@lodestar/reqresp"; import {Slot, phase0} from "@lodestar/types"; +import {toRootHex} from "@lodestar/utils"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; import {getSlotFromSignedBeaconBlockSerialized} from "../../../util/sszBytes.js"; @@ -33,7 +33,7 @@ export async function* onBeaconBlocksByRoot( if (slot === undefined) { const slotFromBytes = getSlotFromSignedBeaconBlockSerialized(blockBytes); if (slotFromBytes === null) { - throw Error(`Invalid block bytes for block root ${toHexString(root)}`); + throw Error(`Invalid block bytes for block root ${toRootHex(root)}`); } slot = slotFromBytes; } diff --git a/packages/beacon-node/src/sync/backfill/backfill.ts b/packages/beacon-node/src/sync/backfill/backfill.ts index 6d9716a37329..15ba34f4e583 100644 --- a/packages/beacon-node/src/sync/backfill/backfill.ts +++ b/packages/beacon-node/src/sync/backfill/backfill.ts @@ -1,10 +1,9 @@ import {EventEmitter} from "events"; import {StrictEventEmitter} from "strict-event-emitter-types"; -import {toHexString} from "@chainsafe/ssz"; import {BeaconStateAllForks, blockToHeader} from "@lodestar/state-transition"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {phase0, Root, SignedBeaconBlock, Slot, ssz} from "@lodestar/types"; -import {ErrorAborted, Logger, sleep, toHex} from "@lodestar/utils"; +import {ErrorAborted, Logger, sleep, toHex, toRootHex} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {IBeaconChain} from "../../chain/index.js"; @@ -251,7 +250,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} lte: backfillStartFromSlot, }); modules.logger.info("Initializing from Checkpoint", { - root: toHexString(anchorCp.root), + root: toRootHex(anchorCp.root), epoch: anchorCp.epoch, backfillStartFromSlot, previousBackfilledRanges: JSON.stringify(previousBackfilledRanges), @@ -327,7 +326,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} this.logger.error( `Backfilled till ${ this.syncAnchor.lastBackSyncedBlock.slot - } but not found previous saved finalized or wsCheckpoint with root=${toHexString( + } but not found previous saved finalized or wsCheckpoint with root=${toRootHex( this.prevFinalizedCheckpointBlock.root )}, slot=${this.prevFinalizedCheckpointBlock.slot}` ); @@ -341,7 +340,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} this.logger.error( `Invalid root synced at a previous finalized or wsCheckpoint, slot=${ this.prevFinalizedCheckpointBlock.slot - }: expected=${toHexString(this.prevFinalizedCheckpointBlock.root)}, actual=${toHexString( + }: expected=${toRootHex(this.prevFinalizedCheckpointBlock.root)}, actual=${toRootHex( this.syncAnchor.lastBackSyncedBlock.root )}` ); @@ -349,7 +348,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} break; } this.logger.verbose("Validated current prevFinalizedCheckpointBlock", { - root: toHexString(this.prevFinalizedCheckpointBlock.root), + root: toRootHex(this.prevFinalizedCheckpointBlock.root), slot: this.prevFinalizedCheckpointBlock.slot, }); @@ -379,7 +378,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} if (this.syncAnchor.lastBackSyncedBlock.slot === GENESIS_SLOT) { if (!byteArrayEquals(this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot, ZERO_HASH)) { Error( - `Invalid Gensis Block with non zero parentRoot=${toHexString( + `Invalid Gensis Block with non zero parentRoot=${toRootHex( this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot )}` ); @@ -537,7 +536,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} }` ); this.logger.info("wsCheckpoint validated!", { - root: toHexString(this.wsCheckpointHeader.root), + root: toRootHex(this.wsCheckpointHeader.root), epoch: this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH, }); this.wsValidated = true; @@ -584,7 +583,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} this.prevFinalizedCheckpointBlock.slot === prevBackfillCpBlock.message.slot ) { this.logger.verbose("Validated current prevFinalizedCheckpointBlock", { - root: toHexString(this.prevFinalizedCheckpointBlock.root), + root: toRootHex(this.prevFinalizedCheckpointBlock.root), slot: prevBackfillCpBlock.message.slot, }); } else { @@ -689,7 +688,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} throw Error( `Skipped a prevFinalizedCheckpointBlock with slot=${toHex( this.prevFinalizedCheckpointBlock.root - )}, root=${toHexString(this.prevFinalizedCheckpointBlock.root)}` + )}, root=${toRootHex(this.prevFinalizedCheckpointBlock.root)}` ); } if (anchorBlock.message.slot === this.prevFinalizedCheckpointBlock.slot) { @@ -700,7 +699,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} throw Error( `Invalid root for prevFinalizedCheckpointBlock at slot=${ this.prevFinalizedCheckpointBlock.slot - }, expected=${toHexString(this.prevFinalizedCheckpointBlock.root)}, found=${toHex(anchorBlockRoot)}` + }, expected=${toRootHex(this.prevFinalizedCheckpointBlock.root)}, found=${toHex(anchorBlockRoot)}` ); } @@ -770,7 +769,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.blockbyroot}); this.logger.verbose("Fetched new anchorBlock", { - root: toHexString(anchorBlockRoot), + root: toRootHex(anchorBlockRoot), slot: anchorBlock.data.message.slot, }); @@ -883,7 +882,7 @@ async function extractPreviousFinOrWsCheckpoint( const root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header); prevFinalizedCheckpointBlock = {root, slot: nextPrevFinOrWsBlock.message.slot}; logger?.debug("Extracted new prevFinalizedCheckpointBlock as potential previous finalized or wsCheckpoint", { - root: toHexString(prevFinalizedCheckpointBlock.root), + root: toRootHex(prevFinalizedCheckpointBlock.root), slot: prevFinalizedCheckpointBlock.slot, }); } else { diff --git a/packages/beacon-node/src/sync/range/chain.ts b/packages/beacon-node/src/sync/range/chain.ts index 41bbce3da820..0a2fc1a3f7c3 100644 --- a/packages/beacon-node/src/sync/range/chain.ts +++ b/packages/beacon-node/src/sync/range/chain.ts @@ -1,6 +1,5 @@ -import {toHexString} from "@chainsafe/ssz"; import {Epoch, Root, Slot, phase0} from "@lodestar/types"; -import {ErrorAborted, Logger} from "@lodestar/utils"; +import {ErrorAborted, Logger, toRootHex} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; import {BlockInput, BlockInputType} from "../../chain/blocks/types.js"; import {PeerAction} from "../../network/index.js"; @@ -234,7 +233,7 @@ export class SyncChain { /** Full debug state for lodestar API */ getDebugState(): SyncChainDebugState { return { - targetRoot: toHexString(this.target.root), + targetRoot: toRootHex(this.target.root), targetSlot: this.target.slot, syncType: this.syncType, status: this.status, diff --git a/packages/beacon-node/src/sync/unknownBlock.ts b/packages/beacon-node/src/sync/unknownBlock.ts index 3c15b32eb8d8..d985847d450f 100644 --- a/packages/beacon-node/src/sync/unknownBlock.ts +++ b/packages/beacon-node/src/sync/unknownBlock.ts @@ -1,6 +1,6 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {fromHexString} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {Logger, pruneSetToMax} from "@lodestar/utils"; +import {Logger, pruneSetToMax, toRootHex} from "@lodestar/utils"; import {Root, RootHex, deneb} from "@lodestar/types"; import {INTERVALS_PER_SLOT} from "@lodestar/params"; import {sleep} from "@lodestar/utils"; @@ -139,8 +139,8 @@ export class UnknownBlockSync { private addUnknownParent(blockInput: BlockInput, peerIdStr: string): void { const block = blockInput.block.message; const blockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block); - const blockRootHex = toHexString(blockRoot); - const parentBlockRootHex = toHexString(block.parentRoot); + const blockRootHex = toRootHex(blockRoot); + const parentBlockRootHex = toRootHex(block.parentRoot); // add 1 pending block with status downloaded let pendingBlock = this.pendingBlocks.get(blockRootHex); @@ -180,9 +180,7 @@ export class UnknownBlockSync { } else { if (blockInputOrRootHex.block !== null) { const {block} = blockInputOrRootHex; - blockRootHex = toHexString( - this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message) - ); + blockRootHex = toRootHex(this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)); unknownBlockType = PendingBlockType.UNKNOWN_BLOBS; } else { unknownBlockType = PendingBlockType.UNKNOWN_BLOCKINPUT; @@ -304,7 +302,7 @@ export class UnknownBlockSync { ...block, status: PendingBlockStatus.downloaded, blockInput, - parentBlockRootHex: toHexString(blockInput.block.message.parentRoot), + parentBlockRootHex: toRootHex(blockInput.block.message.parentRoot), }; this.pendingBlocks.set(block.blockRootHex, block); const blockSlot = blockInput.block.message.slot; @@ -336,7 +334,7 @@ export class UnknownBlockSync { this.logger.debug("Downloaded block is before finalized slot", { finalizedSlot, blockSlot, - parentRoot: toHexString(blockRoot), + parentRoot: toRootHex(blockRoot), unknownBlockType, }); this.removeAndDownscoreAllDescendants(block); @@ -384,7 +382,7 @@ export class UnknownBlockSync { .BeaconBlock.hashTreeRoot(pendingBlock.blockInput.block.message); this.logger.verbose("Avoid proposer boost for this block of known proposer", { blockSlot, - blockRoot: toHexString(blockRoot), + blockRoot: toRootHex(blockRoot), proposerIndex, }); await sleep(this.proposerBoostSecWindow * 1000); @@ -466,7 +464,7 @@ export class UnknownBlockSync { connectedPeers: PeerIdStr[] ): Promise<{blockInput: BlockInput; peerIdStr: string}> { const shuffledPeers = shuffle(connectedPeers); - const blockRootHex = toHexString(blockRoot); + const blockRootHex = toRootHex(blockRoot); let lastError: Error | null = null; for (let i = 0; i < MAX_ATTEMPTS_PER_BLOCK; i++) { @@ -483,7 +481,7 @@ export class UnknownBlockSync { const block = blockInput.block.message; const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block); if (!byteArrayEquals(receivedBlockRoot, blockRoot)) { - throw Error(`Wrong block received by peer, got ${toHexString(receivedBlockRoot)} expected ${blockRootHex}`); + throw Error(`Wrong block received by peer, got ${toRootHex(receivedBlockRoot)} expected ${blockRootHex}`); } return {blockInput, peerIdStr: peer}; @@ -527,7 +525,7 @@ export class UnknownBlockSync { blockRoot = this.config .getForkTypes(unavailableBlock.message.slot) .BeaconBlock.hashTreeRoot(unavailableBlock.message); - blockRootHex = toHexString(blockRoot); + blockRootHex = toRootHex(blockRoot); blobKzgCommitmentsLen = (unavailableBlock.message.body as deneb.BeaconBlockBody).blobKzgCommitments.length; pendingBlobs = blobKzgCommitmentsLen - unavailableBlockInput.cachedData.blobsCache.size; } @@ -554,7 +552,7 @@ export class UnknownBlockSync { const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block); if (!byteArrayEquals(receivedBlockRoot, blockRoot)) { - throw Error(`Wrong block received by peer, got ${toHexString(receivedBlockRoot)} expected ${blockRootHex}`); + throw Error(`Wrong block received by peer, got ${toRootHex(receivedBlockRoot)} expected ${blockRootHex}`); } if (unavailableBlockInput.block === null) { this.logger.debug("Fetched NullBlockInput", {attempts: i, blockRootHex}); diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index f87b899e9591..ce4125140075 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -1,6 +1,5 @@ import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz"; import {BLSSignature, RootHex, Slot} from "@lodestar/types"; -import {toHex} from "@lodestar/utils"; import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params"; export type BlockRootHex = RootHex; @@ -25,6 +24,10 @@ const SLOT_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; const SIGNATURE_SIZE = 96; +// shared Buffers to convert bytes to hex/base64 +const blockRootBuf = Buffer.alloc(ROOT_SIZE); +const attDataBuf = Buffer.alloc(ATTESTATION_DATA_SIZE); + /** * Extract slot from attestation serialized bytes. * Return null if data is not long enough to extract slot. @@ -46,7 +49,10 @@ export function getBlockRootFromAttestationSerialized(data: Uint8Array): BlockRo return null; } - return toHex(data.subarray(ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE)); + blockRootBuf.set( + data.subarray(ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE) + ); + return "0x" + blockRootBuf.toString("hex"); } /** @@ -59,7 +65,8 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att } // base64 is a bit efficient than hex - return toBase64(data.slice(VARIABLE_FIELD_OFFSET, VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE)); + attDataBuf.set(data.subarray(VARIABLE_FIELD_OFFSET, VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE)); + return attDataBuf.toString("base64"); } /** @@ -130,12 +137,13 @@ export function getBlockRootFromSignedAggregateAndProofSerialized(data: Uint8Arr return null; } - return toHex( + blockRootBuf.set( data.subarray( SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET, SIGNED_AGGREGATE_AND_PROOF_BLOCK_ROOT_OFFSET + ROOT_SIZE ) ); + return "0x" + blockRootBuf.toString("hex"); } /** @@ -148,9 +156,13 @@ export function getAttDataBase64FromSignedAggregateAndProofSerialized(data: Uint } // base64 is a bit efficient than hex - return toBase64( - data.slice(SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET, SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE) + attDataBuf.set( + data.subarray( + SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET, + SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE + ) ); + return attDataBuf.toString("base64"); } /** @@ -217,7 +229,3 @@ function getSlotFromOffsetTrusted(data: Uint8Array, offset: number): Slot { function checkSlotHighBytes(data: Uint8Array, offset: number): boolean { return (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; } - -function toBase64(data: Uint8Array): string { - return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64"); -} diff --git a/packages/beacon-node/test/perf/network/gossip/encoding.test.ts b/packages/beacon-node/test/perf/network/gossip/encoding.test.ts new file mode 100644 index 000000000000..693c91f59249 --- /dev/null +++ b/packages/beacon-node/test/perf/network/gossip/encoding.test.ts @@ -0,0 +1,46 @@ +import {itBench} from "@dapplion/benchmark"; +import {toHex} from "@lodestar/utils"; + +/** + * This is a benchmark for different ways of converting a gossipsub message id to a hex string using Mac M1 + * encoding + ✔ toHex 6463330 ops/s 154.7190 ns/op - 7170 runs 1.26 s + ✔ Buffer.from 6696982 ops/s 149.3210 ns/op - 2023 runs 0.454 s + ✔ shared Buffer 1.013911e+7 ops/s 98.62800 ns/op - 3083 runs 0.404 s + */ +describe("encoding", function () { + const msgId = Uint8Array.from(Array.from({length: 20}, (_, i) => i)); + + const runsFactor = 1000; + itBench({ + id: "toHex", + fn: () => { + for (let i = 0; i < runsFactor; i++) { + toHex(msgId); + } + }, + runsFactor, + }); + + itBench({ + id: "Buffer.from", + fn: () => { + for (let i = 0; i < runsFactor; i++) { + "0x" + Buffer.from(msgId.buffer, msgId.byteOffset, msgId.byteLength).toString("hex"); + } + }, + runsFactor, + }); + + const sharedBuf = Buffer.from(msgId); + itBench({ + id: "shared Buffer", + fn: () => { + for (let i = 0; i < runsFactor; i++) { + sharedBuf.set(msgId); + "0x" + sharedBuf.toString("hex"); + } + }, + runsFactor, + }); +}); diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 2e2abed95eb7..2405a442c8cd 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -1,5 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; -import {Logger, fromHex} from "@lodestar/utils"; +import {Logger, fromHex, toRootHex} from "@lodestar/utils"; import {SLOTS_PER_HISTORICAL_ROOT, SLOTS_PER_EPOCH, INTERVALS_PER_SLOT} from "@lodestar/params"; import {bellatrix, Slot, ValidatorIndex, phase0, ssz, RootHex, Epoch, Root, BeaconBlock} from "@lodestar/types"; import { @@ -472,7 +472,7 @@ export class ForkChoice implements IForkChoice { dataAvailabilityStatus: DataAvailabilityStatus ): ProtoBlock { const {parentRoot, slot} = block; - const parentRootHex = toHexString(parentRoot); + const parentRootHex = toRootHex(parentRoot); // Parent block must be known const parentBlock = this.protoArray.getBlock(parentRootHex); if (!parentBlock) { @@ -529,7 +529,7 @@ export class ForkChoice implements IForkChoice { } const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block); - const blockRootHex = toHexString(blockRoot); + const blockRootHex = toRootHex(blockRoot); // Assign proposer score boost if the block is timely // before attesting interval = before 1st interval @@ -628,14 +628,14 @@ export class ForkChoice implements IForkChoice { slot: slot, blockRoot: blockRootHex, parentRoot: parentRootHex, - targetRoot: toHexString(targetRoot), - stateRoot: toHexString(block.stateRoot), + targetRoot: toRootHex(targetRoot), + stateRoot: toRootHex(block.stateRoot), timeliness: isTimely, justifiedEpoch: stateJustifiedEpoch, - justifiedRoot: toHexString(state.currentJustifiedCheckpoint.root), + justifiedRoot: toRootHex(state.currentJustifiedCheckpoint.root), finalizedEpoch: finalizedCheckpoint.epoch, - finalizedRoot: toHexString(state.finalizedCheckpoint.root), + finalizedRoot: toRootHex(state.finalizedCheckpoint.root), unrealizedJustifiedEpoch: unrealizedJustifiedCheckpoint.epoch, unrealizedJustifiedRoot: unrealizedJustifiedCheckpoint.rootHex, unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch, @@ -694,7 +694,7 @@ export class ForkChoice implements IForkChoice { // to genesis just by being present in the chain. const attestationData = attestation.data; const {slot, beaconBlockRoot} = attestationData; - const blockRootHex = toHexString(beaconBlockRoot); + const blockRootHex = toRootHex(beaconBlockRoot); const targetEpoch = attestationData.target.epoch; if (ssz.Root.equals(beaconBlockRoot, ZERO_HASH)) { return; @@ -770,11 +770,11 @@ export class ForkChoice implements IForkChoice { /** Returns `true` if the block is known **and** a descendant of the finalized root. */ hasBlock(blockRoot: Root): boolean { - return this.hasBlockHex(toHexString(blockRoot)); + return this.hasBlockHex(toRootHex(blockRoot)); } /** Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root. */ getBlock(blockRoot: Root): ProtoBlock | null { - return this.getBlockHex(toHexString(blockRoot)); + return this.getBlockHex(toRootHex(blockRoot)); } /** @@ -793,7 +793,7 @@ export class ForkChoice implements IForkChoice { * Same to hasBlock but without checking if the block is a descendant of the finalized root. */ hasBlockUnsafe(blockRoot: Root): boolean { - return this.hasBlockHexUnsafe(toHexString(blockRoot)); + return this.hasBlockHexUnsafe(toRootHex(blockRoot)); } /** @@ -1221,7 +1221,7 @@ export class ForkChoice implements IForkChoice { forceImport?: boolean ): void { const epochNow = computeEpochAtSlot(this.fcStore.currentSlot); - const targetRootHex = toHexString(attestationData.target.root); + const targetRootHex = toRootHex(attestationData.target.root); // Attestation must be from the current of previous epoch. if (targetEpoch > epochNow) { diff --git a/packages/fork-choice/src/forkChoice/store.ts b/packages/fork-choice/src/forkChoice/store.ts index faf700241fa8..d16e021529db 100644 --- a/packages/fork-choice/src/forkChoice/store.ts +++ b/packages/fork-choice/src/forkChoice/store.ts @@ -1,4 +1,4 @@ -import {toHexString} from "@chainsafe/ssz"; +import {toRootHex} from "@lodestar/utils"; import {EffectiveBalanceIncrements, CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {phase0, Slot, RootHex, ValidatorIndex} from "@lodestar/types"; import {CheckpointHexWithTotalBalance, CheckpointHexWithBalance} from "./interface.js"; @@ -103,7 +103,7 @@ export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWi return { epoch: checkpoint.epoch, root, - rootHex: toHexString(root), + rootHex: toRootHex(root), }; } diff --git a/packages/utils/src/bytes.ts b/packages/utils/src/bytes.ts index 5395c4315d22..f4a622da021e 100644 --- a/packages/utils/src/bytes.ts +++ b/packages/utils/src/bytes.ts @@ -56,6 +56,17 @@ export function toHex(buffer: Uint8Array | Parameters[0]): s } } +// Shared buffer to convert root to hex +const rootBuf = Buffer.alloc(32); + +/** + * Convert a Uint8Array, length 32, to 0x-prefixed hex string + */ +export function toRootHex(root: Uint8Array): string { + rootBuf.set(root); + return `0x${rootBuf.toString("hex")}`; +} + export function fromHex(hex: string): Uint8Array { const b = Buffer.from(hex.replace("0x", ""), "hex"); return new Uint8Array(b.buffer, b.byteOffset, b.length); diff --git a/packages/utils/test/perf/bytes.test.ts b/packages/utils/test/perf/bytes.test.ts new file mode 100644 index 000000000000..368758d12efe --- /dev/null +++ b/packages/utils/test/perf/bytes.test.ts @@ -0,0 +1,27 @@ +import {itBench} from "@dapplion/benchmark"; +import {toHex, toRootHex} from "../../src/bytes.js"; + +describe("bytes utils", function () { + const runsFactor = 1000; + const blockRoot = new Uint8Array(Array.from({length: 32}, (_, i) => i)); + + itBench({ + id: "block root to RootHex using toHex", + fn: () => { + for (let i = 0; i < runsFactor; i++) { + toHex(blockRoot); + } + }, + runsFactor, + }); + + itBench({ + id: "block root to RootHex using toRootHex", + fn: () => { + for (let i = 0; i < runsFactor; i++) { + toRootHex(blockRoot); + } + }, + runsFactor, + }); +}); diff --git a/packages/utils/vitest.config.ts b/packages/utils/vitest.config.ts index 7a6069341168..b2b7cc82b0e2 100644 --- a/packages/utils/vitest.config.ts +++ b/packages/utils/vitest.config.ts @@ -6,6 +6,11 @@ export default mergeConfig( defineConfig({ test: { globalSetup: ["./test/globalSetup.ts"], + typecheck: { + // For some reason Vitest tries to run perf test files which causes an error + // as we use Mocha for those. This ignores all errors outside of test files. + ignoreSourceErrors: true, + }, }, }) );