Skip to content

perf(wallet): bring createAction under remote latency budgets - #426

Merged
ty-everett merged 4 commits into
mainfrom
codex/create-action-sub-500
Aug 4, 2026
Merged

perf(wallet): bring createAction under remote latency budgets#426
ty-everett merged 4 commits into
mainfrom
codex/create-action-sub-500

Conversation

@ty-everett

@ty-everett ty-everett commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Program and scope

  • Tracker or issue: Operator-requested createAction latency program (no separate issue).
  • Program gate(s) advanced: normal authenticated remote p50 <100 ms; 153-input authenticated remote p95 <500 ms; direct storage p50 <150 ms; patch coverage >=90%.
  • Why this change is needed: createAction routinely took multiple seconds. The successful path repeated proof reads/merges, key derivation, verification serialization, output writes, and authenticated session persistence instead of batching the work.
  • Explicitly out of scope: changing BRC-103/104 wire semantics; AuthFetch headers or behavior; Auth Express middleware; AuthSocket; publishing packages; downstream wallet releases; production deployment.
  • Exact head SHA reviewed: e7d8f93ee769d51aa0082234baabf79be5ba925f.

Impact

  • No public package source or manifest changed
  • Public package source or manifest changed; affected packages are listed below
  • Infrastructure source, dependency, image, or deployment configuration changed
  • Public API, exports, types, runtime targets, or browser/mobile behavior changed
  • Security-sensitive boundary changed
  • Documentation or examples changed

Affected packages/services and intended patch versions (publication occurs only through the release workflow after approval):

  • @bsv/sdk 2.3.0: additive batch BEEF/key-derivation APIs and optimized validated Merkle/P2PKH paths.
  • @bsv/wallet-toolbox 2.5.0: atomic set-based createAction persistence, proof batching, signing/verification batching, storage indexes, session-touch coalescing, and privacy-safe spans.
  • @bsv/wallet-toolbox-client 2.5.0 and @bsv/wallet-toolbox-mobile 2.5.0: coordinated compatible candidates and measured platform budgets.

Review follow-up

  • Automatic noSend batching remains the default. No opt-in or interface change was introduced. This preserves the intentional feat(wallet-toolbox): plan noSend batches locally and commit atomically #289 behavior: intermediate workspaces are session-scoped and the final sendWith operation is the atomic durability boundary; explicit legacy mode remains available only as a rollback/comparison path.
  • Regression coverage reconstructs a wallet without actionBatchMode, proves the default is auto, proves the intermediate action is visible through listNoSendActions but not prematurely persisted, and proves sendWith atomically commits it. A second regression proves the listed txid can be passed to abortAction and aborts the staged batch without a persisted transaction.
  • Cold-client BEEF assembly now expands txid-only ancestors that are not declared known by the caller (while retaining the existing known/trust-self shortcut). Single-root and batched regressions reproduce the previously reported merge failure and cover both expansion and known-ancestor behavior.
  • The funded-but-no-viable-change error now reports the exact required total and shortfall rather than a constant ten-satoshi deficit; regression coverage reproduces the fixed-input case.
  • listActions keeps its existing SDK response contract. No nonstandard reference field was added: abortAction already accepts either an action reference or txid, and the staged-batch regression proves the listed txid path.
  • No production BRC-103/104, AuthFetch, Auth Express, or AuthSocket path changed.

Verification

  • Local commands and results: full SDK coverage (152 suites, 5,842 tests); full Wallet Toolbox in-band coverage (183 suites, 1,598 pass, one governed skip); focused batch/BEEF/change suites (83 tests); full workspace property suite; selected wallet-action-batch mutation score 89.91% (policy 88%); root lint, health, docs, format, frozen install, audit, build/typecheck, package/license policy, and diff checks all pass.
  • Hosted exact-head runs: CI 30936042725, CodeQL 30936042591, and Codegen 30936042443 on e7d8f93ee769d51aa0082234baabf79be5ba925f.
  • Conformance evidence: Auth core 37, Auth Express 108, AuthSocket 52, and AuthSocket Client 50 tests pass; SDK AuthFetch is included in the full SDK suite. No production AuthFetch/Auth Express/AuthSocket file changed.
  • Coverage delta: repository-owned exact-diff patch coverage is 93.34% (1,276/1,367 changed line/branch points; target 90%); the external Codecov patch check also passes. Type-only .interfaces.ts declarations are excluded because they emit no Istanbul entry; fail-closed executable-file behavior remains tested.
  • Lint/typecheck delta: zero findings; all affected projects build and typecheck.
  • Browser/mobile/packed-consumer evidence: SDK Vite/esbuild/UMD budgets pass; wallet client Vite/esbuild and mobile Metro/Hermes contracts pass; SDK/wallet/client/mobile packs pass; eight compiled consumer examples using exact tarballs pass.
  • Performance or bundle-size delta: synthetic storage benchmark fell from 622 queries / 102 transactions / 107.3 ms to 17 / 1 / 14.2 ms. PXC authenticated remote, 20 runs: normal one-input p50 78.6 ms, p95 105.6 ms; 153-input p50 376.0 ms, p95 461.6 ms. Direct 153-input p50 99.3 ms, p95 137.4 ms. A selective production-shaped clone with 110 inputs measured direct p50 75.5 ms, p95 155.4 ms. All 3,080 measured signatures verified with zero failures.
  • I self-reviewed the complete diff for correctness, security, compatibility, public API, artifacts, dependencies, docs, and operations
  • All applicable checks are terminal and successful on the exact head; any scope-based skip is expected and validated by the merge gate

