1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-02 20:08:31 +00:00

feat(core): join active workflows before restart

It should support asynchronous "success" confirmation layouts
for address, public key and signature confirmation flows.

[no changelog]
This commit is contained in:
Roman Zeyde 2025-05-28 13:17:13 +03:00
parent 23cde4ed72
commit f331b133e3
27 changed files with 108 additions and 56 deletions

View File

@ -0,0 +1 @@
[T3T1] Show confirmation layout after sending address, public key or signature to host.

View File

@ -757,7 +757,6 @@ static void _librust_qstrs(void) {
MP_QSTR_time_ms; MP_QSTR_time_ms;
MP_QSTR_timer; MP_QSTR_timer;
MP_QSTR_title; MP_QSTR_title;
MP_QSTR_title_success;
MP_QSTR_total_fee_new; MP_QSTR_total_fee_new;
MP_QSTR_total_len; MP_QSTR_total_len;
MP_QSTR_touch_event; MP_QSTR_touch_event;

View File

@ -553,7 +553,6 @@ extern "C" fn new_flow_get_address(n_args: usize, args: *const Obj, kwargs: *mut
let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; let account: Option<TString> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?; let path: Option<TString> = kwargs.get(Qstr::MP_QSTR_path)?.try_into_option()?;
let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?; let xpubs: Obj = kwargs.get(Qstr::MP_QSTR_xpubs)?;
let title_success: TString = kwargs.get(Qstr::MP_QSTR_title_success)?.try_into()?;
let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?; let br_code: u16 = kwargs.get(Qstr::MP_QSTR_br_code)?.try_into()?;
let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?; let br_name: TString = kwargs.get(Qstr::MP_QSTR_br_name)?.try_into()?;
@ -568,7 +567,6 @@ extern "C" fn new_flow_get_address(n_args: usize, args: *const Obj, kwargs: *mut
account, account,
path, path,
xpubs, xpubs,
title_success,
br_code, br_code,
br_name, br_name,
)?; )?;
@ -1507,7 +1505,6 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// account: str | None, /// account: str | None,
/// path: str | None, /// path: str | None,
/// xpubs: list[tuple[str, str]], /// xpubs: list[tuple[str, str]],
/// title_success: str,
/// br_code: ButtonRequestType, /// br_code: ButtonRequestType,
/// br_name: str, /// br_name: str,
/// ) -> LayoutObj[UiResult]: /// ) -> LayoutObj[UiResult]:

View File

@ -599,7 +599,6 @@ impl FirmwareUI for UIBolt {
_account: Option<TString<'static>>, _account: Option<TString<'static>>,
_path: Option<TString<'static>>, _path: Option<TString<'static>>,
_xpubs: Obj, _xpubs: Obj,
_title_success: TString<'static>,
_br_code: u16, _br_code: u16,
_br_name: TString<'static>, _br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {

View File

@ -735,7 +735,6 @@ impl FirmwareUI for UICaesar {
_account: Option<TString<'static>>, _account: Option<TString<'static>>,
_path: Option<TString<'static>>, _path: Option<TString<'static>>,
_xpubs: Obj, _xpubs: Obj,
_title_success: TString<'static>,
_br_code: u16, _br_code: u16,
_br_name: TString<'static>, _br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {

View File

@ -172,12 +172,12 @@ impl StatusScreen {
) )
} }
pub fn new_success_timeout(msg: TString<'static>) -> Self { pub fn new_success_timeout(msg: TString<'static>, time_ms: u32) -> Self {
Self::new( Self::new(
theme::ICON_SIMPLE_CHECKMARK30, theme::ICON_SIMPLE_CHECKMARK30,
theme::GREEN_LIME, theme::GREEN_LIME,
theme::GREEN_LIGHT, theme::GREEN_LIGHT,
DismissType::Timeout(Timeout::new(TIMEOUT_MS)), DismissType::Timeout(Timeout::new(time_ms)),
msg, msg,
) )
} }

View File

