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
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user