qml: first part of partially signing tx while not having txid yet
This commit is contained in:
@@ -448,7 +448,7 @@ Pane {
|
|||||||
onAccepted: {
|
onAccepted: {
|
||||||
root.rawtx = rbffeebumper.getNewTx()
|
root.rawtx = rbffeebumper.getNewTx()
|
||||||
if (txdetails.wallet.canSignWithoutCosigner) {
|
if (txdetails.wallet.canSignWithoutCosigner) {
|
||||||
txdetails.sign_and_broadcast()
|
txdetails.signAndBroadcast()
|
||||||
} else {
|
} else {
|
||||||
var dialog = app.messageDialog.createObject(app, {
|
var dialog = app.messageDialog.createObject(app, {
|
||||||
title: qsTr('Transaction fee updated.'),
|
title: qsTr('Transaction fee updated.'),
|
||||||
@@ -475,7 +475,7 @@ Pane {
|
|||||||
// replaces parent tx with cpfp tx
|
// replaces parent tx with cpfp tx
|
||||||
root.rawtx = cpfpfeebumper.getNewTx()
|
root.rawtx = cpfpfeebumper.getNewTx()
|
||||||
if (txdetails.wallet.canSignWithoutCosigner) {
|
if (txdetails.wallet.canSignWithoutCosigner) {
|
||||||
txdetails.sign_and_broadcast()
|
txdetails.signAndBroadcast()
|
||||||
} else {
|
} else {
|
||||||
var dialog = app.messageDialog.createObject(app, {
|
var dialog = app.messageDialog.createObject(app, {
|
||||||
title: qsTr('CPFP fee bump transaction created.'),
|
title: qsTr('CPFP fee bump transaction created.'),
|
||||||
@@ -501,7 +501,7 @@ Pane {
|
|||||||
onAccepted: {
|
onAccepted: {
|
||||||
root.rawtx = txcanceller.getNewTx()
|
root.rawtx = txcanceller.getNewTx()
|
||||||
if (txdetails.wallet.canSignWithoutCosigner) {
|
if (txdetails.wallet.canSignWithoutCosigner) {
|
||||||
txdetails.sign_and_broadcast()
|
txdetails.signAndBroadcast()
|
||||||
} else {
|
} else {
|
||||||
var dialog = app.messageDialog.createObject(app, {
|
var dialog = app.messageDialog.createObject(app, {
|
||||||
title: qsTr('Cancel transaction created.'),
|
title: qsTr('Cancel transaction created.'),
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
self._short_id = tx_mined_info.short_id() or ""
|
self._short_id = tx_mined_info.short_id() or ""
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def sign_and_broadcast(self):
|
def signAndBroadcast(self):
|
||||||
self._sign(broadcast=True)
|
self._sign(broadcast=True)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -320,20 +320,24 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
def _sign(self, broadcast):
|
def _sign(self, broadcast):
|
||||||
# TODO: connecting/disconnecting signal handlers here is hmm
|
# TODO: connecting/disconnecting signal handlers here is hmm
|
||||||
try:
|
try:
|
||||||
self._wallet.transactionSigned.disconnect(self.onSigned)
|
|
||||||
self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded)
|
|
||||||
if broadcast:
|
if broadcast:
|
||||||
|
self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded)
|
||||||
self._wallet.broadcastfailed.disconnect(self.onBroadcastFailed)
|
self._wallet.broadcastfailed.disconnect(self.onBroadcastFailed)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self._wallet.transactionSigned.connect(self.onSigned)
|
|
||||||
self._wallet.broadcastSucceeded.connect(self.onBroadcastSucceeded)
|
|
||||||
if broadcast:
|
if broadcast:
|
||||||
|
self._wallet.broadcastSucceeded.connect(self.onBroadcastSucceeded)
|
||||||
self._wallet.broadcastFailed.connect(self.onBroadcastFailed)
|
self._wallet.broadcastFailed.connect(self.onBroadcastFailed)
|
||||||
self._wallet.sign(self._tx, broadcast=broadcast)
|
|
||||||
|
self._wallet.sign(self._tx, broadcast=broadcast, on_success=self.on_signed_tx)
|
||||||
# side-effect: signing updates self._tx
|
# side-effect: signing updates self._tx
|
||||||
# we rely on this for broadcast
|
# we rely on this for broadcast
|
||||||
|
|
||||||
|
def on_signed_tx(self, tx: Transaction):
|
||||||
|
self._logger.debug('on_signed_tx')
|
||||||
|
self.update()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def broadcast(self):
|
def broadcast(self):
|
||||||
assert self._tx.is_complete()
|
assert self._tx.is_complete()
|
||||||
@@ -349,15 +353,6 @@ class QETxDetails(QObject, QtEventListener):
|
|||||||
|
|
||||||
self._wallet.broadcast(self._tx)
|
self._wallet.broadcast(self._tx)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def onSigned(self, txid):
|
|
||||||
if txid != self._txid:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._logger.debug('onSigned')
|
|
||||||
self._wallet.transactionSigned.disconnect(self.onSigned)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def onBroadcastSucceeded(self, txid):
|
def onBroadcastSucceeded(self, txid):
|
||||||
if txid != self._txid:
|
if txid != self._txid:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
|||||||
|
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.transaction import PartialTxOutput, PartialTransaction
|
from electrum.transaction import PartialTxOutput, PartialTransaction, Transaction
|
||||||
from electrum.util import NotEnoughFunds, profiler
|
from electrum.util import NotEnoughFunds, profiler
|
||||||
from electrum.wallet import CannotBumpFee, CannotDoubleSpendTx, CannotCPFP
|
from electrum.wallet import CannotBumpFee, CannotDoubleSpendTx, CannotCPFP
|
||||||
from electrum.network import NetworkException
|
from electrum.network import NetworkException
|
||||||
@@ -368,31 +368,18 @@ class QETxFinalizer(TxFeeSlider):
|
|||||||
self._logger.error('no valid tx')
|
self._logger.error('no valid tx')
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: f_accept handler not used
|
self._wallet.sign(self._tx, broadcast=False, on_success=self.on_signed_tx)
|
||||||
# if self.f_accept:
|
|
||||||
# self.f_accept(self._tx)
|
|
||||||
# return
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._wallet.transactionSigned.disconnect(self.onSigned)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self._wallet.transactionSigned.connect(self.onSigned)
|
|
||||||
self._wallet.sign(self._tx)
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
|
||||||
def onSigned(self, txid):
|
|
||||||
if txid != self._tx.txid():
|
|
||||||
return
|
|
||||||
|
|
||||||
self._logger.debug('onSigned')
|
|
||||||
self._wallet.transactionSigned.disconnect(self.onSigned)
|
|
||||||
|
|
||||||
|
def on_signed_tx(self, tx: Transaction):
|
||||||
|
self._logger.debug('on_signed_tx')
|
||||||
if not self._wallet.save_tx(self._tx):
|
if not self._wallet.save_tx(self._tx):
|
||||||
self._logger.error('Could not save tx')
|
self._logger.error('Could not save tx')
|
||||||
else:
|
else:
|
||||||
|
# FIXME: don't rely on txid. (non-segwit tx don't have a txid
|
||||||
|
# until tx is complete, and can't save to backend without it).
|
||||||
self.finishedSave.emit(self._tx.txid())
|
self.finishedSave.emit(self._tx.txid())
|
||||||
|
|
||||||
|
|
||||||
# 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 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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Optional, Tuple
|
from typing import TYPE_CHECKING, Optional, Tuple, Callable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, QMetaObject, Qt
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, QMetaObject, Qt
|
||||||
@@ -12,7 +12,7 @@ from electrum.i18n import _
|
|||||||
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_BROADCASTING, PR_BROADCAST
|
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID, PR_BROADCASTING, PR_BROADCAST
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.network import TxBroadcastError, BestEffortRequestFailed
|
from electrum.network import TxBroadcastError, BestEffortRequestFailed
|
||||||
from electrum.transaction import PartialTxOutput, PartialTransaction
|
from electrum.transaction import PartialTxOutput, PartialTransaction, Transaction
|
||||||
from electrum.util import parse_max_spend, InvalidPassword, event_listener, AddTransactionException, get_asyncio_loop
|
from electrum.util import parse_max_spend, InvalidPassword, event_listener, AddTransactionException, get_asyncio_loop
|
||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
from electrum.wallet import Multisig_Wallet
|
from electrum.wallet import Multisig_Wallet
|
||||||
@@ -62,7 +62,8 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
|||||||
paymentSucceeded = pyqtSignal([str], arguments=['key'])
|
paymentSucceeded = pyqtSignal([str], arguments=['key'])
|
||||||
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
|
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
|
||||||
requestNewPassword = pyqtSignal()
|
requestNewPassword = pyqtSignal()
|
||||||
transactionSigned = pyqtSignal([str], arguments=['txid'])
|
signSucceeded = pyqtSignal([str], arguments=['txid'])
|
||||||
|
signFailed = pyqtSignal([str], arguments=['message'])
|
||||||
broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
|
broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
|
||||||
broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
|
broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
|
||||||
saveTxSuccess = pyqtSignal([str], arguments=['txid'])
|
saveTxSuccess = pyqtSignal([str], arguments=['txid'])
|
||||||
@@ -486,28 +487,37 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
|||||||
self.dataChanged.emit()
|
self.dataChanged.emit()
|
||||||
|
|
||||||
@auth_protect()
|
@auth_protect()
|
||||||
def sign(self, tx, *, broadcast: bool = False):
|
def sign(self, tx, *, broadcast: bool = False, on_success: Callable[[Transaction], None] = None, on_failure: Callable[[], None] = None):
|
||||||
sign_hook = run_hook('tc_sign_wrapper', self.wallet, tx, partial(self.on_sign_complete, broadcast),
|
sign_hook = run_hook('tc_sign_wrapper', self.wallet, tx, partial(self.on_sign_complete, on_success, broadcast), partial(self.on_sign_failed, on_failure))
|
||||||
self.on_sign_failed)
|
|
||||||
if sign_hook:
|
if sign_hook:
|
||||||
self.do_sign(tx, False)
|
signSuccess = self.do_sign(tx, False)
|
||||||
self._logger.debug('plugin needs to sign tx too')
|
if signSuccess:
|
||||||
sign_hook(tx)
|
self._logger.debug('plugin needs to sign tx too')
|
||||||
return
|
sign_hook(tx)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
signSuccess = self.do_sign(tx, broadcast)
|
||||||
|
|
||||||
self.do_sign(tx, broadcast)
|
if signSuccess:
|
||||||
|
if on_success: on_success(tx)
|
||||||
|
else:
|
||||||
|
if on_failure: on_failure()
|
||||||
|
|
||||||
def do_sign(self, tx, broadcast):
|
def do_sign(self, tx, broadcast):
|
||||||
tx = self.wallet.sign_transaction(tx, self.password)
|
try:
|
||||||
|
tx = self.wallet.sign_transaction(tx, self.password)
|
||||||
|
except BaseException as e:
|
||||||
|
self._logger.error(f'{e!r}')
|
||||||
|
self.signFailed.emit(str(e))
|
||||||
|
|
||||||
if tx is None:
|
if tx is None:
|
||||||
self._logger.info('did not sign')
|
self._logger.info('did not sign')
|
||||||
return
|
return False
|
||||||
|
|
||||||
txid = tx.txid()
|
txid = tx.txid()
|
||||||
self._logger.debug(f'do_sign(), txid={txid}')
|
self._logger.debug(f'do_sign(), txid={txid}')
|
||||||
|
|
||||||
self.transactionSigned.emit(txid)
|
self.signSucceeded.emit(txid)
|
||||||
|
|
||||||
if not tx.is_complete():
|
if not tx.is_complete():
|
||||||
self._logger.debug('tx not complete')
|
self._logger.debug('tx not complete')
|
||||||
@@ -519,14 +529,19 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
|||||||
# not broadcasted, so refresh history here
|
# not broadcasted, so refresh history here
|
||||||
self.historyModel.init_model(True)
|
self.historyModel.init_model(True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
# this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok
|
# this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok
|
||||||
def on_sign_complete(self, broadcast, tx):
|
def on_sign_complete(self, broadcast, cb: Callable[[Transaction], None] = None, tx: Transaction = None):
|
||||||
self.otpSuccess.emit()
|
self.otpSuccess.emit()
|
||||||
|
if cb: cb(tx)
|
||||||
if broadcast:
|
if broadcast:
|
||||||
self.broadcast(tx)
|
self.broadcast(tx)
|
||||||
|
|
||||||
def on_sign_failed(self, error):
|
# this assumes a 2fa wallet, but there are no other tc_sign_wrapper hooks, so that's ok
|
||||||
|
def on_sign_failed(self, cb: Callable[[], None] = None, error: str = None):
|
||||||
self.otpFailed.emit('error', error)
|
self.otpFailed.emit('error', error)
|
||||||
|
if cb: cb()
|
||||||
|
|
||||||
def request_otp(self, on_submit):
|
def request_otp(self, on_submit):
|
||||||
self._otp_on_submit = on_submit
|
self._otp_on_submit = on_submit
|
||||||
|
|||||||
Reference in New Issue
Block a user