fix: dedupe persistent-merkle-tree to recover hashtree hasher - #9352
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the @chainsafe/persistent-merkle-tree dependency to version 1.2.5 across multiple packages and introduces a shell script to ensure only a single version of this package exists in the lockfile to prevent performance regressions. Feedback focuses on the robustness of the new script: it currently lacks integration into the CI pipeline, uses a brittle regex that may miss pre-releases or peer-dependency suffixes, and fails to verify that at least one instance of the package is found, potentially leading to false positives.
- match any version (drop hardcoded 1.x major), so a future 1.x+2.x split is also caught - handle pre-release tags and pnpm peer-dep suffixes in lockfile keys - exempt @ethereumjs/util's legacy persistent-merkle-tree@0.6.1 (predates setHasher API) - fail on zero matches to catch lockfile-format drift
Performance Report✔️ no performance regression detected Full benchmark results
|
|
please review @lodekeeper |
lodekeeper
left a comment
There was a problem hiding this comment.
LGTM. The dep bumps + CI guard are exactly what's needed to keep the PMT hasher singleton intact across the workspace.
Root-cause coverage: After #9211 bumped @chainsafe/ssz to ^1.4.0 (whose package.json carries an empty PMT constraint), pnpm resolved both 1.2.1 (direct pin) and 1.2.5 (transitive). The 1.2.1 copy got setHasher(hashtree) via applyPreset.ts; ssz's internal merkleize.js digested through the 1.2.5 copy, silently falling back to JS noble. The screenshot in the PR description (mainnet GC regression) is the smoking gun. Bumping every workspace pin to 1.2.5 collapses the resolution graph to a single instance, and the CI guard prevents a regression on future ssz / PMT bumps.
Workspace pins audited: api, beacon-node, cli, light-client, prover, state-transition — all bumped from ^1.2.1 to ^1.2.5. Lockfile dedupes to a single 1.2.5 entry (modulo the exempt legacy 0.6.1 from @ethereumjs/util → ssz@0.11.1).
CI guard correctness: After commit 8307690bbe, the script handles pre-release tags, pnpm peer-dep suffixes, any future major version (no hardcoded MAJOR=1), and explicitly fails on zero matches to catch lockfile-format drift / dependency removal. Wired into .github/workflows/test.yml:55-56 ahead of Lint Code, so a split fails CI early.
Exemption justification: EXEMPT_VERSIONS=(0.6.1) is well-documented in the script — it comes from @ethereumjs/util → ssz@0.11.1 (pre-setHasher), so it can't share a singleton with the 1.x line anyway and would never have benefited from the hashtree configuration.
@gemini-code-assist — all three of your concerns (CI wiring, regex brittleness, zero-match false positive) are addressed by the hardening commit; replies posted in-thread.
|
Thanks for the confirmation @twoeths! Good to see the dedup lint catching the issue and the fix holding up on mainnet. 🌟 |
|
🎉 This PR is included in v1.43.0 🎉 |

Motivation
After #9211 bumped

@chainsafe/sszto^1.4.0, the lockfile resolves two copies of@chainsafe/persistent-merkle-tree:1.2.1(direct pin) and1.2.5(transitive under ssz, which declares an empty PMT constraint).Each PMT module carries its own
hashersingleton.packages/cli/src/applyPreset.tsonly configures PMT 1.2.1 with hashtree; the 1.2.5 copy stays on the JSnobledefault.Every
hashTreeRootgoing through ssz's internalmerkleize.jsnow allocates per digest, causing significantly higher GC on mainnet.Description
@chainsafe/persistent-merkle-treepin from^1.2.1to^1.2.5across all workspace packages so pnpm dedupes to a single instance.scripts/assert_persistent_merkle_tree_dedup.sh(wired into thelintjob) to fail CI if the lockfile ever splits 1.x again.AI Assistance Disclosure
Used Claude Code.