What changed, and why it matters
This commit is a performance cleanup, not a security fix. It calculates the 'is this fee unusually high?' flag once during transaction parsing instead of recalculating it every time a screen asks. The actual fee-checking logic and warning behavior are unchanged, so users still see the same high-fee warnings. The change just makes the device respond faster and avoids redundant work.
No security action needed. Treat as a normal performance/refactoring improvement. Reviewers may optionally verify that is_high_fee is always populated after parse() returns True and that no code path reads it before parse() is called.
Security signals we found
No security-relevant logic change: the same fee threshold and calculation are used
Performance optimization only: eliminates repeated transaction re-summation
No input validation, cryptography, or authorization changes
No mention of vulnerabilities, CVEs, or security disclosures in commit or diff
Evidence from the diff
The patch moves the high-fee determination from on-demand calls to has_high_fee() in PSBTOverviewView and PSBTMathView into a single computation at the end of PSBTParser.parse(), storing the result in self.is_high_fee. The has_high_fee() method remains unchanged as the underlying computation. Views now read the cached boolean. A test verifies that parse() sets the flag consistently with has_high_fee() for both low-fee and high-fee cases.
Changed components
src/seedsigner/models/psbt_parser.pysrc/seedsigner/views/psbt_views.pytests/test_psbt_parser.pyInspect captured patch +32 / −5
### src/seedsigner/models/psbt_parser.py
@@ -177,6 +177,11 @@ def __init__(self, p: PSBT, seed: Seed, network: str = SettingsConstants.MAINNET
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 @@ def parse(self):
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
### src/seedsigner/views/psbt_views.py
@@ -163,8 +163,6 @@ def run(self):
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 @@ def run(self):
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 @@ def run(self):
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 @@ def run(self):
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:
### tests/test_psbt_parser.py
@@ -445,6 +445,27 @@ def test_has_high_fee(self, vin, vout_values, change_data, expected):
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():Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.