0xMiden / 0xMiden/wallet

Guardian private send: post-submit bailout marks the tx "Sent" but the note payload is never relayed — recipient can never receive it

未关闭
#734 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
TypeScript
星标
5
派生
28
平均合并
9 小时 50 分钟
30 天内合并 PR
123

描述

## Environment
- Wallet: 1.15.x mobile builds (sender = Android, receiver = iOS), Miden testnet. Defect verified in source at tag v1.15.21 and unchanged on current main and next.
- Guardian: OpenZeppelin, @openzeppelin/miden-multisig-client 0.16.0.

## Summary
On the guardian route, a private P2ID send that hits a post-submit error is marked Completed with "Sent" — but the early return skips the completion step that performs the note-transport upload (the only relay call site in the codebase). No outbox entry is ever seeded, so nothing retries. The transaction is committed on-chain, the sender UI reports success, and the recipient can never obtain the note payload. Funds end up locked in an unclaimable private note while both sides believe the transfer succeeded.

## Incident (2026-08-19, testnet)
Five private P2ID sends from mtst1apx00elfcmhmzsfprk6h90dj6gskwjyf (Android) to mtst1aqx8wp4us2ll3qt2sd5qana0zyzxq3hl (iOS), one burst, 12:12–12:15 UTC. All five committed on-chain. The receiver claimed two; three are permanently undelivered. Sender UI showed all five as sent successfully.

| Amount | Tx | Block | Note | Outcome |
|---|---|---|---|---|
| 1 MIDEN | 0xbf538096dbac898c62858fbbabfdc8c472316b541aabe2c3388fb364eb737c2b | 1640425 | 0x5a4be717…48ba4197 | LOST |
| 2 MIDEN | 0xba2ffc351f711941006eddbd2205d751f70a1f2618a414053c3ca9d6a3230d7e | 1640447 | 0x69f0b384…49af1768 | LOST |
| 3 MIDEN | 0xc3d0f31290aea30c8b33dd55eec67bb6bad608af8bcffd5a51818134dff61f3d | 1640457 | 0x8154f4eb…6312504c | claimed |
| 4 MIDEN | 0x59f53c8604ddd987cff49dba46b769982abffdd343fcb343f81788e29d8d90e1 | 1640469 | 0x381c8095…39019de1 | LOST |
| 5 MIDEN | 0x901e33138565bc568249e8a083892c51a8eb739283665782497cb381c18a3eb9 | 1640481 | 0xe92c7237…b8c53d4d | claimed |

Transport-side proof (live gRPC probes against transport.miden.io, same day): FetchNotes at cursor 0 for the recipient tag 0x0C744000 (both byte orders, both testnet hostnames, plus StreamNotes) returns zero notes — while a positive control on a neighboring tag returns 42 notes, the store is continuous (no wipe: ~8,490 notes, seq ~8,000, steadily growing), and the server has no delete-on-fetch. The payloads were never uploaded at all. This is therefore NOT the nts#101 cursor class and NOT a transport wipe.

## Root cause
src/lib/miden/transaction/index.ts has two guardian-only bailout branches — ApplyTransactionAfterSubmitFailed (~line 391) and isGuardianCanonicalizationError (~line 418) — that mark the row Completed/"Sent" and return. This bypasses completeSendTransaction, whose private-note relay call (complete.ts, ~line 504) is the only note-transport upload in the codebase. The SDK outbox is only seeded when that call runs at least once, so a skipped call is never retried. The same catch also abandons the guardian proposal candidate, and the guardian server deletes proposals on every terminal path — destroying the only other place the note could have been rebuilt from. Rapid consecutive sends make the canonicalization race likely, which matches the observed 3-of-5 loss in one burst.

Introduced by PR #365 (v1.15.9), which generalized the fix for #356 from consume to send/swap/execute. For consume the reasoning holds (next sync reconciles the note as consumed externally); for a private send there is no reconciliation analogue — nothing in sync re-attempts the upload. #589 already concedes that only consume can be node-verified today, yet these branches mark an unverifiable send as "Sent". On next, the new guard that fails Epoch rows instead of completing them (because there is no result left to repopulate from) applies the identical reasoning — but send was left on the mark-Completed path, so the defect persists through the 0.16 refactor.

## Distinct from existing issues
- #631: opposite leg — payload WAS uploaded, receiver cursor stranded (transport class nts#101). Here the upload never happened.
- #56: asked (Jan 2026, still open, no code) for a distinct "private note layer failure" state — exactly the state this path needs instead of "Sent".

贡献指南

打开贡献指南

调研方向

The bug is in src/lib/miden/transaction/index.ts around lines 391 and 418, where guardian bailout branches mark a transaction as 'Sent' but skip the note upload in completeSendTransaction (complete.ts, ~line 504). Start by examining the ApplyTransactionAfterSubmitFailed and isGuardianCanonicalizationError handlers. Understand the note-transport upload flow and the outbox seeding mechanism. 'Done' means ensuring private sends that hit these errors are not marked 'Sent' and that the note payload is either uploaded or the transaction is marked as failed appropriately.

由索引模型根据 Issue 内容生成。

评估

技术栈
nodejs, typescript
领域
backend, blockchain
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
描述清楚
新手友好度
45/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。