Skip to content

test(api_remote): migrate E2E suite to async Job-API - #187

Merged
TaprootFreak merged 3 commits into
stagingfrom
test/api-remote-async-job-api
Jun 2, 2026
Merged

test(api_remote): migrate E2E suite to async Job-API#187
TaprootFreak merged 3 commits into
stagingfrom
test/api-remote-async-job-api

Conversation

@TaprootFreak

Copy link
Copy Markdown
Contributor

Why

PR #161 removed the synchronous /api/mint, /api/send, /api/commit routes and replaced them with
the async Job-API (POST /api/jobs/mint|send, GET /api/jobs/:id, POST /api/jobs/:id/commit). The
live E2E suite node/tests/api_remote.rs still drove the removed routes, so every mint/send/commit
test returned 404 — surfacing as the failing "API E2E against DEV" check on the develop → main
release PR (#179).

What

Migrate the whole api_remote suite to the documented async client flow (README §"User send",
SPEC §"Wallet flow is now poll-based"):

  • MintPOST /api/jobs/mint (+ Idempotency-Key) → 202 {job_id}, poll GET /api/jobs/:id
    until completed, assert against the job result (the legacy mint body).
  • SendPOST /api/jobs/send → poll to awaiting_signatureGET /api/proof/:id, reconstruct
    account_state_hash/output_coins_root from the proof's public inputs (ProofData::from_field_elements),
    sign Schnorr(ash ‖ ocr)POST /api/jobs/:id/commit → poll to completed.
  • Inline-validation negative paths (401/404/422) keep asserting synchronously; send_coins business
    failures now surface as a terminal failed job with the lockstep error string.
  • Value-bearing assertions, feature-skips, and the _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.balance with an empty coin_queue; the extra mint was
    the only thing repopulating the queue and tripping a coin_history non-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_pubkey omitted still succeeds and
    num_sends advances to 2.
  • history_unknown_address_returns_empty_page — use a fresh random address instead of a hardcoded
    one, which had accumulated a row in the persistent shared DEV DB.

Validation

cargo fmt --check and cargo clippy -p node --tests --all-features clean. Tests pass individually
against 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=0 when a foreign send is parked in awaiting_signature — the single-worker
dispatcher 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.

…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
TaprootFreak marked this pull request as ready for review June 2, 2026 20:44
@TaprootFreak
TaprootFreak merged commit 0fad325 into staging Jun 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant