Skip to content

fix: stop stale device records from shadowing desktop BLE connectId resolution(OK-60878) - #12972

Open
originalix wants to merge 2 commits into
xfrom
fix/desktop-ble-stale-device-record-alias
Open

fix: stop stale device records from shadowing desktop BLE connectId resolution(OK-60878)#12972
originalix wants to merge 2 commits into
xfrom
fix/desktop-ble-stale-device-record-alias

Conversation

@originalix

@originalix originalix commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

OK-60878


Summary

  • Prefer the identity-qualified device lookup (connectId + featuresDeviceId) over the connectId-only lookup in getCompatibleConnectId, keeping the connectId-only match as a strict fallback
  • Add localDb.cleanStaleDeviceConnectIdAliases and invoke it (best-effort) after every identity-verified desktop BLE bind — both the pairing-repair flow and the silent-bind flow — so stale sibling records lose their colliding connect-id aliases and existing poisoned databases self-heal
  • Cover the new semantics with a pure-predicate unit suite plus regression tests for the endless pairing-dialog loop

Intent & Context

A production v6.5.2 desktop (macOS) user wiped their OneKey Pro and re-onboarded it via desktop Bluetooth. Afterwards every hardware call — device details refresh, the batch account derivation right after wallet creation, settings reads — re-opened the "Pairing with your device…" dialog in an endless loop, even though each pairing repair reported success. Production logs showed serviceHardware.repairBleConnectIdWithProgress being called and resolving successfully every ~10 seconds while the dialog kept coming back, with the NobleBLE link itself healthy the whole time.

Root Cause

A device wipe changes the raw deviceId, so wallet creation correctly produces a NEW Device record (getExistingDevice must not silently re-associate a new seed onto an old wallet). But the pre-wipe record survives, and both records share the same serial-based connectId (the USB serial, e.g. PRB09B0058A):

  • getCompatibleConnectId resolved the device with a connectId-only getDeviceByQuery. getAllDevices() is unordered (dbDeviceId is a random UUID), and on the affected machine the stale pre-wipe record won the lookup. It has no bleConnectId, so the pairing dialog opened.
  • The repair flow queries with connectId + featuresDeviceId, so it verified the live device and wrote the freshly bound bleConnectId to the NEW record.
  • Reads kept resolving the stale record while writes went to the live one, so the loop never terminated. The silent-bind path (OK-60091) could not rescue it either: its usb-alias guard bails immediately when the incoming connectId equals the stale record's own connectId.
  • Before wallet creation the same stale record caused every repair to fail outright: its persisted deviceId no longer matches the wiped device, so the repair's identity verification rejected with DeviceNotFound.

Diagnosed from production logs by correlating the repair call/resolve cadence with the device identity change (device_id differs across sessions while the serial stays the same), then verified line-by-line against the code.

Design Decisions

  • Align read and write predicates instead of sorting or deduping lookups: getCompatibleConnectId now tries {connectId, featuresDeviceId} first and falls back to the connectId-only match. The original guarantee — stale device info must never veto a valid transport-ID match — is preserved because the fallback keeps the legacy behavior whenever the combined query misses.
  • Self-heal instead of a DB migration: after any BLE bind whose identity was just verified against the live device (pairing repair or silent bind), clear colliding connect-id aliases from same-vendor sibling records whose persisted deviceId provably differs. Affected installs heal on the first successful bind, which also fixes callers that pass only a connectId (e.g. applySettingsToDevice, getDeviceLabel).
  • Cleanup only touches records with a persisted and different deviceId; records without an identity are never treated as stale. Only colliding fields are cleared: connectId to '' (required column) and usbConnectId/bleConnectId to undefined. No schema change, no LOCAL_DB_VERSION bump.
  • The usb-alias guard in getPersistedDesktopBleConnectId is deliberately untouched — it protects against genuine serial-into-bleConnectId poisoning.
  • Cleanup is best-effort (cleanStaleDeviceConnectIdAliasesSafe): the bind already succeeded, so cleanup failures are logged and never surfaced.
  • Deliberately deferred to follow-ups: wallet-create-time dedupe of stale siblings, and a dedicated "device was reset" UX for dead wallets (identity mismatch currently surfaces as DeviceNotFound).

