Skip to content

Commit 105c5bb

Browse files
committed
qt: defer hidden UTXO list model rebuilds
1 parent 8b70f21 commit 105c5bb

3 files changed

Lines changed: 138 additions & 1 deletion

File tree

electrum/gui/qt/utxo_list.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,16 @@ def cb():
101101
@profiler(min_threshold=0.05)
102102
def update(self):
103103
# not calling maybe_defer_update() as it interferes with coincontrol status bar
104-
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
105104
utxos = self.wallet.get_utxos()
106105
self._maybe_reset_coincontrol(utxos)
107106
self._utxo_dict = dict([(utxo.prevout.to_str(), utxo) for utxo in utxos])
107+
if self.maybe_defer_update():
108+
self.update_coincontrol_bar()
109+
self.num_coins_label.setText(
110+
_('{} unspent transaction outputs').format(len(utxos))
111+
)
112+
return
113+
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
108114
self.std_model.clear()
109115
self.update_headers(self.__class__.headers)
110116
for idx, utxo in enumerate(utxos):

tests/gui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/gui/test_qt_utxo_list.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import importlib.util
2+
import os
3+
import subprocess
4+
import sys
5+
import textwrap
6+
import unittest
7+
8+
9+
class TestUTXOList(unittest.TestCase):
10+
def test_hidden_update_defers_expensive_model_rebuild(self):
11+
if importlib.util.find_spec("PyQt6") is None:
12+
self.skipTest("PyQt6 not available")
13+
14+
env = os.environ.copy()
15+
env.setdefault("QT_QPA_PLATFORM", "offscreen")
16+
script = textwrap.dedent(
17+
r"""
18+
from PyQt6.QtWidgets import QApplication, QLabel, QWidget
19+
20+
from electrum.simple_config import SimpleConfig
21+
from electrum.gui.qt.utxo_list import UTXOList
22+
23+
24+
class _Prevout:
25+
def __init__(self, n):
26+
self.txid = bytes([n % 256]) * 32
27+
self.n = n
28+
29+
def to_str(self):
30+
return f"{self.txid.hex()}:{self.n}"
31+
32+
33+
class _Utxo:
34+
def __init__(self, n):
35+
self.prevout = _Prevout(n)
36+
self.short_id = f"{n}:0"
37+
self.address = f"addr{n}"
38+
self.block_height = n
39+
40+
def value_sats(self):
41+
return 1000
42+
43+
44+
class _AddressDB:
45+
def tx_height_to_sort_height(self, height):
46+
return height
47+
48+
49+
class _Wallet:
50+
def __init__(self, utxo_count):
51+
self.adb = _AddressDB()
52+
self._utxos = [_Utxo(n) for n in range(utxo_count)]
53+
54+
def get_utxos(self):
55+
return self._utxos
56+
57+
def get_num_parents(self, txid):
58+
return 0
59+
60+
def get_label_for_txid(self, txid):
61+
return ""
62+
63+
def is_frozen_address(self, address):
64+
return False
65+
66+
def is_frozen_coin(self, utxo):
67+
return False
68+
69+
70+
class _MainWindow(QWidget):
71+
def __init__(self, utxo_count):
72+
super().__init__()
73+
self.config = SimpleConfig({"electrum_path": "/tmp/electrum-test"})
74+
self.wallet = _Wallet(utxo_count)
75+
self.coincontrol_msg = None
76+
77+
def format_amount(self, amount, **kwargs):
78+
return str(amount)
79+
80+
def format_amount_and_units(self, amount):
81+
return str(amount)
82+
83+
def set_coincontrol_msg(self, msg):
84+
self.coincontrol_msg = msg
85+
86+
87+
app = QApplication([])
88+
window = _MainWindow(100)
89+
utxo_list = UTXOList(window)
90+
utxo_list.num_coins_label = QLabel()
91+
92+
utxo_list._forced_update = True
93+
utxo_list.update()
94+
utxo_list._forced_update = False
95+
first_item = utxo_list.std_model.item(0, UTXOList.Columns.OUTPOINT)
96+
assert utxo_list.std_model.rowCount() == 100
97+
98+
utxo_list.update()
99+
100+
assert first_item is utxo_list.std_model.item(0, UTXOList.Columns.OUTPOINT)
101+
assert utxo_list._pending_update
102+
assert len(utxo_list._utxo_dict) == 100
103+
assert utxo_list.num_coins_label.text() == "100 unspent transaction outputs"
104+
105+
window.wallet._utxos = [_Utxo(n) for n in range(50)]
106+
utxo_list.update()
107+
assert utxo_list.std_model.rowCount() == 100
108+
assert len(utxo_list._utxo_dict) == 50
109+
assert utxo_list.num_coins_label.text() == "50 unspent transaction outputs"
110+
111+
utxo_list._forced_update = True
112+
utxo_list.update()
113+
utxo_list._forced_update = False
114+
assert utxo_list.std_model.rowCount() == 50
115+
app.quit()
116+
"""
117+
)
118+
119+
result = subprocess.run(
120+
[sys.executable, "-c", script],
121+
env=env,
122+
text=True,
123+
capture_output=True,
124+
timeout=30,
125+
)
126+
self.assertEqual(
127+
0,
128+
result.returncode,
129+
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}",
130+
)

0 commit comments

Comments
 (0)