Browse Source

qml: first part of partially signing tx while not having txid yet

master
Sander van Grieken 3 years ago
parent
commit
1aa14e749a
  1. 6
      electrum/gui/qml/components/TxDetails.qml
  2. 25
      electrum/gui/qml/qetxdetails.py
  3. 27
      electrum/gui/qml/qetxfinalizer.py
  4. 47
      electrum/gui/qml/qewallet.py

6
electrum/gui/qml/components/TxDetails.qml

@ -448,7 +448,7 @@ Pane {
onAccepted: {
root.rawtx = rbffeebumper.getNewTx()
if (txdetails.wallet.canSignWithoutCosigner) {
txdetails.sign_and_broadcast()
txdetails.signAndBroadcast()
} else {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Transaction fee updated.'),
@ -475,7 +475,7 @@ Pane {
// replaces parent tx with cpfp tx
root.rawtx = cpfpfeebumper.getNewTx()
if (txdetails.wallet.canSignWithoutCosigner) {
txdetails.sign_and_broadcast()
txdetails.signAndBroadcast()
} else {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('CPFP fee bump transaction created.'),
@ -501,7 +501,7 @@ Pane {
onAccepted: {
root.rawtx = txcanceller.getNewTx()
if (txdetails.wallet.canSignWithoutCosigner) {
txdetails.sign_and_broadcast()
txdetails.signAndBroadcast()
} else {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Cancel transaction created.'),

25
electrum/gui/qml/qetxdetails.py

@ -310,7 +310,7 @@ class QETxDetails(QObject, QtEventListener):
self._short_id = tx_mined_info.short_id() or ""
@pyqtSlot()
def sign_and_broadcast(self):
def signAndBroadcast(self):
self._sign(broadcast=True)
@pyqtSlot()
@ -320,20 +320,24 @@ class QETxDetails(QObject, QtEventListener):
def _sign(self, broadcast):
# TODO: connecting/disconnecting signal handlers here is hmm
try:
self._wallet.transactionSigned.disconnect(self.onSigned)
self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded)
if broadcast:
self._wallet.broadcastSucceeded.disconnect(self.onBroadcastSucceeded)
self._wallet.broadcastfailed.disconnect(self.onBroadcastFailed)
except:
pass
self._wallet.transactionSigned.connect(self.onSigned)
self._wallet.broadcastSucceeded.connect(self.onBroadcastSucceeded)
if broadcast:
self._wallet.broadcastSucceeded.connect(self.onBroadcastSucceeded)
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
# we rely on this for broadcast
def on_signed_tx(self, tx: Transaction):
self._logger.debug('on_signed_tx')
self.update()
@pyqtSlot()
def broadcast(self):
assert self._tx.is_complete()
@ -349,15 +353,6 @@ class QETxDetails(QObject, QtEventListener):
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)
def onBroadcastSucceeded(self, txid):
if txid != self._txid:

27
electrum/gui/qml/qetxfinalizer.py

@ -5,7 +5,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from electrum.logging import get_logger
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.wallet import CannotBumpFee, CannotDoubleSpendTx, CannotCPFP
from electrum.network import NetworkException
@ -368,31 +368,18 @@ class QETxFinalizer(TxFeeSlider):
self._logger.error('no valid tx')
return
# TODO: f_accept handler not used
# 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)
self._wallet.sign(self._tx, broadcast=False, on_success=self.on_signed_tx)
def on_signed_tx(self, tx: Transaction):
self._logger.debug('on_signed_tx')
if not self._wallet.save_tx(self._tx):
self._logger.error('Could not save tx')
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())
# mixin for watching an existing TX based on its txid for verified event
# requires self._wallet to contain a QEWallet instance
# exposes txid qt property

47
electrum/gui/qml/qewallet.py

@ -2,7 +2,7 @@ import asyncio
import queue
import threading
import time
from typing import TYPE_CHECKING, Optional, Tuple
from typing import TYPE_CHECKING, Optional, Tuple, Callable
from functools import partial
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.logging import get_logger
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.plugin import run_hook
from electrum.wallet import Multisig_Wallet
@ -62,7 +62,8 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
paymentSucceeded = pyqtSignal([str], arguments=['key'])
paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
requestNewPassword = pyqtSignal()
transactionSigned = pyqtSignal([str], arguments=['txid'])
signSucceeded = pyqtSignal([str], arguments=['txid'])
signFailed = pyqtSignal([str], arguments=['message'])
broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
saveTxSuccess = pyqtSignal([str], arguments=['txid'])
@ -486,28 +487,37 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
self.dataChanged.emit()
@auth_protect()
def sign(self, tx, *, broadcast: bool = False):
sign_hook = run_hook('tc_sign_wrapper', self.wallet, tx, partial(self.on_sign_complete, broadcast),
self.on_sign_failed)
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, on_success, broadcast), partial(self.on_sign_failed, on_failure))
if sign_hook:
self.do_sign(tx, False)
self._logger.debug('plugin needs to sign tx too')
sign_hook(tx)
return
signSuccess = self.do_sign(tx, False)
if signSuccess:
self._logger.debug('plugin needs to sign tx too')
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):
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:
self._logger.info('did not sign')
return
return False
txid = tx.txid()
self._logger.debug(f'do_sign(), txid={txid}')
self.transactionSigned.emit(txid)
self.signSucceeded.emit(txid)
if not tx.is_complete():
self._logger.debug('tx not complete')
@ -519,14 +529,19 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
# not broadcasted, so refresh history here
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
def on_sign_complete(self, broadcast, tx):
def on_sign_complete(self, broadcast, cb: Callable[[Transaction], None] = None, tx: Transaction = None):
self.otpSuccess.emit()
if cb: cb(tx)
if broadcast:
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)
if cb: cb()
def request_otp(self, on_submit):
self._otp_on_submit = on_submit

Loading…
Cancel
Save