feat: multi-batch merkle payments (one tx per sub-batch, single allowance) - #213
Conversation
…wance ant-core 0.6.0 (ADR-0003, WithAutonomi/ant-client#168) lifts the ~1 GiB external-signer cap by preparing uploads above the 256-chunk tree limit as multiple merkle sub-batches, one payForMerkleTree transaction each. Backend: UploadQuoteEvent carries merkle_batches (one depth/ commitments/timestamp entry per sub-batch) instead of the single batch's fields; confirm_upload_merkle takes one winner hash per batch (null = user never paid it) and finalizes through finalize_upload_merkle_multi, keeping the ensure_all_chunks_stored guard. Frontend: one up-front allowance approval covering the summed worst-case charge (ensureAllowanceForMerkleBatches), then one payment tx per batch with a "confirm payment n of m" wallet instruction (stageDone/stageTotal). Abandoning a later payment still finalizes the paid batches — their chunks store and the row reports the incomplete upload with the DataMap path, so a retry only re-pays the remainder. DRAFT: pins ant-core to the ant-client#168 branch; flip to the 0.6.0 release before merge. approve_payment_n_of_m locale key is en-only — run the locale sweep before merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Paired review against ant-ui head a5e45eaf5d2591ffb610c1751d740989bdae361f and the merged ant-client #168 head 300479257927ddb15a49e8203dd75af43b6e9245.
Verdict: implementation is coherent with #168; one dependency-pinning item should be resolved before this leaves draft.
The cross-repo contract matches: the UI serialises each prepared Merkle sub-batch, pays in order, preserves null for unpaid batches, and passes the ordered vector to finalize_upload_merkle_multi_with_progress. Partial payment and post-payment quorum shortfall are both surfaced as incomplete uploads rather than false success or payment failure.
Before merge, replace the branch dependency in src-tauri/Cargo.toml with the released ant-core version (the comment already anticipates 0.6.0) and refresh Cargo.lock. The current lock resolves #168 at dbc9ca0, not its merged head; the production difference is not material to this UI contract, but a mutable feature-branch dependency is not a durable release input and may disappear after merge.
Verification:
- independent six-seat paired review across both repositories
- local frontend suite: 74/74 passed;
vue-tsc --noEmitpassed - local Tauri library tests: 18/18 passed; Clippy with warnings denied and formatting passed
- #168 CI is fully green, including macOS/Linux builds, unit/E2E/Merkle E2E, Clippy, format, docs, security and ADR validation
- ant-ui #213 GitHub checks all passed;
git diff --checkpassed
dirvine
left a comment
There was a problem hiding this comment.
Superseding the earlier paired review after a completed two-group, six-seat rerun against ant-ui a5e45eaf5d2591ffb610c1751d740989bdae361f and merged ant-client #168 head 300479257927ddb15a49e8203dd75af43b6e9245.
Final verdict: changes requested before merge.
The cross-repository multi-batch contract is coherent: prepared batches serialize compatibly, winner hashes retain batch order, null represents unpaid batches, and #168's finalizer/store lifecycle is sound. However, the payment timeout has an ambiguous-execution failure boundary: withTimeout(payForMerkleTree(...)) can reject after writeContract has broadcast but before receipt polling returns. The underlying promise continues and may complete on-chain, but its winner hash is discarded. If this happens on the first batch, paidCount === 0 reports a clean payment failure and never calls confirm_upload_merkle; retrying obtains a fresh quote and can pay again. In a multi-batch flow this boundary is crossed once per payment, so #213 should not ship relying on the timeout result as proof that nothing was spent.
I reproduced the promise-race mechanics deterministically: the outer flow failed while broadcast=true; the receipt arrived later, after the settled outer promise could no longer recover its result.
Required before merge:
- Remove the outer timeout around the broadcast/receipt operation, or preserve the transaction hash and reconcile its receipt/result before classifying the batch as unpaid. Add a fake-timer test for timeout-after-broadcast-before-receipt and assert that retry cannot repay an ambiguously executed batch.
- Replace the mutable
ant-corefeature-branch dependency with a released version and refreshCargo.lock.cargo search ant-corecurrently reports only0.5.1, so this may mean waiting for the release anticipated by the Cargo comment. The current lock resolves commitdbc9ca0, not merged #168 head3004792.
Verification:
- 6/6 review seats completed; the payment finding was independently reproduced by the parent reviewer
- frontend: 74/74 tests and typecheck passed
- Tauri: 18/18 tests, Clippy (
-D warnings), fmt and diff checks passed - current GitHub checks are green
| // stageDone/stageTotal drive the "payment i of N" wallet | ||
| // instruction while status is `paying` (see pages/files.vue). | ||
| this.updateEntry(id, { stageDone: i, stageTotal: batches.length }) | ||
| const payResult = await withTimeout( |
There was a problem hiding this comment.
Blocker — ambiguous transaction execution: this timeout races the full payForMerkleTree promise, which includes broadcast and receipt polling. If 300s expires after broadcast but before the receipt returns, the eventual winnerPoolHash is discarded. On the first batch paidCount remains zero, so the flow reports a clean payment failure and never finalises the paid batch; retry can pay a fresh quote again. Please remove this outer timeout or retain/reconcile the tx hash before treating the batch as unpaid, with a timeout-after-broadcast regression test.
payForMerkleTree spans broadcast and receipt polling; racing it against a 300s timer could discard a winner hash the chain had already accepted, report the batch as a clean payment failure, and let a retry pay it again. Drop the outer timeout in the multi-batch loop — the wallet's own reject is the cancel path — and pin the behaviour with a fake-timer regression test (receipt at +400s must complete, not fail). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review item 1 addressed in 9a8429e (option 1 — timeout removed): the merkle batch loop no longer races Item 2 (replace the git pin with a released ant-core + lock refresh) is pending the ant-client release cut — the PR stays draft until then. The same broadcast/receipt race in the wave path (inherited from base, reproduced in your #212 review) is tracked separately as a follow-up. 🤖 Generated with Claude Code |
…ch-payments # Conflicts: # stores/files.ts # tests/stores/files.test.ts
…stage label Replace the draft git-branch pin (ant-client#168 is released in ant-cli-v0.3.3) with the crates.io ant-core 0.6.0 dependency, and add the approve_payment_n_of_m stage string to all 17 non-English locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both requested changes are in — ready for re-review:
Verified against the released crate: 76/76 frontend tests, |
Summary
Consumer half of ADR-0003 (WithAutonomi/ant-client#168): uploads above the 256-chunk merkle tree limit (~1 GiB) quote as multiple sub-batches; the app pays one
payForMerkleTreetransaction per batch and finalizes with one winner hash per batch. Implements V2-948.UploadQuoteEvent.merkle_batches(array) replaces the single-batch fields;confirm_upload_merkle(winnerPoolHashes: (string|null)[])routes throughfinalize_upload_merkle_multi; the fix: reject partial merkle uploads instead of reporting success #212 completeness guard stays.ensureAllowanceForMerkleBatches) → the wallet prompts once for approve + once per payment; "Open your wallet and confirm payment n of m" instruction while paying; running cost/gas totals across batches.Review follow-ups (all resolved — ready for re-review)
withTimeout(payForMerkleTree)is removed — an in-flight payment is never abandoned (fake-timer regression test included). Same fix for the WAVE path landed separately via fix: never time out an in-flight upload payment #214.ant-corenow pinned to the released 0.6.0 from crates.io (ant-client#168 shipped in ant-cli-v0.3.3); git pin and draft-pin comment removed, lockfile refreshed.files.stage.approve_payment_n_of_mtranslated in all 18 locales.Tests
[hash, null], row fails with the incomplete-upload error, not "Payment failed"); storage-failure path retained; payment-timeout regression test.vue-tsc,cargo check/cargo testgreen.🤖 Generated with Claude Code