From 83dde335ca5df3b482f072e7d1cb659a7afcad00 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 15 Aug 2023 21:27:05 -0500 Subject: [PATCH 1/5] Detect network mismatch for TESTNET/REGTEST w/MAINNET message --- src/seedsigner/views/seed_views.py | 28 ++++++++++++--------- tests/test_flows_seed.py | 39 +++++++++++++++++++----------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 6de06db82..2a7bb5b8e 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1991,19 +1991,23 @@ def __init__(self): if not addr_format["clean_match"]: raise Exception("Signing messages for custom derivation paths not supported") - if addr_format["network"] != SettingsConstants.MAINNET: - # We're in either Testnet or Regtest or...? - if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in [SettingsConstants.TESTNET, SettingsConstants.REGTEST]: - addr_format["network"] = self.settings.get_value(SettingsConstants.SETTING__NETWORK) - else: - from seedsigner.views.view import NetworkMismatchErrorView - self.set_redirect(Destination(NetworkMismatchErrorView, view_args=dict(text=f"Current network setting ({self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK)}) doesn't match {self.derivation_path}"))) + # addr_format["network"] can be MAINNET or [TESTNET, REGTEST] + message_network = addr_format["network"] + if type(message_network) == str: + message_network = [message_network] + + if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in message_network: + # Does nothing for MAINNET, but uses current setting to decide between TESTNET and REGTEST + addr_format["network"] = self.settings.get_value(SettingsConstants.SETTING__NETWORK) + else: + from seedsigner.views.view import NetworkMismatchErrorView + self.set_redirect(Destination(NetworkMismatchErrorView, view_args=dict(text=f"Current network setting ({self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK)}) doesn't match {self.derivation_path}"))) - # cleanup. Note: We could leave this in place so the user can resume the - # flow, but for now we avoid complications and keep things simple. - self.controller.resume_main_flow = None - self.controller.sign_message_data = None - return + # cleanup. Note: We could leave this in place so the user can resume the + # flow, but for now we avoid complications and keep things simple. + self.controller.resume_main_flow = None + self.controller.sign_message_data = None + return xpub = seed.get_xpub(wallet_path=self.derivation_path, network=addr_format["network"]) embit_network = embit_utils.get_embit_network_name(addr_format["network"]) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index f4b830654..e23874483 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,3 +1,4 @@ +from typing import Callable import pytest # Must import test base before the Controller @@ -420,19 +421,29 @@ def test_sign_message_network_mismatch_flow(self): # Ensure message signing is enabled self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__ENABLED) - # Ensure we're configured for mainnet + def expect_network_mismatch_error(load_message: Callable[[str], None]): + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_message), # simulate read message QR; ret val is ignored + FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True), + FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.SCAN_SEED), + FlowStep(scan_views.ScanView, before_run=self.load_seed_into_decoder), # simulate read SeedQR; ret val is ignored + FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), + FlowStep(seed_views.SeedOptionsView, is_redirect=True), + FlowStep(seed_views.SeedSignMessageConfirmMessageView, before_run=self.inject_mesage_as_paged_message, screen_return_value=0), + FlowStep(seed_views.SeedSignMessageConfirmAddressView, is_redirect=True), + FlowStep(NetworkMismatchErrorView), + FlowStep(settings_views.SettingsEntryUpdateSelectionView), + ]) + + # MAINNET settings vs TESTNET derivation path with the message self.settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.MAINNET) + expect_network_mismatch_error(self.load_testnet_message_into_decoder) - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), - FlowStep(scan_views.ScanView, before_run=self.load_testnet_message_into_decoder), # simulate read message QR; ret val is ignored - FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True), - FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanView, before_run=self.load_seed_into_decoder), # simulate read SeedQR; ret val is ignored - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(seed_views.SeedSignMessageConfirmMessageView, before_run=self.inject_mesage_as_paged_message, screen_return_value=0), - FlowStep(seed_views.SeedSignMessageConfirmAddressView, is_redirect=True), - FlowStep(NetworkMismatchErrorView), - FlowStep(settings_views.SettingsEntryUpdateSelectionView), - ]) + # TESTNET settings vs MAINNET derivation path with the message + self.settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.TESTNET) + expect_network_mismatch_error(self.load_short_message_into_decoder) + + # REGTEST settings vs MAINNET derivation path with the message + self.settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.REGTEST) + expect_network_mismatch_error(self.load_short_message_into_decoder) From d685e5e301a747c64c7d74c1af03b25bd263a33f Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 15 Aug 2023 21:30:57 -0500 Subject: [PATCH 2/5] simplify network test --- src/seedsigner/views/seed_views.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 2a7bb5b8e..bb77abdb0 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1992,11 +1992,7 @@ def __init__(self): raise Exception("Signing messages for custom derivation paths not supported") # addr_format["network"] can be MAINNET or [TESTNET, REGTEST] - message_network = addr_format["network"] - if type(message_network) == str: - message_network = [message_network] - - if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in message_network: + if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in addr_format["network"]: # Does nothing for MAINNET, but uses current setting to decide between TESTNET and REGTEST addr_format["network"] = self.settings.get_value(SettingsConstants.SETTING__NETWORK) else: From 3e58e6240b4c78ae4ef2b455936153bccc464cc4 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 15 Aug 2023 21:35:09 -0500 Subject: [PATCH 3/5] minor cleanup --- src/seedsigner/views/seed_views.py | 2 +- tests/test_flows_seed.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index bb77abdb0..2b86f417e 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1991,7 +1991,7 @@ def __init__(self): if not addr_format["clean_match"]: raise Exception("Signing messages for custom derivation paths not supported") - # addr_format["network"] can be MAINNET or [TESTNET, REGTEST] + # Note: addr_format["network"] can be MAINNET or [TESTNET, REGTEST] if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in addr_format["network"]: # Does nothing for MAINNET, but uses current setting to decide between TESTNET and REGTEST addr_format["network"] = self.settings.get_value(SettingsConstants.SETTING__NETWORK) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index e23874483..bb081bb3b 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -421,7 +421,7 @@ def test_sign_message_network_mismatch_flow(self): # Ensure message signing is enabled self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__ENABLED) - def expect_network_mismatch_error(load_message: Callable[[str], None]): + def expect_network_mismatch_error(load_message: Callable): self.run_sequence([ FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), FlowStep(scan_views.ScanView, before_run=load_message), # simulate read message QR; ret val is ignored From a6dd199e5254684d72222c35f8b6877b8e558ee1 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 16 Aug 2023 07:50:50 -0500 Subject: [PATCH 4/5] Detect network mismatch earlier --- src/seedsigner/views/seed_views.py | 54 +++++++++++++----------------- tests/test_flows_seed.py | 6 ---- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 2b86f417e..16733ca8d 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1921,23 +1921,37 @@ def __init__(self, derivation_path: str, message: str): self.derivation_path = derivation_path self.message = message + # calculate the actual receive address + addr_format = embit_utils.parse_derivation_path(derivation_path) + if not addr_format["clean_match"]: + raise NotYetImplementedView("Signing messages for custom derivation paths not supported") + + # Note: addr_format["network"] can be MAINNET or [TESTNET, REGTEST] + if self.settings.get_value(SettingsConstants.SETTING__NETWORK) not in addr_format["network"]: + from seedsigner.views.view import NetworkMismatchErrorView + self.set_redirect(Destination(NetworkMismatchErrorView, view_args=dict(text=f"Current network setting ({self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK)}) doesn't match {self.derivation_path}"))) + + # cleanup. Note: We could leave this in place so the user can resume the + # flow, but for now we avoid complications and keep things simple. + self.controller.resume_main_flow = None + return + data = self.controller.sign_message_data if not data: data = {} self.controller.sign_message_data = data data["derivation_path"] = derivation_path data["message"] = message + data["addr_format"] = addr_format # May be None self.seed_num = data.get("seed_num") - - def run(self): if self.seed_num is not None: # We already know which seed we're signing with - return Destination(SeedSignMessageConfirmMessageView, skip_current_view=True) + self.set_redirect(Destination(SeedSignMessageConfirmMessageView, skip_current_view=True)) else: - return Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__SIGN_MESSAGE), skip_current_view=True) + self.set_redirect(Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__SIGN_MESSAGE), skip_current_view=True)) @@ -1979,34 +1993,14 @@ class SeedSignMessageConfirmAddressView(View): def __init__(self): super().__init__() data = self.controller.sign_message_data - self.seed_num = data.get("seed_num") + seed = self.controller.storage.seeds[data.get("seed_num")] self.derivation_path = data.get("derivation_path") + addr_format = data.get("addr_format") - if self.seed_num is None or not self.derivation_path: - raise Exception("Routing error: sign_message_data hasn't been set") - - # calculate the actual receive address - seed = self.controller.storage.seeds[self.seed_num] - addr_format = embit_utils.parse_derivation_path(self.derivation_path) - if not addr_format["clean_match"]: - raise Exception("Signing messages for custom derivation paths not supported") - - # Note: addr_format["network"] can be MAINNET or [TESTNET, REGTEST] - if self.settings.get_value(SettingsConstants.SETTING__NETWORK) in addr_format["network"]: - # Does nothing for MAINNET, but uses current setting to decide between TESTNET and REGTEST - addr_format["network"] = self.settings.get_value(SettingsConstants.SETTING__NETWORK) - else: - from seedsigner.views.view import NetworkMismatchErrorView - self.set_redirect(Destination(NetworkMismatchErrorView, view_args=dict(text=f"Current network setting ({self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK)}) doesn't match {self.derivation_path}"))) - - # cleanup. Note: We could leave this in place so the user can resume the - # flow, but for now we avoid complications and keep things simple. - self.controller.resume_main_flow = None - self.controller.sign_message_data = None - return - - xpub = seed.get_xpub(wallet_path=self.derivation_path, network=addr_format["network"]) - embit_network = embit_utils.get_embit_network_name(addr_format["network"]) + # Current settings will differentiate TESTNET and REGTEST (since derivation path + # alone doesn't specify which one we're using). + xpub = seed.get_xpub(wallet_path=self.derivation_path, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)) + embit_network = embit_utils.get_embit_network_name(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) self.address = embit_utils.get_single_sig_address(xpub=xpub, script_type=addr_format["script_type"], index=addr_format["index"], is_change=addr_format["is_change"], embit_network=embit_network) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index bb081bb3b..d2ebc7d22 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -426,12 +426,6 @@ def expect_network_mismatch_error(load_message: Callable): FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), FlowStep(scan_views.ScanView, before_run=load_message), # simulate read message QR; ret val is ignored FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True), - FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.SCAN_SEED), - FlowStep(scan_views.ScanView, before_run=self.load_seed_into_decoder), # simulate read SeedQR; ret val is ignored - FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE), - FlowStep(seed_views.SeedOptionsView, is_redirect=True), - FlowStep(seed_views.SeedSignMessageConfirmMessageView, before_run=self.inject_mesage_as_paged_message, screen_return_value=0), - FlowStep(seed_views.SeedSignMessageConfirmAddressView, is_redirect=True), FlowStep(NetworkMismatchErrorView), FlowStep(settings_views.SettingsEntryUpdateSelectionView), ]) From 05e2909603471258f45145686225450c717b04cb Mon Sep 17 00:00:00 2001 From: kdmukai Date: Fri, 18 Aug 2023 07:23:57 -0500 Subject: [PATCH 5/5] Bugfix & improvements when message signing is disabled --- src/seedsigner/views/scan_views.py | 21 ++++++-------- src/seedsigner/views/seed_views.py | 6 +++- src/seedsigner/views/view.py | 26 +++++++++++++---- tests/screenshot_generator/generator.py | 13 +++++++-- tests/test_flows_seed.py | 38 ++++++++++++++++++++++++- 5 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/seedsigner/views/scan_views.py b/src/seedsigner/views/scan_views.py index 2b83d03b1..9c4e5990d 100644 --- a/src/seedsigner/views/scan_views.py +++ b/src/seedsigner/views/scan_views.py @@ -136,19 +136,16 @@ def run(self): ) elif self.decoder.is_sign_message: - if self.settings.get_value(SettingsConstants.SETTING__MESSAGE_SIGNING) == SettingsConstants.OPTION__ENABLED: - from seedsigner.views.seed_views import SeedSignMessageStartView - qr_data = self.decoder.get_qr_data() - - return Destination( - SeedSignMessageStartView, - view_args=dict( - derivation_path=qr_data["derivation_path"], - message=qr_data["message"], - ) + from seedsigner.views.seed_views import SeedSignMessageStartView + qr_data = self.decoder.get_qr_data() + + return Destination( + SeedSignMessageStartView, + view_args=dict( + derivation_path=qr_data["derivation_path"], + message=qr_data["message"], ) - else: - return Destination(OptionDisabledView, view_args=dict(error_msg="Message signing is currently disabled in Settings")) + ) else: return Destination(NotYetImplementedView) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 16733ca8d..a58b1f7ee 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -23,7 +23,7 @@ from seedsigner.models.settings import Settings, SettingsConstants from seedsigner.models.settings_definition import SettingsDefinition from seedsigner.models.threads import BaseThread, ThreadsafeCounter -from seedsigner.views.view import NotYetImplementedView, View, Destination, BackStackView, MainMenuView +from seedsigner.views.view import NotYetImplementedView, OptionDisabledView, View, Destination, BackStackView, MainMenuView @@ -1921,6 +1921,10 @@ def __init__(self, derivation_path: str, message: str): self.derivation_path = derivation_path self.message = message + if self.settings.get_value(SettingsConstants.SETTING__MESSAGE_SIGNING) == SettingsConstants.OPTION__DISABLED: + self.set_redirect(Destination(OptionDisabledView, view_args=dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING))) + return + # calculate the actual receive address addr_format = embit_utils.parse_derivation_path(derivation_path) if not addr_format["clean_match"]: diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py index d3e3c73cf..3f507a73f 100644 --- a/src/seedsigner/views/view.py +++ b/src/seedsigner/views/view.py @@ -5,6 +5,7 @@ from seedsigner.gui.screens import RET_CODE__POWER_BUTTON, RET_CODE__BACK_BUTTON from seedsigner.gui.screens.screen import BaseScreen, DireWarningScreen, LargeButtonScreen, PowerOffScreen, PowerOffNotRequiredScreen, ResetScreen, WarningScreen from seedsigner.models.settings import Settings, SettingsConstants +from seedsigner.models.settings_definition import SettingsDefinition from seedsigner.models.threads import BaseThread @@ -331,7 +332,7 @@ def run(self): class NetworkMismatchErrorView(ErrorView): title: str = "Network Mismatch" show_back_button: bool = False - button_text: str = "Change Settings" + button_text: str = "Change Setting" next_destination: Destination = None @@ -368,18 +369,33 @@ def run(self): @dataclass class OptionDisabledView(View): - error_msg: str + UPDATE_SETTING = "Update Setting" + DONE = "Done" + settings_attr: str + + def __post_init__(self): + super().__post_init__() + self.settings_entry = SettingsDefinition.get_settings_entry(self.settings_attr) + self.error_msg = f"\"{self.settings_entry.display_name}\" is currently disabled in Settings." def run(self): - WarningScreen( + button_data = [self.UPDATE_SETTING, self.DONE] + selected_menu_num = self.run_screen( + WarningScreen, title="Option Disabled", status_headline=None, text=self.error_msg, - button_data=["OK"], + button_data=button_data, show_back_button=False, allow_text_overflow=True, # Fit what we can, let the rest go off the edges - ).display() + ) + + if button_data[selected_menu_num] == self.UPDATE_SETTING: + from seedsigner.views.settings_views import SettingsEntryUpdateSelectionView + return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=self.settings_attr), clear_history=True) + else: + return Destination(MainMenuView, clear_history=True) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index 3e9494a4a..9afac77d8 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -3,6 +3,9 @@ import sys import time from mock import Mock, patch, MagicMock +from seedsigner.helpers import embit_utils + +from seedsigner.models.settings import Settings # Prevent importing modules w/Raspi hardware dependencies. @@ -28,7 +31,7 @@ from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition from seedsigner.views import (MainMenuView, PowerOptionsView, RestartView, NotYetImplementedView, UnhandledExceptionView, psbt_views, seed_views, settings_views, tools_views) -from seedsigner.views.view import NetworkMismatchErrorView, PowerOffView, View +from seedsigner.views.view import NetworkMismatchErrorView, OptionDisabledView, PowerOffView, View from .utils import ScreenshotComplete, ScreenshotRenderer @@ -80,10 +83,12 @@ def test_generate_screenshots(target_locale): controller.multisig_wallet_descriptor = embit.descriptor.Descriptor.from_string(MULTISIG_WALLET_DESCRIPTOR) # Message signing data + derivation_path = "m/84h/0h/0h/0/0" controller.sign_message_data = { "seed_num": 0, - "derivation_path": "m/84h/0h/0h/0/0", + "derivation_path": derivation_path, "message": "I attest that I control this bitcoin address blah blah blah", + "addr_format": embit_utils.parse_derivation_path(derivation_path) } # Automatically populate all Settings options Views @@ -116,6 +121,7 @@ def test_generate_screenshots(target_locale): (UnhandledExceptionView, dict(error=UnhandledExceptionViewFood)), (settings_views.SettingsIngestSettingsQRView, dict(data="settings::v1 name=factory_reset")), NetworkMismatchErrorView, + (OptionDisabledView, dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING)), ], @@ -231,7 +237,8 @@ def screencap_view(view_cls: View, view_name: str, view_args: dict={}, toast_thr print(f"Completed {view_name}") except Exception as e: # Something else went wrong - print(repr(e)) + from traceback import print_exc + print_exc() raise e finally: if toast_thread: diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index d2ebc7d22..521d77bca 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -8,7 +8,7 @@ from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON from seedsigner.models.settings import Settings, SettingsConstants from seedsigner.models.seed import Seed -from seedsigner.views.view import MainMenuView, RemoveMicroSDWarningView, View, NetworkMismatchErrorView +from seedsigner.views.view import MainMenuView, OptionDisabledView, RemoveMicroSDWarningView, View, NetworkMismatchErrorView from seedsigner.views import seed_views, scan_views, settings_views, tools_views @@ -441,3 +441,39 @@ def expect_network_mismatch_error(load_message: Callable): # REGTEST settings vs MAINNET derivation path with the message self.settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.REGTEST) expect_network_mismatch_error(self.load_short_message_into_decoder) + + + + + def test_sign_message_option_disabled(self): + """ + Should redirect to OptionDisabledView if a `signmessage` QR is scanned with + message signing disabled. + + Should offer the option to route directly to enable that settings or return to + MainMenuView. + """ + # Ensure message signing is disabled + self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__DISABLED) + + sequence = [ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=self.load_short_message_into_decoder), # simulate read message QR; ret val is ignored + FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True), + ] + + # First test routing to update the setting + self.run_sequence( + sequence + [ + FlowStep(OptionDisabledView, button_data_selection=OptionDisabledView.UPDATE_SETTING, is_redirect=True), + FlowStep(settings_views.SettingsEntryUpdateSelectionView), + ] + ) + + # Now test exiting to Main Menu + self.run_sequence( + sequence + [ + FlowStep(OptionDisabledView, button_data_selection=OptionDisabledView.DONE, is_redirect=True), + FlowStep(MainMenuView), + ] + )