Repository navigation
refactor(bbapi): migrate bb + bb.js to ipc-codegen/ipc-runtime; delete legacy bb::ipc - #25362
Merged
Merged
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
charlielye
force-pushed
the
cl/ipc-bb-bbjs-migrate
branch
from
August 29, 2026 17:17
fb6fad4 to
edcb954
Compare
charlielye
marked this pull request as ready for review
August 29, 2026 17:17
This was referenced Aug 29, 2026
charlielye
force-pushed
the
cl/ipc-bb-bbjs-migrate
branch
24 times, most recently
from
August 31, 2026 18:35
c321053 to
f7cdb7d
Compare
charlielye
added a commit
that referenced
this pull request
Sep 30, 2026
A test that binds a port has to run in its own container. Which tests those were was a list of package prefixes in labs' `test_cmds`: ```bash # These need isolation due to network stack usage (p2p, anvil, etc). if [[ "$test" =~ ^(prover-node|p2p|ethereum|aztec|prover-client/src/test|...) ]]; then prefix+=":ISOLATE=1:NAME=$test" ``` A list only works if whoever adds a test in a new package knows to extend it, and an unisolated test that spawns anvil passes until the day it races another one. ### Two tests were already in that position `cli/src/cmds/l1/attester_exit`, added on 18 September, fails before reaching any assertion: ``` Anvil exited with code 0 before listening. Output: Error: Address already in use (os error 98) ``` three retries, all the same. It failed the `x-full-no-test-cache` run on #25362, a bb and bb.js migration it has nothing to do with. `no-test-cache` is what surfaced it: every test executes in the same window rather than most being served from cache, which is exactly when two unisolated anvils meet. The two it lost to were `ethereum/src/test/blob_kzg_warmup`, isolated because `ethereum` is listed, and `epoch-cache/src/epoch_cache.integration`, which is not listed and has been unisolated since April. Neither is at fault; `cli` and `epoch-cache` were simply never on the list. ### The change Name a test `*.isolate.test.ts` and it gets `ISOLATE=1`, wherever it lives. The two above are renamed accordingly. The package list stays, so nothing loses the isolation it has today. Prefer the suffix for anything new; an entry can leave the list later by renaming its tests. The point is that the requirement is stated where someone writes the test, instead of somewhere else they have to remember. Not a default-port change: fixed ports with full isolation is the established preference here, and ephemeral ports have brought their own flakiness before. ### Verified - The condition isolates both renamed tests, leaves ordinary tests shared, and keeps every currently listed package isolated. - The glob still enumerates both renamed files, 649 tests in all, and `.isolate.test.ts` is not caught by the `.bench.test.ts` skip. - `bootstrap.sh` parses with extglob and globstar, which is how ci3 runs it. Plain `bash -n` reports a syntax error on the `!(...)` glob both before and after this change. ### Landing Rides as a labs patch, `labs-patches/0009`, so CI gets it without waiting on a pin bump. It should be upstreamed to aztec-node and the patch dropped once the pin passes it. Worth a look from whoever owns `cli`, since it is their test failing other people's PRs. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
charlielye
force-pushed
the
cl/ipc-bb-bbjs-migrate
branch
from
September 30, 2026 10:04
c79d6db to
9ff4d14
Compare
charlielye
added a commit
that referenced
this pull request
Oct 1, 2026
…request (#25548) Addresses AztecProtocol/barretenberg-claude#4427 from the bb.js side. The aztec-node side is [aztec-node#354](aztec-labs-eng/aztec-node#354). A pooled bb verifier whose process dies is handed back to the pool and handed out again on every later borrow. The pool cannot tell, because the socket backend marks itself permanently unusable and every later call fails with a bare `Socket not connected`. The node therefore reports a dead helper process as an invalid transaction proof, persistently, and books it into the metric that means users are submitting bad proofs. This gives an owner the two things it needs, in the shape the AVM simulator pool already relies on. ### A failed call says whether retrying can help An environmental failure now carries `retry: true`: the bb process died, its connection broke, or it could not be started. Only a binary that cannot be executed stays non-retryable, because retrying cannot fix it. The bare property is the whole contract, feature-detected rather than imported: ```ts if (err instanceof Error && (err as Error & { retry?: unknown }).retry === true) { ... } ``` That is deliberately the same convention `ipc-runtime`'s transports use, so a caller written against one works unchanged against the other. It is also what lets a verifier distinguish a bad proof from a dead helper, which is the misattribution half of the issue. ### The socket backend can replace a dead process, on request `respawn` is a new `BackendOptions` flag, off by default. With it on, the bb process and its connection are one unit swapped as a whole: the next call starts a replacement, while calls already in flight still fail retryably. Concurrent callers that find the connection down share one replacement, so a single death costs a single process, and a replacement that arrives after `destroy()` is not left running. It is opt-in because a replacement has none of the state a command sequence establishes: no SRS loaded over the connection, no Chonk accumulation, no batch-verifier session with its registered keys. An owner that runs any such sequence must leave it off, so a death fails loudly instead of the next call quietly running against a process that has forgotten everything. The verifier pool qualifies because every verification carries its proof and key in the call. With this, a pool needs no liveness check and no maintenance loop: returning an instance unconditionally becomes correct, exactly as it already is for the AVM pool. ### Also `BarretenbergSync.initSingleton()` cached a failed initialization for the life of the process, so one bad spawn was permanent. It now clears the failure, as the asynchronous singleton already did. ### Testing `native_socket.test.ts`, against the fake bb the existing tests use: a killed bb fails the call retryably and keeps failing without the option; with it on the next call is served by a replacement process, a different pid; four concurrent calls that find the connection down start exactly one replacement; `destroy()` during a replacement leaves no process running; a replacement that cannot start fails retryably too; a connection that breaks while the process keeps running leaves no bb behind. `singleton.test.ts` covers the initialization fix and fails without it. Checked against a real bb as well: with the option off a killed bb gives a retryable error and keeps doing so, and with it on the next call transparently returns the same hash from a fresh process. ### What this does not cover `BarretenbergSync` runs on the shared-memory backend, which has no respawn and no way to report a death mid-call: the NAPI receive loop retries without a deadline, and because the call blocks the event loop the process exit is never even observed, so the caller wedges rather than fails. That path is untouched here and is deliberately left alone: the synchronous bb runs one thread doing hashes and signatures, so it is the least likely process on the machine to be killed, and the fix would mean threading a liveness check into the C++ client for a case that may never happen. #25546 covers the idle-death half of it by replacing a dead singleton, so the two PRs cover different backends rather than one superseding the other. ### Note on direction bb.js's hand-written backends are replaced by `ipc-runtime`'s in the codegen migration (#25362). Nothing above is lost in that move: `ipc-runtime`'s spawned backend already carries the same `retry` contract and the same opt-in respawn, so the callers written against this keep working and the implementation here is deleted. That is why this is expressed as the retry contract rather than as a liveness query, which would have to become part of the generated client's interface. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
charlielye
force-pushed
the
cl/ipc-bb-bbjs-migrate
branch
from
October 1, 2026 18:06
b4c4cc3 to
53105d0
Compare
…e legacy bb::ipc Combined redo of #23612 + #23614 + #23613. Those predate envelope ids (the 8-byte request-id frame prefix), which broke wire compatibility between the old and new stacks, so bb, bb.js and bb-rs have to move together. bb now serves msgpack through ipc-runtime on every transport: `bb msgpack run` maps ""/"-" to a stdio pipe server, .sock to UDS and .shm to shared memory, all dispatching through one generated handler with envelope framing (a plain file keeps the offline bare-frame replay). The command surface comes from a checked-in bb_schema.json in the friendly ipc-codegen dialect, with handlers implemented as thin adapters over the existing domain commands. The typed cbind entrypoint is replaced by the ipc-codegen FFI contract ipc_ffi_entry, so wasm and static-linking consumers speak the same payload as the transports. That makes the legacy machinery consumer-free, so it goes: the in-tree ipc library and its benchmark, the nodejs_module msgpack client (bb.js now uses ipc-runtime's NAPI addon, and nodejs_module only exports LMDBStore), the Command/CommandResponse named unions with their execute dispatcher, and the func_traits/schema reflection behind the CBIND macros. Both client libraries now generate from ipc-codegen rather than forks of it. bb.js/src/cbind (a stale copy of the generator: schema visitor, TS and rust backends, naming, friendly-schema lowering) is deleted in favour of calling ipc-codegen, and barretenberg-rs drops its hand-written Backend trait, error type, Fr/Point types and both backends — codegen emits those, and ipc_runtime::IpcClient plugs in as the Backend, so the crate is ~89% generated with a deprecated BarretenbergApi shim keeping the published surface. The hand-rolled PipeBackend goes with them; its tests now run over ipc-runtime's UDS transport. ipc-codegen gains --strip-type-prefix (bb.js and barretenberg-rs publish unprefixed names while wire tags keep the service prefix) and three rust serde fixes: fq[2] pairs, [u8; N] and above-cutoff byte arrays were encoded as sequences of integers rather than msgpack bin, which the C++ side rejects. ipc-runtime gains the pipe transport (fd pair or stdio, peer EOF requests shutdown), a sync spawned-process backend for callers that cannot await, pre-spawn removal of stale shm segments (they are created O_EXCL, so a killed server's leftovers blocked the next one), and an unref option so a spawned backend cannot hold the Node event loop open while idle. Its rust and zig build files now discover the C++ sources instead of keeping hand-written copies of the CMake list, which had silently drifted. Also fixes two latent breakages from the ts/ -> ts/bb.js/ restructure that blocked wasm and pinned-flow tests: the barretenberg_wasm symlinks and the chonk pinned-inputs repo-root resolution were both one directory short. Raises MAX_FRAME_SIZE from 256 MiB to 1 GiB. bb.js reaches bb over ipc-runtime's socket transport now rather than the deleted nodejs_module msgpack client, so proving requests are newly subject to the frame cap: the rollup circuits exceed it, and bb_prover_full_rollup failed with the server refusing a 314525552-byte frame and the client seeing the closed connection as "write EPIPE". The TS client also checks the outgoing size, so a request over the cap names itself instead of surfacing as an unexplained EPIPE on the next write. Fixes two gaps in unref handling that left the Node event loop open. The shared-memory backends dropped the caller's option -- their new() did not take it and createBackend never passed it -- so unrefStdio was never set and the child's stdout/stderr pipes (which exist whenever a logger is set) kept the loop alive; they thread it through as the socket backend already does. And BarretenbergSync.initSingleton did not force unref: true, unlike its async counterpart, so the sync backend it creates held the loop for the life of the process. Nothing destroys a singleton, so it opts in now too. Together these had a labs pxe test pass all 7 cases and then hang until the job timed out. Rebased onto next, which added two things to the surface this replaces. #25487 hardened the msgpack entrypoints: the generated dispatch already answers a malformed envelope, an unknown command and a throwing handler with an error frame, but an unpack or convert failure still escapes it, so api_msgpack.cpp and ipc_ffi_entry catch that rather than lose the response. Poseidon2AbsorbChain was a new command registered in the NamedUnion this deletes, so it moves to bb_schema.json with a handler alongside the other crypto ones, and its malformed -length test is reexpressed against the FFI entrypoint. barretenberg/acir_tests is its own yarn project and consumes bb.js through a portal, so it has to resolve bb.js's dependencies itself. bb.js declares @aztec-foundation/ipc-runtime with the placeholder specifier that only a resolution gives meaning to, which barretenberg/ts has and acir_tests did not: `yarn install` there failed with "isn't supported by any available resolver". It gets the same resolution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016einpgfthfjLYGwCB3iQqD
…ull bb build It takes ~35s idle but exceeded its 360s jest timeout (408s) when the lane ran concurrently with a full bb rebuild. It uses the published bb.js, so the slowdown is load, not the tree: raise the per-test timeout to 20 minutes and the lane's to 25. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
…al run() loop The shared-memory transport woke the other side with a futex syscall on every publish and release, about six per bb request, which roughly doubled the pipelined poseidon benchmark against next. Those wakes were made unconditional to fix an intermittent hang, whose cause turned out to be a startup race rather than the conditional wake itself: segments are mappable from ftruncate, before create() initialises them, so a fast client could attach, set its blocked flag on a response ring, and have the server's initialisation reset it; the next publish then skipped the wake and the client slept on published data forever. connect() now refuses a segment until it is fully initialised. create() writes the ring capacity, and the doorbell's num_slots, last with release ordering, and a zero value means not ready: SpscShm::connect and MpscProducer::connect throw and the existing connect-retry loops try again. With that in place the conditional wake is restored on the rings, the doorbell and the reactor's notify(). bb also served every request through run_reactor, paying a request copy, a heap-allocated respond closure, a locked completion queue and a notify() (a self-pipe write on sockets) per request, although every bb handler responds before returning. It now uses the serial run() loop, asserting that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
…and stop the MPSC spin reading the clock every iteration Pipelined bb.js calls over shared memory were still ~50% slower than next on the poseidon benchmark after the futex fix. Layer by layer, the transport itself was at parity; the extra cost was main-thread JavaScript per call, which left the main thread saturated so the API's own encode/decode no longer overlapped with bb's work. Most of it was SpawnedProcessBackend.call: an async function awaiting ensureUp() before every call. A live process now takes a direct path with no async frame; only a missing or dead process goes through ensureUp(). On the benchmark's pattern (the first pipelined burst after 10k sequential calls) this takes the fixed build from 17.3 to 13.4 us/call (min of 8, next 14.1). MpscConsumer::wait_for_data's spin also called clock_gettime on every iteration, about a tenth of bb's instructions while serving; it now checks the clock every 256 iterations, as SpscShm's spin does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
… with small-int ids NapiShmAsyncClient kept pending calls in a Map<bigint>. Each call allocated two heap BigInts (the id and the one napi hands back) and hashed them three times. Under jest, where the main thread saturates during a pipelined burst, that overhead was directly visible in bb.js's poseidon bench. Ids are now numbers below 2^30, so V8 keeps them as small integers. Pending calls sit in an issue-order queue: in-order servers (bb's serial run() loop) always match the head, and out-of-order responses fall back to a search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
…a per-call .catch SpawnedProcessBackend.call chained .catch(attributeCallError) onto every call, so each successful call paid for an extra promise and microtask. In a pipelined burst under jest that was ~0.8us/call of main-thread CPU in steady state and ~2us in the first burst. The SHM and UDS clients now take an optional mapError hook, applied only on their rejection paths. SpawnedProcessBackend passes attributeCallError for the incarnation when it connects, and the fast path returns the client's promise directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
Every mode was timed on its first run, so the numbers included JIT warm-up for that call pattern. A pipelined burst exercises different code paths from the sequential warm-up that preceded it, so it was the most affected. Its first-burst result swung by several µs between CI runs. Each mode now runs once untimed first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
UdsIpcClient still kept pending calls in a Map<bigint>, with the same per-call BigInt allocation and hashing the SHM client had. Both clients now use one PendingQueue: small-int ids, head-of-queue fast path, search fallback. The UDS wire id stays u64; live ids sit in the low word, and a nonzero high word never matches a call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
The 7.0 line is a breaking release, and older barretenberg-rs versions stay on crates.io for anyone who needs the old API (semver keeps `"6"` manifests off 7.x). Removes the legacy BarretenbergApi/PipeBackend/byte-valued types, the old module paths and the inert `native`/`async` features, so the crate exposes only the generated BbApi. The tests move to BbApi with typed scalars; the pipe tests spawn bb and drive it through ipc_runtime::IpcClient::from_fds. The README gains a 6.x migration note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
barretenberg-rs now depends on the ipc-runtime crate, which had no version and was not on crates.io, so `cargo publish` refused the package. The release step now publishes ipc-runtime at the same version first and pins it exactly. ipc-runtime's build compiles the C++ sources from ../cpp, which a published crate does not have; the release copies them into the crate's cpp/ directory and build.rs falls back to it. Publishing happens from the barretenberg-rs release rather than ipc-runtime's, because the latter also runs for private releases, which must not reach crates.io. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
…release ipc-runtime owns its crate, so its release publishes it. The release also runs for private releases, which publish only to the internal npm registry, so the crates.io step is skipped when assert_public_release refuses. ipc-runtime is released before barretenberg/rust, whose release now only pins the version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
The wake is skipped unless the waiter has flagged that it is about to sleep. Each side stores (head/tail/seq, or the flag) and then loads the other's value, and release/acquire lets each load run ahead of its own store. Both sides could then miss each other, leaving the waiter asleep on published data, with no timeout on the default call path. A seq_cst fence between the store and the load on both sides guarantees one of them sees the other. The futex lock already fenced the waiter in practice; the SPSC producer had no such cover. No measurable cost in a pipelined poseidon A/B on a loaded host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
UDS and pipe frames were capped at 1 GiB (256 MiB until bb proving requests outgrew it). Nothing in a stream transport needs a limit below the u32 length field; the cap only guarded against a corrupt prefix driving a huge up-front allocation. Receivers now grow their buffer as payload bytes arrive (C++ read_payload, TS FrameReader), so that guard is no longer needed and the limit is the wire format's own. SHM stays bounded by half its ring capacity. FrameReader also fixes the TS client and server re-concatenating their whole buffer on every chunk, which made receiving a large frame quadratic. A request too large for the length field now rejects with a non-transport IpcError: it is the caller's mistake on a healthy connection, and as a transport error SpawnedProcessBackend killed the server for it. Senders also check against the payload limit (frame limit minus the 8-byte id) rather than the frame limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
Serving "-" made the process's stdout the frame stream, so any other write to it (std::cout, printf, a library's diagnostics) landed inside a frame and desynced the peer. next's bb guarded this by redirecting std::cout; that guard did not carry over to the ipc-runtime pipe path. make_server now serves on a private duplicate of stdout and points fd 1 at stderr, which also covers C stdio and raw writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
It looked for nodejs_module.node, which no longer ships now that bb.js uses ipc-runtime's addon, so it could only ever return null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NwZD6M5xfpDdAFkdbkWT67
charlielye
force-pushed
the
cl/ipc-bb-bbjs-migrate
branch
from
October 13, 2026 20:02
9f50140 to
b8a89ca
Compare
…60s deadline The sync NAPI client gave up on any call after 60s, failing a slow but healthy server and reporting a dead one only as a timeout. It now has no deadline: given the server's pid, it checks between 1s waits whether the server has exited (waitid with WNOWAIT, so Node still reaps the child) and fails with its exit code or signal, which SpawnedProcessBackendSync reports as IpcProcessExitedError. A request too large for the ring now fails at once rather than waiting for space that never frees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Prk3bcqkopGENJUuUpT52q
It takes ~250s on an idle machine, but timed out at 600s while the bb cross-object builds compiled alongside the tests: the run queue held 150-350 threads on 64 cores, and the test is pinned to all of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Prk3bcqkopGENJUuUpT52q
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Combined redo of #23612 + #23614 off
next. Those PRs predate envelope ids (the 8-byte request-id frame prefix), which broke wire compatibility between old and new stacks — bb, bb.js, and bb-rs must move together, so this is one PR. The old PRs were used as the recipe; they'll be closed pointing here.bb (C++)
bbapi/bb_schema.json(64 commands) andbb_curve_constants.jsonchecked in, in the friendly ipc-codegen dialect (same as avm/cdb/wsdb). CMake runs ipc-codegen at build time (client+server,bb::bbapinamespace); the schema is also embedded sobb msgpack schemaprints it.bbapi_handlers.*(+bbapi_chonk_handlers.cpp,bbapi_ultra_honk_handlers.cpp,bbapi_wire_convert.hpp) implement the generated dispatch (Responder-style, exactly-once ok/error) as thin adapters over the existing domainCmd{...}.execute(ctx)commands.bb msgpack runserves every transport through ipc-runtime —-/empty → stdio pipe server,.sock→ UDS,.shm→ shared memory — one generated handler, envelope framing everywhere. It uses ipc-runtime's serial zero-copyrun()loop rather thanrun_reactor(): every bb handler responds inline, so the reactor's per-request copy, closure, lock and wake bought nothing. A plain-file input keeps the offline bare-frame replay for wasm/debug flows.bbapi()replaced by the ipc-codegen FFI contractipc_ffi_entry(input, len, out, out_len)— same msgpack payload as the transports, no envelope, output is free()-compatible.barretenberg/ipc/+ipc_bench; the wholenodejs_module/(it only hosted the msgpack_client NAPI wrappers — bb.js now uses ipc-runtime's addon) along with its CMake preset targets, thebb-cpp-yarnMakefile step, and the copies ofnodejs_module.nodeinto bb.js;bbapi_execute.*(Command/CommandResponse named unions + dispatcher); func_traits/schema reflection + CBIND macros + msgpack_schema test.ipc-runtime
PipeServer/PipeClientover an fd pair or stdio, identical envelope framing to UDS, peer EOF → shutdown;serve_helpermaps-to stdio, serving on a private duplicate of stdout and pointing fd 1 at stderr so stray stdout writes can't corrupt frames (next's bb redirectedstd::coutfor the same reason). Exposed through the C ABI (ipc_client_create_pipe, dup'ing the caller's fds) and the Rust crate (IpcClient::from_fds). Used bybb msgpack runon stdio and by bb-rs. The Rust and Zig builds now discover sources instead of listing them.read_payload, TSFrameReader), so a corrupt prefix can't force a large allocation, and the TS client/server no longer re-concatenate per chunk (receiving a large frame was quadratic). SHM stays bounded by half its ring capacity.ftruncatebut beforecreate()initialised it, and that init then wiped the client'sconsumer_blockedflag, so a later publish skipped the wake and the client slept forever.create()now publishescapacity(rings) /num_slots(doorbell) last with release ordering,connect()refuses a segment until it is set, and wakes are conditional again (~6 futex syscalls per request saved). Each side of the wake handshake stores then loads the other's value, so both carry aseq_cstfence between the two; release/acquire alone would let both miss each other. Regression testShmTest.ConnectRefusesSizedButUninitialisedSegments. The MPSC consumer spin now reads the clock every 256 iterations rather than every iteration.SpawnedProcessBackend:unref/unrefStdiooptions (child + idle-socket unref, re-ref while calls are in flight) so bb.js callers that never destroy() can't hang node; stale shm segments/sockets from a SIGKILLed previous occupant are removed before spawn; a transport error against a still-live server kills it so respawn can replace it; the connect is aborted when the child dies first.SpawnedProcessBackendSyncfor the SHM sync client.await ensureUp()per call. Errors are attributed to the dead process (exit code, log path) by anIpcErrorMapperhook the clients apply on their rejection paths only, instead of a per-call.catch.PendingQueue: small-integer ids (V8 Smis) and an issue-order queue with a head-of-queue fast path and a search fallback for out-of-order servers. This replacesMap<bigint>, which allocated and hashed heap BigInts on every call. The NAPI async client passes ids as numbers; the wire id stays u64.IpcError, so it doesn't makeSpawnedProcessBackendretire a healthy server.waitidwithWNOWAIT, so Node still reaps the child). A death fails the call with the exit code or signal, whichSpawnedProcessBackendSyncreports asIpcProcessExitedError; a live server is waited on however long it takes. A request too large for the ring fails at once, rather than waiting for space that never frees. The wait loop lives inipc_runtime/blocking_call.hpp. The async SHM client already had no deadline (it is destroyed when the child exits).ipc-codegen
--strip-type-prefix: strips the service prefix from generated type and converter names too (BbCircuitProve→CircuitProve); wire tags always keep the full schema name.[u8; N]fields get an explicit bytes codec (msgpack bin, not an integer sequence — the C++ side rejects the latter); generatedBin32keeps the byte-oriented surface callers had (deref to slice, comparisons withVec/arrays);fq[2](Fq2) fields getserde_array2_bytes, so G2 coordinates serialize as bin32.bb.js
src/cbindgenerator is deleted.scripts/generate.shruns ipc-codegen on the checked-inbb_schema.json(plainnode --experimental-strip-types, ts-node dropped) with--strip-method-prefix --strip-type-prefix, so the public API surface is unchanged while wire tags carry theBbprefix.SpawnedProcessBackend/SpawnedProcessBackendSyncand NAPI clients (no separate bb.js NAPI addon); wasm backends call theipc_ffi_entryexport. Unused stdio pipe backend deleted, andfindNapiBinaryremoved (it looked for the no-longer-shippednodejs_module.node).poseidon.bench.test.tswarms each mode with one untimed pass before timing it; previously every mode was timed cold, and the pipelined result in particular swung by several µs between CI runs.Field2is kept as a deprecated alias ([Uint8Array, Uint8Array]) so existing consumers compile; Fq2 coordinates are now typed per curve.chonkStarttakeskindsas aUint8Array, sinceCircuitKindtravels as au8.AztecClientBackendpipelines its accumulation calls and awaits only the finalchonkProve, where any failure surfaces.BBApiException, through the generated API's error factory.BarretenbergSync.initSingletonnow forcesunref: true, like the async singleton. Nothing destroys a singleton, and without it a process using one hung at exit.IpcProcessExitedError(exit code or signal, log path) instead of never returning; a slow but live bb is waited on indefinitely.napiPathis an optional override; by default ipc-runtime's addon is used.@aztec-foundation/ipc-runtime, and so doesacir_tests(portal intoipc-runtime/ts).src/generated/) is excluded from prettier and eslint: it is gitignored, but CI runners reuse working directories, so it can be present whenyarn formattingruns.barretenberg/tsandbb.jsbootstraps includeipc-runtime/ts/package.jsonin the node-modules cache key, since the workspaces portal into it.bb-rs
Backendtrait has a bridge impl foripc_runtime::IpcClient(UDS / SHM / pipe), behind theipc-runtimefeature; the FFI backend (bindingipc_ffi_entry) is generated. The in-crate pipe/FFI backends, error and types modules are replaced by the generatedbackend,bb_client,bb_types,error,ffi_backend.BarretenbergApi→BbApi,PipeBackend→IpcClient::from_fds, typed scalars (Fr/Fq/… asBin32) in arguments and responses, noshutdown()(bb exits on EOF),nativefeature →ipc-runtime,asyncremoved. Older versions stay on crates.io and semver keeps"6"manifests off 7.x; the README has a migration note. The only public dependent,noir-zk-backend, pins an exact 7.0 nightly.barretenberg-rsnow depends on theipc-runtimecrate, which wasn't on crates.io (cargo packagerefused the path-only dependency). ipc-runtime's release now also publishes its crate (it runs before barretenberg/rust's), and the bb-rs release pins it exactly (=$version). The published crate carries a copy ofipc-runtime/cpp/ipc_runtimeincpp/, andbuild.rsfalls back to it when../cppis absent. ipc-runtime's release also runs for private releases (internal npm only), so the crates.io step is skipped whenassert_public_releaserefuses.Shutdownis gone (pipe EOF replaces it — it was the only sender)../bootstrap.sh generate, ipc-codegen onbb_schema.json) instead of via bb.js'syarn generate, so its cache hash covers the schema and ipc-codegen rather than the whole ts build.fr_ext.rskeeps theFrconstructors (from_u64,from_buffer_reduce, …) as a separate impl, so the generated types stay a pure regen target.Schema dialect notes (vs the raw dump the old PRs used)
Shutdownremoved;CircuitKindis a plainu8on the wire (the alias collided with the domain enum atbb::bbapiscope); Fq2/G2 coords arefq[2]; the shared vk-data struct isVkData(prefix-stripping made the old name collide withCircuitComputeVkResponse).Drive-by fixes
nodejs_module,findNapiBinaryandbbapi()(bb and vm2CLAUDE.md, the benchmark-avm skill,-fPICcomments in CMake).ts/→ts/bb.js/restructure, both one directory short: thesrc/barretenberg_wasm/*.wasm.gzsymlinks resolved tobarretenberg/ts/cpp/..., andchonk_pinned_inputs.test.ts'sfindRepoRoot()producedbarretenberg/barretenberg/cpp/chonk-pinned-flows.barretenberg/docsrecursive example: 1500s lane / 1200s jest budget. It uses the published bb.js, and runs alongside the bb build, which slows it by an order of magnitude when bb rebuilds everything.dsl_testsroot rollup Tampering: 900s timeout, up from 600s. It takes ~250s alone, but timed out when it ran alongside the bb cross builds, which miss the cache whenever bb changes. With 150–350 threads ready to run on 64 cores, both ASAN tests ran 3–5x slower.Performance
The poseidon bench's "Native Shared Pipelined" (2 fields) was ~19–20 µs/call on this branch vs ~12 µs on next. Causes, each measured in isolation: the always-wake futex traffic, the reactor serve loop, the async hop in
SpawnedProcessBackend.call, the per-call.catch, and theMap<bigint>pending map (only visible under jest, whose VM realm runs this JS ~3.5x slower and saturates the main thread in a pipelined burst). With the fixes above, CI reports 12.71 µs/call — level with next — and the other modes match next too (shared sync 21.5, shared 34.7, socket pipelined 29.8, socket 48.3).Validation
SpawnedProcessBackendexit attribution, kill-on-broken-connection and respawn).bbapi_testsgreen;c_bindexception tests green throughipc_ffi_entry.native_shm.test.tskills bb and calls it in the same tick; the call fails in under a second withIpcProcessExitedError(signalSIGKILL), where before it took 60s. Full suite (66 pass), exercising every transport — wasm, native socket, socket pipelined, SHM async, SHM pipelined, SHM sync.ipc_ffi_entrypath returns the identical hash. (In wasm a failing command aborts via thethrow_or_aborthost import rather than returning an error response — unchanged behavior, since thewasmpreset definesBB_NO_EXCEPTIONSand the old cbind guarded its catch the same way.)libbb-external.a. Release dry run in a scratch copy: the packagedipc-runtimecrate builds with no../cpppresent,barretenberg-rspackages against it, and a consumer built only from the two packages hashes through bb over UDS.🤖 Generated with Claude Code
https://claude.ai/code/session_01NuUzj3qpkpJor6GMpWB4T4