chore: update rust-sdk to 0.17.0-rc.1 - #406
Conversation
Adopts miden-client 0.17.0-rc.1 (protocol 0.17.0-rc.5, VM 0.33), so the web SDK can cut an rc matching the Rust SDK's. Requires a node on the matching protocol and a new client database. A client now needs a protocol configuration to execute at all: 0.17 moved the fee asset out of the block header into that configuration, and execution and note screening both resolve it by the commitment in the reference block. The node does not serve it over RPC yet, so the SDK builds it from the chain's fee faucet, taken from the new ClientOptions.feeFaucetId or from a per-network table, and client.feeFaucetId() replaces the removed BlockHeader.feeFaucetId(). A mock client registers the mock chain's own configuration. The rest follows upstream removals: ForeignAccount::Prefetched and AccountInputs are gone, so the prefetched foreign-account API goes with them; AccountVaultDelta now carries whole assets added and removed rather than signed per-faucet balances, so fungible() and FungibleAssetDelta go; txKernelCommitment() becomes protocolConfigCommitment(). Each CHANGELOG entry names the migration.
The integration jobs build their node from the pinned rust-sdk revision, and a 0.16 node rejects a 0.17 client at the accept header before serving anything, so the pin has to move with the client dependency.
A 0.17 client cannot execute without a protocol configuration, and the local test node mints a fresh native faucet at every genesis, so the harness reads TEST_MIDEN_FEE_FAUCET_ID and passes it to every client it builds. CI fills it from the node's bootstrap log after starting the node. The Node adapter was passing a positional `false` where the napi binding now takes the fee faucet, so it forwards the id instead.
MidenConfig.feeFaucetId reaches the WebClient the provider builds. Without it a React consumer on a network the SDK knows no fee faucet for has no way to give its client a protocol configuration, so it can neither execute nor screen notes.
The browser fixture now exposes window.helpers.createClient and createClientWithKeystore, and the suites that opened a second client call those instead of spelling out the wrapper's positional list. The list had grown to eight arguments, and passing the fee faucet in the wrong slot silently lands it in logLevel - the client then builds with no protocol configuration and fails later, far from the call. The cross-tab pages in sync_lock set themselves up without the fixture, so they take the faucet through their evaluate payload. Also documents feeFaucetId on the JS WebClient constructor and corrects the external docs snippet that read the fee faucet off a block header.
tools/strip-masp-debug requested miden-core and miden-mast-package 0.29 while the 0.17 client embeds packages from 0.33, so cargo resolved a second, older copy and the tool parsed a format the binary no longer contains. It recognised no packages, exited 1, the rollup plugin logged the wasm-opt failure as a warning and carried on, and the ST artifact came out at 118.5 MiB against the 25 MiB the size gate allows (MT 184.7 against 35). Workspace membership does not pin the tool to what the binary holds; the version requirement does, so these move with miden-client. Dropping the stale requirement also takes a duplicate VM 0.29 tree out of the lock. Package::digest is gone in 0.33; the identity the tool wants is dependency_commitment, which is documented to exclude exactly what the tool removes (debug data) and adds (an opaque custom section). The fixture built its debug_info section from filler bytes. 0.33 validates that section when a package is read, so filler now makes the package unreadable and the tool skips it rather than stripping it; the section is built through the debug-info builder instead, in sub-4 KiB chunks because a single debug string is capped there. The expected stripped length now comes from without_debug_info() rather than a reader round-trip, which keeps every section.
…e network constructors The vault-delta doc claimed a fungible balance that both grew and shrank appears as "the net asset the kernel encoded" - an inference about the kernel rather than something the source says. What it does say is that a delta carrying the same asset ID twice is rejected when it is built, so an asset appears at most once. feeFaucetId joins READ_METHODS, which is what the method-classification gate reads: every async method on the WASM client has to be classified, and it takes no borrow of the client's cell. createTestnet carries a line saying the chain's fee faucet has to come in through options until the SDK knows one for the network.
Six defects CI found in the upgrade, five of them mine: A multisig request now declares a fee conversion salt on a fee-free chain. 0.17's multisig auth resolves its AUTH_ARGS unconditionally - the bound block and the summary salt come from them, and only the fee note is skipped at a zero base fee - so declaring none left the account with no auth args and the component aborted piping a preimage that was never written. That is the 'advice stack read failed' every summary path hit: executeForSummary, chain-anchored co-signing, and any multisig transaction on a chain that charges nothing. createNetworkNote declares the target network account as a foreign account, which 0.17 requires because the kernel prices the note through a procedure call on the target. buildNetworkNote only builds the note, so its caller owns the declaration; its docs now say so, and the integration test that builds the request by hand declares it too. The JS WebClient statics were declared 0-6 and 0-9 arguments while the implementation had taken an observability bag since 0.16 and now takes a fee faucet, so every React consumer failed to typecheck. The declarations now match, with the observability bag named as an interface. extraArgs.wasmOpt: [] does not bypass wasm-opt - optimize.wasmOpt does - so fast builds ran it bare, with no --strip-dwarf, over a module carrying its DWARF, and the runner OOM-killed it once the 0.17 module grew. Both CI comments already claimed fast builds skip the pass; now they do. node-index.js is generated from the napi surface and was hand-edited, which also left two removed exports behind. Regenerated. The high-level clients in miden_client_api.test.ts take the chain's fee faucet the same way the fixture's low-level clients do.
clippy's doc_markdown lint is denied on the wasm build and reads a bare capitalised identifier as a missing code span.
🚀 WASM proving — -63.15% faster:
|
| Head | 66d2ff3 |
| Base | 860a4fe (next) |
| Method | mean of per-rep fastest, 6 reps × 3 warm proves, release / mt, 8 threads on warp-ubuntu-latest-x64-8x — lower is better |
| Workload | 1 benchmark (ms) — prove / consume / ecdsa-k256-keccak |
Moved beyond the noise floor
| Benchmark | Base (ms) | Head (ms) | Δ | Δ % | |
|---|---|---|---|---|---|
| 🔻 | prove / consume / ecdsa-k256-keccak |
7,780.2 | 2,866.7 | -4,913.5 | -63.15% |
Methodology and raw samples
- The timed interval is the
proveTransactioncall on a client constructed without a web worker. Prover construction, transaction execution, the faucet draw and the production worker round-trip are outside it, so a change confined to any of those does not move this number. - The run configuration below — runner, profile, variant, thread count, repetition and prove counts — is reported by the benchmark job, which runs from this pull request's own code. It describes what that job says it did. Every figure and every verdict above is recomputed here from the raw samples.
- Each benchmark keeps 6 reps × 3 warm proves on
warp-ubuntu-latest-x64-8x(8 threads,release/mt). One extra repetition and the first prove of every page run first and are discarded. - The reported figure is the mean of each repetition's fastest prove. Within one repetition every prove is bit-identical work, so interference — which only ever adds time — is all that varies, and the repetition's fastest prove is its best observed warm prove — a lower-tail statistic, not a measured "clean" cost, and so blind to a regression that leaves the best case alone (see the mean cross-check). Across repetitions the faucet differs, which shifts the proof-of-work grind, so averaging the per-repetition minima shrinks that lottery — it averages the grind down rather than cancelling it, because each side draws its own.
- Measured over six calibration runs of identical binaries, this estimator holds a standard deviation of 1.79%, against 2.96% for a global minimum and 5.39% for a plain median.
- A movement is only called significant when it clears the noise floor and no repetition's paired difference contradicts the direction, with a majority of repetitions positively agreeing. Base and head run interleaved within a repetition, so the pairs saw the same machine; a movement whose repetitions disagree is reported as unresolved rather than as a result. Runs shorter than 6 repetitions are never called significant — the floor is calibrated at that count and does not transfer below it.
- Base and head are driven one prove at a time, alternating, with the order flipped every prove. Running each side's batch back to back let the second side pay a consistent penalty — measured at +1.19% before this was fixed, which is a bias no number of repetitions removes.
- A wide gap between the reported figure and the max has two causes and the samples below do not separate them: interference within a repetition, and the proof-of-work grind differing between repetitions. Neither invalidates the comparison — both sides ran interleaved on the same machine — but a gap much wider than usual is worth a second look.
- Base and head are measured in the same job on the same runner, so runner-to-runner drift cancels out. The base dist may have been built by an earlier run of this workflow and restored from cache — the cache key covers the toolchain and the build commands, so the bytes match what this run would have produced.
- Δ % = (head − base) / base on that figure. Lower is better for every benchmark in this suite.
- Every figure above is recomputed here from the per-rep samples in the artifact; the summary statistics the bench script reported alongside them are not used.
- The ±1.90% threshold is calibrated on
warp-ubuntu-latest-x64-8x(2026-08-27): 30 runs of one build against a copy of itself, at 6 repetitions, gave a standard deviation of 0.62% and a largest movement of 1.17%. It is 3σ (1.85%) rounded up, which here is the procedure rather than a deviation from it: the largest movement across those 30 no-change runs stayed below 3σ, so nothing observed argues the tail is heavier than normal. How this is measured. - Full machine-readable results are attached to the run as
results.json.
Per-rep samples
prove / consume / ecdsa-k256-keccak
base rep 1: 8,325.4, 7,726.8, 7,755.2
base rep 2: 7,945.4, 7,962.9, 7,767.0
base rep 3: 7,737.1, 7,626.1, 7,940.4
base rep 4: 7,721.5, 7,832.5, 7,847.9
base rep 5: 7,861.6, 7,939.5, 8,499.7
base rep 6: 7,999.5, 8,023.1, 7,978.4
base median 7,900.6 ms min 7,626.1 max 8,499.7
head rep 1: 2,871.2, 2,929.8, 3,332.6
head rep 2: 2,835.8, 2,818.7, 2,792.4
head rep 3: 2,870.8, 2,872.3, 2,974.7
head rep 4: 2,854.0, 3,009.8, 3,012.0
head rep 5: 3,029.3, 2,887.6, 2,921.0
head rep 6: 2,924.5, 2,964.1, 2,959.7
head median 2,922.8 ms min 2,792.4 max 3,332.6
🔺 slower beyond the noise floor · 🔻 faster beyond the noise floor · ❔ no verdict — see the heading and notes above · ➖ within the noise floor
Generated by bench run 35629280805 for 66d2ff3. This comment is informational only and never blocks merge.
miden-client commits the two-word fee pair a fixed-salt component reads, so a multisig was handed a preimage one word short and aborted piping it; on a fee-free chain it got no auth args at all. Build MultisigAuthArgs here and set it as the request's auth argument, which miden-client leaves alone. Also stops `feeFaucetId` on the wrapper instance shadowing the WASM accessor of the same name: createClientProxy forwards only properties missing from the instance, so client.feeFaucetId() returned the option string.
0.17's network auth component asserts the transaction had an effect before fee payment, so the scriptless deploy the test used now aborts with ERR_NETWORK_ACCOUNT_TRANSACTION_HAS_NO_EFFECT. A minted P2ID note is the cheapest effect the account's own allowlist already permits; receiving it needs the basic wallet component.
Reading one accessor twice and comparing cannot fail. The mock chain gives the test no faucet of its own choosing, so the real assertion goes where the input is known: an integration client created with the configured faucet. Adds the tripwire for the docs that now call the option mandatory - while KNOWN_FEE_FAUCETS is empty a client without one must fail, naming the option.
All five callers pass Some. The Option carried no behaviour and made the fee-faucet cell look as if it could legitimately stay empty after setup.
Five sites spelled the eight-argument positional list out by hand, which is how the trailing fee faucet gets dropped silently. The two self-setup pages cannot reach window.helpers and say so.
feeAwareTransactionRequestBuilder takes an optional delta: the approvers' signatures stop authorizing the transaction that many blocks after the block the summary binds, bound by the summary so the executing party cannot change it. Omitted, the approval does not expire, which is what every path had. These two files also carry the constructor and zero-fee corrections for the clusters below, since they document the same methods.
The zero-fee invariant was copied verbatim in six places and is now wrong in all of them: since 0.17 a multisig resolves its auth args whatever the chain charges, so a zero base fee is no longer a reason the builder comes back untouched. Only the executing account's auth component decides. Separately, while the SDK knows a fee faucet for no network, every non-mock client has to be told one - including the React provider, whose config the change adds feeFaucetId to. The constructors say so themselves rather than leaving it to a quickstart the reader may not have opened. Also drops the two symbols 0.17 removed from the React patterns skill, and corrects the array-macro line range in the wasm-bridge skill.
…with setupBrowserPage never set window.feeFaucetId, so every createIntegrationClient call threw, the bare catch reported it as "no node reachable", and the bodies skipped. Measured against next: ci-shard-2 went 33 passed / 0 skipped to 23 / 10 and ci-shard-3 66 / 1 to 64 / 3 - 12 tests stopped running while both shards reported success. A missing fee faucet now throws with its own message instead of borrowing the "no node" meaning, and the remaining catch logs what it swallowed.
ci-shard-1-tx-flows aborted with error code 1094550424789963168, which decodes to ERR_NETWORK_ACCOUNT_TRANSACTION_HAS_NO_EFFECT: 0.17 asserts a network account's transaction consumed a note, created one, or changed account state before it pays the fee, and the NTB test still deployed with an empty request. That account carries a counter and no wallet, so there is no note it can receive; it deploys by running an allowlisted script that bumps its own counter, which is why the counter now reads 1 after deploy and 2 after the node consumes the note. The two consumer-facing recipes that still taught the dead shape are corrected with it.
get_latest_block_header itself begins with a sync-height read, so taking the bound block from a separate get_sync_height call read it twice and let a sync landing between them bind the summary to one block while reading the fee asset from another. Both now come off one header.
feeAwareTransactionRequestBuilder's second argument becomes an options object carrying approvalExpirationDelta, feeConversionSalt and boundBlockNum. The positional delta it replaces shipped in this branch only. Without the latter two a co-signer could not reproduce a proposal: the salt is drawn per build and the block is the local sync height, and both are bound by the summary. Receiving the proposer's serialized request still needs neither - it carries the auth argument and its advice-map preimage. Six sites told a multisig caller to reach for withFeeConversionSalt instead. That setter and withAuthArg clear each other, so on a builder this returns it discards the three-word auth args and the transaction aborts in the auth procedure. Each now says so, including the setter's own rustdoc. The zero-delta rejection message was assembled by tooling that ate its line continuation and shipped a 22-space run; concat! removes the continuation, and a test asserts the message has no double space.
The getter's summary line claimed it reports what the client executes against; its body already said the truth, which is the configuration registered at creation. The CHANGELOG entry catches up with the options object.
The run fixture hands the callback the raw client, not MidenClient, so the options object I passed was a type error in napi and a silent coercion to Some(0) in wasm - CI failed both guards, differently per realm. They now use the positional export and build the salt with u64Array like every other caller. Adds the case that makes the pinning real: two builds differing only in the bound block must produce different auth args. The equality assertion alone passed even if the override were ignored, because the closure produces no blocks so the default bound block is the same for both.
…aucet with inner The builder drew a word from the client RNG and discarded it whenever the caller pinned a salt; seed documents that stream as reproducible, so a pinned build shifted every later draw relative to a defaulted one. The gate and the draw are now separate, which also retires a helper that did both. The browser setup_client published fee_faucet before building the client, so a failed creation left the accessor reporting a faucet while every other method said the client was not initialized. The napi twin was already correct.
useCreateNetworkNote re-implements the emit rather than delegating to client.transactions.createNetworkNote, so the declaration added to the SDK path never reached it. Since 0.17 the kernel prices a NetworkAccountTarget note by calling estimate_note_fee on the target; declaring it pins that state at the reference block instead of relying on the client resolving it lazily. The hook's test asserted only that the builder and submit were called, so it passed on the broken hook. It now reads back what was pushed into the declared array - asserting the call alone would pass on an empty one - which meant teaching the three builder doubles about withForeignAccounts.
- network_note.test.ts is the third emitter of a NetworkAccountTarget note and declared no foreign account. It resolves either way because this client holds the target locally, but the gate should exercise the shape a consumer needs. - withAuthArg's rustdoc lacked the warning its mutually-exclusive twin got this round; it reaches the public typedoc through docs-entry.d.ts. - The node createIntegrationClient still turned a missing fee faucet into a skip, the defect the browser twin was fixed for. - The node WasmWebClient double reconstructed feeFaucetId from the environment instead of forwarding it, so deleting the wiring in client.js failed no node-mode test. - The React external-keystore factory - eleven positionals, the branch every signer provider takes - had no assertion that the fee faucet reaches it.
…ock cap The CHANGELOG said a missing foreign-account declaration made the emitting transaction abort inside the kernel. I cannot support that: network_note.test.ts emits the same note shape with no declaration and passes, and 0.17 resolves a public foreign account lazily on cache miss. The abort I attributed to it was ERR_NETWORK_ACCOUNT_TRANSACTION_HAS_NO_EFFECT, which I decoded later and fixed separately. The declaration stays - it pins the foreign state at the reference block rather than depending on lazy fetch reaching the account - but the entry and both code comments now claim only that. Documents the cap that pricing imposes: estimate_note_fee applies the standards' default expiration delta, so the emit must land within 20 blocks of its reference block and nothing can widen it.
…ion gaps
The emit is capped at 20 blocks by the pricing call, and a local WASM prove on
a CI runner can exceed it. The test now re-executes against a fresh reference
block, which is what a consumer must do; it is not a blanket retry - any other
failure still fails on the first attempt, and the cap is documented on every
consumer surface so green CI does not imply the failure mode is absent.
The Turnkey example built a client with six positionals where the list has
eleven, putting the signing callback in the insertKey slot and passing no fee
faucet. The earlier doc sweep covered crates/web-client and
packages/{react-sdk,telemetry-*,vite-plugin} but not the example trees.
The strip tool's safety check moved from digest() to dependency_commitment()
with every fixture dependency-free, so nothing proved the new value could tell
two packages apart. It can; the test now says so.
0.17 added P2ID to AuthNetworkAccount's default allowlist - a stopgap until a DEPLOY note script lands - but the factory still priced exactly two roots. An allowlisted root with no fee entry aborts fee estimation, so any caller who did not name P2ID got an account that auto-consumes those notes and then cannot price them. The zero-priced set is now derived from upstream rather than restated, so the next added root cannot slip through.
The rule the comment states held only on the success path: the draw preceded the header read, the protocol-config read, the zero rejection, and with_approval_expiration_delta, which is fallible too because the expiration can overflow the maximum block number. A caller who erred, caught, and retried derived a different salt than one who never erred. The expiration is now validated before the draw - including that bound, checked locally since the setter needs args that need the salt - so no fallible step sits between drawing and returning.
Both multisig guards reused one salt Word across every call. The parameter is owned, so wasm-bindgen moved the handle and later calls passed a consumed one, which arrives as None and draws a salt instead of erroring: the reproducibility guard measured nothing after its first call, and the expiration guard passed because the salts differed rather than because the delta reached the preimage. The retry loop had the same defect with its note and foreign-account arrays - it could never retry. The allowlist assertion moves 3 -> 4 with the reason: 0.17 added P2ID to the component's defaults. It had never been evaluated on 0.17 before, because every earlier run died at the deploy or the emit. Drops the redundant wallet component - createNetworkAuthComponents installs BasicWallet itself - and the two comments that contradicted each other about whether that account has one.
Making a missing fee faucet fail loudly was right for CI and wrong for contributors: no Make target set the variable, so four local entry points would have hard-failed where they used to skip. They now resolve it from the test node's bootstrap log, the same extraction CI performs, with an actionable message when it cannot be found. Option<&Word> would remove the reuse footgun, but OptionFromWasmAbi is not implemented for a reference, so the wasm arm refuses it - settled by compiling it, not by argument. The consumption is documented instead, on every surface that shows a salt shared across calls or parties. Also corrects a zero-fee sentence the earlier sweep left contradicting another line in the same file, and a positional list that still documented seven slots.
Pricing the note caps the transaction at 20 blocks and a local WASM prove on a CI runner takes longer, so it expired however many times the retry re-executed: 79 vs 80, then 144 vs 146. The node runs a prover beside its RPC; the emit now uses it. Done per call rather than by setting TEST_MIDEN_PROVER_URL, because playwright.config.ts branches on that variable to flip fullyParallel for every project - the whole run's scheduling for one test's prover. The helper now honours a prover it is handed instead of rebuilding one from the run's config, which is what its parameter always implied.
args[args.length - 1] and .at(-1) are true by construction whenever the value is passed last: delete an earlier placeholder and every later argument shifts a slot left while the assertion still holds. One of these tests I added this round to close exactly that gap, in exactly that vacuous form. client-options-observer.test.js:354 was already reading the wrong slot - .at(-1) became feeFaucetId when it was added as the trailing argument - and its optional-chained assertions passed whatever the observability bag held.
… guaranteed - The expiration delta is validated by calling the upstream setter on a throwaway args value rather than re-deriving its bound here, so the real call after the salt draw is provably infallible and nothing local has to track what upstream rejects. - The Makefile lookup moves behind an ifndef guard with :=. An exported recursive variable is re-expanded while make builds the environment for every recipe line of every target, so the sed ran across the whole build. - createNetworkNote's recovery advice names the sync: the method does not sync, so calling it again alone rebuilds against the same reference block. The React hook and its narrative page document the 20-block cap for the first time - the hook declares the foreign account itself and inherits it. - Two comments state only what holds: the fee asset and the bound block come from different blocks when the caller pins one, which cannot differ today but is the assumption; and a test comment states its invariant rather than the review that produced it.
…report it With the deploy and the emit both landing, this test reaches its final assertion for the first time on 0.17: the node's NTB had not consumed the note within 15 blocks. Widening to 40 distinguishes a builder that needs longer from one that never acts - and the assertion now names the block span it waited, because '1 !== 2' says neither, and the page console is not forwarded to the run log.
shard-1 proved this is not a timing problem: with the window widened to 40 blocks the builder had blocks 24..64, and again 74..114 on the retry, and never consumed the note. The test passes on next against a 0.16 node with the same custom script, so 0.17 is where the node stopped resolving it. miden-client has carried expected_ntx_scripts since 0.16 and the web SDK never exposed it. The node executes a network note's script to consume it and only does so for a script its registry knows; a missing one is not reported, which is exactly the symptom - a silent no-op, not an error. Declaring it makes the client register the script before the emitting transaction. createNetworkNote now declares the caller's script itself, since a consumer cannot be expected to know the node keeps a registry. Adds NoteScriptArray, which needs impl_napi_from_value! for the array wrapper's Vec conversion.
Exposing withExpectedNtxScripts was not enough: miden-client performs the registration inside its own submit methods, and this crate builds the execute/prove/submit pipeline itself, so the declaration sat on the request and never reached the node. shard-1 failed identically with it set - blocks 18..58, then 66..106. Both submit paths now call ensure_ntx_scripts_registered first, which is public and documented for exactly this. It is guarded on a non-empty declaration, so every existing caller is unaffected. The test submits through that path instead of the hand-rolled helper, which is also the shape a consumer uses.
A network transaction the builder declines is not an error anywhere the test can observe: the note simply stays unconsumed, and the assertion can only say the counter did not move. Three fixes have now been attempted against that symptom without the builder's own account of why it declined. Prints the last 200 lines of ntx-builder, node and validator logs on failure, which is where the reason lives.
The previous step assumed a path and reported three missing files, which says nothing about where they are - and named a 'node' service the script does not have (it runs validator, sequencer, prover and ntx-builder). It now locates every test-node log and prints each, so a failure cannot come back empty.
…osis Reverts a46b8ad and aa9811f. Both were written to get shard-1's network_transaction test consuming its note, on the theory that the node's script registry lacked the note script. The node's own log disproves it: the network-transaction builder refuses the account before it gets anywhere near scripts, with "network account fee asset does not match the protocol configuration". The real fix follows. They were not harmless to keep. createNetworkNote declared the note's own script, so every emit paid a get_note_script_by_root round trip, and for a script the node had not yet seen - always true of a new script, since the note carrying it has not committed - the client submitted a separate registration transaction first. A public note carries its script anyway. NoteScriptArray also had no Node polyfill, so that path likely threw under napi. Exposing expected_ntx_scripts is legitimate miden-client parity, but it is a feature of its own and belongs in its own change with a test that needs it.
shard-1's network_transaction test never saw its note consumed, however long it waited. The node's ntx-builder log says why: network account fee asset does not match the protocol configuration Node 0.17's network-transaction builder compares a network account's fee-asset slot with the chain's ProtocolConfig fee asset and refuses to execute on a mismatch (bin/ntx-builder/src/actor/execute.rs, NtxDataStore::new, FeeAssetMismatch). The check is absent at node 0.16.0, which is why the same test passed there with a faucet it minted itself. Nothing reaches the client. Both tests that build a network account now pass client.feeFaucetId(). network_note keeps its minted faucet for the note that deploys the account. The rustdoc behind the published typings, the README, the network-notes guide, three shipped skills and the CHANGELOG now say the faucet must be the chain's, and why the failure is silent.
|
Warning Changes requested and applied. 44 findings fixed across 5 rounds and a CI-driven plan round; 3 rejected with evidence, 1 deferred to #408. The loop converged on a stopping rule fixed before the final round ran. This panel was degraded - Codex hit its usage limit, so all three seats were Claude and the review lost cross-lab decorrelation. Treat the verdict accordingly. 44 fixed · 3 rejected · 1 deferred · 5 rounds · 7 plan gates · 4 defects found by CI that no seat saw Panel: Opus (max) · Sonnet (max) · Opus, padded seat (max) - no Codex, no Gemini. Decisions for you
What the loop actually caught (and what it cost)Three findings were worth the whole exercise:
Rounds 2, 3 and 4 each found real defects in the previous round's fixes - an inert retry that could never retry, two guards that measured nothing after their first call, a "fail loudly" change that would have failed loudly on contributors' machines. That is the churn regime, which is why the stopping rule was fixed in advance. The last CI defect cost the most. The network-transaction test never saw its note consumed, and two commits chased a note-script registration theory before a CI step dumping the node's logs showed the real refusal: Three corrections landed in the tree, not just in conversation: a CHANGELOG entry claiming a kernel abort that was never observed, a comment asserting a wallet component was required when the auth component installs it, and a source comment citing the review process. Rejected, with reasons
Verified soundThe multisig auth-args path end to end (bound block readable only at the reference block or an input-note creation block); Generated from the review ledger. Panel models named by explicit exception to the no-attribution rule; commits and code carry none. |
Upgrades
miden-clientto 0.17.0-rc.1 (protocol 0.17.0-rc.5, VM 0.33), so the web SDK can cut an rc matching the Rust SDK's.API
ClientOptions.feeFaucetIdsupplies it, andclient.feeFaucetId()replaces the removedBlockHeader.feeFaucetId().ForeignAccount::PrefetchedandAccountInputsupstream.AccountVaultDeltareports whole assets added and removed rather than signed per-faucet balances, sofungible()andFungibleAssetDeltaare gone;addedFungibleAssets()andremovedFungibleAssets()are unchanged.BlockHeader.txKernelCommitment()is nowprotocolConfigCommitment().feeAwareTransactionRequestBuilder(account, approvalExpirationDelta?)expires a multisig approval that many blocks after the block its summary binds. Omitted, nothing changes.Behaviour, caught only by the node-backed suite
miden-clientcommits the two-word fee pair a fixed-salt component reads, so the multisig was piping a preimage one word short and aborted withadvice stack read failed.NetworkAccountTargetdeclares the target as a foreign account, since the kernel now prices such a note by calling a procedure on it.client.feeFaucetId()). A 0.17 node refuses to run network transactions for an account whose fee asset differs from the protocol configuration, and the client is never told; A network account built with the wrong fee faucet fails silently: newAccount could reject it #408 tracks rejecting it atnewAccount.Requires a node on the matching protocol and a new client database. The public testnet is still 0.16 and rejects a 0.17 client at the accept header, so this is exercised against the mock chain and a local node.
Why the protocol configuration reaches the client this way
Every removal above follows one upstream, and each CHANGELOG entry names the migration. The one design decision is the configuration: 0.17 moved the fee asset out of the block header into it, execution and note screening both resolve it by the reference block's commitment, and the node does not serve it over RPC yet. The SDK builds it from the chain's fee faucet, taken from
feeFaucetIdor from a per-network table.That table is empty today, because no 0.17 network has published a genesis to read a fee faucet from. An unknown network therefore fails at client creation with a message naming the option, rather than at the first transaction with a store error about a commitment nobody can act on.
miden_client_api.test.tspins that: a client built without a fee faucet must fail, naming the option, and the test fails the day the table gains an entry, which is the day the docs calling it mandatory have to change.ProtocolConfigis registered on the builder, so it is in place before the first execution. A mock client registers the mock chain's own instead, which is why the mock suites need no fee faucet.The MASP strip, and a size gate that passed while broken
tools/strip-masp-debugasked for VM 0.29 while the client embeds 0.33. Cargo honours a member's own version requirement, so both resolved: against a 0.17 artifact the tool recognised no packages, exited non-zero, and the build carried on withwasm-optskipped and the ST WASM at 118.5 MiB against a 25 MiB gate. The requirement now moves withmiden-client, which also takes the duplicate VM 0.29 tree out ofCargo.lock.Verification
cargo checkandcargo clippy -D warningsclean onwasm32-unknown-unknownand on the napinodejs,testingbuild.feeFaucetIdand approval-expiration forwarding tests each fail when the forwarding alone is removed (mutation-checked against a prediction).make lint,make toml-check,make typos-check,make check-agent-docs, prettier and the react-sdk sync gate all clean.MIDEN_FAST_BUILD) release build strips 28 MASP packages, 9.64 MB, and both variants sit under the size gate.Reviewers: the multisig auth-args path is the part worth your time, in particular that the bound block is chosen when the request is built rather than when it executes. Everything else is forced by upstream.