Browse Source

qml: TxListModel: don't rely on wallet.db.get_transaction() finding tx

tx might get removed from wallet after wallet.get_full_history() but before the model is populated

closes https://github.com/spesmilo/electrum/issues/8339
master
SomberNight 3 years ago
parent
commit
ea864cd5c9
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 33
      electrum/gui/qml/qetransactionlistmodel.py
  2. 12
      electrum/gui/qt/history_list.py
  3. 9
      electrum/util.py

33
electrum/gui/qml/qetransactionlistmodel.py

@ -1,5 +1,5 @@
from datetime import datetime, timedelta
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Dict, Any
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
@ -97,9 +97,9 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
self.tx_history = []
self.endResetModel()
def tx_to_model(self, tx):
#self._logger.debug(str(tx))
item = tx
def tx_to_model(self, tx_item):
#self._logger.debug(str(tx_item))
item = tx_item
item['key'] = item['txid'] if 'txid' in item else item['payment_hash']
@ -118,15 +118,19 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
if 'txid' in item:
tx = self.wallet.db.get_transaction(item['txid'])
assert tx is not None
item['complete'] = tx.is_complete()
if tx:
item['complete'] = tx.is_complete()
else: # due to races, tx might have already been removed from history
item['complete'] = False
# newly arriving txs, or (partially/fully signed) local txs have no (block) timestamp
# FIXME just use wallet.get_tx_status, and change that as needed
if not item['timestamp']: # onchain: local or mempool or unverified txs
txinfo = self.wallet.get_tx_info(tx)
item['section'] = 'mempool' if item['complete'] and not txinfo.can_broadcast else 'local'
status, status_str = self.wallet.get_tx_status(item['txid'], txinfo.tx_mined_status)
txid = item['txid']
assert txid
tx_mined_info = self._tx_mined_info_from_tx_item(tx_item)
item['section'] = 'local' if tx_mined_info.is_local_like() else 'mempool'
status, status_str = self.wallet.get_tx_status(txid, tx_mined_info=tx_mined_info)
item['date'] = status_str
else: # lightning or already mined (and SPV-ed) onchain txs
item['section'] = self.get_section_by_timestamp(item['timestamp'])
@ -162,6 +166,17 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
section = 'older'
return date.strftime(dfmt[section])
@staticmethod
def _tx_mined_info_from_tx_item(tx_item: Dict[str, Any]) -> TxMinedInfo:
# FIXME a bit hackish to have to reconstruct the TxMinedInfo... same thing in qt-gui
tx_mined_info = TxMinedInfo(
height=tx_item['height'],
conf=tx_item['confirmations'],
timestamp=tx_item['timestamp'],
wanted_height=tx_item.get('wanted_height', None),
)
return tx_mined_info
# initial model data
@pyqtSlot()
@pyqtSlot(bool)

12
electrum/gui/qt/history_list.py

@ -28,7 +28,7 @@ import sys
import time
import datetime
from datetime import date
from typing import TYPE_CHECKING, Tuple, Dict
from typing import TYPE_CHECKING, Tuple, Dict, Any
import threading
import enum
from decimal import Decimal
@ -128,7 +128,7 @@ class HistoryNode(CustomNode):
try:
status, status_str = self.model.tx_status_cache[tx_hash]
except KeyError:
tx_mined_info = self.model.tx_mined_info_from_tx_item(tx_item)
tx_mined_info = self.model._tx_mined_info_from_tx_item(tx_item)
status, status_str = window.wallet.get_tx_status(tx_hash, tx_mined_info)
if role == ROLE_SORT_ORDER:
@ -353,7 +353,7 @@ class HistoryModel(CustomModel, Logger):
self.tx_status_cache.clear()
for txid, tx_item in self.transactions.items():
if not tx_item.get('lightning', False):
tx_mined_info = self.tx_mined_info_from_tx_item(tx_item)
tx_mined_info = self._tx_mined_info_from_tx_item(tx_item)
self.tx_status_cache[txid] = self.window.wallet.get_tx_status(txid, tx_mined_info)
# update counter
num_tx = len(self.transactions)
@ -404,7 +404,7 @@ class HistoryModel(CustomModel, Logger):
for tx_hash, tx_item in list(self.transactions.items()):
if tx_item.get('lightning'):
continue
tx_mined_info = self.tx_mined_info_from_tx_item(tx_item)
tx_mined_info = self._tx_mined_info_from_tx_item(tx_item)
if tx_mined_info.conf > 0:
# note: we could actually break here if we wanted to rely on the order of txns in self.transactions
continue
@ -441,8 +441,8 @@ class HistoryModel(CustomModel, Logger):
return super().flags(idx) | int(extra_flags)
@staticmethod
def tx_mined_info_from_tx_item(tx_item):
# FIXME a bit hackish to have to reconstruct the TxMinedInfo...
def _tx_mined_info_from_tx_item(tx_item: Dict[str, Any]) -> TxMinedInfo:
# FIXME a bit hackish to have to reconstruct the TxMinedInfo... same thing in qml-gui
tx_mined_info = TxMinedInfo(
height=tx_item['height'],
conf=tx_item['confirmations'],

9
electrum/util.py

@ -1340,6 +1340,15 @@ class TxMinedInfo(NamedTuple):
return f"{self.height}x{self.txpos}"
return None
def is_local_like(self) -> bool:
"""Returns whether the tx is local-like (LOCAL/FUTURE)."""
from .address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
if self.height > 0:
return False
if self.height in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT):
return False
return True
class ShortID(bytes):

Loading…
Cancel
Save