Changes Detail

  • packages/kit-bg/src/services/ServiceHardware/ServiceHardware.ts — identity-first lookup order in getCompatibleConnectId; new private cleanStaleDeviceConnectIdAliasesSafe wired into the repairBleConnectIdWithProgress and silentlyBindLiveDesktopBleConnectId success paths, with an sdkLog entry when records are cleaned.
  • packages/kit-bg/src/dbs/local/LocalDbBase.ts — exported pure helpers collectDeviceConnectIdAliases / isStaleDeviceConnectIdAliasRecord and the cleanStaleDeviceConnectIdAliases method (vendor-scoped, transaction-based, returns cleaned ids).
  • packages/kit-bg/src/dbs/local/LocalDbBase.deviceConnectIdAliases.test.ts — new predicate unit suite (9 cases) covering every guard branch.
  • packages/kit-bg/src/services/ServiceHardware/ServiceHardware.getCompatibleConnectId.test.ts — regression test for the duplicate-record pairing loop, a repair-flow cleanup wiring test, cleanup assertion added to the silent-bind success test; five existing tests migrated from sequence mocks to predicate-honoring mocks so they genuinely exercise the "stale identity does not veto the connectId match" fallback.

Risk Assessment

  • Risk Level: Medium
  • Affected Platforms: Desktop (primary — desktop BLE binding and repair); the lookup-order change is shared code, with native BLE (iOS/Android) behavior covered by updated tests
  • Risk Areas: getCompatibleConnectId sits on every hardware call path — mitigated by the strict fallback (byte-for-byte legacy behavior whenever the combined query misses) and 435 passing tests across 26 hardware-related suites. Alias cleanup writes to sibling records — gated on a just-verified live identity plus a persisted-and-different deviceId, and it never breaks the bind on failure.

Test plan

  • yarn jest on all ServiceHardware suites + the new predicate suite + related ServiceAccount/ServiceFirmwareUpdate/ServiceThirdPartyHardware suites — 26 suites, 435 tests pass
  • yarn agent:check --profile commit passes
  • Manual (desktop): wipe a device that already has a wallet in the app, re-onboard it via desktop Bluetooth, and confirm batch account creation completes without repeated pairing dialogs; the stale record's aliases are cleared (sdkLog cleanStaleDeviceConnectIdAliases)
  • Manual (desktop): an install already showing the endless pairing dialog self-heals after the first successful pairing repair on this build

@sidmorizon

Copy link
Copy Markdown
Contributor

@codex review

@sidmorizon

Copy link
Copy Markdown
Contributor

@codex security review

@sidmorizon

Copy link
Copy Markdown
Contributor

@cursoragent review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@cursor

cursor Bot commented Aug 22, 2026

Copy link
Copy Markdown

PR #12972 代码审查报告

审查概要

  • 变更范围: 4 个文件, +494 / -16 行
  • 风险等级: Medium
  • 涉及平台: Desktop(主路径:桌面 BLE bind / repair);getCompatibleConnectId 查找顺序是共享代码,Native BLE 由更新后的测试覆盖
  • Codex 交叉验证: ⏭️ 未启用(本环境无 Codex rescue subagent;PR 上 Codex security review 也因额度失败)
  • PR 评论分析: ✅ 已分析(5 条评论, 其中 2 条来自 Bot)— 无实质技术审查意见

评分

维度 得分 说明
🔒 安全性 8.5/10 清理门闩充分(刚验证的 live identity + 已持久化且不同的 deviceId + vendor 隔离);无密钥泄露。死钱包关联记录被清空 connectId 后,ServiceAccount 会 fail-closed。
💎 代码质量 8.0/10 谓词抽成纯函数、best-effort 包装合理、fallback 保留了「stale identity 不得否决 transport-id」的原保证。新增 console.error 不符合项目 logger 规范。
🏛️ 架构合理性 8.5/10 层次正确(localDb 写、ServiceHardware 编排);无 import 越界;自愈优于 migration。
✅ 完整性 8.0/10 谓词 9 例 + pairing-loop 回归 + 接线测试到位。缺少对写路径的 LocalDb 集成测试。
总分 8.3/10 ✅ 可直接合入

PR 评论分析

来源 类型 发现 判定 说明
@sidmorizon 👤 Human @codex / @cursoragent review ⏭️ 非技术评论 触发审查,无具体缺陷
chatgpt-codex-connector 🤖 Bot Codex 额度用尽 ⏭️ 非技术评论 交叉验证未发生
cursor 🤖 Bot Taking a look ⏭️ 非技术评论 本审查的占位回复

