Skip to content

Commit ca29594

Browse files
committed
ui: use a trait for workflows
This allows us to plug in a local trait impl for unit tests, allowin us to remove the ugly unsafe static muts, RefCells and UI_COUNTERS in the testing code and makes UI unit tests cleaner. Another benefit of this is that the API code would not hardcode the UI workflows, so future BitBox products with different UI can use the same API code but plug its own UI workflows. We do this by using a `Workflows` trait param in all functions that need workflows, and use an implementation suitable for unit tests. One instance of `RealWorkflows` is created that can be added as an arg in any API function. This commit is not a complete migration, we can migrate file by file or function by function. The end goal is to remove all direct calls to `workflow::*`.
1 parent 2e9f5e0 commit ca29594

File tree

17 files changed

+1332
-1106
lines changed

17 files changed

+1332
-1106
lines changed

src/rust/bitbox02-rust/src/hww/api.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ use pb::request::Request;
4646
use pb::response::Response;
4747
use prost::Message;
4848

49+
use crate::workflow::{RealWorkflows, Workflows};
50+
4951
/// Encodes a protobuf Response message.
5052
pub fn encode(response: Response) -> Vec<u8> {
5153
let response = pb::Response {
@@ -65,21 +67,27 @@ pub fn decode(input: &[u8]) -> Result<Request, Error> {
6567
}
6668

6769
#[cfg(any(feature = "app-bitcoin", feature = "app-litecoin"))]
68-
async fn process_api_btc(request: &Request) -> Result<Response, Error> {
70+
async fn process_api_btc<W: Workflows>(
71+
workflows: &mut W,
72+
request: &Request,
73+
) -> Result<Response, Error> {
6974
match request {
70-
Request::BtcPub(ref request) => bitcoin::process_pub(request).await,
71-
Request::BtcSignInit(ref request) => bitcoin::signtx::process(request).await,
75+
Request::BtcPub(ref request) => bitcoin::process_pub(workflows, request).await,
76+
Request::BtcSignInit(ref request) => bitcoin::signtx::process(workflows, request).await,
7277
Request::Btc(pb::BtcRequest {
7378
request: Some(request),
74-
}) => bitcoin::process_api(request)
79+
}) => bitcoin::process_api(workflows, request)
7580
.await
7681
.map(|r| Response::Btc(pb::BtcResponse { response: Some(r) })),
7782
_ => Err(Error::Generic),
7883
}
7984
}
8085

8186
#[cfg(not(any(feature = "app-bitcoin", feature = "app-litecoin")))]
82-
async fn process_api_btc(_request: &Request) -> Result<Response, Error> {
87+
async fn process_api_btc<W: Workflows>(
88+
_workflows: &mut W,
89+
_request: &Request,
90+
) -> Result<Response, Error> {
8391
Err(Error::Disabled)
8492
}
8593

@@ -152,6 +160,7 @@ fn can_call(request: &Request) -> bool {
152160

153161
/// Handle a protobuf api call.
154162
async fn process_api(request: &Request) -> Result<Response, Error> {
163+
let workflows = &mut RealWorkflows;
155164
match request {
156165
Request::Reboot(ref request) => system::reboot(request).await,
157166
Request::DeviceInfo(_) => device_info::process(),
@@ -176,7 +185,7 @@ async fn process_api(request: &Request) -> Result<Response, Error> {
176185
#[cfg(feature = "app-ethereum")]
177186
Request::Eth(pb::EthRequest {
178187
request: Some(ref request),
179-
}) => ethereum::process_api(request)
188+
}) => ethereum::process_api(workflows, request)
180189
.await
181190
.map(|r| Response::Eth(pb::EthResponse { response: Some(r) })),
182191
#[cfg(not(feature = "app-ethereum"))]
@@ -185,12 +194,12 @@ async fn process_api(request: &Request) -> Result<Response, Error> {
185194
Request::Fingerprint(pb::RootFingerprintRequest {}) => rootfingerprint::process(),
186195
request @ Request::BtcPub(_)
187196
| request @ Request::Btc(_)
188-
| request @ Request::BtcSignInit(_) => process_api_btc(request).await,
197+
| request @ Request::BtcSignInit(_) => process_api_btc(workflows, request).await,
189198

190199
#[cfg(feature = "app-cardano")]
191200
Request::Cardano(pb::CardanoRequest {
192201
request: Some(ref request),
193-
}) => cardano::process_api(request)
202+
}) => cardano::process_api(workflows, request)
194203
.await
195204
.map(|r| Response::Cardano(pb::CardanoResponse { response: Some(r) })),
196205
#[cfg(not(feature = "app-cardano"))]

src/rust/bitbox02-rust/src/hww/api/bitcoin.rs

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub mod signtx;
3232
use super::pb;
3333
use super::Error;
3434

35-
use crate::workflow::confirm;
35+
use crate::workflow::{confirm, Workflows};
3636

3737
use util::bip32::HARDENED;
3838

@@ -189,7 +189,8 @@ async fn address_simple(
189189
}
190190

191191
/// Processes a multisig address api call.
192-
pub async fn address_multisig(
192+
pub async fn address_multisig<W: Workflows>(
193+
workflows: &mut W,
193194
coin: BtcCoin,
194195
multisig: &Multisig,
195196
keypath: &[u32],
@@ -206,7 +207,7 @@ pub async fn address_multisig(
206207
};
207208
let title = "Receive to";
208209
if display {
209-
multisig::confirm(title, coin_params, &name, multisig).await?;
210+
multisig::confirm(workflows, title, coin_params, &name, multisig).await?;
210211
}
211212
let address = common::Payload::from_multisig(
212213
coin_params,
@@ -216,19 +217,21 @@ pub async fn address_multisig(
216217
)?
217218
.address(coin_params)?;
218219
if display {
219-
confirm::confirm(&confirm::Params {
220-
title,
221-
body: &address,
222-
scrollable: true,
223-
..Default::default()
224-
})
225-
.await?;
220+
workflows
221+
.confirm(&confirm::Params {
222+
title,
223+
body: &address,
224+
scrollable: true,
225+
..Default::default()
226+
})
227+
.await?;
226228
}
227229
Ok(Response::Pub(pb::PubResponse { r#pub: address }))
228230
}
229231

230232
/// Processes a policy address api call.
231-
async fn address_policy(
233+
async fn address_policy<W: Workflows>(
234+
workflows: &mut W,
232235
coin: BtcCoin,
233236
policy: &Policy,
234237
keypath: &[u32],
@@ -247,26 +250,30 @@ async fn address_policy(
247250

248251
if display {
249252
parsed
250-
.confirm(title, coin_params, &name, policies::Mode::Basic)
253+
.confirm(workflows, title, coin_params, &name, policies::Mode::Basic)
251254
.await?;
252255
}
253256

254257
let address =
255258
common::Payload::from_policy(coin_params, &parsed, keypath)?.address(coin_params)?;
256259
if display {
257-
confirm::confirm(&confirm::Params {
258-
title,
259-
body: &address,
260-
scrollable: true,
261-
..Default::default()
262-
})
263-
.await?;
260+
workflows
261+
.confirm(&confirm::Params {
262+
title,
263+
body: &address,
264+
scrollable: true,
265+
..Default::default()
266+
})
267+
.await?;
264268
}
265269
Ok(Response::Pub(pb::PubResponse { r#pub: address }))
266270
}
267271

268272
/// Handle a Bitcoin xpub/address protobuf api call.
269-
pub async fn process_pub(request: &pb::BtcPubRequest) -> Result<Response, Error> {
273+
pub async fn process_pub<W: Workflows>(
274+
workflows: &mut W,
275+
request: &pb::BtcPubRequest,
276+
) -> Result<Response, Error> {
270277
let coin = BtcCoin::try_from(request.coin)?;
271278
coin_enabled(coin)?;
272279
match request.output {
@@ -283,24 +290,27 @@ pub async fn process_pub(request: &pb::BtcPubRequest) -> Result<Response, Error>
283290
}
284291
Some(Output::ScriptConfig(BtcScriptConfig {
285292
config: Some(Config::Multisig(ref multisig)),
286-
})) => address_multisig(coin, multisig, &request.keypath, request.display).await,
293+
})) => address_multisig(workflows, coin, multisig, &request.keypath, request.display).await,
287294
Some(Output::ScriptConfig(BtcScriptConfig {
288295
config: Some(Config::Policy(ref policy)),
289-
})) => address_policy(coin, policy, &request.keypath, request.display).await,
296+
})) => address_policy(workflows, coin, policy, &request.keypath, request.display).await,
290297
_ => Err(Error::InvalidInput),
291298
}
292299
}
293300

294301
/// Handle a nexted Bitcoin protobuf api call.
295-
pub async fn process_api(request: &Request) -> Result<pb::btc_response::Response, Error> {
302+
pub async fn process_api<W: Workflows>(
303+
workflows: &mut W,
304+
request: &Request,
305+
) -> Result<pb::btc_response::Response, Error> {
296306
match request {
297307
Request::IsScriptConfigRegistered(ref request) => {
298308
registration::process_is_script_config_registered(request)
299309
}
300310
Request::RegisterScriptConfig(ref request) => {
301-
registration::process_register_script_config(request).await
311+
registration::process_register_script_config(workflows, request).await
302312
}
303-
Request::SignMessage(ref request) => signmsg::process(request).await,
313+
Request::SignMessage(ref request) => signmsg::process(workflows, request).await,
304314
// These are streamed asynchronously using the `next_request()` primitive in
305315
// bitcoin/signtx.rs and are not handled directly.
306316
Request::PrevtxInit(_)
@@ -317,6 +327,7 @@ mod tests {
317327

318328
use crate::bb02_async::block_on;
319329
use crate::bip32::parse_xpub;
330+
use crate::workflow::RealWorkflows; // instead of TestingWorkflows until the tests are migrated
320331
use alloc::boxed::Box;
321332
use alloc::vec::Vec;
322333
use bitbox02::testing::{
@@ -471,7 +482,7 @@ mod tests {
471482
};
472483

473484
assert_eq!(
474-
block_on(process_pub(&req)),
485+
block_on(process_pub(&mut RealWorkflows, &req)),
475486
Ok(Response::Pub(pb::PubResponse {
476487
r#pub: test.expected_xpub.into(),
477488
})),
@@ -492,7 +503,7 @@ mod tests {
492503
});
493504
mock_unlocked_using_mnemonic(test.mnemonic, "");
494505
assert_eq!(
495-
block_on(process_pub(&req)),
506+
block_on(process_pub(&mut RealWorkflows,&req)),
496507
Ok(Response::Pub(pb::PubResponse {
497508
r#pub: test.expected_xpub.into(),
498509
})),
@@ -513,7 +524,7 @@ mod tests {
513524
});
514525
mock_unlocked();
515526
assert_eq!(
516-
block_on(process_pub(&pb::BtcPubRequest {
527+
block_on(process_pub(&mut RealWorkflows, &pb::BtcPubRequest {
517528
coin: BtcCoin::Btc as _,
518529
keypath: [1 + HARDENED, 2 + HARDENED, 3 + HARDENED, 4].to_vec(),
519530
display: false,
@@ -553,7 +564,7 @@ mod tests {
553564
});
554565
mock_unlocked();
555566
assert_eq!(
556-
block_on(process_pub(&pb::BtcPubRequest {
567+
block_on(process_pub(&mut RealWorkflows, &pb::BtcPubRequest {
557568
coin: BtcCoin::Btc as _,
558569
keypath: [1 + HARDENED, 2 + HARDENED, 3 + HARDENED, 4].to_vec(),
559570
display: true,
@@ -575,11 +586,11 @@ mod tests {
575586
// -- Wrong coin: MIN-1
576587
let mut req_invalid = req.clone();
577588
req_invalid.coin = BtcCoin::Btc as i32 - 1;
578-
assert!(block_on(process_pub(&req_invalid)).is_err());
589+
assert!(block_on(process_pub(&mut RealWorkflows, &req_invalid)).is_err());
579590
// -- Wrong coin: MAX + 1
580591
let mut req_invalid = req.clone();
581592
req_invalid.coin = BtcCoin::Rbtc as i32 + 1;
582-
assert!(block_on(process_pub(&req_invalid)).is_err());
593+
assert!(block_on(process_pub(&mut RealWorkflows, &req_invalid)).is_err());
583594
}
584595

585596
#[test]
@@ -778,7 +789,7 @@ mod tests {
778789
// Without display.
779790
mock_unlocked_using_mnemonic(test.mnemonic, "");
780791
assert_eq!(
781-
block_on(process_pub(&req)),
792+
block_on(process_pub(&mut RealWorkflows, &req)),
782793
Ok(Response::Pub(pb::PubResponse {
783794
r#pub: test.expected_address.into(),
784795
})),
@@ -799,7 +810,7 @@ mod tests {
799810
});
800811
mock_unlocked_using_mnemonic(test.mnemonic, "");
801812
assert_eq!(
802-
block_on(process_pub(&req)),
813+
block_on(process_pub(&mut RealWorkflows, &req)),
803814
Ok(Response::Pub(pb::PubResponse {
804815
r#pub: test.expected_address.into()
805816
})),
@@ -817,28 +828,31 @@ mod tests {
817828
config: Some(Config::SimpleType(SimpleType::P2wpkhP2sh as _)),
818829
})),
819830
};
820-
assert!(block_on(process_pub(&req)).is_ok());
831+
assert!(block_on(process_pub(&mut RealWorkflows, &req)).is_ok());
821832
// -- Wrong coin: MIN-1
822833
let mut req_invalid = req.clone();
823834
req_invalid.coin = BtcCoin::Btc as i32 - 1;
824-
assert!(block_on(process_pub(&req_invalid)).is_err());
835+
assert!(block_on(process_pub(&mut RealWorkflows, &req_invalid)).is_err());
825836
// -- Wrong coin: MAX + 1
826837
let mut req_invalid = req.clone();
827838
req_invalid.coin = BtcCoin::Tltc as i32 + 1;
828-
assert!(block_on(process_pub(&req_invalid)).is_err());
839+
assert!(block_on(process_pub(&mut RealWorkflows, &req_invalid)).is_err());
829840
// -- Wrong keypath
830841
let mut req_invalid = req.clone();
831842
req_invalid.keypath = [49 + HARDENED, 0 + HARDENED, 1 + HARDENED, 1, 10000].to_vec();
832-
assert!(block_on(process_pub(&req_invalid)).is_err());
843+
assert!(block_on(process_pub(&mut RealWorkflows, &req_invalid)).is_err());
833844
// -- No taproot in Litecoin
834-
assert!(block_on(process_pub(&pb::BtcPubRequest {
835-
coin: BtcCoin::Ltc as _,
836-
keypath: [86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0].to_vec(),
837-
display: false,
838-
output: Some(Output::ScriptConfig(BtcScriptConfig {
839-
config: Some(Config::SimpleType(SimpleType::P2tr as _)),
840-
})),
841-
}))
845+
assert!(block_on(process_pub(
846+
&mut RealWorkflows,
847+
&pb::BtcPubRequest {
848+
coin: BtcCoin::Ltc as _,
849+
keypath: [86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0].to_vec(),
850+
display: false,
851+
output: Some(Output::ScriptConfig(BtcScriptConfig {
852+
config: Some(Config::SimpleType(SimpleType::P2tr as _)),
853+
})),
854+
}
855+
))
842856
.is_err());
843857
}
844858

@@ -1053,7 +1067,7 @@ mod tests {
10531067
})),
10541068
};
10551069
assert_eq!(
1056-
block_on(process_pub(&req)),
1070+
block_on(process_pub(&mut RealWorkflows, &req)),
10571071
Ok(Response::Pub(pb::PubResponse {
10581072
r#pub: test.expected_address.into(),
10591073
})),
@@ -1202,7 +1216,7 @@ mod tests {
12021216
})),
12031217
};
12041218
assert_eq!(
1205-
block_on(process_pub(&req)),
1219+
block_on(process_pub(&mut RealWorkflows, &req)),
12061220
Ok(Response::Pub(pb::PubResponse {
12071221
r#pub: test.expected_address.into(),
12081222
})),

0 commit comments

Comments
 (0)