Skip to content

perf: snappier BitBox channel-hash poll after PIN unlock#305

Merged
TaprootFreak merged 1 commit into
RealUnitCH:developfrom
joshuakrueger-dfx:perf/bitbox-channel-hash-poll-faster
May 8, 2026
Merged

perf: snappier BitBox channel-hash poll after PIN unlock#305
TaprootFreak merged 1 commit into
RealUnitCH:developfrom
joshuakrueger-dfx:perf/bitbox-channel-hash-poll-faster

Conversation

@joshuakrueger-dfx
Copy link
Copy Markdown
Collaborator

Summary

  • Check the channel hash at the start of every poll iteration instead of sleeping first; break out as soon as a non-empty value comes back
  • Drop the inter-poll sleep from 500 ms to 100 ms

Why

After the PIN is entered on the device, pair()'s noise handshake sets device.channelHash within a couple of BLE round-trips. Before this PR the poll loop slept 500 ms at the top of each iteration, so once the hash became available the app could still lag up to half a second behind the device's display — users perceived a noticeable hang "after the last PIN digit".

bitbox.ChannelHash() is a plain field read on the Go side, so the tighter cadence (100 ms) costs effectively nothing and the immediate first read removes the leading 500 ms of dead time.

Test plan

  • Pair a BitBox, enter the PIN — the pairing code appears in the app within ~100 ms of being shown on the device
  • Cancel during connecting still returns to the welcome flow cleanly
  • Re-pair without restarting the app still works

After the PIN is entered on the device, init's noise handshake
sets device.channelHash within a couple of BLE round-trips. The
poll loop previously slept 500 ms before each call, so the app
would lag up to half a second behind the device once the hash
became available.

Check the channel hash at the start of every iteration and break
out as soon as a non-empty value comes back, then sleep 100 ms
before the next attempt. getChannelHash is a plain field read on
the Go side, so the tighter cadence costs nothing meaningful and
shaves the post-PIN wait noticeably.
@joshuakrueger-dfx joshuakrueger-dfx force-pushed the perf/bitbox-channel-hash-poll-faster branch from 195f438 to 5ae2da6 Compare May 8, 2026 09:33
@TaprootFreak TaprootFreak merged commit 8037e72 into RealUnitCH:develop May 8, 2026
1 check passed
TaprootFreak added a commit that referenced this pull request May 8, 2026
## Summary
- Snapshot `getChannelHash()` before starting `init()` and require the
polled hash to differ from it
- Drop the `_pendingInit ?? Future.value(true)` fallback in
`confirmPairing` and use the bang assertion the state guard already
guarantees

## Why

### Stale hash on re-pair
`BitboxService` is registered as a DI singleton
(`lib/setup/di.dart:121`), and the `BitboxManager` instance inside it
persists across pairings. The connect modal is reachable from at least
two entry points (`welcome_page.dart`, `kyc_registration_page.dart`),
and any second pairing attempt within the same app session goes through
that same SDK instance.

Before this PR, the polling loop accepted any non-empty hash. If
`bitboxManager.getChannelHash()` still holds the previous session's hash
when polling starts, the app shows the *old* hash while the BitBox
displays the *new* one — codes don't match, user is stuck.

The 500 ms first-iteration delay introduced in #305 (`...so the SDK can
finish setting up its Go-side device pointer...`) confirms there is a
real timing gap during which the SDK is not yet up-to-date for the new
session, which is precisely the window where stale hashes surface.

The fix records the hash once before `init()` runs and ignores any
polled hash that matches the snapshot.

### `??` fallback in confirmPairing
`_pendingInit ?? Future.value(true)` silently substitutes "init
succeeded" if the future is missing. The `is! BitboxCheckHash` state
guard at the top of `confirmPairing` already guarantees that
`connectToBitbox` set `_pendingInit`. The fallback only triggers in an
impossible state, and if that state ever became reachable, returning
`true` would let `channelHashVerify()` run on an unverified channel —
exactly the crash #301 set out to prevent.

Replacing it with the bang assertion makes the precondition explicit and
matches the project's bang usage in similar state-guarded code paths.

## Changes
- `lib/screens/hardware_connect_bitbox/bloc/connect_bitbox_cubit.dart`:
add `priorHash` snapshot + `hash != priorHash` check in polling; replace
`??` fallback with bang on `_pendingInit`

## Test plan
- [ ] Happy path on iOS BitBox02 Plus: open Welcome → pair → wallet
creates (regression check, no behavioural change expected on first pair)
- [ ] **Re-pair in same session:** pair successfully → trigger a second
pairing without restarting the app (e.g. via KYC retry flow, or by
opening the connect modal again). Verify the code shown in app matches
the code on the BitBox — not the previous session's code
- [ ] Cancel during `BitboxConnecting` (swipe modal down before code
appears): no `StateError`, no orphaned timer (regression check for #303)
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