发现的问题

[🟢 低] [🔵 High] 新增失败路径使用 console.error 而非项目 logger

文件: packages/kit-bg/src/services/ServiceHardware/ServiceHardware.ts:4501
类型: 规范
描述: cleanStaleDeviceConnectIdAliasesSafe 的 catch 使用 console.error。成功路径已经走 defaultLogger.hardware.sdkLog.log,失败日志也应进 sdkLog 才能和 cleanedIds 条目对得上。
Auto-fix:

     } catch (error) {
-      console.error('cleanStaleDeviceConnectIdAliases failed:', error);
+      defaultLogger.hardware.sdkLog.log(
+        'cleanStaleDeviceConnectIdAliases failed',
+        error instanceof Error ? error.message : String(error),
+      );
     }

[🟢 低] [🟠 Medium] cleanStaleDeviceConnectIdAliases 写路径缺少 LocalDb 集成测试

文件: packages/kit-bg/src/dbs/local/LocalDbBase.ts:5544
类型: 规范
描述: 谓词套件覆盖了 guard 分支,ServiceHardware 侧只 assert 了 mock 被调用。真正的风险在写路径:connectId''、碰撞字段清除、vendor 隔离、keepDevice miss 早退。建议用现有 TestLocalDb 补 2–3 个方法级用例。


修改清单

优先级 置信度 文件 类型 描述 Auto-fix
🟢 低 🔵 High ServiceHardware.ts:4501 规范 catch 改用 defaultLogger.hardware.sdkLog
🟢 低 🟠 Medium LocalDbBase.ts:5544 规范 补写路径 LocalDb 集成测试

测试建议

  1. Desktop BLE:已有钱包的设备 wipe 后经桌面蓝牙重新创建,批量派生不应再反复弹出 pairing dialog。
  2. 已中毒安装:第一次成功 repair 后循环应停止;applySettingsToDevice / getDeviceLabel 不再命中 stale 记录。
  3. 死钱包 fail-closed:旧钱包关联 Device 的 connectId 被置空后应跳过解析或明确失败,而不是静默打到新种子设备。若 UI 拿「当前会话 serial」+ 旧钱包 featuresDeviceId 调用,identity miss 后会 fallback 到 live 记录——这是有意保留的 fallback,应用「设备已重置」UX 跟进。
  4. 回归:传入 stale featuresDeviceId 时,connectId-only fallback 仍能命中已绑定 BLE 的记录。

结论: 根因判断正确,读/写谓词对齐 + 验证后自愈是对症的,没有合入阻断项。完整报告已发到 PR #12972

Open in Web Open in Cursor 

@cursor

cursor Bot commented Aug 22, 2026

Copy link
Copy Markdown

PR #12972 代码审查报告

审查概要

  • 变更范围: 4 个文件, +494 / -16 行
  • 风险等级: Medium
  • 涉及平台: Desktop(主路径:桌面 BLE bind / repair);getCompatibleConnectId 查找顺序是共享代码,Native BLE 由更新后的测试覆盖
  • Codex 交叉验证: ⏭️ 未启用(本环境无 Codex rescue subagent;PR 上 Codex security review 也因额度失败)
  • PR 评论分析: ✅ 已分析(5 条评论, 其中 2 条来自 Bot)— 无实质技术审查意见(@codex review / @cursoragent review / Codex 额度提示)

评分

维度 得分 说明
🔒 安全性 8.5/10 清理门闩充分(刚验证的 live identity + 已持久化且不同的 deviceId + vendor 隔离);无密钥/助记词泄露。死钱包关联记录被清空 connectId 后,ServiceAccountif (dbDevice.connectId) 会 fail-closed。
💎 代码质量 8.0/10 谓词抽成纯函数、best-effort 包装合理、读路径 fallback 保留了「stale identity 不得否决 transport-id」的原保证。新增 console.error 与同文件既有风格一致,但不符合项目 logger 规范。
🏛️ 架构合理性 8.5/10 层次正确(localDb 写、ServiceHardware 编排);无 import 越界;自愈优于 migration / LOCAL_DB_VERSION bump。
✅ 完整性 8.0/10 谓词 9 例 + pairing-loop 回归 + repair/silent-bind 接线测试到位。缺少对 cleanStaleDeviceConnectIdAliases 真实写路径(空串 connectId、vendor 过滤、keep-miss)的 LocalDb 集成测试。
总分 8.3/10 ✅ 可直接合入

