fix(files): don't pay for uploads cancelled during the quote phase - #194
Merged
Conversation
Cancelling a row while its post-approval quote was in flight did not stop the upload flow. cancelPendingUpload allows cancelling `quoting` rows and removes the row, but startRealUpload never re-checked the row after awaiting the quote, and updateEntry silently no-ops on removed rows - so the flow marched on into a real on-chain payment (approve + payForQuotes/payForMerkleTree, a wallet prompt on the WalletConnect path, silent signing on the direct-key path) and chunk storage, for an upload with no visible row and no history entry. Guard the paying transition: bail out if the row no longer exists once the quote resolves. After `paying` is set no further removal is possible (cancelPendingUpload refuses paying rows), so the single guard closes the race. The daemon's parked PreparedUpload is reclaimed by gc_pending_uploads on a later start_upload. Regression test drives the exact race: cancel arrives while the quote is in flight, backend still answers, flow must stop before confirm_upload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Problem (community report)
Cancelled uploads still call the smart contracts.
Verified race: after Approve,
startRealUploadsets the row toquotingand awaits the real quote (encrypt + per-chunk quote collection — can take minutes). The row is cancellable in that state:cancelPendingUploadremoves it. ButstartRealUploadnever re-checks the row after the await, andupdateEntrysilently no-ops on removed ids — so when the quote resolves, the flow continues intoensureAllowance+payForQuotes/payForMerkleTree(real ANT + gas; a surprise wallet prompt on the WalletConnect path, and silent signing with no prompt at all on the direct-key path) and thenconfirm_uploadstores the chunks. The result is invisible: no row, no history entry, no datamap surfaced.Fix
One guard at the
payingtransition instores/files.ts::startRealUpload: if the row no longer exists when the quote resolves, bail out before payment. This single check closes the race completely — oncepayingis set (synchronously after the guard),cancelPendingUploadrefuses the row, so there is no later removal window. Cancels during the pre-approval estimate and in the queued states were already safe (no payment reachable).The daemon's parked
PreparedUploadfrom the aborted quote is reclaimed by the existinggc_pending_uploadssweep on a laterstart_upload— no Rust changes needed.Testing
start_upload, the row is cancelled mid-quote, the quote still arrives (payment_required: false, so the pre-fix flow would fall straight through), and the test asserts the flow stops beforeconfirm_upload.npm run test: 43/43 green;npx nuxi typecheck: no app errors.🤖 Generated with Claude Code