Skip to content

test(hardware-wallet): exercise hardware-wallet builds and mocks in CI - #759

Open
TheWeirdDee wants to merge 2 commits into
Nanle-code:masterfrom
TheWeirdDee:fix/666-hardware-wallet-ci
Open

test(hardware-wallet): exercise hardware-wallet builds and mocks in CI#759
TheWeirdDee wants to merge 2 commits into
Nanle-code:masterfrom
TheWeirdDee:fix/666-hardware-wallet-ci

Conversation

@TheWeirdDee

Copy link
Copy Markdown
Contributor

Closes #666.

Objective

hardware-wallet is an optional Cargo feature (hidapi for Ledger, trezor-client for Trezor) that was never actually built or tested in CI — build-and-test only ran cargo build/cargo test with default features, and while clippy did pass --all-features, cargo clippy alone doesn't run the code, only type-check it. As a result the feature's code paths could (and did) silently break without anyone noticing.

Root cause found while fixing this

Compiling with --features hardware-wallet for the first time surfaced that TrezorTransport::sign_transaction called protobuf setters (set_network, set_transaction) that don't exist on the pinned trezor-client = 0.1.5 — Trezor's Stellar protocol has no "raw envelope" field; it requires the transaction to be decomposed into structured per-operation messages, which was never implemented. This is exactly the kind of regression issue #666 asks CI to catch. (Someone had already partially patched the compile error on master since I started; I kept their fix and improved it further — see below.)

What changed

CI (.github/workflows/ci.yml)

  • New hardware-wallet job: installs libudev-dev (hidapi's Linux HID backend) and libusb-1.0-0-dev (trezor-client's rusb transport), then runs cargo build --locked --features hardware-wallet and cargo test --locked --features hardware-wallet -- --test-threads=1.
  • -- --test-threads=1 matches the existing build-and-test job's own precedent, and I confirmed locally it matters here too: running the new hidapi + trezor-client tests in parallel intermittently crashed the test binary (Windows-side hidapi/libusb concurrency issue); serial execution was reliably clean.
  • Also added libusb-1.0-0-dev to the clippy job's deps, since it already lints with --all-features and needs the same headers to link trezor-client.

src/utils/hardware_wallet.rs

  • Extracted the Ledger APDU status-word handling out of LedgerTransport::exchange into standalone classify_status_word/check_apdu_status functions, with unit tests for: approval (0x9000), rejection (0x6985/0x6982 — user declined on-device), unsupported envelope (0x6D00/0x6E00/0x6A81 — outdated app / wrong envelope), an unrecognized status code, and a truncated response.
  • Reordered TrezorTransport::sign_transaction to validate the HD path and report "not supported" before opening a device session — previously it called connect() first, so in any environment without a physical Trezor (i.e. CI) the "not supported" message was unreachable; you'd always get "No Trezor device detected" instead, masking the real limitation. This also means the unsupported-envelope path can now be tested deterministically without hardware.
  • Added map_signing_error guidance for the unsupported-envelope case.
  • Added feature-gated tests that exercise the real hidapi/trezor-client backends against an absent device: connect/device_status for both Ledger and Trezor return a clear disconnect error rather than hanging or panicking.
  • Added HD-path boundary/failure tests (empty path, empty segment, out-of-range index).

tests/hardware_wallet_integration.rs

  • Added feature-gated CLI-level tests: wallet connect ledger --timeout 1s, wallet hw-status trezor, and wallet import ... --hardware ledger all fail cleanly (non-zero exit, clear message) with no device attached.

Docs

  • API_REFERENCE.md: new "Hardware wallets (Ledger / Trezor)" section covering connect/hw-status/hw-address/import --hardware, plus security notes (every signing op needs on-device approval, no headless signing path) and the current Trezor-signing limitation.
  • BUILD_TROUBLESHOOTING.md: per-OS system dependencies for the feature, and added the feature-enabled build/test commands to the "simulates CI" checklist.
  • CONTRIBUTING.md: new "Run Optional-Feature Tests" section pointing contributors at the feature before they touch this file.

