qml: detect transaction removed (e.g. replace-by-fee) for qetxdetails and qetxfinalizer,
don't close active feebump/cancel dialogs, but invalidate them, don't close TxDetails page, but show removed status, show broadcast-failed status in TxDetails
This commit is contained in:
@@ -222,11 +222,4 @@ ElDialog {
|
||||
onClicked: doAccept()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: cpfpfeebumper
|
||||
function onTxMined() {
|
||||
dialog.doReject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,11 +231,4 @@ ElDialog {
|
||||
onClicked: doAccept()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: rbffeebumper
|
||||
function onTxMined() {
|
||||
dialog.doReject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,11 +163,4 @@ ElDialog {
|
||||
onClicked: doAccept()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: txcanceller
|
||||
function onTxMined() {
|
||||
dialog.doReject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,13 +60,14 @@ Pane {
|
||||
}
|
||||
|
||||
InfoTextArea {
|
||||
id: bumpfeeinfo
|
||||
id: txinfo
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: constants.paddingLarge
|
||||
visible: txdetails.isUnrelated || !txdetails.isMined
|
||||
visible: (txdetails.isUnrelated || !txdetails.isMined) && !broadcastinfo.visible
|
||||
text: txdetails.isUnrelated
|
||||
? qsTr('Transaction is unrelated to this wallet.')
|
||||
: txdetails.isRemoved ? qsTr('This transaction has been replaced or removed and is no longer valid')
|
||||
: txdetails.inMempool
|
||||
? qsTr('This transaction is still unconfirmed.') +
|
||||
(txdetails.canBump || txdetails.canCpfp || txdetails.canCancel
|
||||
@@ -84,11 +85,19 @@ Pane {
|
||||
(txdetails.wallet.isWatchOnly
|
||||
? qsTr('Present this transaction to the signing wallet.')
|
||||
: qsTr('Present this transaction to the next cosigner.'))
|
||||
iconStyle: txdetails.isUnrelated
|
||||
iconStyle: txdetails.isUnrelated || txdetails.isRemoved
|
||||
? InfoTextArea.IconStyle.Warn
|
||||
: InfoTextArea.IconStyle.Info
|
||||
}
|
||||
|
||||
InfoTextArea {
|
||||
id: broadcastinfo
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: constants.paddingLarge
|
||||
visible: text
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 1
|
||||
Layout.fillWidth: true
|
||||
@@ -417,7 +426,7 @@ Pane {
|
||||
Layout.preferredWidth: 1
|
||||
icon.source: '../../icons/qrcode_white.png'
|
||||
text: qsTr('Share')
|
||||
enabled: !txdetails.isUnrelated
|
||||
enabled: !txdetails.isUnrelated && !txdetails.isRemoved
|
||||
onClicked: {
|
||||
var msg = ''
|
||||
if (txdetails.isComplete) {
|
||||
@@ -477,9 +486,6 @@ Pane {
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
onTxRemoved: {
|
||||
root.close()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (root.txid) {
|
||||
txdetails.txid = root.txid
|
||||
@@ -512,7 +518,12 @@ Pane {
|
||||
dialog.open()
|
||||
}
|
||||
function onBroadcastSucceeded() {
|
||||
bumpfeeinfo.text = qsTr('Transaction was broadcast successfully')
|
||||
broadcastinfo.text = qsTr('Transaction was broadcast successfully')
|
||||
broadcastinfo.iconStyle = InfoTextArea.IconStyle.Info
|
||||
}
|
||||
function onBroadcastFailed(txid, code, message) {
|
||||
broadcastinfo.text = qsTr('Broadcast of transaction failed')
|
||||
broadcastinfo.iconStyle = InfoTextArea.IconStyle.Warn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from electrum.i18n import _
|
||||
from electrum.logging import get_logger
|
||||
from electrum.bitcoin import DummyAddress
|
||||
from electrum.util import format_time, TxMinedInfo
|
||||
from electrum.transaction import tx_from_any, Transaction, PartialTxInput, Sighash, PartialTransaction, TxOutpoint
|
||||
from electrum.transaction import tx_from_any, Transaction, PartialTransaction
|
||||
from electrum.network import Network
|
||||
from electrum.address_synchronizer import TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE
|
||||
from electrum.wallet import TxSighashDanger
|
||||
@@ -22,7 +22,7 @@ class QETxDetails(QObject, QtEventListener):
|
||||
|
||||
confirmRemoveLocalTx = pyqtSignal([str], arguments=['message'])
|
||||
txRemoved = pyqtSignal()
|
||||
saveTxError = pyqtSignal([str,str], arguments=['code', 'message'])
|
||||
saveTxError = pyqtSignal([str, str], arguments=['code', 'message'])
|
||||
saveTxSuccess = pyqtSignal()
|
||||
|
||||
detailsChanged = pyqtSignal()
|
||||
@@ -59,6 +59,7 @@ class QETxDetails(QObject, QtEventListener):
|
||||
self._is_complete = False
|
||||
self._is_mined = False
|
||||
self._is_rbf_enabled = False
|
||||
self._is_removed = False
|
||||
self._lock_delay = 0
|
||||
self._sighash_danger = TxSighashDanger()
|
||||
|
||||
@@ -90,6 +91,8 @@ class QETxDetails(QObject, QtEventListener):
|
||||
def on_event_removed_transaction(self, wallet, tx):
|
||||
if wallet == self._wallet.wallet and tx.txid() == self._txid:
|
||||
self._logger.debug(f'removed my transaction {tx.txid()}')
|
||||
self._is_removed = True
|
||||
self.update()
|
||||
self.txRemoved.emit()
|
||||
|
||||
walletChanged = pyqtSignal()
|
||||
@@ -184,6 +187,10 @@ class QETxDetails(QObject, QtEventListener):
|
||||
def isMined(self):
|
||||
return self._is_mined
|
||||
|
||||
@pyqtProperty(bool, notify=detailsChanged)
|
||||
def isRemoved(self):
|
||||
return self._is_removed
|
||||
|
||||
@pyqtProperty(str, notify=detailsChanged)
|
||||
def mempoolDepth(self):
|
||||
return self._mempool_depth
|
||||
@@ -267,6 +274,20 @@ class QETxDetails(QObject, QtEventListener):
|
||||
def update(self, from_txid: bool = False):
|
||||
assert self._wallet
|
||||
|
||||
if self._is_removed:
|
||||
self._logger.debug('tx removed, disable gui options')
|
||||
self._can_broadcast = False
|
||||
self._can_bump = False
|
||||
self._can_dscancel = False
|
||||
self._can_cpfp = False
|
||||
self._can_save_as_local = False
|
||||
self._can_remove = False
|
||||
self._can_sign = False
|
||||
self._mempool_depth = ''
|
||||
self._status = _('removed')
|
||||
self.detailsChanged.emit()
|
||||
return
|
||||
|
||||
if from_txid:
|
||||
self._tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||
assert self._tx is not None, f'unknown txid "{self._txid}"'
|
||||
|
||||
@@ -474,13 +474,15 @@ class QETxFinalizer(TxFeeSlider):
|
||||
|
||||
|
||||
class TxMonMixin(QtEventListener):
|
||||
""" mixin for watching an existing TX based on its txid for verified event.
|
||||
""" mixin for watching an existing TX based on its txid for verified or removed event.
|
||||
requires self._wallet to contain a QEWallet instance.
|
||||
exposes txid qt property.
|
||||
calls get_tx() once txid is set.
|
||||
calls tx_verified and emits txMined signal once tx is verified.
|
||||
calls tx_verified() and emits txMined signal once tx is verified.
|
||||
emits txRemoved signal if tx is removed (e.g. replace-by-fee)
|
||||
"""
|
||||
txMined = pyqtSignal()
|
||||
txRemoved = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self._logger.debug('TxMonMixin.__init__')
|
||||
@@ -500,6 +502,13 @@ class TxMonMixin(QtEventListener):
|
||||
self.tx_verified()
|
||||
self.txMined.emit()
|
||||
|
||||
@event_listener
|
||||
def on_event_removed_transaction(self, wallet, tx):
|
||||
if wallet == self._wallet.wallet and tx.txid() == self._txid:
|
||||
self._logger.debug('remove tx for our txid %s' % self._txid)
|
||||
self.tx_removed()
|
||||
self.txRemoved.emit()
|
||||
|
||||
txidChanged = pyqtSignal()
|
||||
@pyqtProperty(str, notify=txidChanged)
|
||||
def txid(self):
|
||||
@@ -520,6 +529,10 @@ class TxMonMixin(QtEventListener):
|
||||
def tx_verified(self) -> None:
|
||||
pass
|
||||
|
||||
# override
|
||||
def tx_removed(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
_logger = get_logger(__name__)
|
||||
@@ -595,6 +608,16 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
self.oldfeeRate = self.feeRate
|
||||
self.update()
|
||||
|
||||
def tx_verified(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction has been mined')
|
||||
|
||||
def tx_removed(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction disappeared')
|
||||
|
||||
def update(self):
|
||||
if not self._txid or not self._orig_tx:
|
||||
# not initialized yet
|
||||
@@ -692,6 +715,16 @@ class QETxCanceller(TxFeeSlider, TxMonMixin):
|
||||
self.oldfeeRate = self.feeRate
|
||||
self.update()
|
||||
|
||||
def tx_verified(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction has been mined')
|
||||
|
||||
def tx_removed(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction disappeared')
|
||||
|
||||
def update(self):
|
||||
if not self._txid or not self._orig_tx:
|
||||
# not initialized yet
|
||||
@@ -829,6 +862,16 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
fee = max(self._total_size, fee) # pay at least 1 sat/byte for combined size
|
||||
return fee
|
||||
|
||||
def tx_verified(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction has been mined')
|
||||
|
||||
def tx_removed(self):
|
||||
self._valid = False
|
||||
self.validChanged.emit()
|
||||
self.warning = _('Base transaction disappeared')
|
||||
|
||||
def update(self):
|
||||
if not self._txid: # not initialized yet
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user