feat: add progressive types#528
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dca3c9673b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case "data": | ||
| return this.representativeType; |
There was a problem hiding this comment.
Avoid using representative type for
data path typing
Returning this.representativeType for getPropertyType("data") makes nested proof-path resolution incorrect when a CompatibleUnion is embedded in parents that still use getPathInfo traversal (e.g. StableContainerType.tree_createProofGindexes in packages/ssz/src/type/stableContainer.ts:434). In that flow, a path like ["unionField", "data", "newField"] is validated against the representative option instead of the active selector, so proofs for selector-specific fields throw Unknown ... property or resolve the wrong subtree even though CompatibleUnion.tree_createProofGindexes itself supports selector-aware traversal.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agree the bug is real, but I'd locate the fix elsewhere.
The PR already updates CompositeType.tree_createProofGindexes to a per-prop recursive dispatch via the new private tree_createProofGindexesForPath (packages/ssz/src/type/composite.ts:290-329), which correctly routes child gindex resolution through childType.tree_createProofGindexes. Because CompatibleUnion overrides that method to dispatch by the selector read off the node (compatibleUnion.ts:251-299), proof generation works correctly when CompatibleUnion is embedded in a default-CompositeType parent (e.g. plain Container, ProgressiveContainer — see the existing valid.test.ts:75-95 cases).
The break is specifically the StableContainerType.tree_createProofGindexes override (stableContainer.ts:434), which goes through this.getPathInfo(jsonPath). getPathInfo walks getPropertyType step-by-step with no node context, so it descends through CompatibleUnion.getPropertyType("data") → representativeType and then keeps resolving against the representative's schema. Concretely:
- path
["u","data","fieldOnlyInB"]on a union whose active selector isB→representativeType.getPropertyGindex(...)throwsUnknown ... property. - path
["u","data"]→ returnedtypeis the representative;tree_getLeafGindices(gindex, getNode(node, gindex))walks representative's field list against a node whose layout isB, yielding phantom leaves atB's zero positions.
Changing getPropertyType("data") doesn't really help — getPathInfo is fundamentally node-less, so returning anything selector-dependent is impossible. Returning this just punts the failure one level deeper (CompatibleUnion.getPropertyGindex("fieldOnlyInB") still throws). Throwing here breaks legitimate "give me a representative type" callers.
Minimal fix: align StableContainerType.tree_createProofGindexes with the new recursive-dispatch pattern the PR just introduced in the base — keep the inactive-optional-field skip at the top level, then for each path delegate to childType.tree_createProofGindexes(childNode, [remainingPath]) (with concatGindices([childGindex, g])) instead of getPathInfo. That makes selector-aware traversal work without touching CompatibleUnion.
Worth adding a test that wraps CompatibleUnion in a StableContainer and proves both a shared-field path and a selector-specific path round-trip through createProof / createFromProof.
Performance Report✔️ no performance regression detected Full benchmark results
|
|
@lodekeeper please review |
lodekeeper
left a comment
There was a problem hiding this comment.
Thanks @wemeetagain. Read through the diff at d3bcb2f. Overall the new types look solid and the spec-test wiring is in good shape. One real blocker before merge, plus a couple of smaller things.
Blocker
CompatibleUnion embedded in StableContainer produces wrong proofs. Detailed in the inline reply to the Codex P1 (#528 (comment)) — TL;DR the PR correctly refactored the base CompositeType.tree_createProofGindexes to a per-prop recursive dispatch, so CompatibleUnion's selector-aware override is reached when the parent is a default-CompositeType (e.g. Container, ProgressiveContainer). But StableContainerType.tree_createProofGindexes (stableContainer.ts:434) still drives off this.getPathInfo(jsonPath), which is node-less and walks through CompatibleUnion.getPropertyType("data") → representativeType. That mis-resolves selector-specific paths (throws on fields not in the representative; silently returns phantom leaves for paths that fall back to tree_getLeafGindices on the representative type).
Suggested minimal fix: align StableContainerType.tree_createProofGindexes with the new recursive-dispatch pattern (keep the inactive-optional skip at the top level, then delegate to childType.tree_createProofGindexes(childNode, [remainingPath]) and concatGindices([childGindex, g])).
Plus a test like the existing valid.test.ts "creates nested selected data proofs using the active selector type" case but with a StableContainer wrapper — would have caught this. Worth covering both:
- a shared/common field path (
["u", "data", "common"]) - a selector-specific field path against the non-representative selector (
["u", "data", "fieldOnlyInB"])
Smaller observations
-
ProgressiveListBasicTreeViewDU.push(progressiveList.ts:634-638) andProgressiveListCompositeTreeViewDU.push(progressiveList.ts:765-769) do a fulltree_toValue→value_toTreeround-trip on every push. That'sO(N)per push andO(N²)forNpushes.ListBasicTreeViewDU/ListCompositeTreeViewDUhave incremental push paths; worth at least a TODO since these are advertised on the public API. Not a correctness issue. -
CompatibleUnionreusesgetLengthFromRootNode/addLengthNodeto stash the selector in the right-leaf "length" slot (compatibleUnion.ts:184, 189, 199, 201, 253, 323, 404, 432). Clever, but the naming will confuse readers a year from now — a one-line comment in the class header explaining "the right leaf encodes the selector via the existing length-node helpers" would help. -
bitList.ts— the newend <= startguard indeserializeUint8ArrayBitListFromBytesis a nice defensive addition. Worth a test that exercises it (e.g.BitListType.deserialize(new Uint8Array(0))) — without it, that input falls through todata[end-1]returningundefinedand produces a confusing error. -
Spec-test versioning bumps to
v1.7.0-alpha.5and the newcompatible_unions/progressive_containerscases ingeneric/types.tslook correct.
Disclosure
🤖 Reviewed with AI assistance (Codex-CLI cross-check).
Motivation
Description
ProgressiveList,ProgressiveBitlist,ProgressiveContainer,CompatibleUnion