Skip to content

bugfix: simulator crashed, then hung, on Destroy Seed - #730

Open
hitechhayekian wants to merge 2 commits into
Coldcard:masterfrom
hitechhayekian:sim-pyb-flash
Open

bugfix: simulator crashed, then hung, on Destroy Seed#730
hitechhayekian wants to merge 2 commits into
Coldcard:masterfrom
hitechhayekian:sim-pyb-flash

Conversation

@hitechhayekian

@hitechhayekian hitechhayekian commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Destroy Seed could not complete in the unix simulator. Two separate problems, one behind the other.

1. pyb.Flash was missing (crash)

wipe_flash_filesystem() drives internal flash as a block device via pyb.Flash(start=0) (shared/files.py:59), but unix/variant/pyb.py never defined Flash:

File "shared/actions.py", line 609, in clear_seed
File "shared/files.py", line 59, in wipe_flash_filesystem
AttributeError: 'module' object has no attribute 'Flash'

This fails after both confirmations and after trick PINs are cleared, so the wallet is left half-wiped.

Adds a Flash block-device stub alongside the existing SDCard one, using the same "pretend to work" approach: 512-byte sectors, as files.py:61 asserts, and writes that go nowhere. Also adds testing/test_sim_pyb.py, which holds the simulator's pyb to the attributes shared/ actually references — that is what would have caught this before it reached a menu action.

2. The callgate then hung the simulator

With the crash fixed, Destroy Seed ran to completion and sat on "Clearing..." forever. ckcc.oneway() printed a NOTE and then looped on utime.sleep(60), so the last drawn frame stayed on screen — a finished wipe and a crash look identical.

It now raises SystemExit and names what the bootloader would have done. All four one-way methods end firmware execution on real hardware (DFU, logout, wipe, brick), so all four stop rather than spin. machine.py already does exactly this for bootloader()/soft_reset()/reset(), and the simulator runs with -i, so this lands at the REPL the same way a simulated reset always has.

machine.py's _flush_data() is deliberately not called here: it does settings.save(), and clear_seed() blanks settings on the line before fast_wipe(), so flushing would write back what was just erased.

Testing

  • testing/test_sim_pyb.py — 7/7 pass
  • Destroy Seed run end to end in the simulator, which now finishes and stops cleanly:
NOTE: One-way callgate into bootloader: method=23 arg2=48879
Simulator: would wipe seed & reboot. Exiting.
  • Nothing under testing/ references oneway, fast_wipe, show_logout, or enter_dfu
  • bare_metal.py installs its own my_oneway, so bare-metal mode is unaffected

Simulator-only — no changes to shared/ or any on-device code path.

Screenshot 2026-08-14 at 7 20 23 PM

hitechhayekian and others added 2 commits August 14, 2026 18:35
wipe_flash_filesystem() drives the internal flash as a block device via
pyb.Flash(start=0), but the simulator's pyb stand-in never defined Flash.
Destroy Seed died partway through with:

  File "shared/actions.py", line 609, in clear_seed
  File "shared/files.py", line 59, in wipe_flash_filesystem
  AttributeError: 'module' object has no attribute 'Flash'

It fails after the confirmations and after trick PINs are cleared, so the
wallet is left half-wiped.

Add a Flash block-device stub alongside the existing SDCard one, using the
same "pretend to work" approach: 512-byte sectors, as files.py asserts, and
writes that go nowhere. Also add a test holding the simulator's pyb to the
attributes the shared firmware actually uses, which is what would have
caught this before it reached a menu action.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ckcc.oneway() stands in for the bootloader callgate that never returns.
It printed a NOTE and then looped on utime.sleep(60) forever, so the
simulator sat on whatever frame was last drawn. After Destroy Seed that
is "Clearing...", which is indistinguishable from a crash - even though
the wipe had in fact run to completion.

Raise SystemExit instead, and say what the bootloader would have done.
All four one-way methods end firmware execution on real hardware (DFU,
logout, wipe, brick), so all four stop here rather than spin. machine.py
already does exactly this for bootloader()/soft_reset()/reset(), and the
simulator is launched with -i, so this lands at the REPL the same way a
simulated reset always has.

Deliberately not calling machine.py's _flush_data() alongside it: that
does settings.save(), and clear_seed() blanks settings on the line
before fast_wipe(), so flushing here would write back what was just
erased.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@scgbckbone scgbckbone left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the contribution. We already cover the Destroy Seed UX in testing/test_ux.py::test_destroy_seed. The destructive behavior itself needs to be verified on real hardware, so I don’t think emulating it in the simulator adds enough testing value to justify the additional code.

@doc-hex doc-hex added the simulator Impact limited to CC simulator/tests label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

simulator Impact limited to CC simulator/tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants