Browse Source

add support for manual coinjoins

master
SomberNight 6 years ago
parent
commit
7eb7eb8674
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 64
      electrum/gui/qt/transaction_dialog.py
  2. 22
      electrum/transaction.py

64
electrum/gui/qt/transaction_dialog.py

@ -115,7 +115,15 @@ class TxDialog(QDialog, MessageBoxMixin):
self.add_tx_stats(vbox)
vbox.addSpacing(10)
self.add_io(vbox)
self.inputs_header = QLabel()
vbox.addWidget(self.inputs_header)
self.inputs_textedit = QTextEditWithDefaultSize()
vbox.addWidget(self.inputs_textedit)
self.outputs_header = QLabel()
vbox.addWidget(self.outputs_header)
self.outputs_textedit = QTextEditWithDefaultSize()
vbox.addWidget(self.outputs_textedit)
self.sign_button = b = QPushButton(_("Sign"))
b.clicked.connect(self.sign)
@ -123,9 +131,6 @@ 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)
@ -154,10 +159,22 @@ class TxDialog(QDialog, MessageBoxMixin):
self.export_actions_button.setMenu(export_actions_menu)
self.export_actions_button.setPopupMode(QToolButton.InstantPopup)
partial_tx_actions_menu = QMenu()
ptx_merge_sigs_action = QAction(_("Merge signatures from"), self)
ptx_merge_sigs_action.triggered.connect(self.merge_sigs)
partial_tx_actions_menu.addAction(ptx_merge_sigs_action)
ptx_join_txs_action = QAction(_("Join inputs/outputs"), self)
ptx_join_txs_action.triggered.connect(self.join_tx_with_another)
partial_tx_actions_menu.addAction(ptx_join_txs_action)
self.partial_tx_actions_button = QToolButton()
self.partial_tx_actions_button.setText(_("Combine with other"))
self.partial_tx_actions_button.setMenu(partial_tx_actions_menu)
self.partial_tx_actions_button.setPopupMode(QToolButton.InstantPopup)
# Action buttons
self.buttons = []
if isinstance(tx, PartialTransaction):
self.buttons.append(self.merge_sigs_button)
self.buttons.append(self.partial_tx_actions_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]
@ -253,7 +270,9 @@ class TxDialog(QDialog, MessageBoxMixin):
def merge_sigs(self):
if not isinstance(self.tx, PartialTransaction):
return
text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
text = text_dialog(self, _('Input raw transaction'),
_("Transaction to merge signatures from") + ":",
_("Load transaction"))
if not text:
return
tx = self.main_window.tx_from_text(text)
@ -266,7 +285,26 @@ class TxDialog(QDialog, MessageBoxMixin):
return
self.update()
def join_tx_with_another(self):
if not isinstance(self.tx, PartialTransaction):
return
text = text_dialog(self, _('Input raw transaction'),
_("Transaction to join with") + " (" + _("add inputs and outputs") + "):",
_("Load transaction"))
if not text:
return
tx = self.main_window.tx_from_text(text)
if not tx:
return
try:
self.tx.join_with_other_psbt(tx)
except Exception as e:
self.show_error(_("Error joining partial transactions") + ":\n" + repr(e))
return
self.update()
def update(self):
self.update_io()
desc = self.desc
base_unit = self.main_window.base_unit()
format_amount = self.main_window.format_amount
@ -326,8 +364,8 @@ class TxDialog(QDialog, MessageBoxMixin):
self.size_label.setText(size_str)
run_hook('transaction_dialog_update', self)
def add_io(self, vbox):
vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
def update_io(self):
self.inputs_header.setText(_("Inputs") + ' (%d)'%len(self.tx.inputs()))
ext = QTextCharFormat()
rec = QTextCharFormat()
rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))
@ -349,7 +387,8 @@ class TxDialog(QDialog, MessageBoxMixin):
def format_amount(amt):
return self.main_window.format_amount(amt, whitespaces=True)
i_text = QTextEditWithDefaultSize()
i_text = self.inputs_textedit
i_text.clear()
i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True)
cursor = i_text.textCursor()
@ -368,9 +407,9 @@ class TxDialog(QDialog, MessageBoxMixin):
cursor.insertText(format_amount(txin.value_sats()), ext)
cursor.insertBlock()
vbox.addWidget(i_text)
vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
o_text = QTextEditWithDefaultSize()
self.outputs_header.setText(_("Outputs") + ' (%d)'%len(self.tx.outputs()))
o_text = self.outputs_textedit
o_text.clear()
o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True)
cursor = o_text.textCursor()
@ -381,7 +420,6 @@ class TxDialog(QDialog, MessageBoxMixin):
cursor.insertText('\t', ext)
cursor.insertText(format_amount(v), ext)
cursor.insertBlock()
vbox.addWidget(o_text)
def add_tx_stats(self, vbox):
hbox_stats = QHBoxLayout()

22
electrum/transaction.py

@ -36,6 +36,7 @@ from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable,
Callable, List, Dict, Set, TYPE_CHECKING)
from collections import defaultdict
from enum import IntEnum
import itertools
from . import ecc, bitcoin, constants, segwit_addr, bip32
from .bip32 import BIP32Node
@ -1537,6 +1538,27 @@ class PartialTransaction(Transaction):
txout.combine_with_other_txout(other_txout)
self.invalidate_ser_cache()
def join_with_other_psbt(self, other_tx: 'PartialTransaction') -> None:
"""Adds inputs and outputs from other_tx into this one."""
if not isinstance(other_tx, PartialTransaction):
raise Exception('Can only join partial transactions.')
# make sure there are no duplicate prevouts
prevouts = set()
for txin in itertools.chain(self.inputs(), other_tx.inputs()):
prevout_str = txin.prevout.to_str()
if prevout_str in prevouts:
raise Exception(f"Duplicate inputs! "
f"Transactions that spend the same prevout cannot be joined.")
prevouts.add(prevout_str)
# copy global PSBT section
self.xpubs.update(other_tx.xpubs)
self._unknown.update(other_tx._unknown)
# copy and add inputs and outputs
self.add_inputs(list(other_tx.inputs()))
self.add_outputs(list(other_tx.outputs()))
self.remove_signatures()
self.invalidate_ser_cache()
def inputs(self) -> Sequence[PartialTxInput]:
return self._inputs

Loading…
Cancel
Save