Security and dependencies

  • No dependency or lockfile change
  • Changelog, runtime relevance, peer compatibility, transitive graph, and audit results were reviewed
  • CodeQL/negative tests cover any changed trust boundary
  • The exact-head CodeQL analysis has no new alert
  • The exact-head repository quality gate reports zero new Sonar findings (including accepted or false-positive issue states) and zero unreviewed hotspots; Sonar's aggregate Quality Gate passed verdict alone is not merge evidence
  • No new override, advisory dismissal, quality suppression, or skipped test
  • Any temporary exception is registered with owner, evidence, review date, and removal condition
  • Workflow permissions and lifecycle-script behavior remain least privilege

Dependency evidence

  • Release notes and necessity: wallet benchmarks directly exercise the existing workspace VeriFast backend so the optimized digest-verification path remains measurable; release notes cover all four candidate packages.
  • Runtime, build, and peer compatibility: @bsv/verifast is a workspace-only devDependency used by the retained benchmark. No runtime dependency or peer range changed. Additive SDK APIs retain older compatible peer behavior through the sequential proof-merge fallback.
  • Patched transitive releases: exact-lock compatible refreshes move fast-uri to 3.1.5 and ip-address to patched releases; the existing governed brace-expansion override moves from 5.0.8 to 5.0.9 across the root, codegen, message-box, UHRP basic/cloud, and WAB workspaces. No new broad override or advisory dismissal was added.
  • Audit and CodeQL: root, codegen, and all eight infrastructure audits report no known vulnerabilities; exact-head hosted CodeQL passed with no new alerts. Affected infrastructure lint, build, test, and Linux image scan jobs pass.
  • Package and consumer tests: all affected pack checks, platform contracts, full package suites, property/mutation tests, and exact-tarball consumer examples pass.
  • Bundle and performance impact: measured SDK/client/mobile budgets pass; performance evidence is listed above.
  • Affected public package versions: SDK 2.3.0; Wallet Toolbox, Client, and Mobile 2.5.0.

Release and operations

  • No npm publication was performed from a workstation or from this PR
  • Required npm patch bumps are included or intentionally deferred by the controlling program
  • Image/SBOM/provenance/deployment/rollback impact is documented
  • Documentation, changelog, migration, and operational guidance are current

The exact governed Node 24.18.0 release path staged, checksum/SBOM-verified, and dry-run-published all four candidate tarballs. Actual publication remains exclusively in the approved release workflow. Knex migrations are reversible and passed fresh up/down/up tests; IndexedDB advances additively to schema version 3. Rollback is the prior package set plus the down migration. No service image or live deployment is part of this PR.

Completion evidence

  • The linked tracker is updated only for work fully proved by merged code, passing checks, resolved alerts, measurements, or an approved exception
  • Review conversations are resolved
  • Documentation, changelog, migration notes, release notes, and operator guidance are current or concretely not applicable
  • No pending, failed, stale, cancelled, or unexpectedly skipped check is being handed to another contributor as “complete”
  • One qualified maintainer approval is sufficient; no last-pusher restriction is assumed

@ty-everett
ty-everett force-pushed the codex/create-action-sub-500 branch 3 times, most recently from e1eba93 to 56132aa Compare August 3, 2026 09:27
@ty-everett
ty-everett force-pushed the codex/create-action-sub-500 branch from 56132aa to 8659dfb Compare August 3, 2026 09:48
@ty-everett
ty-everett marked this pull request as ready for review August 3, 2026 21:08
@sambabbage

sambabbage commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

E2E test report — real mainnet deployment

Deployed this branch (head 8659dfb) to our production wallet-storage instance (@cwi/storage + SQLite, 286 MB live DB, ~10.7k transactions) and ran E2E tests with a funded mainnet wallet. Migration from a production 2.4.2 DB was clean (17 → 19 migrations, idx_outputs_funding_selection + action_batches.manifest applied, funds intact, zero downtime cutover).

Performance vs main: confirmed ✅

