Conversation
This comment has been minimized.
This comment has been minimized.
|
I pushed e447862 with original Shop and order follow-up recovery, exact native observation acknowledgement, earlier-payment isolation, shared verified proof metadata and original transfer accounting across wallet changes. The focused simulator build and 90 tests passed. This addresses accepted payments becoming stuck after local proof/activity/tracking failures without funding again. Process loss before a durable result still remains guarded. Corrected artifacts, hardware proof integration and current consumer journeys remain draft gates. |
|
I pushed 9c84067 with the hardware Shop acceptance gates. A Core txid remains an original lookup candidate until a fresh exact outgoing observation in the original hardware wallet verifies it. Only that positive result can create Shop proof, Sent activity or Success; Pending retains the original payer/request/wallet and does not inspect an unrelated Savings guard. The frozen batch passed its simulator build and 70 focused tests with no failures or skips. The UI, payer and activity regressions failed before their fixes; mocks do not establish a real hardware acceptance journey. Physical hardware and native fault fixtures remain QA gaps. This remains draft while current rc68 remote consumption, affected tests and fixed/Manual Max native journeys finish. Historical rc67 media is labelled accordingly. |
|
I pushed f16736d with the production rc68 package pin and current journey documentation. The normal SwiftPM revision is 773792d; the published archive, Package.swift checksum and extracted/resolved simulator framework independently match the canonical artifact. The current simulator build and 101 affected software, hardware and backup tests passed. The matching installed app completed funded fixed 1,000-sat and Manual Max 198,745-sat native sends; both exact UI transaction IDs matched native successful-broadcast logs and independent backend lookup. Current Preview replaces the historical accepted-send media. The hardware Shop journey is specified and parsed but unrun; controlled native refusal/response-loss and physical USB/BLE remain explicit QA gaps. Autopilot Max remains outside this fix. |
jvsena42
left a comment
There was a problem hiding this comment.
One HIGH, one MEDIUM and two LOW inline. I reviewed this as a funds change. The refusal lockout and the legacy-proof gap are shared with synonymdev/bitkit-android#1384.
Checked and clean:
- Only
NodeErrormaps to.preDispatch; in rc.68send_with_broadcast_resulterrors only before submission. Panics and other errors stay.unresolved, and the non-cancellable queue records the outcome. - Success is shown only on
.accepted, and Electrum returnsAcceptedonly for a matching txid. Observation promotion needs BDK sync events. HW Shop needs a matchinggetTransactionDetailwithsent > 0. admitis synchronous, so a concurrent send sees.pending.- The new keychain entry holds no key material and uses the existing accessibility/group, and full wipes cover it.
- The ldk-node rc.66 → rc.68 bump carries the broadcast-result API with no storage migrations. It also brings rc.67 coin-selection changes.
Pre-existing and out of scope: ordinary HW sends and Boost still use the legacy broadcast.
| var followupContext: OnchainSendFollowupContext? = nil | ||
| var transferContext: OnchainSendTransferContext? = nil | ||
|
|
||
| var blocksNewSend: Bool { |
There was a problem hiding this comment.
A refused broadcast locks on-chain sends for good.
Unknown and Rejected both block the next send, and the only exit is observing that exact txid on chain. There is no release on sync and no user action; only a wallet reset or reinstall clears the stored attempt. Ordinary sends, Shop payments and Blocktank funding all go through the same guard.
In ldk-node rc.68, classify_electrum_broadcast (src/chain/electrum.rs) returns Rejected only for -26 non-final. Every other daemon refusal becomes Unknown, including mempool min fee not met, min relay fee not met, txn-mempool-conflict and bad-txns-inputs-missingorspent. Those mean the backend did not take the transaction, and nothing rebroadcasts a user send, so no Received/Confirmed event ever arrives. A Slow or Custom fee during a mempool purge, or a send built on stale UTXO state, leaves the wallet unable to send on-chain while its funds stay in BDK.
Out of Scope accepts indefinite blocking for a lost result. This is a definite, common refusal that lands in the same state.
Fix: allow a release once a post-refusal sync shows the txid unknown to the backend and the attempt's inputs still unspent. If it could surface later, require the next send to spend at least one of those inputs so it conflicts rather than double-pays.
Same in synonymdev/bitkit-android#1384.
There was a problem hiding this comment.
The lockout is real when a refused transaction never surfaces. It is an explicit limitation of this bounded change, including refusal as well as a lost response.
rc.68 Rejected reports a recognized refusal and explicitly allows earlier delivery: outcome contract. The pinned electrum-client 0.24.1 retries transport failures up to three times after the original call. A later protocol refusal returns without the earlier transport-error history; a request may already have been written before a receive failure. The final refusal therefore does not prove that earlier delivery or acceptance never occurred. See the retry wrapper. This is a possibility supported by the transport contract, not a claim that it happened in the tested journeys.
Only a proven pre-dispatch failure releases this guard. Relabelling fee/conflict/missing-input responses as Rejected would preserve diagnostics but would not prove retry safety. One backend reporting absence and unspent inputs cannot retract previously delivered bytes or an in-flight request.
Requiring a later send to conflict with the original adds a replacement/recovery policy, which the agreed scope excludes. I’m keeping the guard and clarifying the refusal limitation. Does this remain a blocker for the bounded acceptance-reporting change, or can the recovery policy be considered separately?
|
Two independent reviews. needs changing before merge
worth doing, does not block
|
|
Pushed 2039d39 to address callback admission, proof mutation races, and delayed/reopened hardware follow-up. Callback errors clear only the exact attempt before native dispatch; a failed clear remains guarded. Proof mutations now serialize without holding the lock through SDK network work. Delayed verification restores original Sent/contact/tags before delivery, and Pending can reopen after the resolution event was consumed. Accepted payments are never broadcast again. Local verification: simulator build and 99 affected tests passed against published rc68 (0 failures/skips). The three regressions failed on the preceding head. Geographic test state is isolated while the production check remains unchanged. Prior native fixed/Manual Max media is historical for this batch. Hardware journey and controlled native faults remain unrun. Previous-head E2E failures remain unresolved; new-head CI is pending. Refusal lockout and software legacy migration remain open review discussions. |
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 2039d39. The callback-failure MEDIUM and the lock LOW are resolved, and the greptile threads on delayed HW verification and the pending screen check out. No new findings.
Still open, waiting on my decision: the refusal lockout thread and the legacy-proof migration thread, where you asked whether they block this PR.
|
I’m moving this PR back to draft while synonymdev/ldk-node#119 awaits approval. The early app review was valuable: it validated the integration against rc68 and caught issues that have now been addressed. The app integration depends on the node proposal being accepted. I’ll revisit readiness once LDK is approved, and update and revalidate the integration if the node API changes. |
|
Pushed c339e9e to resolve the conflicts with master 1eabf70. The merge retains current contact/payment authorization before software dispatch and every hardware retry, together with the original payer/request/wallet context, positive-acceptance gates and duplicate-payment protection. Validation: simulator build passed and 263 affected tests passed with 0 failures/skips. The exact tracked source and resolved rc68 simulator binary match the tested snapshot. The base-inherited Paykit rc56 update is preserved; LDK rc68 and Core are unchanged. The PR remains draft pending LDK approval. New hardware/native-fault journeys and media were not run; the existing Preview is historical. The earlier cancelled E2E run remains unattributed; current-head checks are pending. No refusal-release or recovery-policy changes were added. |
| ) | ||
| }, | ||
| authorizeContactPayment: { | ||
| try await authorizeHardwareContactPayment(context: contactContext, paymentIdentity: paymentIdentity) |
There was a problem hiding this comment.
On the first hardware attempt, this authorization can fail after prepareHardwareContactPayment has persisted paymentStarted = true. The coordinator then drops the signed payment without broadcasting, but cancelHardwareContactPayment only calls cancelPreparation, which keeps the started hardware proof. The request stays in flight with no transaction ID. Could we release the exact proof when authorization fails before any broadcast, while keeping attempted retries guarded, and cover the durable proof in the denied-first-attempt test?
| let feeRate = metadata?.feeRate ?? existing?.feeRate ?? UInt64(observed?.feeRate ?? 0) | ||
| guard await CoreService.shared.activity.createSentOnchainActivityFromSendResult( | ||
| txid: txid, address: address, amount: amount, fee: fee, feeRate: UInt32(clamping: feeRate), | ||
| contact: proof.requestId.counterparty, walletId: walletId |
There was a problem hiding this comment.
If SDK proof submission fails after the hardware activity is saved, the next reconciliation runs this again and overwrites any contact the user has since removed or reassigned in Details. Could we record completion of the local follow-up and preserve later contact edits when retrying proof submission?
| } | ||
| lightningService.onchainTransactionReceived = { txid in | ||
| do { | ||
| _ = try await onchainAttemptService.resumeAcceptedOrdinarySend( |
There was a problem hiding this comment.
This callback also resumes attempts whose local follow-up is already complete. If the user removes or changes the activity's contact before a delayed transaction event arrives, localFollowup.save passes the original contact back to createSentOnchainActivityFromSendResult, which overwrites the user's edit. Could we skip completed follow-up here and cover a contact edit followed by the received event?
Closes #717
Twin: synonymdev/bitkit-android#1384
Refs: synonymdev/ldk-node#119
Description
The published rc68 archive and resolved framework match the canonical artifact. The current merge at c339e9e integrates master 1eabf70 and passed its simulator build and 263 affected tests. Funded fixed-amount and Manual Max device evidence below predates this merge. This PR remains draft pending approval of the LDK change.
0.7.0-rc.68at 773792d; the downloaded archive, SwiftPM checksum and extracted simulator framework match.Out of Scope
Transaction recovery: journals, raw transaction storage, automatic rebroadcast, replacement, RBF recovery and abandonment.
Chain handling: reorg and event-delivery redesign.
Coin selection: Autopilot Max failed before Confirm with
CoinSelectionFailedin the regtest fixture; the accepted-send journey uses Manual selection. Coin-selection recovery is outside this change.Unresolved sends: no timeout/reset escape. A refused or unknown result can block further on-chain sends indefinitely when the transaction never becomes positively observed; backend acceptance does not guarantee confirmation.
Legacy software proofs: queued-era transaction IDs without positive acceptance and original wallet provenance are not promoted or delivered. Opted-in released users can have these records; migration is excluded from this PR, with the documented limitation accepted in review.
Design
N/A — no design available for the new unresolved-send state.
Preview
Historical rc68 candidate before the current feedback batch: fixed 1,000-sat and Manual Max 198,745-sat regtest sends using the actual published and resolved LDK package. Both exact UI transaction IDs matched native successful-broadcast logs and independent backend lookup; Max spends the original fixed-send change output with no change remaining.
QA Notes
Journeys
shop-onchain-proof.xml— linked issuer and funded Bridge hardware wallet: exact original payer, request, transaction, proof and wallet Details, including delayed Sent/contact/tags and reopened Pending after listener consumption, with no repayment while observation is pending. Spec parsed; native hardware journey unrun.onchain-accepted-result.xml— accepted fixed-amount and send-all with Manual coin selection finish local activity and expose distinct exact transaction IDs in Details.Manual Tests
Automated Checks
OnchainSendAttemptServiceTests.swift— durable admission, outcome persistence, exact observation and callback failure release with zero dispatch; failed release storage remains guarded.PaykitPaymentProofServiceTests.swift,PaykitPaymentStateBackupTests.swift— proof acceptance evidence, request protection and a suspended Lightning save cannot overwrite the concurrent on-chain start marker.HwFundingSignerTests.swift,HwWalletManagerTests.swift— original hardware account context and verified Shop versus Pending navigation.ChannelPurchaseFlow.swift,UtxoSelectionTests.swift— matching outcome API integration.TransferServiceActivityTests.swift— original local follow-up and actual reopened hardware Pending navigation; delayed verification restores original Core Sent/contact/tags before proof delivery. Existing accepted-order tests inject the geo decision while production retains the same geographic check.1955a179fdb6acc5159ead68225dd27029d2f2a74c4b8b13164038eb9f95462f, and downloaded/SwiftPM simulator binaries agree.Local verification: c339e9e merges master 1eabf70; the simulator build and 263 affected tests passed (0 failures/skips), including hardware authorization/retry, request/proof protection, exact outcome reconciliation, transfer activity and contact lifecycle. All tracked source hashes and the rc68 simulator binary match the tested snapshot. The base-inherited Paykit rc56 pin and API adaptation are preserved. Standard Debug geographic checks remain enabled; local-only
-packageFingerprintPolicy warnhandles the existing VSS fingerprint conflict.Earlier feedback validation at 2039d39: 99 affected tests passed; three semantic regressions failed on its preceding head and passed with those fixes. This is historical evidence, not a fresh red run for the merge.
Earlier CI at 2039d39 passed unit/integration/build checks; the E2E run was cancelled and its aggregate status failed. The cancellation cause remains unknown. No rerun of that unchanged cancelled run was requested; checks for the new merge head are pending.
Prior rc68 validation: 101 affected tests and funded fixed/Manual Max native journeys passed with independently verified exact backend transactions. Those device runs were not repeated for this batch. The updated hardware journey, physical hardware and controlled native fault scenarios remain unrun; no new hardware Preview is claimed. Missing original metadata after a failed write or crash cannot restore lost tags.