1
0

integrate PSBT support natively. WIP

This commit is contained in:
SomberNight
2019-10-23 17:09:41 +02:00
parent 6d12ebabbb
commit bafe8a2fff
61 changed files with 3405 additions and 3310 deletions

View File

@@ -26,8 +26,8 @@
import sys
import copy
import datetime
import json
import traceback
import time
from typing import TYPE_CHECKING
from PyQt5.QtCore import QSize, Qt
@@ -42,11 +42,11 @@ from electrum.i18n import _
from electrum.plugin import run_hook
from electrum import simple_config
from electrum.util import bfh
from electrum.transaction import SerializationError, Transaction
from electrum.transaction import SerializationError, Transaction, PartialTransaction, PartialTxInput
from electrum.logging import get_logger
from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton,
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit)
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit, text_dialog)
if TYPE_CHECKING:
from .main_window import ElectrumWindow
@@ -60,9 +60,9 @@ _logger = get_logger(__name__)
dialogs = [] # Otherwise python randomly garbage collects the dialogs...
def show_transaction(tx, parent, *, invoice=None, desc=None, prompt_if_unsaved=False):
def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', invoice=None, desc=None, prompt_if_unsaved=False):
try:
d = TxDialog(tx, parent, invoice, desc, prompt_if_unsaved)
d = TxDialog(tx, parent=parent, invoice=invoice, desc=desc, prompt_if_unsaved=prompt_if_unsaved)
except SerializationError as e:
_logger.exception('unable to deserialize the transaction')
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
@@ -73,7 +73,7 @@ def show_transaction(tx, parent, *, invoice=None, desc=None, prompt_if_unsaved=F
class TxDialog(QDialog, MessageBoxMixin):
def __init__(self, tx: Transaction, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
'''Transactions in the wallet will show their description.
Pass desc to give a description for txs not yet in the wallet.
'''
@@ -97,7 +97,7 @@ class TxDialog(QDialog, MessageBoxMixin):
# if the wallet can populate the inputs with more info, do it now.
# as a result, e.g. we might learn an imported address tx is segwit,
# in which case it's ok to display txid
tx.add_inputs_info(self.wallet)
tx.add_info_from_wallet(self.wallet)
self.setMinimumWidth(950)
self.setWindowTitle(_("Transaction"))
@@ -123,6 +123,9 @@ class TxDialog(QDialog, MessageBoxMixin):
self.broadcast_button = b = QPushButton(_("Broadcast"))
b.clicked.connect(self.do_broadcast)
self.merge_sigs_button = b = QPushButton(_("Merge sigs from"))
b.clicked.connect(self.merge_sigs)
self.save_button = b = QPushButton(_("Save"))
save_button_disabled = not tx.is_complete()
b.setDisabled(save_button_disabled)
@@ -152,7 +155,10 @@ class TxDialog(QDialog, MessageBoxMixin):
self.export_actions_button.setPopupMode(QToolButton.InstantPopup)
# Action buttons
self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button]
self.buttons = []
if isinstance(tx, PartialTransaction):
self.buttons.append(self.merge_sigs_button)
self.buttons += [self.sign_button, self.broadcast_button, self.cancel_button]
# Transaction sharing buttons
self.sharing_buttons = [self.export_actions_button, self.save_button]
@@ -190,7 +196,7 @@ class TxDialog(QDialog, MessageBoxMixin):
self.close()
def show_qr(self):
text = bfh(str(self.tx))
text = self.tx.serialize_as_bytes()
text = base_encode(text, base=43)
try:
self.main_window.show_qrcode(text, 'Transaction', parent=self)
@@ -222,15 +228,43 @@ class TxDialog(QDialog, MessageBoxMixin):
self.saved = True
self.main_window.pop_top_level_window(self)
def export(self):
name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
if fileName:
if isinstance(self.tx, PartialTransaction):
self.tx.finalize_psbt()
if self.tx.is_complete():
name = 'signed_%s.txn' % (self.tx.txid()[0:8])
else:
name = self.wallet.basename() + time.strftime('-%Y%m%d-%H%M.psbt')
fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn;;*.psbt")
if not fileName:
return
if self.tx.is_complete(): # network tx hex
with open(fileName, "w+") as f:
f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
self.show_message(_("Transaction exported successfully"))
self.saved = True
network_tx_hex = self.tx.serialize_to_network()
f.write(network_tx_hex + '\n')
else: # if partial: PSBT bytes
assert isinstance(self.tx, PartialTransaction)
with open(fileName, "wb+") as f:
f.write(self.tx.serialize_as_bytes())
self.show_message(_("Transaction exported successfully"))
self.saved = True
def merge_sigs(self):
if not isinstance(self.tx, PartialTransaction):
return
text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
if not text:
return
tx = self.main_window.tx_from_text(text)
if not tx:
return
try:
self.tx.combine_with_other_psbt(tx)
except Exception as e:
self.show_error(_("Error combining partial transactions") + ":\n" + repr(e))
return
self.update()
def update(self):
desc = self.desc
@@ -319,19 +353,19 @@ class TxDialog(QDialog, MessageBoxMixin):
i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True)
cursor = i_text.textCursor()
for x in self.tx.inputs():
if x['type'] == 'coinbase':
for txin in self.tx.inputs():
if txin.is_coinbase():
cursor.insertText('coinbase')
else:
prevout_hash = x.get('prevout_hash')
prevout_n = x.get('prevout_n')
prevout_hash = txin.prevout.txid.hex()
prevout_n = txin.prevout.out_idx
cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext)
addr = self.wallet.get_txin_address(x)
addr = self.wallet.get_txin_address(txin)
if addr is None:
addr = ''
cursor.insertText(addr, text_format(addr))
if x.get('value'):
cursor.insertText(format_amount(x['value']), ext)
if isinstance(txin, PartialTxInput) and txin.value_sats() is not None:
cursor.insertText(format_amount(txin.value_sats()), ext)
cursor.insertBlock()
vbox.addWidget(i_text)
@@ -340,8 +374,8 @@ class TxDialog(QDialog, MessageBoxMixin):
o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True)
cursor = o_text.textCursor()
for o in self.tx.get_outputs_for_UI():
addr, v = o.address, o.value
for o in self.tx.outputs():
addr, v = o.get_ui_address_str(), o.value
cursor.insertText(addr, text_format(addr))
if v is not None:
cursor.insertText('\t', ext)