A/B on identical hardware: two server instances against byte-identical copies of the same production DB backup, localhost, matching client/server toolbox versions per side. Op: createAction (signAndProcess: false) + abortAction, 2 rounds per side. main already includes #425, so this isolates this PR's marginal gain:

createAction main @ 1ab58bd (2.4.22) this PR (2.5.0) delta
1 output, p50 168 / 171 ms 73 / 87 ms ~2.2× faster
1 output, p95 189 / 190 ms 91 / 96 ms ~2×
82 explicit inputs, p50 320 / 314 ms 264 / 249 ms ~20% faster

The <100 ms p50 claim reproduces on a direct connection. Over TLS + reverse proxy our remote p50 was ~400 ms (transport-dominated), so budgets hold at the storage layer, not necessarily WAN paths. (Our snapshot had 82 spendable custom outputs, so the many-input test is 82 inputs rather than 153 — identical on both sides.) Cross-version spot-check: a cold 2.4.22 client performed a real broadcast against the upgraded 2.5.0 server successfully.

noSend / batch reservation lifecycle: verified working ✅

Confirmed the full prepared-action flow E2E on mainnet: createAction({options:{noSend:true}}) creates a reservation batch (no transaction record — by design), and from there all three exits work: abort by txid (abortAction({reference: txid}) → batch aborted, outputs released), expiry (unattended batches release their reserved outputs at hardExpiresAt), and completion (createAction({options:{sendWith:[txid]}}) → batch committed, transaction records materialize, both txs broadcast). An earlier revision of this comment mis-read the deferred persistence as a regression — retracted; it works as designed and avoids the orphaned nosend rows that 2.4.2 could leave behind.

Two issues worth tracking (both reproduce on main, not introduced here)

  1. Client WERR_INTERNAL: unable to merge txid … into beef (Wallet.verifyReturnedTxidOnly) — hit twice on live with a cold 2.5.0 client and no options.knownTxids: once on a plain outputs-only createAction (153 outputs), and once while processing a signAction response after the server had already broadcast (client sees an exception for a succeeded action). Trigger appears to be txid-only BEEF entries for allocated change whose source txs are proven, with an empty client-side beef cache. Workaround: pass wallet txids in options.knownTxids.

  2. Misleading WERR_INSUFFICIENT_FUNDS (generateChange.ts L476, same on main): when the tx is funded but no change output survives, moreSatoshisNeeded is the constant params.changeFirstSatoshis (= minimumDesiredUTXOValue / 4 = 10 for the default basket). We got "10 more satoshis are needed" across four scenarios whose real totals were 705 / 2,078 / 30,371 / 31,071. The message should state the actual constraint (change below dust floor).

Operational note for anyone upgrading from ≤2.4.2 deployments: 2.4.22+ requires express-rate-limit at runtime, bumps better-sqlite3 to ^13 (native rebuild), and behind a reverse proxy throws ValidationError until Express trust proxy is set.


Update: re-verified at head e7d8f93 (fix commits deployed to the same production instance)

  • unable to merge txid crash: fixed. The exact scenario that crashed before (cold client, no options.knownTxids, 153-output full-process createAction) now completes and broadcasts — 814 ms E2E on mainnet. The getBeefForTransaction placeholder handling resolves it at the root.
  • WERR_INSUFFICIENT_FUNDS message: fixed. The same forced-shortfall probe now reports the real deficit ("5895 more satoshis are needed") instead of the constant 10.
  • actionBatchMode: auto default retained, legacy escape hatch works for consumers that need durable per-action noSend rows.
  • Perf unchanged after the fixes: single-output createAction p50 77 ms / p95 88 ms on the direct path — same as the pre-fix numbers in the table above; 20-explicit-input createAction p50 294 ms.
  • Regression sweep at e7d8f93 (live deployment): noSend lifecycle (abort-by-txid / expiry / sendWith commit+broadcast) ✓, actionBatchMode: 'legacy' durable nosend rows ✓, listOutputs with entire transactions returns valid BEEF ✓, explicit-input createActionsignAction broadcast ✓, zero server errors. Bonus: in a head-to-head on identical DB copies, the pre-fix head intermittently hung (client auth timeout) on repeated explicit-input create/abort cycles; e7d8f93 ran the same workload 10/10 cleanly — the getBeefForTransaction rework fixed a hang we hadn't isolated yet.

Summary: perf gains are real and reproduce against main (~2.2× typical createAction, ~20% many-input), production migration is clean, the batch/noSend lifecycle checks out end-to-end, and both client-facing issues we reported are now fixed and re-verified on a live mainnet deployment — LGTM.

@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@BraydenLangley BraydenLangley left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran all the changes through E2E integration testing, and everything seems good to merge now.

@ty-everett
ty-everett merged commit ef710c3 into main Aug 4, 2026
75 checks passed
@ty-everett
ty-everett deleted the codex/create-action-sub-500 branch August 4, 2026 19:59
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.

3 participants