diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts index 49cd46220008..fda245bc5000 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts @@ -12,6 +12,12 @@ import {BlockProcessOpts} from "../options.js"; import {byteArrayEquals} from "../../util/bytes.js"; import {nextEventLoop} from "../../util/eventLoop.js"; import {BlockInput, ImportBlockOpts} from "./types.js"; +import {HashComputationGroup} from "@chainsafe/persistent-merkle-tree"; + +/** + * Data in a BeaconBlock is bounded so we can use a single HashComputationGroup for all blocks + */ +const blockHCGroup = new HashComputationGroup(); /** * Verifies 1 or more blocks are fully valid running the full state transition; from a linear sequence of blocks. @@ -63,7 +69,7 @@ export async function verifyBlocksStateTransitionOnly( const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({ source: StateHashTreeRootSource.blockTransition, }); - const stateRoot = postState.hashTreeRoot(); + const stateRoot = postState.hashTreeRoot(blockHCGroup); hashTreeRootTimer?.(); // Check state root matches diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 48724ab25b0b..da77665377a5 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -18,6 +18,7 @@ import {isQueueErrorAborted} from "../util/queue/index.js"; import {prepareExecutionPayload, getPayloadAttributesForSSE} from "./produceBlock/produceBlockBody.js"; import {IBeaconChain} from "./interface.js"; import {RegenCaller} from "./regen/index.js"; +import {HashComputationGroup} from "@chainsafe/persistent-merkle-tree"; /* With 12s slot times, this scheduler will run 4s before the start of each slot (`12 / 3 = 4`). */ export const SCHEDULER_LOOKAHEAD_FACTOR = 3; @@ -25,6 +26,11 @@ export const SCHEDULER_LOOKAHEAD_FACTOR = 3; /* We don't want to do more epoch transition than this */ const PREPARE_EPOCH_LIMIT = 1; +/** + * The same HashComputationGroup to be used for all epoch transition. + */ +const balancesHCGroup = new HashComputationGroup(); + /** * At Bellatrix, if we are responsible for proposing in next slot, we want to prepare payload * 4s (1/3 slot) before the start of next slot @@ -229,7 +235,16 @@ export class PrepareNextSlotScheduler { const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ source: isEpochTransition ? StateHashTreeRootSource.prepareNextEpoch : StateHashTreeRootSource.prepareNextSlot, }); - state.hashTreeRoot(); + if (isEpochTransition) { + // balances are completely changed per epoch and it's not much different so we can reuse the HashComputationGroup + state.balances.hashTreeRoot(balancesHCGroup); + // it's more performant to use normal hashTreeRoot() for the rest of the state + // this saves ~10ms per ~100ms as monitored on mainnet as of Jul 2024 + state.node.rootHashObject; + } else { + // normal slot, not worth to batch hash + state.node.rootHashObject; + } hashTreeRootTimer?.(); } } diff --git a/packages/state-transition/src/epoch/processParticipationFlagUpdates.ts b/packages/state-transition/src/epoch/processParticipationFlagUpdates.ts index 3879d8ef8446..54d609cd1e16 100644 --- a/packages/state-transition/src/epoch/processParticipationFlagUpdates.ts +++ b/packages/state-transition/src/epoch/processParticipationFlagUpdates.ts @@ -21,7 +21,6 @@ export function processParticipationFlagUpdates(state: CachedBeaconStateAltair): state.currentEpochParticipation.node, zeroNode(ssz.altair.EpochParticipation.chunkDepth), state.currentEpochParticipation.length, - null ); state.currentEpochParticipation = ssz.altair.EpochParticipation.getViewDU(currentEpochParticipationNode); diff --git a/packages/types/src/phase0/viewDU/listValidator.ts b/packages/types/src/phase0/viewDU/listValidator.ts index 30c6cbebfaf4..aa518c4a0208 100644 --- a/packages/types/src/phase0/viewDU/listValidator.ts +++ b/packages/types/src/phase0/viewDU/listValidator.ts @@ -5,7 +5,7 @@ import { ByteViews, ContainerNodeStructTreeViewDU, } from "@chainsafe/ssz"; -import {HashComputationGroup, Node, digestNLevel, setNodesAtDepth} from "@chainsafe/persistent-merkle-tree"; +import {HashComputationGroup, HashComputationLevel, Node, digestNLevel, setNodesAtDepth} from "@chainsafe/persistent-merkle-tree"; import {byteArrayIntoHashObject} from "@chainsafe/as-sha256"; import {ValidatorNodeStructType, ValidatorType, validatorToChunkBytes} from "../validator.js"; @@ -50,10 +50,10 @@ export class ListValidatorTreeViewDU extends ListCompositeTreeViewDU entry.index); const nodes = nodesChanged.map((entry) => entry.node); const chunksNode = this.type.tree_getChunksNode(this._rootNode); - const hashCompsThis = - hashComps != null && isOldRootHashed - ? { - byLevel: hashComps.byLevel, - offset: hashComps.offset + this.type.tree_chunksNodeOffset(), - } - : null; - const newChunksNode = setNodesAtDepth(chunksNode, this.type.chunkDepth, indexes, nodes, hashCompsThis); + const offsetThis = hcOffset + this.type.tree_chunksNodeOffset(); + const byLevelThis = hcByLevel != null && isOldRootHashed ? hcByLevel : null; + const newChunksNode = setNodesAtDepth(chunksNode, this.type.chunkDepth, indexes, nodes, offsetThis, byLevelThis); this._rootNode = this.type.tree_setChunksNode( this._rootNode, newChunksNode, this.dirtyLength ? this._length : null, - hashComps + hcOffset, + hcByLevel ); - if (!isOldRootHashed && hashComps !== null) { + if (!isOldRootHashed && hcByLevel !== null) { // should never happen, handle just in case // not possible to get HashComputations due to BranchNodeStruct this._rootNode.root;