0xMiden / 0xMiden/wallet

Guardian seed recovery resurrects already-spent private notes as claimable — auto-consume retries them forever

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

描述

@WiktorStarczewski I think we have to address this ASAP; the wallet that is being recovered isn't usable due to this issue. It's impossible to initiate a new transaction because of this issue.

## Summary

After a guardian seed recovery, the wallet re-imports the account's full private-note history from the Note Transport Service, which re-serves every in-retention note with no consumption filter. The client never checks whether those notes' nullifiers are already spent — so notes consumed or sender-reclaimed *before* recovery reappear as pending/claimable, and auto-consume queues them all. Each submit is rejected by the node with `nullifiers already exist`, the failure never marks the note consumed, and it re-queues on the retry backoff — permanently, on every recovered install.

Observed on wallet extension v1.15.21 and the Android app (`@miden-sdk/miden-sdk` 0.15.9, testnet): after recovering the same account on both devices, 37 pending notes appeared whose nullifiers were all provably spent on-chain days-to-weeks before recovery. Result: 148 consume rows / 123 Failed on the extension alone, retrying daily forever, plus queue-expiry failures (`Transaction expired after being queued too long`) because 37 simultaneous doomed consumes can't drain within the 30-minute queue window.

## Reproduction

1. On a guardian account, receive private notes; let some be consumed and/or sender-reclaimed.
2. Wipe the install (or use a second device) and recover the account from seed.
3. Let sync and auto-consume run.

**Expected:** already-spent notes are recognized as consumed (or filtered before auto-consume); nothing pending.
**Actual:** all in-retention notes reappear as claimable; consumes of spent notes fail `nullifiers already exist` and retry forever; the pending list never shrinks. Deterministic — 2/2 installs.

## Root cause

- The NTS fetch path imports notes as `NoteFile::NoteDetails` → `import_note_records_by_details`, which authenticates them to `Committed` via a backward note-tree scan and never queries nullifiers (miden-client v0.15.5, `note_transport/mod.rs:401-412`, `import.rs:344-457`). The correct backward spent-check (`get_nullifier_commit_heights`, `import.rs:252-294`) exists but is wired only to the proof-import path, which NTS never uses.
- The only later consumption trigger is forward-sync `sync_nullifiers(prefixes, checkpoint+1, tip)` (`state_sync.rs:1005-1024`); the checkpoint is monotonic with no rewind, and by the time a recovered account's notes are imported it already sits at chain tip — so pre-recovery spends are invisible forever. Confirmed on-device: the one note spent two minutes after recovery transitioned correctly to consumed-external; the 37 spent before recovery stayed `Committed`.
- Not a node limitation: `/rpc.Api/SyncNullifiers` returns the historical spends when asked with a low `block_from`.

Related, distinct: #215, #615, #617, #453.

贡献指南

打开贡献指南

调研方向

The issue is in the note import logic. Start by examining the NTS fetch path in `note_transport/mod.rs` lines 401-412 and `import.rs` lines 344-457. Compare it with the proof-import path that uses `get_nullifier_commit_heights` (lines 252-294). The fix likely involves wiring the spent-check into the NTS import flow. Check `state_sync.rs` lines 1005-1024 for the forward sync logic. Testing requires setting up a guardian account, creating spent notes, and performing a seed recovery to verify the fix.

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

评估

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

把新 issue 发到你的邮箱

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