diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index f7300eff..73293dc3 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -177,6 +177,11 @@ class PSBTParser(): self.destination_amounts = [] self.op_return_data: bytes = None + # Whether the fee is high relative to what is being sent; see has_high_fee(). + # Computed once at the end of parse() so the views can read it without each + # re-walking the outputs. + self.is_high_fee: bool = False + # Contains one entry per input in psbt.inputs and per output in psbt.outputs. Each # entry is either the derivation path the seed genuinely owns there, or it is set # to `None`. @@ -303,6 +308,9 @@ class PSBTParser(): if rt == False: return False + # Every total is known now, so settle this once rather than per view. + self.is_high_fee = self.has_high_fee() + return True diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 213b8ae6..7ac58062 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -163,8 +163,6 @@ class PSBTOverviewView(View): else: num_self_transfer_outputs += 1 - is_high_fee_tx = psbt_parser.has_high_fee() - # Run the overview screen selected_menu_num = self.run_screen( PSBTOverviewScreen, @@ -176,7 +174,7 @@ class PSBTOverviewView(View): num_change_outputs=num_change_outputs, destination_addresses=psbt_parser.destination_addresses, has_op_return=psbt_parser.op_return_data is not None, - is_high_fee_tx=is_high_fee_tx, + is_high_fee_tx=psbt_parser.is_high_fee, ) if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -188,7 +186,7 @@ class PSBTOverviewView(View): if psbt_parser.policy == None: return Destination(PSBTUnsupportedScriptTypeWarningView) - elif is_high_fee_tx: + elif psbt_parser.is_high_fee: return Destination(PSBTHighFeeWarningView, view_args={"warning_threshold_percent": psbt_parser.HIGH_FEES_WARNING_THRESHOLD}) elif psbt_parser.change_amount == 0: @@ -298,7 +296,7 @@ class PSBTMathView(View): num_recipients=psbt_parser.num_destinations, fee_amount=psbt_parser.fee_amount, change_amount=psbt_parser.change_amount, - is_high_fee_tx=psbt_parser.has_high_fee(), + is_high_fee_tx=psbt_parser.is_high_fee, ) if selected_menu_num == RET_CODE__BACK_BUTTON: diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py index c055b921..ee8506fe 100644 --- a/tests/test_psbt_parser.py +++ b/tests/test_psbt_parser.py @@ -445,6 +445,27 @@ class TestPSBTParser: assert parser.has_high_fee() is expected + def test_parse_sets_is_high_fee(self): + """ + parse() should settle is_high_fee once, from the real totals, so the views + can read it without recomputing. Checked in both directions: a realistic fee + leaves it False, a fee dwarfing the spend sets it True. + """ + # 272 sat fee on a 2 BTC spend: nowhere near the threshold + psbt = PSBT.parse(a2b_base64(PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_2_INPUTS)) + psbt_parser = PSBTParser(p=psbt, seed=PSBTTestData.two_input_seed, network=SettingsConstants.REGTEST) + assert psbt_parser.is_high_fee is False + assert psbt_parser.is_high_fee == psbt_parser.has_high_fee() + + # 1 BTC input paying a 50,000 sat recipient and 10,000 sats change: almost all fee + psbt = PSBT.parse(a2b_base64(PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_1_INPUT)) + psbt.outputs.append(create_output(PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_RECEIVE, 50_000)) + psbt.outputs.append(create_output(PSBTTestData.SINGLE_SIG_NATIVE_SEGWIT_CHANGE, 10_000)) + psbt_parser = PSBTParser(p=psbt, seed=PSBTTestData.seed, network=SettingsConstants.REGTEST) + assert psbt_parser.is_high_fee is True + assert psbt_parser.is_high_fee == psbt_parser.has_high_fee() + + # TODO: Refactor all tests to be in the TestPSBTParser class(?) def test_p2tr_change_detection():