SeedSigner / seedsigner

Use an air-gapped Raspberry Pi Zero to sign for Bitcoin transactions! (and do other cool stuff)
MIT License
699 stars 161 forks source link

[Enhancement] improve FlowTest + Exception handling interactions #573

Closed kdmukai closed 2 months ago

kdmukai commented 2 months ago

Description

Quick Background

A FlowTest sequence normally proceeds from View to View, exactly as if a user was clicking from one screen to the next.

But we also have redirects that are often invisible to the user (e.g. only Native Segwit is enabled, so skip the script type selection View).

There are three types of possible redirects:

The Problem

A FlowStep can specify is_redirect=True to indicate that the expected View should execute, but is expected to immediately return a new Destination without ever running its Screen.

But lack of clarity on the above three distinct redirect scenarios made that one single is_redirect attr ineffective and confusing in FlowTest.run_sequence(). This was wholly my fault, by the way! The original implementation more or less properly handled the middle case (redirect triggered in View.run()), maybe handled the first case correctly (triggered via View.set_redirect()), and definitely did NOT handle the third case correctly (UnhandledExceptionView).

Yes, FlowTests have been passing all this time, but partially due to silent, unnoticed failures. A typical example is that the last or penultimate FlowStep triggers an UnhandledExceptionView but the heart of the FlowTest is already over and so there's no next step (next expected View) that would fail.

But if the penultimate FlowStep lands on UnhandledExceptionView, shouldn't the FINAL step detect some kind of mismatch? This is where the second aspect of the problem comes in: when do we sequence.pop(0) the current FlowStep out of the sequence?

In this example, that final FlowStep was being improperly popped before the FlowTest could verify that we got the expected View. So the FlowTest was totally unaware that our sequence ended on the wrong View.

The Fix

First, just getting mental clarity on those three possible redirect scenarios was huge.

Second, remove ALL the sequence.pop(0) calls that created too many confusing hell scenarios.

Instead, wrap it all in a try...finally block to guarantee that sequence.pop(0) is always called after each iteration AND that it's only called once.

The explicit handling for each of the redirect scenarios:

Additional:


This pull request is categorized as a:

Checklist

If you modified or added functionality/workflow, did you add new unit tests?

I have tested this PR on the following platforms/os:

Note: Keep your changes limited in scope; if you uncover other issues or improvements along the way, ideally submit those as a separate PR. The more complicated the PR the harder to review, test, and merge.

Marc-Gee commented 2 months ago

Keith's idea of a large Try... Finally block and then just 1 sequence.pop(0) definitely is an exciting one!

newtonick commented 2 months ago

tACK. I can't say I fully grok this code change. I certainly could not have written the code. However I understand the purpose, scope, and function of the changes in this PR. I'm comfortable merging this PR.