Browse Source

support Replace-By-Fee in GUI

283
ThomasV 9 years ago
parent
commit
bc1bef60a0
  1. 13
      gui/qt/history_widget.py
  2. 46
      gui/qt/main_window.py
  3. 23
      lib/wallet.py

13
gui/qt/history_widget.py

@ -117,11 +117,16 @@ class HistoryWidget(MyTreeWidget):
if not tx_hash: if not tx_hash:
return return
tx_URL = block_explorer_URL(self.config, 'tx', tx_hash) tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
if not tx_URL: conf, timestamp = self.wallet.get_confirmations(tx_hash)
return tx = self.wallet.transactions.get(tx_hash)
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
rbf = is_mine and (conf == 0) and tx and not tx.is_final()
menu = QMenu() menu = QMenu()
menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash)) menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash))
menu.addAction(_("Details"), lambda: self.parent.show_transaction(self.wallet.transactions.get(tx_hash))) menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
menu.addAction(_("Edit description"), lambda: self.editItem(item, self.editable_columns[0])) menu.addAction(_("Edit description"), lambda: self.editItem(item, self.editable_columns[0]))
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) if rbf:
menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx))
if tx_URL:
menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec_(self.viewport().mapToGlobal(position))

46
gui/qt/main_window.py

@ -2785,6 +2785,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
qr_combo.currentIndexChanged.connect(on_video_device) qr_combo.currentIndexChanged.connect(on_video_device)
gui_widgets.append((qr_label, qr_combo)) gui_widgets.append((qr_label, qr_combo))
rbf_cb = QCheckBox(_('Enable Replace-By-Fee'))
rbf_cb.setChecked(self.wallet.use_rbf)
if not self.config.is_modifiable('use_rbf'):
rbf_cb.setEnabled(False)
def on_rbf(x):
rbf_result = x == Qt.Checked
if self.wallet.use_rbf != rbf_result:
self.wallet.use_rbf = rbf_result
self.wallet.storage.put('use_rbf', self.wallet.use_rbf)
rbf_cb.stateChanged.connect(on_rbf)
rbf_cb.setToolTip(_('Enable RBF'))
tx_widgets.append((rbf_cb, None))
usechange_cb = QCheckBox(_('Use change addresses')) usechange_cb = QCheckBox(_('Use change addresses'))
usechange_cb.setChecked(self.wallet.use_change) usechange_cb.setChecked(self.wallet.use_change)
if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False) if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
@ -2796,6 +2809,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
multiple_cb.setEnabled(self.wallet.use_change) multiple_cb.setEnabled(self.wallet.use_change)
usechange_cb.stateChanged.connect(on_usechange) usechange_cb.stateChanged.connect(on_usechange)
usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.')) usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
tx_widgets.append((usechange_cb, None))
def on_multiple(x): def on_multiple(x):
multiple = x == Qt.Checked multiple = x == Qt.Checked
@ -2812,7 +2826,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
])) ]))
multiple_cb.setChecked(multiple_change) multiple_cb.setChecked(multiple_change)
multiple_cb.stateChanged.connect(on_multiple) multiple_cb.stateChanged.connect(on_multiple)
tx_widgets.append((usechange_cb, None))
tx_widgets.append((multiple_cb, None)) tx_widgets.append((multiple_cb, None))
showtx_cb = QCheckBox(_('View transaction before signing')) showtx_cb = QCheckBox(_('View transaction before signing'))
@ -2973,20 +2986,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def show_account_details(self, k): def show_account_details(self, k):
account = self.wallet.accounts[k] account = self.wallet.accounts[k]
d = WindowModalDialog(self, _('Account Details')) d = WindowModalDialog(self, _('Account Details'))
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
name = self.wallet.get_account_name(k) name = self.wallet.get_account_name(k)
label = QLabel('Name: ' + name) label = QLabel('Name: ' + name)
vbox.addWidget(label) vbox.addWidget(label)
vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type())) vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
vbox.addWidget(QLabel(_('Derivation') + ': ' + k)) vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
vbox.addWidget(QLabel(_('Master Public Key:'))) vbox.addWidget(QLabel(_('Master Public Key:')))
text = QTextEdit() text = QTextEdit()
text.setReadOnly(True) text.setReadOnly(True)
text.setMaximumHeight(170) text.setMaximumHeight(170)
@ -2995,3 +3002,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
text.setText(mpk_text) text.setText(mpk_text)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.exec_() d.exec_()
def bump_fee_dialog(self, tx):
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
fee = -fee
d = WindowModalDialog(self, _('Bump Fee'))
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(_('Current fee') + ': %s'% self.format_amount(fee) + ' ' + self.base_unit()))
vbox.addWidget(QLabel(_('New Fee') + ': '))
e = BTCAmountEdit(self.get_decimal_point)
e.setAmount(fee + self.wallet.relayfee())
vbox.addWidget(e)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_():
return
new_fee = e.get_amount()
delta = new_fee - fee
if delta < 0:
self.show_error("fee too low")
return
try:
new_tx = self.wallet.bump_fee(tx, delta)
except BaseException as e:
self.show_error(e)
return
self.show_transaction(new_tx)

