test(api_remote): migrate E2E suite to async Job-API - #187
Merged
Conversation
…nc routes) PR #161 removed the synchronous /api/mint, /api/send and /api/commit routes and replaced them with the async Job-API. Migrate the api_remote.rs E2E suite to the new contract so the "API E2E against DEV" check passes again. Client flow: - mint: POST /api/jobs/mint (Idempotency-Key) -> 202 -> poll GET /api/jobs/:id to completed; result == legacy mint body. - send: POST /api/jobs/send (Idempotency-Key) -> 202; signature + timestamp + hex gates run inline (still 401/422 synchronously), poll to awaiting_signature which carries proof_id; fetch the send CoinProof, decode ash/ocr from the Plonky2 proof public inputs (commitment is None on the send proof), sign ash||ocr, POST /api/jobs/:id/commit -> 200 {status:"broadcasting"}, poll to completed; result == legacy commit body. Helpers added: random_idempotency_key, uuid_v4_like, poll_job_until_terminal, poll_job_until_status, mint_via_job, submit_send_job, ash_ocr_from_send_proof, commit_send_job, fetch_coin_proof, and a bounded retry for the send->commit->send scanner-indexing race (the async commit_flow no longer advances the in-process SMT synchronously, unlike the old /api/commit). Assertion changes vs. the removed sync routes (verified live against DEV): - Job-API validation errors use the JobErrorResponse envelope ({error: ...}) instead of the legacy {success:false,error:...}; negative-path body assertions updated accordingly (error string preserved, so the app KNOWN_SERVER_ERRORS lockstep still holds). - send_coins business failures (unknown account, insufficient funds) are no longer synchronous 404/422: the job is admitted (202) and fails asynchronously, so those tests now assert the terminal job error string. - mint/commit response state-hash + coins-root field-coverage tests now read the populated job result object. No production (non-test) code changed. Test names preserved, including *_roundtrip_* (deploy-prd --skip _roundtrip_ semantics) and feature_skip!.
Two test-only fixes; no production code touched. (A) second_send_roundtrip_succeeds_without_prev_commitment_pubkey_field Stop minting a second time into Alice before send #2. The extra mint pushed a fresh coin into Alice's coin_queue, forcing send #2 through send_coins_inner's in-coin loop. That loop inserts each spent coin id into account.coin_history BEFORE the prove, and the prove leg has no rollback on failure: a single transient prove failure (the genuine "Unable to get merkle proofs for provided public key" scanner race) leaves the coin in BOTH coin_queue and coin_history, after which every retry fails permanently with "Should provide an inclusion proof" and the retry budget can never clear it. Spending Alice's send #1 change directly from account.balance with an empty coin_queue skips the in-coin loop entirely, keeping retries idempotent and isolating the assertion to its subject: the omitted prev_commitment_pubkey. Intent unchanged — the second send still omits prev_commitment_pubkey and num_sends advances to 2. (B) history_unknown_address_returns_empty_page Use a freshly-generated keypair's address instead of a hardcoded one. DEV is a persistent, shared closed-env DB, so the hardcoded address had accumulated a history row and total == 0 no longer held. A random address is provably untouched, guaranteeing the empty-page contract.
…dispatcher worker A send job left in awaiting_signature pins the single inline dispatcher worker for the full awaiting_signature_timeout (600s on DEV), starving every later test in the serial suite. Drive send #2 through commit so the worker is released and the roundtrip completes; surface the job_id from submit_send_no_prev_until_awaiting so the caller can commit. See #186 for the underlying node-side head-of-line-blocking issue.
TaprootFreak
marked this pull request as ready for review
June 2, 2026 20:44
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.
Why
PR #161 removed the synchronous
/api/mint,/api/send,/api/commitroutes and replaced them withthe async Job-API (
POST /api/jobs/mint|send,GET /api/jobs/:id,POST /api/jobs/:id/commit). Thelive E2E suite
node/tests/api_remote.rsstill drove the removed routes, so every mint/send/committest returned
404— surfacing as the failing "API E2E against DEV" check on the develop → mainrelease PR (#179).
What
Migrate the whole
api_remotesuite to the documented async client flow (README §"User send",SPEC §"Wallet flow is now poll-based"):
POST /api/jobs/mint(+Idempotency-Key) →202 {job_id}, pollGET /api/jobs/:iduntil
completed, assert against the jobresult(the legacy mint body).POST /api/jobs/send→ poll toawaiting_signature→GET /api/proof/:id, reconstructaccount_state_hash/output_coins_rootfrom the proof's public inputs (ProofData::from_field_elements),sign
Schnorr(ash ‖ ocr)→POST /api/jobs/:id/commit→ poll tocompleted.send_coinsbusinessfailures now surface as a terminal
failedjob with the locksteperrorstring._roundtrip_naming (PRD subset--skip _roundtrip_)are all preserved.
Two deterministic residual fixes after the first migration pass:
second_send_roundtrip_...— dropped the redundant second mint into the sender. After send Release: develop -> main #1 +commit the sender's change lives in
account.balancewith an emptycoin_queue; the extra mint wasthe only thing repopulating the queue and tripping a
coin_historynon-inclusion collision("Should provide an inclusion proof") on the second send's prove leg. Send Release: develop -> main #2 now spends the change
directly. Intent unchanged: a second send with
prev_commitment_pubkeyomitted still succeeds andnum_sendsadvances to 2.history_unknown_address_returns_empty_page— use a fresh random address instead of a hardcodedone, which had accumulated a row in the persistent shared DEV DB.
Validation
cargo fmt --checkandcargo clippy -p node --tests --all-featuresclean. Tests pass individuallyagainst a healthy DEV node; mint/send/commit roundtrips, the two fixed tests, and the negative-path
contract all green.
Known: shared-node head-of-line blocking (#186)
Full-suite runs against the shared DEV node can intermittently stall with jobs frozen in
queued/progress=0when a foreign send is parked inawaiting_signature— the single-workerdispatcher parks inline, blocking the queue. This is a node-side architectural issue tracked in #186,
not a test defect: the suite commits each send promptly, and a clean single run on a freshly-deployed
node with an empty queue does not hit it.
Test-only change — no production code touched.