Out of scope (flagged, not fixed here)

  • Trezor transaction signing itself remains unimplemented. It never worked (the feature never compiled), so this isn't a regression — but implementing it properly means decomposing a Stellar transaction into Trezor's structured per-operation protobuf messages against stellar-xdr, which is a substantial, security-sensitive feature on its own. wallet sign --hardware trezor / tx send --hardware trezor now fail with a clear "not supported" error instead of a compile error or a misleading device error.
  • Unrelated pre-existing breakage on master. While validating this change I found cargo test --locked currently fails to compile on master for reasons that have nothing to do with hardware wallets — at the time I branched, 34 errors across templates.rs, database.rs, ai.rs, compliance.rs, template_analytics.rs, and template_recommender.rs (missing struct fields, Option<Vec<_>> vs Vec<_> mismatches, immutable-borrow errors, etc.). I did not touch any of those files — that's a separate, pre-existing issue and well outside this PR's scope. Practically, this means build-and-test/clippy/smoke may show red on this PR through no fault of this change; I validated hardware_wallet.rs in isolation (a standalone scratch crate with the same pinned hidapi/trezor-client/clap/stellar-strkey versions) to confirm this PR's own code is correct independent of that.

Testing performed

  • cargo fmt --check on every file touched by this PR (clean).
  • Isolated scratch-crate build of hardware_wallet.rs against the exact pinned dependency versions (clap = 4.4.18, stellar-strkey = 0.0.9, hidapi = 2.6.5, trezor-client = 0.1.5): 22 tests pass with default features, 28 pass / 1 ignored with --features hardware-wallet (the ignored one requires a physical Ledger, as before).
  • Manually reproduced and diagnosed the parallel-execution crash mentioned above, confirmed --test-threads=1 resolves it.

…/rejection/disconnect/unsupported envelopes

The hardware-wallet Cargo feature (hidapi + trezor-client) was never
built or tested in CI, so its code paths silently bit-rotted. Add a
dedicated CI job that builds and tests it, extract and unit-test the
Ledger APDU status-word classification (approval / rejection /
unsupported envelope), reorder Trezor sign_transaction to validate its
input and report "not supported" before touching the device (so that
path is deterministically testable without hardware), and add
feature-gated tests exercising the real hidapi/trezor-client backends'
disconnect (no-device) behavior. Document compatibility, security, and
system-dependency notes for the feature.
Building with --features hardware-wallet surfaced pre-existing,
unrelated breakage on master that also blocks the default build:

- database.rs used thiserror::Error/#[error(...)] without thiserror
  being a declared dependency, and passed &mut Transaction where the
  Migration trait expects &mut Connection (Transaction has no
  DerefMut). Added the dependency and switched the trait/impls to
  &Connection, which is all any Migration actually needs.
- commands/mod.rs and utils/mod.rs were missing `pub mod ai_doc_qa;`
  from the ai-documentation-Q&A merge, so main.rs referenced modules
  that didn't exist.

None of this is hardware-wallet specific; it was simply never caught
because nothing built the crate with every feature/target combination
CI now exercises.
@drips-wave

drips-wave Bot commented Aug 25, 2026

Copy link
Copy Markdown

@TheWeirdDee Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@TheWeirdDee

Copy link
Copy Markdown
Contributor Author

Pushed a follow-up commit that fixes the build-breaking bugs this PR's CI job surfaced (unrelated to hardware wallets, but they block the whole crate from compiling):

  • database.rs used thiserror::Error/#[error(...)] without thiserror being a declared dependency, and passed &mut Transaction where the Migration trait expects &mut Connection (Transaction has no DerefMut). Added the dependency and switched the trait to &Connection, which is all it actually needs.
  • commands/mod.rs and utils/mod.rs were both missing pub mod ai_doc_qa; from the recent AI-documentation-Q&A merge (feat(ai): implement AI Documentation Q&A (#512) #718), so main.rs referenced modules that didn't exist.

Confirmed locally: cargo build --features hardware-wallet now completes cleanly. Re-running CI to see how far it gets now — there may still be other unrelated pre-existing issues elsewhere in the tree (I'd already flagged ~34 test-compile errors across templates.rs/ai.rs/compliance.rs/etc. in the PR description), but this at least gets the crate itself compiling.

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.

[2026 Wallet] Exercise hardware-wallet builds and mocks in CI

1 participant