feat(shuffle): add swap-or-not shuffling module and binding - #559
Conversation
71ec60d to
01edd6b
Compare
| // Namespace-level constants mirroring @chainsafe/swap-or-not-shuffle; the | ||
| // zapi DSL only auto-exports functions and classes, so values are attached | ||
| // here instead of via a native register hook. | ||
| bindings.shuffle.SHUFFLE_ROUNDS_MAINNET = 90; | ||
| bindings.shuffle.SHUFFLE_ROUNDS_MINIMAL = 10; | ||
| bindings.shuffle.ByteCount = {One: 1, Two: 2}; |
There was a problem hiding this comment.
This will soon go away as we will support namepsace pub const and enum in the Zapi.
|
Metrics check from beta vs unstable, sampled 2026-08-17 around 08:30 UTC. Compared the PR-related beta build against same-network
Verdict: I do not see a performance regression from this beta deployment. Health is clean:
Block/state/epoch processing is broadly comparable:
Resources do not support a regression hypothesis:
Shuffle/cache-specific signals look clean:
PTC-specific caveat:
Watch item: beta-sas has slightly more validator misses than unstable-sas over the sample (about 45 vs 4, miss ratio ~0.41% vs ~0.32%), but target/source hit rates remain above 99.5%, head correctness is close to unstable, and this is not backed by CPU/memory/queue pressure. I would keep an eye on it during soak, but I would not call it a regression from the current metrics. Recommendation: continue soak, but from current beta vs unstable metrics this looks OK. |
Review feedback (#559): required members (compute/resolve/deinit) are now enforced with a comptime error, errorMessage becomes optional with an @Errorname default, and an optional reject hook lets a task build its own rejection value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
spiral-ladder
left a comment
There was a problem hiding this comment.
mostly doc related comments, i kinda am of the opinion that we should cut down or limit the amount of comments or the quality of AI comments because they tend to write in a very roundabout way. in an isolated PR it seems fine but over the course of many PRs our codebase comments will become meaningless
wemeetagain
left a comment
There was a problem hiding this comment.
LGTM
Worth noting, we don't actually use the async variants of any shuffling code in production in lodestar (and I don't see a future where we ever will use it -- we'd rather just lower the entire STF!).
wemeetagain
left a comment
There was a problem hiding this comment.
Just noticed @spiral-ladder 's comments, changing my review to red on his behalf
## Motivation While porting `@chainsafe/swap-or-not-shuffle` into lodestar-z (ChainSafe/lodestar-z#559), scalar `pub const` decls and enums inside exported modules were silently skipped by `js.exportModule`'s reflection — the binding had to assign constants like `SHUFFLE_ROUNDS_MAINNET` and `ByteCount` on the JS side. Classes already auto-export such consts as statics (`applyStaticFields`); this extends the same behavior to namespaces and the module root. ## Changes - **Const export**: scalar/string `pub const` decls (int, float, bool, `[]const u8`, string literals) export as enumerable value properties at the module root and inside namespaces, reusing `wrap_class.isStaticValueType`/`createStaticFieldValue` (now `pub`). A namespace containing only consts now exports too. - **Enum export**: `pub const X = enum {...}` exports as a **frozen** plain object mapping each tag name (verbatim, no case conversion) to its integer value, mirroring napi-rs `#[napi] pub enum`. Signed tag values preserved. - **Docs**: `TypedArray.fromExternal` doc now states it copies (pointing at `OwnedTypedArray.fromOwnedSlice`/`intoValue` for ownership transfer) and warns it panics outside DSL callbacks. New README "Constants and Enums" section. ## Decisions - Consts are **enumerable** (`setNamedProperty`), matching namespace function properties. Verified class statics are also enumerable, so the two paths are consistent. - Enum objects are **frozen** — safer semantics for constants; napi-rs leaves them mutable. - Other const shapes (struct values, arrays) remain skipped; the `.register` hook stays the escape hatch. ## Testing 10 new vitest cases in `examples/js_dsl` (root/namespace consts, const-only namespace, enumerability, verbatim enum tags, signed values, frozen-object semantics), written first and watched fail. Full suite: `zig build test:zapi` + 130 vitest tests green. ## Consumer cleanup Once released, lodestar-z deletes its `bindings.js` constant assignments and declares the consts/enum in `bindings/napi/shuffle.zig` (guarded by its "should expose the reference constants" test). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: bing <spiralladder@fastmail.com>
|
We decided we don't need async functions here based on our current usage of this, save the zapi enhancements for a future release |
Port the full @chainsafe/swap-or-not-shuffle surface into the module (exact validation order and errors, ComputeShuffledIndex, proposer and sync committee index functions) and expose it as bindings.swapOrNotShuffle with reference-identical names, semantics, and error messages. Differential tests compare every function against the npm package; 2M-index sweeps show zero divergence. Perf benches show equal or faster results than the Rust binding. Needed for ChainSafe/lodestar#9263. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Everything now lives under bindings.shuffle: the reference-parity API plus the pre-existing in-place innerShuffleList variant. Drops the separate swap_or_not_shuffle.zig binding file and namespace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bindings/test/swapOrNotShuffle.test.ts merges into shuffle.test.ts and bindings/perf/swapOrNotShuffle.test.ts becomes perf/shuffle.test.ts, matching bindings/napi/shuffle.zig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Confines the raw napi async-work/Deferred plumbing to one reusable module. Upstream candidate for a zapi js.AsyncTask; transferOwnedSlice is superseded by zapi#68 OwnedTypedArray once released. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shuffle.zig is now zapi DSL plus a small ShuffleTask; all raw napi plumbing lives in async_task.zig. No JS-visible behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ComputeShuffledIndex.get and the committee weighting comparison now use wrapping ops matching the Rust reference's release-mode behavior, so out-of-range JS inputs produce reference-identical values instead of aborting the process. Also fixes ByteCount d.ts typing, adds error-path tests, and refreshes stale comments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tation Ports the reference repo's test suite and its pure-TS reference implementation (deps inlined: sha256 via node:crypto, spec constants, naive computeShuffledIndex/committee sampling) so the shuffle tests are self-contained. Out-of-range wrapping vectors are hardcoded from @chainsafe/swap-or-not-shuffle v1.2.1 output. Drops that package as a devDependency; it remains only as a transitive dep of @lodestar/state-transition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The zapi DSL only auto-exports functions and classes; rather than a native register hook, the loader attaches the three reference constants to the shuffle namespace. Can move back into Zig once zapi auto-exports namespace-level consts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
package.json is net-unchanged on this branch (devDependency added then removed), so the lockfile drift from those installs (refreshed deprecation metadata, re-resolved sax) does not belong in the diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback (#559): required members (compute/resolve/deinit) are now enforced with a comptime error, errorMessage becomes optional with an @Errorname default, and an optional reject hook lets a task build its own rejection value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zapi 4.0.0 (ChainSafe/zapi#73) auto-exports namespace-level consts and enums, so SHUFFLE_ROUNDS_MAINNET/MINIMAL and ByteCount move next to the functions that use them and the JS loader shim in bindings.js is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fd545e5 to
59de52e
Compare
Review feedback: the async variants are unused (ChainSafe/lodestar#9829 calls unshuffleList, computeProposerIndex and computeSyncCommitteeIndices), so drop asyncShuffleList/asyncUnshuffleList and the async_task helper -- the zapi AsyncTask work waits for a future release. Also unbind the other unused package exports (forward shuffleList, ComputeShuffledIndex, the Electra wrappers, SHUFFLE_ROUNDS_* and ByteCount); the Zig module still implements them, so re-binding later is a few lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback: drop narration the next reader does not need (zapi internals, package-parity restatements, per-file headers other bindings do not carry) and keep only constraint notes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| const allocator = if (builtin.mode == .Debug) gpa.allocator() else std.heap.c_allocator; | ||
|
|
||
| /// Verbatim reference error messages. | ||
| fn shuffleErrorMessage(err: anyerror) [:0]const u8 { |
There was a problem hiding this comment.
Why must do there conversion for the first three error? Seems we don't need next two functions if we don't do this
There was a problem hiding this comment.
Good point. With the async/zapi-enhancement work out of scope, I do not think we need the extra reference-message conversion here.
I simplified the binding so it surfaces the native Zig/zapi error names consistently instead of mapping the first shuffle errors to @chainsafe/swap-or-not-shuffle prose messages, and removed the now-unneeded helper path. The tests now assert InvalidSeedLength / InvalidNumberOfRounds for these cases.
I cannot push directly to ChainSafe/lodestar-z (lodekeeper only has read permission), so I pushed the signed patch here: lodekeeper@a8148ba
Verification:
PATH=/tmp/zig-0.16.0:$PATH zig fmt --check bindings/napi/shuffle.zig src/swap_or_not_shuffle/root.zigPATH=/tmp/zig-0.16.0:$PATH sh -c 'git ls-files "*.zig" | xargs zig fmt --check'PATH=/tmp/zig-0.16.0:$PATH pnpm prepare-mainnetpnpm exec vitest run bindings/test/shuffle.test.ts(18 tests)pnpm exec biome check bindings/test/shuffle.test.tspnpm lintexited 0 with existing warnings inbindings/src/index.d.ts
Note: plain zig fmt --check . still reports unformatted files under downloaded zig-pkg/ dependency sources after the local build, so I used the tracked-file format check above for the actual repo files.
Review feedback: the reference message mapping existed only for parity with the npm package's strings; nothing in Lodestar reads them, and the other bindings here already surface error names (see blst tests). Drops both helper functions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| //! JS binding for the `swap_or_not_shuffle` module. Function names, argument | ||
| //! order and error messages match `@chainsafe/swap-or-not-shuffle`. |
There was a problem hiding this comment.
| //! JS binding for the `swap_or_not_shuffle` module. Function names, argument | |
| //! order and error messages match `@chainsafe/swap-or-not-shuffle`. | |
| //! JS binding for the `swap_or_not_shuffle` module. |
There was a problem hiding this comment.
these tests look kinda different from what we have in that repo, where are these from?
There was a problem hiding this comment.
These tests were not just coming from that repo, it is mix of those and existing shuffling tests.
| return error.InvalidSeedLength; | ||
| } | ||
|
|
||
| var arena = std.heap.ArenaAllocator.init(parent_allocator); |
There was a problem hiding this comment.
Documented the usage in the comments.
Arena seems fit for this use case.
The caches below are written until
deinitand never freed piecemeal,
so an arena keeps their many small allocations to one teardown.
Co-authored-by: bing <spiralladder@fastmail.com>
Review questions: the module tests are a mix of upstream unit-test vectors, vectors carried over from committee_indices.zig, one captured from the npm package, and error-path cases the reference cannot express. ComputeShuffledIndex uses an arena because its caches are freed only at deinit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lodestar-z into nh/swap-or-not-shuffle-parity
wemeetagain
left a comment
There was a problem hiding this comment.
Looks good!
Can you simplify / shorten the PR description before merging to only the most important information?
+1, im a bit tired of reading ai pr descriptions to be completely honest, i skipped this description entirely |
|
@wemeetagain @spiral-ladder agreed. I cannot edit Nazar's PR body from ## Summary
Adds a Zig `swap_or_not_shuffle` module and JS bindings for the shuffle APIs Lodestar uses, so ChainSafe/lodestar can drop `@chainsafe/swap-or-not-shuffle`.
- Shares one shuffle implementation between `state_transition` and the JS binding.
- Matches the upstream Rust/reference behavior for implemented paths.
- Returns explicit errors for degenerate inputs that the reference package would crash or hang on.
- Leaves async binding support for a future zapi release, since Lodestar does not use it.
## Testing
- Ported upstream shuffle reference tests and existing lodestar-z shuffle vectors.
- Added Zig coverage for validation order, error paths, proposer index, and sync committee selection.
- Checked beta metrics against unstable; no performance regression found.
## Follow-ups
- #563 adds PTC sampling on top of this.
- Async shuffle bindings can be added later if a consumer appears.
AI assistance was used for drafting and implementation support. |
|
thanks @lodekeeper I'm just going with that |
🤖 I have created a release *beep* *boop* --- ## [1.0.0](v0.1.2...v1.0.0) (2026-08-19) ### Features * add `state.getBuildersLength()` binding ([#472](#472)) ([be2b5ab](be2b5ab)) * **beacon-node:** add block state cache and checkpoint datastore ([#452](#452)) ([2145faa](2145faa)) * bindings to `getExpectedWithdrawals` and native tweaks ([#350](#350)) ([f47bc66](f47bc66)) * **bindings:** add pubkey cache syncPubkeys ([#537](#537)) ([542779f](542779f)) * **bindings:** aggregate cached public keys by validator index ([#397](#397)) ([2f90603](2f90603)) * **bindings:** align `BeaconStateView` with `IBeaconStateView` ([#347](#347)) ([b8ec273](b8ec273)) * **bindings:** configurable pubkey cache growth step ([#481](#481)) ([133ef24](133ef24)) * **bindings:** expose more APIs for STF ([#444](#444)) ([7fe2609](7fe2609)) * **bls:** add small MSM for npoints < 32 ([#393](#393)) ([b430638](b430638)) * **blst:** use external buffers for blst operations ([#358](#358)) ([78e4678](78e4678)) * **ci:** conditionally publish bindings with tag ([#355](#355)) ([ea77919](ea77919)) * **clock:** add clock module for slot/epoch timing ([#354](#354)) ([385b077](385b077)) * **fork_choice:** add Prometheus metrics module ([#309](#309)) ([cbc9d8d](cbc9d8d)) * **forkchoice:** implement the forkchoice module ([#246](#246)) ([7c62a9b](7c62a9b)) * getSyncCommitteesWitness ([#367](#367)) ([ef77649](ef77649)) * implement `loadState` API and binding ([#165](#165)) ([f903519](f903519)), closes [#159](#159) * **metrics:** metrics bindings ([#455](#455)) ([dd41999](dd41999)) * migrate blst,pubkeys to use zapi js dsl ([#331](#331)) ([fcd26ca](fcd26ca)) * **pubkeys:** add getPubkeyBytes binding ([#555](#555)) ([4ca51cf](4ca51cf)) * publish ARM64 musl bindings ([#482](#482)) ([ac764c9](ac764c9)) * **shuffle:** add swap-or-not shuffling module and binding ([#559](#559)) ([c2db37c](c2db37c)) * split nextValue fn ([#464](#464)) ([b47faeb](b47faeb)) * support getLatestWeakSubjectivityCheckpointEpoch ([#366](#366)) ([dcf3883](dcf3883)) * update fulu deposit processing ([#442](#442)) ([064335c](064335c)) ### Bug Fixes * avoid set ([#484](#484)) ([2e25d97](2e25d97)) * better generation of rand scalar ([#388](#388)) ([74dce77](74dce77)) * **bindings:** accept `dontTransferCache` in processSlots for backward compatibility ([#460](#460)) ([65df5af](65df5af)) * **bindings:** check signature infinity by default ([#509](#509)) ([2f5f281](2f5f281)) * **bindings:** clean up failed async BLS work ([#527](#527)) ([1111b00](1111b00)) * **bindings:** free metrics writer on scrape failure ([#529](#529)) ([4c8d94a](4c8d94a)) * **bindings:** harden random aggregate scalars ([#528](#528)) ([8e89a63](8e89a63)) * **bindings:** log level for missing fields ([#435](#435)) ([08faf41](08faf41)) * **bindings:** misordering of print for cpu count ([#381](#381)) ([752a972](752a972)) * **bindings:** populate epoch participation for test fixtures ([#436](#436)) ([8dbdd2e](8dbdd2e)) * **bindings:** refcount Pool to fix teardown panic ([#352](#352)) ([23b2f68](23b2f68)) * **bindings:** roll back partial N-API initialization ([#491](#491)) ([31c5ebb](31c5ebb)) * **bindings:** size BLS thread pool by cgroup-aware CPU count ([#386](#386)) ([3ae9522](3ae9522)) * **bindings:** validate class types before unwrap ([#514](#514)) ([2fd2ad5](2fd2ad5)) * **bindings:** validate secret key hex length ([#517](#517)) ([136e415](136e415)) * **bls:** align PublicKey.uncompress validation with Signature.uncompress ([#508](#508)) ([5a8dbe9](5a8dbe9)) * **bls:** bound randomized aggregation inputs ([#548](#548)) ([779d0bf](779d0bf)), closes [#542](#542) * **bls:** clean up partial thread pool initialization ([#490](#490)) ([d55e598](d55e598)) * **bls:** convert pippenger scratch bytes to element counts ([#513](#513)) ([a12ca92](a12ca92)) * **bls:** enforce 32-byte signing roots ([#545](#545)) ([72fd308](72fd308)) * **bls:** make batch cardinality structural ([#547](#547)) ([a06d8b2](a06d8b2)) * **bls:** preserve aggregate outputs on failure ([#521](#521)) ([e0b6dd1](e0b6dd1)) * **bls:** reject empty keygen salts ([#524](#524)) ([d2a9c86](d2a9c86)) * **bls:** reject unknown BLST error codes ([#525](#525)) ([9e4a6ad](9e4a6ad)) * **bls:** size pairing buffers for 32-bit targets ([#531](#531)) ([dc64a27](dc64a27)) * **blst:** default signature infinity check to true if not provided ([#387](#387)) ([021cdcb](021cdcb)) * **build:** remove `zig-out` from `files` ([#360](#360)) ([c52af09](c52af09)) * **ci:** fix caching spec test version ([#439](#439)) ([96885a1](96885a1)) * dangling state pointer in loadOtherState ([#450](#450)) ([81cbd5f](81cbd5f)) * **epoch_cache:** compute missing `next_proposers` ([#447](#447)) ([0088a29](0088a29)) * **epoch_cache:** populate decision roots in afterProcessEpoch ([#453](#453)) ([4b70a5e](4b70a5e)) * export asyncAggregateWithRandomness through napi binding ([#371](#371)) ([1d04c2b](1d04c2b)) * harden memory safety across PMT, SSZ tree views, and state transition ([#377](#377)) ([d6f5897](d6f5897)) * improve atomic ordering in ThreadPool and NAPI init ([#310](#310)) ([4b0a1cc](4b0a1cc)) * interface compatbility with NativeBeaconStateView ([#445](#445)) ([89e13d1](89e13d1)) * missing deinits in loadOtherState ([#459](#459)) ([094d278](094d278)) * missing state commits ([#454](#454)) ([a432b55](a432b55)) * no-op when syncPubkeys run on a pk cache with shrinking validator set ([#432](#432)) ([ed05a99](ed05a99)) * param order in BeaconBlockBody ([#348](#348)) ([d8b9c06](d8b9c06)) * pendingConsolidations bindings ([#449](#449)) ([b9c497e](b9c497e)) * **pmt,ssz:** harden chunked-leaf and zero-copy tree-view memory safety ([#400](#400)) ([de50c53](de50c53)) * populate cache balances during rewards/penalties processing ([#474](#474)) ([5bf23dc](5bf23dc)) * re-expose sizes ([#369](#369)) ([64b81f3](64b81f3)) * remove `slashValidator` gating on active status ([#448](#448)) ([d319a0d](d319a0d)) * **ssz:** drop redundant default-init pass in fixed-list decode ([#468](#468)) ([0c757be](0c757be)) * **ssz:** publish child cache entries after lookup ([#565](#565)) ([21e78c9](21e78c9)) * state transition binding exports ([#456](#456)) ([895982c](895982c)) * **state-transition:** group-check signature sets ([#515](#515)) ([42774e9](42774e9)), closes [#502](#502) * **state-transition:** isolate epoch step cache mutations ([#535](#535)) ([a83741a](a83741a)) * **state-transition:** repair Pool.init call broken by [#346](https://github.com/ChainSafe/lodestar-z/issues/346)×[#367](https://github.com/ChainSafe/lodestar-z/issues/367) merge skew ([#394](#394)) ([b42944f](b42944f)) * various fixes around config ([#433](#433)) ([c4f082c](c4f082c)) ### Performance Improvements * **bindings:** drop TS BLS comparison benches and report benchmarks on PRs ([#552](#552)) ([c909c6f](c909c6f)) * **bls:** add cache-aware signature verifier ([#562](#562)) ([063857e](063857e)) * **bls:** bypass worker queue for small batches ([#553](#553)) ([3f8a6df](3f8a6df)) * **epoch:** replace AutoHashMap with array lookup in reward/penalty caches ([#286](#286)) ([e4e181b](e4e181b)), closes [#243](#243) * **pmt:** chunked-leaf packing for basic lists and container_struct ([#346](#346)) ([ba156c4](ba156c4)) ### Code Refactoring * allocate `AsyncAggRandData` in one obj ([#384](#384)) ([459750f](459750f)) * **bindings/pubkeys:** simplify allocation strategy for aggregate ([#518](#518)) ([b82750f](b82750f)) * **bindings:** rename blst Lifecycle to State ([#516](#516)) ([0a9c179](0a9c179)) * **bindings:** use zapi js.io() instead of local io module ([#469](#469)) ([2b34cc0](2b34cc0)) * **bindings:** wake only required number of workers ([#383](#383)) ([1db57f1](1db57f1)) * **bls:** allocations around VMAS ([#395](#395)) ([dfda58c](dfda58c)) * **bls:** clean up bls ([#398](#398)) ([e0f3b9b](e0f3b9b)) * **bls:** remove need for tracking results for verifyMultipleAggregateSignatures ([#389](#389)) ([6fe5c3f](6fe5c3f)) * **bls:** remove single-threaded fallback ([#390](#390)) ([e057713](e057713)) * **clock:** single public Clock; internalize SlotClock ([#463](#463)) ([fbab1fa](fbab1fa)) * make XXXDecisionRoot fns return `js.String` ([#342](#342)) ([aef4420](aef4420)) * move shuffle into swap_or_not_shuffle module ([#558](#558)) ([e56efb2](e56efb2)) * **pubkeys:** centralize the process-wide cache ([#522](#522)) ([dc9669d](dc9669d)) ### Miscellaneous Chores * avoid slow tests in AGENTS.md ([#546](#546)) ([c60f2a9](c60f2a9)) * bump zapi to include musl build ([#485](#485)) ([0b488cc](0b488cc)) * **ci:** pin github actions with sha hashes ([#507](#507)) ([167b8f5](167b8f5)) * deprecate unused blst APIs ([#575](#575)) ([7b547fa](7b547fa)) * **deps:** bump zapi v2.1.0 -> v2.2.0 ([#376](#376)) ([0c240d8](0c240d8)) * **deps:** bump zbuild ([#403](#403)) ([e2545de](e2545de)) * **deps:** compile blst with ReleaseFast ([#391](#391)) ([753a896](753a896)) * **deps:** update zapi to 3.1.0 ([#483](#483)) ([f3e5827](f3e5827)) * **deps:** use zapi v2.1.0 ([#372](#372)) ([88f403a](88f403a)) * disable gemini auto code review ([#382](#382)) ([63e42a4](63e42a4)), closes [#380](#380) * **docs:** add comments section in AGENTS.md ([#566](#566)) ([0c09750](0c09750)) * move state clones out of benchmark run functions ([#324](#324)) ([e4035de](e4035de)) * prepare 1.0.0 release ([#576](#576)) ([20b657b](20b657b)) * release v0.1.2-rc.3 ([#370](#370)) ([e4fc551](e4fc551)) * **release:** 0.1.2-rc.2 ([#365](#365)) ([7046128](7046128)) * **release:** v0.1.2-rc.10 ([#477](#477)) ([9a4fad5](9a4fad5)) * **release:** v0.1.2-rc.4 ([#373](#373)) ([09468f1](09468f1)) * **release:** v0.1.2-rc.5 ([#374](#374)) ([f344efa](f344efa)) * **release:** v0.1.2-rc.6 ([#375](#375)) ([bdf5b67](bdf5b67)) * **release:** v0.1.2-rc.8 ([#401](#401)) ([06f91c2](06f91c2)) * **release:** v0.1.2-rc.9 ([#404](#404)) ([6024800](6024800)) * remove merge transition code ([#359](#359)) ([09b175d](09b175d)) * remove stale epoch cache TODOs ([#534](#534)) ([27a547a](27a547a)) * rename era shortHistoricalRoot to shortEraRoot ([#473](#473)) ([c75a4d3](c75a4d3)) * **scripts:** build bindings with preset ([#434](#434)) ([a1b5ef7](a1b5ef7)) * silence debug log when used in release builds ([#486](#486)) ([c5377d7](c5377d7)) * support dev workflow ([#364](#364)) ([fcb9a78](fcb9a78)) * update gloas types to align with the latest specs ([#431](#431)) ([1f065b5](1f065b5)) * update spec test version to v1.7.0-alpha.11 ([#451](#451)) ([5875660](5875660)) * update spec-test-version: v1.6.0-beta.2 -> v1.7.0-alpha.10 ([#441](#441)) ([f932b1c](f932b1c)) * update zapi to 4.0.0 ([#571](#571)) ([de8e3fd](de8e3fd)) ### Documentation * document security threat model ([#557](#557)) ([e678b87](e678b87)) * more comprehensive AGENTS.md ([#520](#520)) ([c74b386](c74b386)) * **pkix:** document load provenance requirement ([#556](#556)) ([37e0aa2](37e0aa2)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Adds a Zig
swap_or_not_shufflemodule and JS bindings for the shuffle APIs Lodestar uses, so ChainSafe/lodestar can drop@chainsafe/swap-or-not-shuffle.state_transitionand the JS binding.Testing
Follow-ups
AI assistance was used for drafting and implementation support.