23
lib/wallet.py

@ -172,15 +172,17 @@ class Abstract_Wallet(PrintError):
self.gap_limit_for_change = 6 # constant self.gap_limit_for_change = 6 # constant
# saved fields # saved fields
self.seed_version = storage.get('seed_version', NEW_SEED_VERSION) self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
self.use_change = storage.get('use_change',True) self.use_change = storage.get('use_change', True)
self.multiple_change = storage.get('multiple_change', False) self.multiple_change = storage.get('multiple_change', False)
self.use_encryption = storage.get('use_encryption', False) self.use_encryption = storage.get('use_encryption', False)
self.use_rbf = storage.get('use_rbf', False)
self.seed = storage.get('seed', '') # encrypted self.seed = storage.get('seed', '') # encrypted
self.labels = storage.get('labels', {}) self.labels = storage.get('labels', {})
self.frozen_addresses = set(storage.get('frozen_addresses',[])) self.frozen_addresses = set(storage.get('frozen_addresses',[]))
self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode) self.stored_height = storage.get('stored_height', 0) # last known height (for offline mode)
self.history = storage.get('addr_history',{}) # address -> list(txid, height) self.history = storage.get('addr_history',{}) # address -> list(txid, height)
# imported_keys is deprecated. The GUI should call convert_imported_keys # imported_keys is deprecated. The GUI should call convert_imported_keys
self.imported_keys = self.storage.get('imported_keys',{}) self.imported_keys = self.storage.get('imported_keys',{})
@ -988,6 +990,7 @@ class Abstract_Wallet(PrintError):
def add_input_info(self, txin): def add_input_info(self, txin):
address = txin['address'] address = txin['address']
txin['sequence'] = 0 if self.use_rbf else 0xffffffff
account_id, sequence = self.get_address_index(address) account_id, sequence = self.get_address_index(address)
account = self.accounts[account_id] account = self.accounts[account_id]
redeemScript = account.redeem_script(*sequence) redeemScript = account.redeem_script(*sequence)
@ -998,7 +1001,6 @@ class Abstract_Wallet(PrintError):
txin['pubkeys'] = list(pubkeys) txin['pubkeys'] = list(pubkeys)
txin['x_pubkeys'] = list(x_pubkeys) txin['x_pubkeys'] = list(x_pubkeys)
txin['signatures'] = [None] * len(pubkeys) txin['signatures'] = [None] * len(pubkeys)
if redeemScript: if redeemScript:
txin['redeemScript'] = redeemScript txin['redeemScript'] = redeemScript
txin['num_sig'] = account.m txin['num_sig'] = account.m
@ -1176,6 +1178,23 @@ class Abstract_Wallet(PrintError):
age = tx_age age = tx_age
return age > age_limit return age > age_limit
def bump_fee(self, tx, delta):
if tx.is_final():
raise BaseException("cannot bump fee: transaction is final")
inputs = copy.deepcopy(tx.inputs())
outputs = copy.deepcopy(tx.outputs())
for txin in inputs:
txin['signatures'] = [None] * len(txin['signatures'])
for i, o in enumerate(outputs):
otype, address, value = o
if self.is_mine(address) and value >= delta:
outputs[i] = otype, address, value - delta
break
else:
raise BaseException("cannot bump fee")
new_tx = Transaction.from_io(inputs, outputs)
return new_tx
def can_sign(self, tx): def can_sign(self, tx):
if self.is_watching_only(): if self.is_watching_only():
return False return False

Loading…
Cancel
Save