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()
|
onClicked: doAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: cpfpfeebumper
|
|
||||||
function onTxMined() {
|
|
||||||
dialog.doReject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,11 +231,4 @@ ElDialog {
|
|||||||
onClicked: doAccept()
|
onClicked: doAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: rbffeebumper
|
|
||||||
function onTxMined() {
|
|
||||||
dialog.doReject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,11 +163,4 @@ ElDialog {
|
|||||||
onClicked: doAccept()
|
onClicked: doAccept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: txcanceller
|
|
||||||
function onTxMined() {
|
|
||||||
dialog.doReject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,13 +60,14 @@ Pane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InfoTextArea {
|
InfoTextArea {
|
||||||
id: bumpfeeinfo
|
id: txinfo
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.bottomMargin: constants.paddingLarge
|
Layout.bottomMargin: constants.paddingLarge
|
||||||
visible: txdetails.isUnrelated || !txdetails.isMined
|
visible: (txdetails.isUnrelated || !txdetails.isMined) && !broadcastinfo.visible
|
||||||
text: txdetails.isUnrelated
|
text: txdetails.isUnrelated
|
||||||
? qsTr('Transaction is unrelated to this wallet.')
|
? qsTr('Transaction is unrelated to this wallet.')
|
||||||
|
: txdetails.isRemoved ? qsTr('This transaction has been replaced or removed and is no longer valid')
|
||||||
: txdetails.inMempool
|
: txdetails.inMempool
|
||||||
? qsTr('This transaction is still unconfirmed.') +
|
? qsTr('This transaction is still unconfirmed.') +
|
||||||
(txdetails.canBump || txdetails.canCpfp || txdetails.canCancel
|
(txdetails.canBump || txdetails.canCpfp || txdetails.canCancel
|
||||||
@@ -84,11 +85,19 @@ Pane {
|
|||||||
(txdetails.wallet.isWatchOnly
|
(txdetails.wallet.isWatchOnly
|
||||||
? qsTr('Present this transaction to the signing wallet.')
|
? qsTr('Present this transaction to the signing wallet.')
|
||||||
: qsTr('Present this transaction to the next cosigner.'))
|
: qsTr('Present this transaction to the next cosigner.'))
|
||||||
iconStyle: txdetails.isUnrelated
|
iconStyle: txdetails.isUnrelated || txdetails.isRemoved
|
||||||
? InfoTextArea.IconStyle.Warn
|
? InfoTextArea.IconStyle.Warn
|
||||||
: InfoTextArea.IconStyle.Info
|
: InfoTextArea.IconStyle.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfoTextArea {
|
||||||
|
id: broadcastinfo
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: constants.paddingLarge
|
||||||
|
visible: text
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.preferredWidth: 1
|
Layout.preferredWidth: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -417,7 +426,7 @@ Pane {
|
|||||||
Layout.preferredWidth: 1
|
Layout.preferredWidth: 1
|
||||||
icon.source: '../../icons/qrcode_white.png'
|
icon.source: '../../icons/qrcode_white.png'
|
||||||
text: qsTr('Share')
|
text: qsTr('Share')
|
||||||
enabled: !txdetails.isUnrelated
|
enabled: !txdetails.isUnrelated && !txdetails.isRemoved
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var msg = ''
|
var msg = ''
|
||||||
if (txdetails.isComplete) {
|
if (txdetails.isComplete) {
|
||||||
@@ -477,9 +486,6 @@ Pane {
|
|||||||
})
|
})
|
||||||
dialog.open()
|
dialog.open()
|
||||||
}
|
}
|
||||||
onTxRemoved: {
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (root.txid) {
|
if (root.txid) {
|
||||||
txdetails.txid = root.txid
|
txdetails.txid = root.txid
|
||||||
@@ -512,7 +518,12 @@ Pane {
|
|||||||
dialog.open()
|
dialog.open()
|
||||||
}
|
}
|
||||||
function onBroadcastSucceeded() {
|
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.logging import get_logger
|
||||||
from electrum.bitcoin import DummyAddress
|
from electrum.bitcoin import DummyAddress
|
||||||
from electrum.util import format_time, TxMinedInfo
|
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.network import Network
|
||||||
from electrum.address_synchronizer import TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE
|
from electrum.address_synchronizer import TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE
|
||||||
from electrum.wallet import TxSighashDanger
|
from electrum.wallet import TxSighashDanger
|
||||||
@@ -22,7 +22,7 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
|
|
||||||
confirmRemoveLocalTx = pyqtSignal([str], arguments=['message'])
|
confirmRemoveLocalTx = pyqtSignal([str], arguments=['message'])
|
||||||
txRemoved = pyqtSignal()
|
txRemoved = pyqtSignal()
|
||||||
saveTxError = pyqtSignal([str,str], arguments=['code', 'message'])
|
saveTxError = pyqtSignal([str, str], arguments=['code', 'message'])
|
||||||
saveTxSuccess = pyqtSignal()
|
saveTxSuccess = pyqtSignal()
|
||||||
|
|
||||||
detailsChanged = pyqtSignal()
|
detailsChanged = pyqtSignal()
|
||||||
@@ -59,6 +59,7 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
self._is_complete = False
|
self._is_complete = False
|
||||||
self._is_mined = False
|
self._is_mined = False
|
||||||
self._is_rbf_enabled = False
|
self._is_rbf_enabled = False
|
||||||
|
self._is_removed = False
|
||||||
self._lock_delay = 0
|
self._lock_delay = 0
|
||||||
self._sighash_danger = TxSighashDanger()
|
self._sighash_danger = TxSighashDanger()
|
||||||
|
|
||||||
@@ -90,6 +91,8 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
def on_event_removed_transaction(self, wallet, tx):
|
def on_event_removed_transaction(self, wallet, tx):
|
||||||
if wallet == self._wallet.wallet and tx.txid() == self._txid:
|
if wallet == self._wallet.wallet and tx.txid() == self._txid:
|
||||||
self._logger.debug(f'removed my transaction {tx.txid()}')
|
self._logger.debug(f'removed my transaction {tx.txid()}')
|
||||||
|
self._is_removed = True
|
||||||
|
self.update()
|
||||||
self.txRemoved.emit()
|
self.txRemoved.emit()
|
||||||
|
|
||||||
walletChanged = pyqtSignal()
|
walletChanged = pyqtSignal()
|
||||||
@@ -184,6 +187,10 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
def isMined(self):
|
def isMined(self):
|
||||||
return self._is_mined
|
return self._is_mined
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify=detailsChanged)
|
||||||
|
def isRemoved(self):
|
||||||
|
return self._is_removed
|
||||||
|
|
||||||
@pyqtProperty(str, notify=detailsChanged)
|
@pyqtProperty(str, notify=detailsChanged)
|
||||||
def mempoolDepth(self):
|
def mempoolDepth(self):
|
||||||
return self._mempool_depth
|
return self._mempool_depth
|
||||||
@@ -267,6 +274,20 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
def update(self, from_txid: bool = False):
|
def update(self, from_txid: bool = False):
|
||||||
assert self._wallet
|
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:
|
if from_txid:
|
||||||
self._tx = self._wallet.wallet.db.get_transaction(self._txid)
|
self._tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||||
assert self._tx is not None, f'unknown txid "{self._txid}"'
|
assert self._tx is not None, f'unknown txid "{self._txid}"'
|
||||||
|
|||||||
@@ -474,13 +474,15 @@ class QETxFinalizer(TxFeeSlider):
|
|||||||
|
|
||||||
|
|
||||||
class TxMonMixin(QtEventListener):
|
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.
|
requires self._wallet to contain a QEWallet instance.
|
||||||
exposes txid qt property.
|
exposes txid qt property.
|
||||||
calls get_tx() once txid is set.
|
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()
|
txMined = pyqtSignal()
|
||||||
|
txRemoved = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
self._logger.debug('TxMonMixin.__init__')
|
self._logger.debug('TxMonMixin.__init__')
|
||||||
@@ -500,6 +502,13 @@ class TxMonMixin(QtEventListener):
|
|||||||
self.tx_verified()
|
self.tx_verified()
|
||||||
self.txMined.emit()
|
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()
|
txidChanged = pyqtSignal()
|
||||||
@pyqtProperty(str, notify=txidChanged)
|
@pyqtProperty(str, notify=txidChanged)
|
||||||
def txid(self):
|
def txid(self):
|
||||||
@@ -520,6 +529,10 @@ class TxMonMixin(QtEventListener):
|
|||||||
def tx_verified(self) -> None:
|
def tx_verified(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# override
|
||||||
|
def tx_removed(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
@@ -595,6 +608,16 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
|||||||
self.oldfeeRate = self.feeRate
|
self.oldfeeRate = self.feeRate
|
||||||
self.update()
|
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):
|
def update(self):
|
||||||
if not self._txid or not self._orig_tx:
|
if not self._txid or not self._orig_tx:
|
||||||
# not initialized yet
|
# not initialized yet
|
||||||
@@ -692,6 +715,16 @@ class QETxCanceller(TxFeeSlider, TxMonMixin):
|
|||||||
self.oldfeeRate = self.feeRate
|
self.oldfeeRate = self.feeRate
|
||||||
self.update()
|
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):
|
def update(self):
|
||||||
if not self._txid or not self._orig_tx:
|
if not self._txid or not self._orig_tx:
|
||||||
# not initialized yet
|
# 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
|
fee = max(self._total_size, fee) # pay at least 1 sat/byte for combined size
|
||||||
return fee
|
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):
|
def update(self):
|
||||||
if not self._txid: # not initialized yet
|
if not self._txid: # not initialized yet
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user