PR 评论分析

来源 类型 发现 判定 说明
@sidmorizon 👤 Human @codex review / @codex security review / @cursoragent review ⏭️ 非技术评论 触发审查,无具体缺陷
chatgpt-codex-connector 🤖 Bot Codex 额度用尽 ⏭️ 非技术评论 交叉验证未发生
cursor 🤖 Bot Taking a look ⏭️ 非技术评论 本审查的占位回复

发现的问题

[🟢 低] [🔵 High] 新增失败路径使用 console.error 而非项目 logger

文件: packages/kit-bg/src/services/ServiceHardware/ServiceHardware.ts:4501
类型: 规范
描述: cleanStaleDeviceConnectIdAliasesSafe 的 catch 使用 console.error。成功路径已经走 defaultLogger.hardware.sdkLog.log;同文件 repairBleConnectIdWithProgress / silentlyBindLiveDesktopBleConnectId 也有历史 console.error,但本次新增不应再扩大。失败日志进 sdkLog 才能和成功的 cleanedIds 条目对得上。
Auto-fix:

     } catch (error) {
-      console.error('cleanStaleDeviceConnectIdAliases failed:', error);
+      defaultLogger.hardware.sdkLog.log(
+        'cleanStaleDeviceConnectIdAliases failed',
+        error instanceof Error ? error.message : String(error),
+      );
     }

[🟢 低] [🟠 Medium] cleanStaleDeviceConnectIdAliases 写路径缺少 LocalDb 集成测试

文件: packages/kit-bg/src/dbs/local/LocalDbBase.ts:5544
类型: 规范
描述: 谓词套件覆盖了每个 guard 分支,ServiceHardware 侧只 assert 了 mock 被调用。真正的风险在写路径:connectId''usbConnectId/bleConnectIdundefined、vendor 隔离、keepDevice miss 早退、transaction 内只清碰撞字段。建议用现有 TestLocalDb 补 2–3 个方法级用例,避免 Realm/IndexedDB 列约束或 cache 失效回归漏掉。


修改清单

优先级 置信度 文件 类型 描述 Auto-fix
🟢 低 🔵 High ServiceHardware.ts:4501 规范 catch 改用 defaultLogger.hardware.sdkLog
🟢 低 🟠 Medium LocalDbBase.ts:5544 规范 补写路径 LocalDb 集成测试

测试建议

  1. 主修复路径(Desktop BLE): 已有钱包的设备 wipe 后经桌面蓝牙重新创建钱包;批量派生账户不应再反复弹出 pairing dialog;sdkLog 出现 cleanStaleDeviceConnectIdAliases
  2. 已中毒安装自愈: 当前已陷入 pairing 循环的库,第一次成功 repair 后循环应停止;connectId-only 调用方(applySettingsToDevice / getDeviceLabel)不再命中 stale 记录。
  3. 死钱包 fail-closed: 旧钱包走 getWalletDeviceHwInfo 时,关联 Device 的 connectId 已被置空,应跳过解析或明确失败,而不是静默打到新种子设备。若 UI 拿「当前会话 serial」+ 旧钱包 featuresDeviceId 调用 getCompatibleConnectId,identity 查询 miss 后会 fallback 到 live 记录——这是本 PR 有意保留的 fallback,合入后应用「设备已重置」UX 跟进。
  4. 回归: 传入 stale featuresDeviceId 时,connectId-only fallback 仍能命中已绑定 BLE 的记录(现有 native/desktop 用例已覆盖)。
  5. Trezor desktop BLE: vendor 隔离应保证 OneKey 清理不会碰到 Trezor 记录;Trezor RPA bleConnectId 不与 OneKey serial 碰撞。

结论: 根因判断正确,读/写谓词对齐 + 验证后自愈是对症的,没有合入阻断项。上面两条都是 follow-up 级别。

— Auto-review by Cursor agent

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b120b15e50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/kit-bg/src/dbs/local/LocalDbBase.ts Outdated
Comment thread packages/kit-bg/src/services/ServiceHardware/ServiceHardware.ts Outdated
@originalix originalix changed the title fix: stop stale device records from shadowing desktop BLE connectId resolution fix: stop stale device records from shadowing desktop BLE connectId resolution(OK-60878) Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants