fix: reject partial merkle uploads instead of reporting success - #212
Conversation
ant-core's external-signer merkle finalize returns Ok with chunks_failed > 0 when chunks stay short of quorum after retries (WithAutonomi/ant-client#166), so a paid, partially stored — and unretrievable — file was shown as complete with a shareable address. Backend: ensure_all_chunks_stored() guards all three upload commands after the DataMap is persisted, so a shortfall errors with the counts and the saved DataMap path (re-uploading the same file skips chunks already stored). Frontend: the merkle branch's catch now wraps only the on-chain payment; confirm_upload_merkle rejections fall through to the outer catch and surface as an upload failure instead of "Payment failed", mirroring the wave-batch path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.5.1 is a merkle pricing release: the payable amounts in pool commitments now carry the same 3x median multiplier as the single-node path (merkle previously underpaid nodes 3x), the cost estimator bills the padded tree (median16 x 2^depth — 65 chunks pay for 128 leaves) instead of raw chunk count, and an external-signer batch over MAX_LEAVES (256 chunks, ~1 GB) is now refused cleanly at quote time with MerkleBatchTooLarge instead of attempting a split the one-signature contract cannot express. User-visible: merkle upload payments and cost previews rise to the correct network price; the app signs what the backend hands it, so no frontend change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Review against head ed3d9c46f1e7b371223dca32ced1b04afee6314c.
Verdict: no material blocker found.
The incomplete-upload guard is applied consistently to external wave-batch, external Merkle, and wallet uploads, after the DataMap is persisted and before the UI emits completion. The frontend now correctly distinguishes a post-payment storage shortfall from a payment failure, while preserving retry guidance.
Verification:
- independent six-seat review across correctness, payment/storage lifecycle, UI state, API compatibility, tests, and adversarial failure handling
- local frontend suite: 74/74 passed;
vue-tsc --noEmitpassed - local Tauri library tests: 18/18 passed; Clippy with warnings denied and formatting passed
git diff --checkpassed- all current GitHub checks passed
dirvine
left a comment
There was a problem hiding this comment.
Superseding review after a completed two-group, six-seat rerun against exact head ed3d9c46f1e7b371223dca32ced1b04afee6314c.
Final verdict: no material blocker in #212.
The panel independently confirmed that ensure_all_chunks_stored rejects either a non-zero failure count or a stored/total shortfall, and that all three successful upload-result paths pass through the guard before completion is persisted/emitted. The frontend also preserves post-payment storage failures as upload failures rather than mislabelling them as payment failures.
One reviewer challenged the 300-second withTimeout payment race. I reproduced that race, but it is inherited unchanged from base 9827d28 and is not introduced by #212. It should be fixed in the payment flow; it does not alter this PR's partial-upload correctness verdict.
Fresh verification on this exact head/descendant integration state:
- review panel: 6/6 seats completed; no #212 blocker
- frontend: 74/74 tests passed;
vue-tsc --noEmitpassed - Tauri: 18/18 library tests passed; Clippy (
-D warnings), fmt andgit diff --checkpassed - all current GitHub checks passed
Problem
Merkle payments (auto-selected for uploads of >=64 chunks) could report success while not all chunks were actually stored. The user pays on-chain, the app marks the row complete, persists the DataMap, and shows a shareable address — for a file that cannot be fully downloaded.
Root cause is upstream: ant-core's external-signer merkle finalize returns
Okwithchunks_failed > 0when chunks stay short of quorum after all retries, instead ofErr(PartialUpload)like the wave-batch path (WithAutonomi/ant-client#166, fixed by WithAutonomi/ant-client#167). The app then droppedchunks_failed/total_chunkson the floor and emitted complete unconditionally.Fix
Backend — new
ensure_all_chunks_stored()guard, called in all three upload commands (confirm_upload_merkle,confirm_upload,wallet_upload) after the DataMap is persisted:chunks_failed > 0orchunks_stored < total_chunks, so the invariant holds even if a core path miscountswrite_datamap_forso the user keeps the DataMap handle; the error message includes the stored/total/failed counts and the DataMap path, and notes that re-uploading the same file skips already-stored chunks (prepare's preflight detects them)Errfrom core on partial failure — there the guard is stated defense in depthFrontend — the merkle branch's try/catch wrapped both the payment and
confirm_upload_merkle, so a storage failure surfaced as "Payment failed: …". The catch now wraps only the on-chain payment; confirm rejections fall through to the outer catch and surface as an upload failure, mirroring the wave-batch path.ant-core 0.5.0 → 0.5.1 bump
Second commit bumps to the current ant-core. 0.5.1 is a merkle pricing release, so this is user-visible in the right direction:
median16 × 2^depth; 65 chunks pay for 128 leaves), so cost previews match what the contract actually chargesMAX_LEAVES(256 chunks ≈ 1 GB) is now refused cleanly at quote time (MerkleBatchTooLarge) instead of attempting a split the one-signature contract cannot expressNo frontend change needed — the app signs whatever pool commitments the backend hands it.
Tests
chunks_failed == 0shortfall still rejected)confirm_upload_merklerejection marks the row failed with the storage error, not a payment failurenuxi typecheck,cargo test/clippy/fmtall greenOnce ant-client#167 ships in a release, finalize itself will error and the backend guard becomes a no-op safety net — no changes needed here.
🤖 Generated with Claude Code