@ -20,7 +20,7 @@ use crate::{
}; };
use super::super::{ use super::super::{
component::{AddressDetails, Frame, PromptScreen, StatusScreen, SwipeContent, VerticalMenu}, component::{AddressDetails, Frame, PromptScreen, SwipeContent, VerticalMenu},
theme, theme,
}; };
@ -30,7 +30,6 @@ const QR_BORDER: i16 = 4;
pub enum GetAddress { pub enum GetAddress {
Address, Address,
Tap, Tap,
Confirmed,
Menu, Menu,
QrCode, QrCode,
AccountInfo, AccountInfo,
@ -57,9 +56,8 @@ impl FlowController for GetAddress {
fn handle_event(&'static self, msg: FlowMsg) -> Decision { fn handle_event(&'static self, msg: FlowMsg) -> Decision {
match (self, msg) { match (self, msg) {
(Self::Address, FlowMsg::Info) => Self::Menu.goto(), (Self::Address, FlowMsg::Info) => Self::Menu.goto(),
(Self::Tap, FlowMsg::Confirmed) => Self::Confirmed.swipe_up(), (Self::Tap, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
(Self::Tap, FlowMsg::Info) => Self::Menu.swipe_left(), (Self::Tap, FlowMsg::Info) => Self::Menu.swipe_left(),
(Self::Confirmed, _) => self.return_msg(FlowMsg::Confirmed),
(Self::Menu, FlowMsg::Choice(0)) => Self::QrCode.swipe_left(), (Self::Menu, FlowMsg::Choice(0)) => Self::QrCode.swipe_left(),
(Self::Menu, FlowMsg::Choice(1)) => Self::AccountInfo.swipe_left(), (Self::Menu, FlowMsg::Choice(1)) => Self::AccountInfo.swipe_left(),
(Self::Menu, FlowMsg::Choice(2)) => Self::Cancel.swipe_left(), (Self::Menu, FlowMsg::Choice(2)) => Self::Cancel.swipe_left(),
@ -86,7 +84,6 @@ pub fn new_get_address(
account: Option<TString<'static>>, account: Option<TString<'static>>,
path: Option<TString<'static>>, path: Option<TString<'static>>,
xpubs: Obj, // TODO: get rid of Obj xpubs: Obj, // TODO: get rid of Obj
title_success: TString<'static>,
br_code: u16, br_code: u16,
br_name: TString<'static>, br_name: TString<'static>,
) -> Result<SwipeFlow, error::Error> { ) -> Result<SwipeFlow, error::Error> {
@ -122,14 +119,6 @@ pub fn new_get_address(
.with_swipe(Direction::Down, SwipeSettings::default()) .with_swipe(Direction::Down, SwipeSettings::default())
.map(super::util::map_to_confirm); .map(super::util::map_to_confirm);
let content_confirmed = Frame::left_aligned(
TR::words__title_success.into(),
StatusScreen::new_success_timeout(title_success),
)
.with_footer(TR::instructions__continue_in_app.into(), None)
.with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT)
.map(|_| Some(FlowMsg::Confirmed));
// Menu // Menu
let content_menu = Frame::left_aligned( let content_menu = Frame::left_aligned(
"".into(), "".into(),
@ -187,7 +176,6 @@ pub fn new_get_address(
let mut res = SwipeFlow::new(&GetAddress::Address)?; let mut res = SwipeFlow::new(&GetAddress::Address)?;
res.add_page(&GetAddress::Address, content_address)? res.add_page(&GetAddress::Address, content_address)?
.add_page(&GetAddress::Tap, content_tap)? .add_page(&GetAddress::Tap, content_tap)?
.add_page(&GetAddress::Confirmed, content_confirmed)?
.add_page(&GetAddress::Menu, content_menu)? .add_page(&GetAddress::Menu, content_menu)?
.add_page(&GetAddress::QrCode, content_qr)? .add_page(&GetAddress::QrCode, content_qr)?
.add_page(&GetAddress::AccountInfo, content_account)? .add_page(&GetAddress::AccountInfo, content_account)?

View File

@ -9,6 +9,7 @@ use crate::{
ui::{ ui::{
component::{ component::{
connect::Connect, connect::Connect,
swipe_detect::SwipeSettings,
text::{ text::{
op::OpTextLayout, op::OpTextLayout,
paragraphs::{ paragraphs::{
@ -19,7 +20,7 @@ use crate::{
}, },
Border, CachedJpeg, ComponentExt, Empty, FormattedText, Never, Timeout, Border, CachedJpeg, ComponentExt, Empty, FormattedText, Never, Timeout,
}, },
geometry::{self, Offset}, geometry::{self, Direction, Offset},
layout::{ layout::{
obj::{LayoutMaybeTrace, LayoutObj, RootComponent}, obj::{LayoutMaybeTrace, LayoutObj, RootComponent},
util::{PropsList, RecoveryType}, util::{PropsList, RecoveryType},
@ -628,7 +629,6 @@ impl FirmwareUI for UIDelizia {
account: Option<TString<'static>>, account: Option<TString<'static>>,
path: Option<TString<'static>>, path: Option<TString<'static>>,
xpubs: Obj, xpubs: Obj,
title_success: TString<'static>,
br_code: u16, br_code: u16,
br_name: TString<'static>, br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error> { ) -> Result<impl LayoutMaybeTrace, Error> {
@ -643,7 +643,6 @@ impl FirmwareUI for UIDelizia {
account, account,
path, path,
xpubs, xpubs,
title_success,
br_code, br_code,
br_name, br_name,
)?; )?;
@ -1088,24 +1087,34 @@ impl FirmwareUI for UIDelizia {
fn show_success( fn show_success(
title: TString<'static>, title: TString<'static>,
_button: TString<'static>, button: TString<'static>,
description: TString<'static>, description: TString<'static>,
_allow_cancel: bool, _allow_cancel: bool,
_time_ms: u32, time_ms: u32,
) -> Result<Gc<LayoutObj>, Error> { ) -> Result<Gc<LayoutObj>, Error> {
let instruction = if button.is_empty() {
TR::instructions__tap_to_continue.into()
} else {
button
};
// description used in the Footer // description used in the Footer
let description = if description.is_empty() { let description = if description.is_empty() {
None None
} else { } else {
Some(description) Some(description)
}; };
let content = StatusScreen::new_success(title); let content = if time_ms > 0 {
StatusScreen::new_success_timeout(title, time_ms)
} else {
StatusScreen::new_success(title)
};
let layout = LayoutObj::new(SwipeUpScreen::new( let layout = LayoutObj::new(SwipeUpScreen::new(
Frame::left_aligned( Frame::left_aligned(
TR::words__title_success.into(), TR::words__title_success.into(),
SwipeContent::new(content).with_no_attach_anim(), SwipeContent::new(content).with_no_attach_anim(),
) )
.with_swipeup_footer(description) .with_footer(instruction, description)
.with_swipe(Direction::Up, SwipeSettings::default())
.with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT), .with_result_icon(theme::ICON_BULLET_CHECKMARK, theme::GREEN_LIGHT),
))?; ))?;
Ok(layout) Ok(layout)

View File

@ -205,7 +205,6 @@ pub trait FirmwareUI {
account: Option<TString<'static>>, account: Option<TString<'static>>,
path: Option<TString<'static>>, path: Option<TString<'static>>,
xpubs: Obj, // TODO: replace Obj xpubs: Obj, // TODO: replace Obj
title_success: TString<'static>,
br_code: u16, br_code: u16,
br_name: TString<'static>, br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error>; ) -> Result<impl LayoutMaybeTrace, Error>;

View File

@ -367,7 +367,6 @@ def flow_get_address(
account: str | None, account: str | None,
path: str | None, path: str | None,
xpubs: list[tuple[str, str]], xpubs: list[tuple[str, str]],
title_success: str,
br_code: ButtonRequestType, br_code: ButtonRequestType,
br_name: str, br_name: str,
) -> LayoutObj[UiResult]: ) -> LayoutObj[UiResult]:

View File

@ -52,8 +52,10 @@ async def sign_tx(
coin: CoinInfo, coin: CoinInfo,
authorization: CoinJoinAuthorization | None = None, authorization: CoinJoinAuthorization | None = None,
) -> TxRequest: ) -> TxRequest:
from trezor import TR
from trezor.enums import RequestType from trezor.enums import RequestType
from trezor.messages import TxRequest from trezor.messages import TxRequest
from trezor.ui.layouts import show_continue_in_app
from trezor.wire.context import call from trezor.wire.context import call
from ..common import BITCOIN_NAMES from ..common import BITCOIN_NAMES
@ -93,6 +95,7 @@ async def sign_tx(
request_class, req = req request_class, req = req
assert TxRequest.is_type_of(req) assert TxRequest.is_type_of(req)
if req.request_type == RequestType.TXFINISHED: if req.request_type == RequestType.TXFINISHED:
show_continue_in_app(TR.send__transaction_signed)
return req return req
res = await call(req, request_class) res = await call(req, request_class)
elif isinstance(req, helpers.UiConfirm): elif isinstance(req, helpers.UiConfirm):

View File

@ -12,9 +12,10 @@ if TYPE_CHECKING:
async def sign_tx( async def sign_tx(
msg: CardanoSignTxInit, keychain: seed.Keychain msg: CardanoSignTxInit, keychain: seed.Keychain
) -> CardanoSignTxFinished: ) -> CardanoSignTxFinished:
from trezor import log, wire from trezor import TR, log, wire
from trezor.enums import CardanoTxSigningMode from trezor.enums import CardanoTxSigningMode
from trezor.messages import CardanoSignTxFinished from trezor.messages import CardanoSignTxFinished
from trezor.ui.layouts import show_continue_in_app
from .signer import Signer from .signer import Signer
@ -49,4 +50,5 @@ async def sign_tx(
log.exception(__name__, e) log.exception(__name__, e)
raise wire.ProcessError("Signing failed") raise wire.ProcessError("Signing failed")
show_continue_in_app(TR.send__transaction_signed)
return CardanoSignTxFinished() return CardanoSignTxFinished()

View File

@ -10,9 +10,11 @@ if TYPE_CHECKING:
@auto_keychain(__name__) @auto_keychain(__name__)
async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx: async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx:
from trezor import TR
from trezor.crypto.curve import secp256k1 from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha256 from trezor.crypto.hashlib import sha256
from trezor.messages import EosSignedTx, EosTxActionAck, EosTxActionRequest from trezor.messages import EosSignedTx, EosTxActionAck, EosTxActionRequest
from trezor.ui.layouts import show_continue_in_app
from trezor.utils import HashWriter from trezor.utils import HashWriter
from trezor.wire import DataError from trezor.wire import DataError
from trezor.wire.context import call from trezor.wire.context import call
@ -55,4 +57,5 @@ async def sign_tx(msg: EosSignTx, keychain: Keychain) -> EosSignedTx:
node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS node.private_key(), digest, True, secp256k1.CANONICAL_SIG_EOS
) )
show_continue_in_app(TR.send__transaction_signed)
return EosSignedTx(signature=base58_encode("SIG_", "K1", signature)) return EosSignedTx(signature=base58_encode("SIG_", "K1", signature))

View File

@ -40,6 +40,7 @@ async def sign_tx(
) -> EthereumTxRequest: ) -> EthereumTxRequest:
from trezor import TR from trezor import TR
from trezor.crypto.hashlib import sha3_256 from trezor.crypto.hashlib import sha3_256
from trezor.ui.layouts import show_continue_in_app
from trezor.ui.layouts.progress import progress from trezor.ui.layouts.progress import progress
from trezor.utils import HashWriter from trezor.utils import HashWriter
@ -111,6 +112,7 @@ async def sign_tx(
progress_obj.stop() progress_obj.stop()
show_continue_in_app(TR.send__transaction_signed)
return result return result

View File

@ -35,9 +35,10 @@ async def sign_tx_eip1559(
keychain: Keychain, keychain: Keychain,
defs: Definitions, defs: Definitions,
) -> EthereumTxRequest: ) -> EthereumTxRequest:
from trezor import wire from trezor import TR, wire
from trezor.crypto import rlp # local_cache_global from trezor.crypto import rlp # local_cache_global
from trezor.crypto.hashlib import sha3_256 from trezor.crypto.hashlib import sha3_256
from trezor.ui.layouts import show_continue_in_app
from trezor.utils import HashWriter from trezor.utils import HashWriter
from apps.common import paths from apps.common import paths
@ -121,6 +122,7 @@ async def sign_tx_eip1559(
digest = sha.get_digest() digest = sha.get_digest()
result = _sign_digest(msg, keychain, digest) result = _sign_digest(msg, keychain, digest)
show_continue_in_app(TR.send__transaction_signed)
return result return result

View File

@ -14,7 +14,8 @@ if TYPE_CHECKING:
async def sign_tx(received_msg, keychain: Keychain) -> MoneroTransactionFinalAck: async def sign_tx(received_msg, keychain: Keychain) -> MoneroTransactionFinalAck:
import gc import gc
from trezor import log, utils from trezor import TR, log, utils
from trezor.ui.layouts import show_continue_in_app
from trezor.wire.context import get_context from trezor.wire.context import get_context
from apps.monero.signing.state import State from apps.monero.signing.state import State
@ -45,6 +46,7 @@ async def sign_tx(received_msg, keychain: Keychain) -> MoneroTransactionFinalAck
received_msg = await ctx.read(accept_msgs) received_msg = await ctx.read(accept_msgs)
utils.unimport_end(mods) utils.unimport_end(mods)
show_continue_in_app(TR.send__transaction_signed)
return result_msg return result_msg

View File

@ -11,10 +11,12 @@ if TYPE_CHECKING:
# NOTE: it is one big function because that way it is the most flash-space-efficient # NOTE: it is one big function because that way it is the most flash-space-efficient
@auto_keychain(__name__) @auto_keychain(__name__)
async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx: async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
from trezor import TR
from trezor.crypto import der from trezor.crypto import der
from trezor.crypto.curve import secp256k1 from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha512 from trezor.crypto.hashlib import sha512
from trezor.messages import RippleSignedTx from trezor.messages import RippleSignedTx
from trezor.ui.layouts import show_continue_in_app
from trezor.wire import ProcessError from trezor.wire import ProcessError
from apps.common import paths from apps.common import paths
@ -58,4 +60,5 @@ async def sign_tx(msg: RippleSignTx, keychain: Keychain) -> RippleSignedTx:
sig_encoded = der.encode_seq((sig[1:33], sig[33:65])) sig_encoded = der.encode_seq((sig[1:33], sig[33:65]))
tx = serialize(msg, source_address, node.public_key(), sig_encoded) tx = serialize(msg, source_address, node.public_key(), sig_encoded)
show_continue_in_app(TR.send__transaction_signed)
return RippleSignedTx(signature=sig_encoded, serialized_tx=tx) return RippleSignedTx(signature=sig_encoded, serialized_tx=tx)

View File

@ -23,7 +23,7 @@ async def sign_tx(
from trezor.crypto.curve import ed25519 from trezor.crypto.curve import ed25519
from trezor.enums import ButtonRequestType from trezor.enums import ButtonRequestType
from trezor.messages import SolanaTxSignature from trezor.messages import SolanaTxSignature
from trezor.ui.layouts import confirm_metadata, show_warning from trezor.ui.layouts import confirm_metadata, show_continue_in_app, show_warning
from apps.common import seed from apps.common import seed
@ -79,7 +79,7 @@ async def sign_tx(
) )
signature = ed25519.sign(node.private_key(), serialized_tx) signature = ed25519.sign(node.private_key(), serialized_tx)
show_continue_in_app(TR.send__transaction_signed)
return SolanaTxSignature(signature=signature) return SolanaTxSignature(signature=signature)

View File

@ -12,10 +12,12 @@ if TYPE_CHECKING:
async def sign_tx(msg: StellarSignTx, keychain: Keychain) -> StellarSignedTx: async def sign_tx(msg: StellarSignTx, keychain: Keychain) -> StellarSignedTx:
from ubinascii import hexlify from ubinascii import hexlify
from trezor import TR
from trezor.crypto.curve import ed25519 from trezor.crypto.curve import ed25519
from trezor.crypto.hashlib import sha256 from trezor.crypto.hashlib import sha256
from trezor.enums import StellarMemoType from trezor.enums import StellarMemoType
from trezor.messages import StellarSignedTx, StellarTxOpRequest from trezor.messages import StellarSignedTx, StellarTxOpRequest
from trezor.ui.layouts import show_continue_in_app
from trezor.wire import DataError, ProcessError from trezor.wire import DataError, ProcessError
from trezor.wire.context import call_any from trezor.wire.context import call_any
@ -114,6 +116,7 @@ async def sign_tx(msg: StellarSignTx, keychain: Keychain) -> StellarSignedTx:
# sign # sign
digest = sha256(w).digest() digest = sha256(w).digest()
signature = ed25519.sign(node.private_key(), digest) signature = ed25519.sign(node.private_key(), digest)
show_continue_in_app(TR.send__transaction_signed)
# Add the public key for verification that the right account was used for signing # Add the public key for verification that the right account was used for signing
return StellarSignedTx(public_key=pubkey, signature=signature) return StellarSignedTx(public_key=pubkey, signature=signature)

View File

@ -32,10 +32,12 @@ if TYPE_CHECKING:
@with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE) @with_slip44_keychain(*PATTERNS, slip44_id=SLIP44_ID, curve=CURVE)
async def sign_tx(msg: TezosSignTx, keychain: Keychain) -> TezosSignedTx: async def sign_tx(msg: TezosSignTx, keychain: Keychain) -> TezosSignedTx:
from trezor import TR
from trezor.crypto import hashlib from trezor.crypto import hashlib
from trezor.crypto.curve import ed25519 from trezor.crypto.curve import ed25519
from trezor.enums import TezosBallotType from trezor.enums import TezosBallotType
from trezor.messages import TezosSignedTx from trezor.messages import TezosSignedTx
from trezor.ui.layouts import show_continue_in_app
from apps.common.paths import validate_path from apps.common.paths import validate_path
@ -155,6 +157,7 @@ async def sign_tx(msg: TezosSignTx, keychain: Keychain) -> TezosSignedTx:
sig_prefixed = base58_encode_check(signature, helpers.TEZOS_SIGNATURE_PREFIX) sig_prefixed = base58_encode_check(signature, helpers.TEZOS_SIGNATURE_PREFIX)
show_continue_in_app(TR.send__transaction_signed)
return TezosSignedTx( return TezosSignedTx(
signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash
) )

View File

@ -443,6 +443,10 @@ def show_success(
) )
def show_continue_in_app(content: str) -> None:
return
async def confirm_output( async def confirm_output(
address: str, address: str,
amount: str, amount: str,

View File

@ -507,6 +507,10 @@ def show_success(
) )
def show_continue_in_app(content: str) -> None:
return
async def confirm_output( async def confirm_output(
address: str, address: str,
amount: str, amount: str,

View File

@ -10,7 +10,7 @@ if __debug__:
from trezor import log from trezor import log
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any, Awaitable, Callable, TypeVar from typing import Any, Awaitable, Callable, Coroutine, TypeVar
PropertyType = tuple[str | None, str | bytes | None] PropertyType = tuple[str | None, str | bytes | None]
ExceptionType = BaseException | type[BaseException] ExceptionType = BaseException | type[BaseException]
@ -62,7 +62,7 @@ def raise_if_not_confirmed(
br_name: str | None, br_name: str | None,
br_code: ButtonRequestType = ButtonRequestType.Other, br_code: ButtonRequestType = ButtonRequestType.Other,
exc: ExceptionType = ActionCancelled, exc: ExceptionType = ActionCancelled,
) -> Awaitable[None]: ) -> Coroutine[Any, Any, None]:
action = interact(layout_obj, br_name, br_code, exc) action = interact(layout_obj, br_name, br_code, exc)
return action # type: ignore ["UiResult" is incompatible with "None"] return action # type: ignore ["UiResult" is incompatible with "None"]

View File

@ -1,14 +1,14 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import trezorui_api import trezorui_api
from trezor import TR, ui, utils from trezor import TR, ui, utils, workflow
from trezor.enums import ButtonRequestType, RecoveryType from trezor.enums import ButtonRequestType, RecoveryType
from trezor.wire import ActionCancelled from trezor.wire import ActionCancelled
from ..common import draw_simple, interact, raise_if_not_confirmed, with_info from ..common import draw_simple, interact, raise_if_not_confirmed, with_info
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Awaitable, Iterable, NoReturn, Sequence, TypeVar from typing import Any, Awaitable, Coroutine, Iterable, NoReturn, Sequence, TypeVar
from ..common import ExceptionType, PropertyType from ..common import ExceptionType, PropertyType
@ -279,12 +279,6 @@ async def show_address(
) )
return result return result
title_success = (
TR.address__public_key_confirmed
if title in ("XPUB", TR.address__public_key)
else TR.address__confirmed
)
await raise_if_not_confirmed( await raise_if_not_confirmed(
trezorui_api.flow_get_address( trezorui_api.flow_get_address(
address=address, address=address,
@ -297,13 +291,18 @@ async def show_address(
account=account, account=account,
path=path, path=path,
xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)], xpubs=[(xpub_title(i), xpub) for i, xpub in enumerate(xpubs)],
title_success=title_success,
br_name=br_name, br_name=br_name,
br_code=br_code, br_code=br_code,
), ),
None, None,
) )
show_continue_in_app(
TR.address__public_key_confirmed
if title in ("XPUB", TR.address__public_key)
else TR.address__confirmed
)
def show_pubkey( def show_pubkey(
pubkey: str, pubkey: str,
@ -393,22 +392,34 @@ def show_danger(
def show_success( def show_success(
br_name: str, br_name: str | None,
content: str, content: str,
subheader: str | None = None, subheader: str | None = None,
button: str | None = None, button: str | None = None,
) -> Awaitable[None]: time_ms: int = 0,
) -> Coroutine[Any, Any, None]:
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui_api.show_success( trezorui_api.show_success(
title=content, title=content,
button="", button=button or "",
description=subheader if subheader else "", description=subheader or "",
time_ms=time_ms,
), ),
br_name, br_name,
ButtonRequestType.Success, ButtonRequestType.Success,
) )
def show_continue_in_app(content: str) -> None:
task = show_success(
content=content,
button=TR.instructions__continue_in_app,
time_ms=3200,
br_name=None,
)
workflow.spawn(task)
async def confirm_output( async def confirm_output(
address: str, address: str,
amount: str | None = None, amount: str | None = None,

View File

@ -1,14 +1,14 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import trezorui_api import trezorui_api
from trezor import TR, ui, utils from trezor import TR, ui, utils, workflow
from trezor.enums import ButtonRequestType, RecoveryType from trezor.enums import ButtonRequestType, RecoveryType
from trezor.wire import ActionCancelled from trezor.wire import ActionCancelled
from ..common import draw_simple, interact, raise_if_not_confirmed, with_info from ..common import draw_simple, interact, raise_if_not_confirmed, with_info
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Awaitable, Iterable, NoReturn, Sequence, TypeVar from typing import Any, Awaitable, Coroutine, Iterable, NoReturn, Sequence, TypeVar
from ..common import ExceptionType, PropertyType from ..common import ExceptionType, PropertyType
@ -297,6 +297,12 @@ async def show_address(
None, None,
) )
show_continue_in_app(
TR.address__public_key_confirmed
if title in ("XPUB", TR.address__public_key)
else TR.address__confirmed
)
def show_pubkey( def show_pubkey(
pubkey: str, pubkey: str,
@ -393,7 +399,7 @@ def show_success(
subheader: str | None = None, subheader: str | None = None,
button: str | None = None, button: str | None = None,
time_ms: int = 0, time_ms: int = 0,
) -> Awaitable[None]: ) -> Coroutine[Any, Any, None]:
button = button or TR.buttons__continue # def_arg button = button or TR.buttons__continue # def_arg
return raise_if_not_confirmed( return raise_if_not_confirmed(
trezorui_api.show_success( trezorui_api.show_success(
@ -408,13 +414,14 @@ def show_success(
) )
def show_continue_in_app(content: str) -> Awaitable[None]: def show_continue_in_app(content: str) -> None:
return show_success( task = show_success(
content=content, content=content,
button=TR.instructions__continue_in_app, button=TR.instructions__continue_in_app,
time_ms=3200, time_ms=3200,
br_name=None, br_name=None,
) )
workflow.spawn(task)
async def confirm_output( async def confirm_output(

View File

@ -27,6 +27,7 @@ from typing import TYPE_CHECKING
from trezor import loop, protobuf, utils from trezor import loop, protobuf, utils
from .. import workflow
from . import message_handler, protocol_common from . import message_handler, protocol_common
from .codec.codec_context import CodecContext from .codec.codec_context import CodecContext
from .context import UnexpectedMessageException from .context import UnexpectedMessageException
@ -118,6 +119,8 @@ async def handle_session(iface: WireInterface) -> None:
utils.unimport_end(modules) utils.unimport_end(modules)
if not do_not_restart: if not do_not_restart:
# Wait for all active workflows to finish.
await workflow.join_all()
# Let the session be restarted from `main`. # Let the session be restarted from `main`.
loop.clear() loop.clear()
return # pylint: disable=lost-exception return # pylint: disable=lost-exception

View File

@ -84,6 +84,16 @@ def spawn(workflow: loop.Task) -> loop.spawn:
return task return task
async def join_all() -> None:
"""Block until all workflows are over."""
if __debug__:
log.debug(__name__, "joining %d workflows", len(tasks))
# Don't iterate over `tasks` since it will be modified by its finalizers
while tasks:
await next(iter(tasks))
def start_default() -> None: def start_default() -> None:
"""Start a default workflow. """Start a default workflow.