diff --git a/balqt/amountedit.py b/balqt/amountedit.py deleted file mode 100644 index cc77ba6..0000000 --- a/balqt/amountedit.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Union -from decimal import Decimal - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy) - from PyQt5.QtGui import QPalette, QPainter - from PyQt5.QtCore import pyqtSignal, Qt, QSize -else: - from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy) - from PyQt6.QtGui import QPalette, QPainter - from PyQt6.QtCore import pyqtSignal, Qt, QSize - - -from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_name, - FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT, UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE) - -from electrum.gui.qt.amountedit import BTCAmountEdit, char_width_in_lineedit, ColorScheme - -_NOT_GIVEN = object() # sentinel value - - -class PercAmountEdit(BTCAmountEdit): - def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN): - super().__init__(decimal_point, is_int, parent, max_amount=max_amount) - - def numbify(self): - text = self.text().strip() - if text == '!': - self.shortcut.emit() - return - pos = self.cursorPosition() - chars = '0123456789%' - chars += DECIMAL_POINT - - s = ''.join([i for i in text if i in chars]) - - if '%' in s: - self.is_perc=True - s=s.replace('%','') - else: - self.is_perc=False - - if DECIMAL_POINT in s: - p = s.find(DECIMAL_POINT) - s = s.replace(DECIMAL_POINT, '') - s = s[:p] + DECIMAL_POINT + s[p:p+8] - if self.is_perc: - s+='%' - - - #if self.max_amount: - # if (amt := self._get_amount_from_text(s)) and amt >= self.max_amount: - # s = self._get_text_from_amount(self.max_amount) - self.setText(s) - # setText sets Modified to False. Instead we want to remember - # if updates were because of user modification. - self.setModified(self.hasFocus()) - self.setCursorPosition(pos) - #if len(s>0) - # self.drawText("") - - def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]: - try: - text = text.replace(DECIMAL_POINT, '.') - text = text.replace('%', '') - return (Decimal)(text) - except Exception: - return None - - def _get_text_from_amount(self, amount): - out = super()._get_text_from_amount(amount) - if self.is_perc: out+='%' - return out - - def paintEvent(self, event): - QLineEdit.paintEvent(self, event) - if self.base_unit: - panel = QStyleOptionFrame() - self.initStyleOption(panel) - textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self) - textRect.adjust(2, 0, -10, 0) - painter = QPainter(self) - painter.setPen(ColorScheme.GRAY.as_color()) - if len(self.text())==0: - painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit() + " or perc value") - diff --git a/balqt/baldialog.py b/balqt/baldialog.py deleted file mode 100644 index 86dacfb..0000000 --- a/balqt/baldialog.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import Callable,Any - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtCore import Qt,pyqtSignal - from PyQt5.QtWidgets import QLabel, QVBoxLayout, QCheckBox -else: - from PyQt6.QtCore import Qt,pyqtSignal - from PyQt6.QtWidgets import QLabel, QVBoxLayout, QCheckBox - -from electrum.gui.qt.util import WindowModalDialog, TaskThread -from electrum.i18n import _ -from electrum.logging import get_logger - -_logger = get_logger(__name__) - -class BalDialog(WindowModalDialog): - - def __init__(self,parent,title=None, icon = 'bal32x32.png'): - self.parent=parent - WindowModalDialog.__init__(self,self.parent,title) - self.setWindowIcon(qt_resources.read_QIcon(icon)) - -class BalWaitingDialog(BalDialog): - updatemessage=pyqtSignal([str], arguments=['message']) - def __init__(self, bal_window: 'BalWindow', message: str, task, on_success=None, on_error=None, on_cancel=None,exe=True): - assert bal_window - BalDialog.__init__(self, bal_window.window, _("Please wait")) - self.message_label = QLabel(message) - vbox = QVBoxLayout(self) - vbox.addWidget(self.message_label) - self.updatemessage.connect(self.update_message) - if on_cancel: - self.cancel_button = CancelButton(self) - self.cancel_button.clicked.connect(on_cancel) - vbox.addLayout(Buttons(self.cancel_button)) - self.accepted.connect(self.on_accepted) - self.task=task - self.on_success = on_success - self.on_error = on_error - self.on_cancel = on_cancel - if exe: - self.exe() - - def exe(self): - self.thread = TaskThread(self) - self.thread.finished.connect(self.deleteLater) # see #3956 - self.thread.finished.connect(self.finished) - self.thread.add(self.task, self.on_success, self.accept, self.on_error) - self.exec() - - def hello(self): - pass - def finished(self): - _logger.info("finished") - def wait(self): - self.thread.wait() - - def on_accepted(self): - self.thread.stop() - def update_message(self,msg): - self.message_label.setText(msg) - - def update(self, msg): - self.updatemessage.emit(msg) - - def getText(self): - return self.message_label.text() - - def closeEvent(self,event): - self.thread.stop() - - - -class BalBlockingWaitingDialog(BalDialog): - def __init__(self, bal_window: 'BalWindow', message: str, task: Callable[[], Any]): - BalDialog.__init__(self, bal_window, _("Please wait")) - self.message_label = QLabel(message) - vbox = QVBoxLayout(self) - vbox.addWidget(self.message_label) - self.finished.connect(self.deleteLater) # see #3956 - # show popup - self.show() - # refresh GUI; needed for popup to appear and for message_label to get drawn - QCoreApplication.processEvents() - QCoreApplication.processEvents() - try: - # block and run given task - task() - finally: - # close popup - self.accept() - -class bal_checkbox(QCheckBox): - def __init__(self, plugin,variable,window=None): - QCheckBox.__init__(self) - self.setChecked(plugin.config_get(variable)) - def on_check(v): - plugin.config.set_key(variable, v == 2) - plugin.config_get(variable) - self.stateChanged.connect(on_check) - diff --git a/balqt/closedialog.py b/balqt/closedialog.py deleted file mode 100644 index 595129d..0000000 --- a/balqt/closedialog.py +++ /dev/null @@ -1,384 +0,0 @@ -from .baldialog import BalDialog -from . import qt_resources - -if qt_resources.QT_VERSION == 5: - from PyQt5.QtCore import Qt - from PyQt5.QtWidgets import QLabel, QVBoxLayout, QCheckBox,QWidget - from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject,QEventLoop -else: - from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject,QEventLoop - from PyQt6.QtCore import Qt - from PyQt6.QtWidgets import QLabel, QVBoxLayout, QCheckBox, QWidget -import time -from electrum.i18n import _ -from electrum.gui.qt.util import WindowModalDialog, TaskThread -from electrum.network import Network,TxBroadcastError, BestEffortRequestFailed -from electrum.logging import get_logger - - -from functools import partial -import copy - -from .. import util as Util -from .. import will as Will - -from .. import willexecutors as Willexecutors -_logger = get_logger(__name__) - - -class BalCloseDialog(BalDialog): - updatemessage=pyqtSignal() - def __init__(self,bal_window): - BalDialog.__init__(self,bal_window.window,"Closing BAL") - self.updatemessage.connect(self.update) - self.bal_window=bal_window - self.message_label = QLabel("Closing BAL:") - self.vbox = QVBoxLayout(self) - self.vbox.addWidget(self.message_label) - self.qwidget=QWidget() - self.vbox.addWidget(self.qwidget) - self.labels=[] - #self.checking.connect(self.msg_set_checking) - #self.invalidating.connect(self.msg_set_invalidating) - #self.building.connect(self.msg_set_building) - #self.signing.connect(self.msg_set_signing) - #self.pushing.connect(self.msg_set_pushing) - - #self.askpassword.connect(self.ask_password) - #self.passworddone.connect(self.password_done) - self.check_row = None - self.inval_row = None - self.build_row = None - self.sign_row = None - self.push_row = None - self.network = Network.get_instance() - self._stopping = False - self.thread = TaskThread(self) - self.thread.finished.connect(self.task_finished) # see #3956 - def task_finished(self): - pass - #_logger.trace("task finished") - - def close_plugin_task(self): - _logger.debug("close task to be started") - self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1) - self.show() - self.exec() - - def task_phase1(self): - _logger.debug("close plugin phase 1 started") - try: - self.bal_window.init_class_variables() - except Will.NoHeirsException: - return False, None - self.msg_set_status("checking variables","Waiting") - try: - Will.check_amounts(self.bal_window.heirs,self.bal_window.willexecutors,self.bal_window.window.wallet.get_utxos(),self.bal_window.date_to_check,self.bal_window.window.wallet.dust_threshold()) - except Will.AmountException: - self.msg_edit_row(''+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+"")) - #self.bal_window.show_warning(_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"),parent=self) - - self.msg_set_checking() - have_to_build=False - try: - self.bal_window.check_will() - self.msg_set_checking('Ok') - except Will.WillExpiredException as e: - self.msg_set_checking("Expired") - fee_per_byte=self.bal_window.will_settings.get('tx_fees',1) - return None, Will.invalidate_will(self.bal_window.willitems,self.bal_window.wallet,fee_per_byte) - except Will.NoHeirsException: - self.msg_set_checking("No Heirs") - except Will.NotCompleteWillException as e: - message = False - have_to_build=True - if isinstance(e,Will.HeirChangeException): - message ="Heirs changed:" - elif isinstance(e,Will.WillExecutorNotPresent): - message = "Will-Executor not present" - elif isinstance(e,Will.WillexecutorChangeException): - message = "Will-Executor changed" - elif isinstance(e,Will.TxFeesChangedException): - message = "Txfees are changed" - elif isinstance(e,Will.HeirNotFoundException): - message = "Heir not found" - if message: - self.msg_set_checking(message) - else: - self.msg_set_checking("New") - - if have_to_build: - self.msg_set_building() - try: - self.bal_window.build_will() - self.bal_window.check_will() - for wid in Will.only_valid(self.bal_window.willitems): - self.bal_window.wallet.set_label(wid,"BAL Transaction") - self.msg_set_building("Ok") - except Exception as e: - self.msg_set_building(self.msg_error(e)) - return False,None - have_to_sign = False - for wid in Will.only_valid(self.bal_window.willitems): - if not self.bal_window.willitems[wid].get_status("COMPLETE"): - have_to_sign = True - break - return have_to_sign, None - - def on_accept(self): - pass - - def on_accept_phase2(self): - pass - - def on_error_push(self): - pass - - def wait(self,secs): - wait_row=None - for i in range(secs,0,-1): - if self._stopping: - return - wait_row = self.msg_edit_row(f"Please wait {i}secs", wait_row) - time.sleep(1) - self.msg_del_row(wait_row) - - def loop_broadcast_invalidating(self,tx): - self.msg_set_invalidating("Broadcasting") - try: - tx.add_info_from_wallet(self.bal_window.wallet) - self.network.run_from_another_thread(tx.add_info_from_network(self.network)) - txid = self.network.run_from_another_thread(self.network.broadcast_transaction(tx,timeout=120),timeout=120) - self.msg_set_invalidating("Ok") - if not txid: - _logger.debug(f"should not be none txid: {txid}") - - - except TxBroadcastError as e: - _logger.error(e) - msg = e.get_message_for_gui() - self.msg_set_invalidating(self.msg_error(msg)) - except BestEffortRequestFailed as e: - self.msg_set_invalidating(self.msg_error(e)) - # self.loop_broadcast_invalidating(tx) - - def loop_push(self): - self.msg_set_pushing("Broadcasting") - retry = False - try: - willexecutors=Willexecutors.get_willexecutor_transactions(self.bal_window.willitems) - for url,willexecutor in willexecutors.items(): - try: - if Willexecutors.is_selected(self.bal_window.willexecutors.get(url)): - _logger.debug(f"{url}: {willexecutor}") - if not Willexecutors.push_transactions_to_willexecutor(willexecutor): - for wid in willexecutor['txsids']: - self.bal_window.willitems[wid].set_status('PUSH_FAIL',True) - retry=True - else: - for wid in willexecutor['txsids']: - self.bal_window.willitems[wid].set_status('PUSHED',True) - except Willexecutors.AlreadyPresentException: - for wid in willexecutor['txsids']: - row = self.msg_edit_row("checking {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid, "Waiting")) - self.bal_window.willitems[wid].check_willexecutor() - row = self.msg_edit_row("checked {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid,self.bal_window.willitems[wid].get_status("CHECKED" )),row) - - - except Exception as e: - - _logger.error(e) - raise e - if retry: - raise Exception("retry") - - except Exception as e: - self.msg_set_pushing(self.msg_error(e)) - self.wait(10) - if not self._stopping: - self.loop_push() - - - def invalidate_task(self,tx,password): - _logger.debug(f"invalidate tx: {tx}") - tx = self.bal_window.wallet.sign_transaction(tx,password) - try: - if tx: - if tx.is_complete(): - self.loop_broadcast_invalidating(tx) - self.wait(5) - else: - raise - else: - raise - except Exception as e: - self.msg_set_invalidating("Error") - raise Exception("Impossible to sign") - def on_success_invalidate(self,success): - self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1) - def on_error(self,error): - _logger.error(error) - pass - def on_success_phase1(self,result): - self.have_to_sign,tx = list(result) - #if have_to_sign is False and tx is None: - #self._stopping=True - #self.on_success_phase2() - # return - _logger.debug("have to sign {}".format(self.have_to_sign)) - password=None - if self.have_to_sign is None: - self.msg_set_invalidating() - #need to sign invalidate and restart phase 1 - - password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self.bal_window.window) - if password is False: - self.msg_set_invalidating("Aborted") - self.wait(3) - self.close() - return - self.thread.add(partial(self.invalidate_task,tx,password),on_success=self.on_success_invalidate, on_done=self.on_accept, on_error=self.on_error) - - return - - elif self.have_to_sign: - password = self.bal_window.get_wallet_password("Sign your will",parent=self.bal_window.window) - if password is False: - self.msg_set_signing('Aborted') - else: - self.msg_set_signing('Nothing to do') - self.thread.add(partial(self.task_phase2,password),on_success=self.on_success_phase2,on_done=self.on_accept_phase2,on_error=self.on_error_phase2) - return - - def on_success_phase2(self,arg=False): - self.thread.stop() - self.bal_window.save_willitems() - self.msg_edit_row("Finished") - self.close() - - def closeEvent(self,event): - self._stopping=True - self.thread.stop() - - def task_phase2(self,password): - if self.have_to_sign: - try: - if txs:=self.bal_window.sign_transactions(password): - for txid,tx in txs.items(): - self.bal_window.willitems[txid].tx = copy.deepcopy(tx) - self.bal_window.save_willitems() - self.msg_set_signing("Ok") - except Exception as e: - self.msg_set_signing(self.msg_error(e)) - - self.msg_set_pushing() - have_to_push = False - for wid in Will.only_valid(self.bal_window.willitems): - w=self.bal_window.willitems[wid] - if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"): - have_to_push = True - if not have_to_push: - self.msg_set_pushing("Nothing to do") - else: - try: - self.loop_push() - self.msg_set_pushing("Ok") - - except Exception as e: - self.msg_set_pushing(self.msg_error(e)) - self.msg_edit_row("Ok") - self.wait(5) - - def on_error_phase1(self,error): - _logger.error(f"error phase1: {error}") - - def on_error_phase2(self,error): - _logger.error("error phase2: { error}") - - - def msg_set_checking(self, status = None, row = None): - row = self.check_row if row is None else row - self.check_row = self.msg_set_status("Checking your will", row, status) - - def msg_set_invalidating(self, status = None, row = None): - row = self.inval_row if row is None else row - self.inval_row = self.msg_set_status("Invalidating old will", self.inval_row, status) - - def msg_set_building(self, status = None, row = None): - row = self.build_row if row is None else row - self.build_row = self.msg_set_status("Building your will", self.build_row, status) - - def msg_set_signing(self, status = None, row = None): - row = self.sign_row if row is None else row - self.sign_row = self.msg_set_status("Signing your will", self.sign_row, status) - - def msg_set_pushing(self, status = None, row = None): - row = self.push_row if row is None else row - self.push_row = self.msg_set_status("Broadcasting your will to executors", self.push_row, status) - - def msg_set_waiting(self, status = None, row = None): - row = self.wait_row if row is None else row - self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row) - - def msg_error(self,e): - return "Error: {}".format(e) - - def msg_set_status(self,msg,row,status=None): - status= "Wait" if status is None else status - line="{}:\t{}".format(_(msg), status) - return self.msg_edit_row(line,row) - - #return v$msg_edit_row("{}:\t{}".format(_(msg), status), row) - - def ask_password(self,msg=None): - self.password=self.bal_window.get_wallet_password(msg,parent=self) - - def msg_edit_row(self,line,row=None): - _logger.debug(f"{row},{line}") - - #msg=self.get_text() - #rows=msg.split("\n") - #try: - # rows[row]=line - #except Exception as e: - # rows.append(line) - #row=len(rows)-1 - #self.update("\n".join(rows)) - - #return row - - #def msg_edit_label(self,line,row=None): - #_logger.trace(f"{row},{line}") - - #msg=self.get_text() - #rows=msg.split("\n") - try: - self.labels[row]=line - except Exception as e: - self.labels.append(line) - row=len(self.labels)-1 - - self.updatemessage.emit() - - return row - - def msg_del_row(self,row): - #_logger.trace(f"del row: {row}") - try: - del self.labels[row] - except Exception as e: - pass - self.updatemessage.emit() - - def update(self): - self.vbox.removeWidget(self.qwidget) - self.qwidget=QWidget(self) - labelsbox = QVBoxLayout(self.qwidget) - for label in self.labels: - labelsbox.addWidget(QLabel(label)) - self.vbox.addWidget(self.qwidget) - - def get_text(self): - return self.message_label.text() -def ThreadStopped(Exception): - pass diff --git a/balqt/heir_list.py b/balqt/heir_list.py deleted file mode 100644 index cb2fa50..0000000 --- a/balqt/heir_list.py +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2015 Thomas Voegtlin -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import enum -from typing import TYPE_CHECKING -from datetime import datetime - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtGui import QStandardItemModel, QStandardItem - from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex - from PyQt5.QtWidgets import (QAbstractItemView, QMenu,QWidget,QHBoxLayout,QLabel,QSpinBox,QPushButton) -else: - from PyQt6.QtGui import QStandardItemModel, QStandardItem - from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex - from PyQt6.QtWidgets import (QAbstractItemView, QMenu,QWidget,QHBoxLayout,QLabel,QSpinBox,QPushButton) - -from electrum.i18n import _ -from electrum.bitcoin import is_address -from electrum.util import block_explorer_URL -from electrum.plugin import run_hook -from electrum.gui.qt.util import webopen, MessageBoxMixin,HelpButton -from electrum.gui.qt.my_treeview import MyTreeView, MySortModel - -from .. import util as Util -from .locktimeedit import HeirsLockTimeEdit -if TYPE_CHECKING: - from electrum.gui.qt.main_window import ElectrumWindow - - -class HeirList(MyTreeView,MessageBoxMixin): - - class Columns(MyTreeView.BaseColumnsEnum): - NAME = enum.auto() - ADDRESS = enum.auto() - AMOUNT = enum.auto() - - headers = { - Columns.NAME: _('Name'), - Columns.ADDRESS: _('Address'), - Columns.AMOUNT: _('Amount'), - #Columns.LOCKTIME:_('LockTime'), - } - filter_columns = [Columns.NAME, Columns.ADDRESS] - - ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000 - - ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001 - key_role = ROLE_HEIR_KEY - - def __init__(self, bal_window: 'BalWindow'): - super().__init__( - parent=bal_window.window, - main_window=bal_window.window, - stretch_column=self.Columns.NAME, - editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT], - ) - self.decimal_point = bal_window.bal_plugin.config.get_decimal_point() - self.bal_window = bal_window - - try: - self.setModel(QStandardItemModel(self)) - self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) - self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - except: - pass - #self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) - #self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - - self.setSortingEnabled(True) - self.std_model = self.model() - - self.update() - - - def on_edited(self, idx, edit_key, *, text): - original = prior_name = self.bal_window.heirs.get(edit_key) - if not prior_name: - return - col = idx.column() - try: - #if col == 3: - # try: - # text = Util.str_to_locktime(text) - # except: - # print("not a valid locktime") - # pass - if col == 2: - text = Util.encode_amount(text,self.decimal_point) - elif col == 0: - self.bal_window.delete_heirs([edit_key]) - edit_key = text - prior_name[col-1] = text - prior_name.insert(0,edit_key) - prior_name = tuple(prior_name) - except Exception as e: - #print("eccezione tupla",e) - prior_name = (edit_key,)+prior_name[:col-1]+(text,)+prior_name[col:] - #print("prior_name",prior_name,original) - - try: - self.bal_window.set_heir(prior_name) - #print("setheir") - except Exception as e: - pass - - #print("heir non valido ripristino l'originale",e) - try: - #print("setup_original",(edit_key,)+original) - self.bal_window.set_heir((edit_key,)+original) - except Exception as e: - #print("errore nellimpostare original",e,original) - self.update() - - def create_menu(self, position): - menu = QMenu() - idx = self.indexAt(position) - column = idx.column() or self.Columns.NAME - selected_keys = [] - for s_idx in self.selected_in_column(self.Columns.NAME): - #print(s_idx) - sel_key = self.model().itemFromIndex(s_idx).data(0) - selected_keys.append(sel_key) - if selected_keys and idx.isValid(): - column_title = self.model().horizontalHeaderItem(column).text() - column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() - for s_idx in self.selected_in_column(column)) - menu.addAction(_("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(column_data, title=column_title)) - if column in self.editable_columns: - item = self.model().itemFromIndex(idx) - if item.isEditable(): - # would not be editable if openalias - persistent = QPersistentModelIndex(idx) - menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p))) - menu.addAction(_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys)) - menu.exec(self.viewport().mapToGlobal(position)) - - def update(self): - if self.maybe_defer_update(): - return - current_key = self.get_role_data_for_current_item(col=self.Columns.NAME, role=self.ROLE_HEIR_KEY) - self.model().clear() - self.update_headers(self.__class__.headers) - set_current = None - for key in sorted(self.bal_window.heirs.keys()): - heir = self.bal_window.heirs[key] - labels = [""] * len(self.Columns) - labels[self.Columns.NAME] = key - labels[self.Columns.ADDRESS] = heir[0] - labels[self.Columns.AMOUNT] = Util.decode_amount(heir[1],self.decimal_point) - #labels[self.Columns.LOCKTIME] = str(Util.locktime_to_str(heir[2])) - - items = [QStandardItem(x) for x in labels] - items[self.Columns.NAME].setEditable(True) - items[self.Columns.ADDRESS].setEditable(True) - items[self.Columns.AMOUNT].setEditable(True) - #items[self.Columns.LOCKTIME].setEditable(True) - items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY+1) - items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY+2) - items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY+3) - #items[self.Columns.LOCKTIME].setData(key, self.ROLE_HEIR_KEY+4) - - self.model().insertRow(self.model().rowCount(), items) - - if key == current_key: - idx = self.model().index(row_count, self.Columns.NAME) - set_current = QPersistentModelIndex(idx) - self.set_current_idx(set_current) - # FIXME refresh loses sort order; so set "default" here: - self.filter() - run_hook('update_heirs_tab', self) - - def refresh_row(self, key, row): - # nothing to update here - pass - - def get_edit_key_from_coordinate(self, row, col): - #print("role_data",self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY)) - #print(col) - return self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col+1) - return col - - def create_toolbar(self, config): - toolbar, menu = self.create_toolbar_with_menu('') - menu.addAction(_("&New Heir"), self.bal_window.new_heir_dialog) - menu.addAction(_("Import"), self.bal_window.import_heirs) - menu.addAction(_("Export"), lambda: self.bal_window.export_heirs()) - #menu.addAction(_("Build Traonsactions"), self.build_transactions) - - self.heir_locktime = HeirsLockTimeEdit(self.window(),0) - def on_heir_locktime(): - if not self.heir_locktime.get_locktime(): - self.heir_locktime.set_locktime('1y') - self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y" - self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True) - self.heir_locktime.valueEdited.connect(on_heir_locktime) - - self.heir_threshold = HeirsLockTimeEdit(self,0) - def on_heir_threshold(): - if not self.heir_threshold.get_locktime(): - self.heir_threshold.set_locktime('180d') - - self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime() - self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True) - self.heir_threshold.valueEdited.connect(on_heir_threshold) - - self.heir_tx_fees = QSpinBox() - self.heir_tx_fees.setMinimum(1) - self.heir_tx_fees.setMaximum(10000) - def on_heir_tx_fees(): - if not self.heir_tx_fees.value(): - self.heir_tx_fees.set_value(1) - self.bal_window.will_settings['tx_fees'] = self.heir_tx_fees.value() - self.bal_window.bal_plugin.config.set_key('will_settings',self.bal_window.will_settings,save = True) - self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees) - - - self.heirs_widget = QWidget() - layout = QHBoxLayout() - self.heirs_widget.setLayout(layout) - - layout.addWidget(QLabel(_("Delivery Time:"))) - layout.addWidget(self.heir_locktime) - layout.addWidget(HelpButton(_("Locktime* to be used in the transaction\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - #+" - b: number of blocks after current block(ex: 144b means tomorrow)\n" - +" - d: number of days after current day(ex: 1d means tomorrow)\n" - +" - y: number of years after currrent day(ex: 1y means one year from today)\n" - +"* locktime can be anticipated to update will\n"))) - - layout.addWidget(QLabel(" ")) - layout.addWidget(QLabel(_("Check Alive:"))) - layout.addWidget(self.heir_threshold) - layout.addWidget(HelpButton(_("Check to ask for invalidation.\n" - +"When less then this time is missing, ask to invalidate.\n" - +"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - #+" - b: number of blocks after current block(ex: 144b means tomorrow)\n" - +" - d: number of days after current day(ex: 1d means tomorrow).\n" - +" - y: number of years after currrent day(ex: 1y means one year from today).\n\n"))) - layout.addWidget(QLabel(" ")) - layout.addWidget(QLabel(_("Fees:"))) - layout.addWidget(self.heir_tx_fees) - layout.addWidget(HelpButton(_("Fee to be used in the transaction"))) - layout.addWidget(QLabel("sats/vbyte")) - layout.addWidget(QLabel(" ")) - newHeirButton = QPushButton(_("New Heir")) - newHeirButton.clicked.connect(self.bal_window.new_heir_dialog) - layout.addWidget(newHeirButton) - - toolbar.insertWidget(2, self.heirs_widget) - - return toolbar - def update_will_settings(self): - self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold']) - self.heir_locktime.set_locktime(self.bal_window.will_settings['locktime']) - self.heir_tx_fees.setValue(int(self.bal_window.will_settings['tx_fees'])) - - def build_transactions(self): - will = self.bal_window.prepare_will() - diff --git a/balqt/locktimeedit.py b/balqt/locktimeedit.py deleted file mode 100644 index cfc1ff2..0000000 --- a/balqt/locktimeedit.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright (C) 2020 The Electrum developers -# Distributed under the MIT software license, see the accompanying -# file LICENCE or http://www.opensource.org/licenses/mit-license.php - -import time -from datetime import datetime -from typing import Optional, Any - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtCore import Qt, QDateTime, pyqtSignal - from PyQt5.QtGui import QPalette, QPainter - from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit) -else: - from PyQt6.QtCore import Qt, QDateTime, pyqtSignal - from PyQt6.QtGui import QPalette, QPainter - from PyQt6.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit) - -from electrum.i18n import _ -from electrum.bitcoin import NLOCKTIME_MIN, NLOCKTIME_MAX, NLOCKTIME_BLOCKHEIGHT_MAX - -from electrum.gui.qt.util import char_width_in_lineedit, ColorScheme - - -class HeirsLockTimeEdit(QWidget): - - valueEdited = pyqtSignal() - locktime_threshold = 50000000 - def __init__(self, parent=None,default_index = 1): - QWidget.__init__(self, parent) - - hbox = QHBoxLayout() - self.setLayout(hbox) - hbox.setContentsMargins(0, 0, 0, 0) - hbox.setSpacing(0) - - self.locktime_raw_e = LockTimeRawEdit(self,time_edit = self) - #self.locktime_height_e = LockTimeHeightEdit(self) - self.locktime_date_e = LockTimeDateEdit(self,time_edit = self) - #self.editors = [self.locktime_raw_e, self.locktime_height_e, self.locktime_date_e] - self.editors = [self.locktime_raw_e, self.locktime_date_e] - - self.combo = QComboBox() - #options = [_("Raw"), _("Block height"), _("Date")] - options = [_("Raw"),_("Date")] - self.option_index_to_editor_map = { - 0: self.locktime_raw_e, - #1: self.locktime_height_e, - 1: self.locktime_date_e, - #2: self.locktime_date_e, - } - self.combo.addItems(options) - - - self.editor = self.option_index_to_editor_map[default_index] - self.combo.currentIndexChanged.connect(self.on_current_index_changed) - self.combo.setCurrentIndex(default_index) - self.on_current_index_changed(default_index) - - hbox.addWidget(self.combo) - for w in self.editors: - hbox.addWidget(w) - hbox.addStretch(1) - - #self.locktime_height_e.textEdited.connect(self.valueEdited.emit) - self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit) - self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit) - self.combo.currentIndexChanged.connect(self.valueEdited.emit) - - def on_current_index_changed(self,i): - for w in self.editors: - w.setVisible(False) - w.setEnabled(False) - prev_locktime = self.editor.get_locktime() - self.editor = self.option_index_to_editor_map[i] - if self.editor.is_acceptable_locktime(prev_locktime): - self.editor.set_locktime(prev_locktime,force=True) - self.editor.setVisible(True) - self.editor.setEnabled(True) - - def get_locktime(self) -> Optional[str]: - return self.editor.get_locktime() - - def set_index(self,index): - self.combo.setCurrentIndex(index) - self.on_current_index_changed(index) - - def set_locktime(self, x: Any,force=True) -> None: - self.editor.set_locktime(x,force) - - -class _LockTimeEditor: - min_allowed_value = NLOCKTIME_MIN - max_allowed_value = NLOCKTIME_MAX - - def get_locktime(self) -> Optional[int]: - raise NotImplementedError() - - def set_locktime(self, x: Any,force=True) -> None: - raise NotImplementedError() - - @classmethod - def is_acceptable_locktime(cls, x: Any) -> bool: - if not x: # e.g. empty string - return True - try: - x = int(x) - except Exception as e: - return False - return cls.min_allowed_value <= x <= cls.max_allowed_value - - -class LockTimeRawEdit(QLineEdit, _LockTimeEditor): - - def __init__(self, parent=None,time_edit=None): - QLineEdit.__init__(self, parent) - self.setFixedWidth(14 * char_width_in_lineedit()) - self.textChanged.connect(self.numbify) - self.isdays = False - self.isyears = False - self.isblocks = False - self.time_edit=time_edit - - def replace_str(self,text): - return str(text).replace('d','').replace('y','').replace('b','') - - def checkbdy(self,s,pos,appendix): - try: - charpos = pos-1 - charpos = max(0,charpos) - charpos = min(len(s)-1,charpos) - if appendix == s[charpos]: - s=self.replace_str(s)+appendix - pos = charpos - except Exception as e: - pass - return pos, s - - def numbify(self): - text = self.text().strip() - #chars = '0123456789bdy' removed the option to choose locktime by block - chars = '0123456789dy' - pos = posx = self.cursorPosition() - pos = len(''.join([i for i in text[:pos] if i in chars])) - s = ''.join([i for i in text if i in chars]) - self.isdays = False - self.isyears = False - self.isblocks = False - - pos,s = self.checkbdy(s,pos,'d') - pos,s = self.checkbdy(s,pos,'y') - pos,s = self.checkbdy(s,pos,'b') - - if 'd' in s: self.isdays = True - if 'y' in s: self.isyears = True - if 'b' in s: self.isblocks = True - - - if self.isdays: s= self.replace_str(s) + 'd' - if self.isyears: s = self.replace_str(s) + 'y' - if self.isblocks: s= self.replace_str(s) + 'b' - - self.set_locktime(s,force=False) - # setText sets Modified to False. Instead we want to remember - # if updates were because of user modification. - self.setModified(self.hasFocus()) - self.setCursorPosition(pos) - - def get_locktime(self) -> Optional[str]: - try: - return str(self.text()) - except Exception as e: - return None - - def set_locktime(self, x: Any,force=True) -> None: - out = str(x) - if 'd' in out: - out = self.replace_str(x)+'d' - elif 'y' in out: - out = self.replace_str(x)+'y' - elif 'b' in out: - out = self.replace_str(x)+'b' - else: - try: - out = int(x) - except Exception as e: - self.setText('') - return - out = max(out, self.min_allowed_value) - out = min(out, self.max_allowed_value) - self.setText(str(out)) - #try: - # if self.time_edit and int(out)>self.time_edit.locktime_threshold and not force: - # self.time_edit.set_index(1) - #except: - # pass - -class LockTimeHeightEdit(LockTimeRawEdit): - max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX - - def __init__(self, parent=None,time_edit=None): - LockTimeRawEdit.__init__(self, parent) - self.setFixedWidth(20 * char_width_in_lineedit()) - self.time_edit = time_edit - - def paintEvent(self, event): - super().paintEvent(event) - panel = QStyleOptionFrame() - self.initStyleOption(panel) - textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) - textRect.adjust(2, 0, -10, 0) - painter = QPainter(self) - painter.setPen(ColorScheme.GRAY.as_color()) - painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height") - - -def get_max_allowed_timestamp() -> int: - ts = NLOCKTIME_MAX - # Test if this value is within the valid timestamp limits (which is platform-dependent). - # see #6170 - try: - datetime.fromtimestamp(ts) - except (OSError, OverflowError): - ts = 2 ** 31 - 1 # INT32_MAX - datetime.fromtimestamp(ts) # test if raises - return ts - - -class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): - min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1 - max_allowed_value = get_max_allowed_timestamp() - - def __init__(self, parent=None,time_edit=None): - QDateTimeEdit.__init__(self, parent) - self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value)) - self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value)) - self.setDateTime(QDateTime.currentDateTime()) - self.time_edit = time_edit - - def get_locktime(self) -> Optional[int]: - dt = self.dateTime().toPyDateTime() - locktime = int(time.mktime(dt.timetuple())) - return locktime - - def set_locktime(self, x: Any,force = False) -> None: - if not self.is_acceptable_locktime(x): - self.setDateTime(QDateTime.currentDateTime()) - return - try: - x = int(x) - except Exception: - self.setDateTime(QDateTime.currentDateTime()) - return - dt = datetime.fromtimestamp(x) - self.setDateTime(dt) diff --git a/balqt/preview_dialog.py b/balqt/preview_dialog.py deleted file mode 100644 index 92a7688..0000000 --- a/balqt/preview_dialog.py +++ /dev/null @@ -1,331 +0,0 @@ - -import enum -import copy -import json -import urllib.request -import urllib.parse -from functools import partial - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtGui import QStandardItemModel, QStandardItem, QPalette, QColor - from PyQt5.QtCore import Qt,QPersistentModelIndex, QModelIndex - from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu,QAbstractItemView,QWidget) -else: - from PyQt6.QtGui import QStandardItemModel, QStandardItem, QPalette, QColor - from PyQt6.QtCore import Qt,QPersistentModelIndex, QModelIndex - from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu,QAbstractItemView,QWidget) - -from electrum.i18n import _ -from electrum.gui.qt.util import (Buttons,read_QIcon, import_meta_gui, export_meta_gui,MessageBoxMixin) -from electrum.util import write_json_file,read_json_file,FileImportFailed -from electrum.gui.qt.my_treeview import MyTreeView -from electrum.transaction import tx_from_any -from electrum.network import Network - - -from ..bal import BalPlugin -from .. import willexecutors as Willexecutors -from .. import util as Util -from .. import will as Will -from .baldialog import BalDialog - -class PreviewList(MyTreeView): - class Columns(MyTreeView.BaseColumnsEnum): - LOCKTIME = enum.auto() - TXID = enum.auto() - WILLEXECUTOR = enum.auto() - STATUS = enum.auto() - - headers = { - Columns.LOCKTIME: _('Locktime'), - Columns.TXID: _('Txid'), - Columns.WILLEXECUTOR: _('Will-Executor'), - Columns.STATUS: _('Status'), - } - - ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000 - key_role = ROLE_HEIR_KEY - - def __init__(self, parent: 'BalWindow',will): - super().__init__( - parent=parent.window, - stretch_column=self.Columns.TXID, - ) - self.decimal_point=parent.bal_plugin.config.get_decimal_point - self.setModel(QStandardItemModel(self)) - self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - - - if not will is None: - self.will = will - else: - self.will = parent.willitems - - self.bal_window = parent - self.wallet=parent.window.wallet - self.setModel(QStandardItemModel(self)) - self.setSortingEnabled(True) - self.std_model = self.model() - self.config = parent.bal_plugin.config - self.bal_plugin=self.bal_window.bal_plugin - - self.update() - - def create_menu(self, position): - menu = QMenu() - idx = self.indexAt(position) - column = idx.column() or self.Columns.TXID - selected_keys = [] - for s_idx in self.selected_in_column(self.Columns.TXID): - sel_key = self.model().itemFromIndex(s_idx).data(0) - selected_keys.append(sel_key) - if selected_keys and idx.isValid(): - column_title = self.model().horizontalHeaderItem(column).text() - column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() - for s_idx in self.selected_in_column(column)) - - menu.addAction(_("details").format(column_title), lambda: self.show_transaction(selected_keys)).setEnabled(len(selected_keys)<2) - menu.addAction(_("check ").format(column_title), lambda: self.check_transactions(selected_keys)) - - menu.addSeparator() - menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys)) - - menu.exec(self.viewport().mapToGlobal(position)) - - def delete(self,selected_keys): - for key in selected_keys: - del self.will[key] - try: - del self.bal_window.willitems[key] - except: - pass - try: - del self.bal_window.will[key] - except: - pass - self.update() - - def check_transactions(self,selected_keys): - wout = {} - for k in selected_keys: - wout[k] = self.will[k] - if wout: - self.bal_window.check_transactions(wout) - self.update() - - def show_transaction(self,selected_keys): - for key in selected_keys: - self.bal_window.show_transaction(self.will[key].tx) - - self.update() - - def select(self,selected_keys): - self.selected += selected_keys - self.update() - - def deselect(self,selected_keys): - for key in selected_keys: - self.selected.remove(key) - self.update() - - def update_will(self,will): - self.will.update(will) - self.update() - - def update(self): - if self.will is None: - return - - current_key = self.get_role_data_for_current_item(col=self.Columns.TXID, role=self.ROLE_HEIR_KEY) - self.model().clear() - self.update_headers(self.__class__.headers) - - - - - set_current = None - for txid,bal_tx in self.will.items(): - if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status('REPLACED'): - continue - if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status('INVALIDATED'): - continue - - - tx=bal_tx.tx - labels = [""] * len(self.Columns) - labels[self.Columns.LOCKTIME] = Util.locktime_to_str(tx.locktime) - labels[self.Columns.TXID] = txid - we = 'None' - if bal_tx.we: - we = bal_tx.we['url'] - labels[self.Columns.WILLEXECUTOR]=we - status = bal_tx.status - if len(bal_tx.status) > 53: - status = "...{}".format(status[-50:]) - labels[self.Columns.STATUS] = status - - - - items=[] - for e in labels: - if type(e)== list: - try: - items.append(QStandardItem(*e)) - except Exception as e: - pass - else: - items.append(QStandardItem(str(e))) - - #pal = QPalette() - #pal.setColor(QPalette.ColorRole.Window, QColor(bal_tx.get_color())) - #items[-1].setAutoFillBackground(True) - #items[-1o].setPalette(pal) - items[-1].setBackground(QColor(bal_tx.get_color())) - - - self.model().insertRow(self.model().rowCount(), items) - if txid == current_key: - idx = self.model().index(row_count, self.Columns.TXID) - set_current = QPersistentModelIndex(idx) - self.set_current_idx(set_current) - - def create_toolbar(self, config): - toolbar, menu = self.create_toolbar_with_menu('') - menu.addAction(_("Prepare"), self.build_transactions) - menu.addAction(_("Display"), self.bal_window.preview_modal_dialog) - menu.addAction(_("Sign"), self.ask_password_and_sign_transactions) - menu.addAction(_("Export"), self.export_will) - #menu.addAction(_("Import"), self.import_will) - menu.addAction(_("Broadcast"), self.broadcast) - menu.addAction(_("Check"), self.check) - menu.addAction(_("Invalidate"), self.invalidate_will) - prepareButton = QPushButton(_("Prepare")) - prepareButton.clicked.connect(self.build_transactions) - signButton = QPushButton(_("Sign")) - signButton.clicked.connect(self.ask_password_and_sign_transactions) - pushButton = QPushButton(_("Broadcast")) - pushButton.clicked.connect(self.broadcast) - displayButton = QPushButton(_("Display")) - displayButton.clicked.connect(self.bal_window.preview_modal_dialog) - hlayout = QHBoxLayout() - widget = QWidget() - hlayout.addWidget(prepareButton) - hlayout.addWidget(signButton) - hlayout.addWidget(pushButton) - hlayout.addWidget(displayButton) - widget.setLayout(hlayout) - toolbar.insertWidget(2,widget) - - return toolbar - - def hide_replaced(self): - self.bal_window.bal_plugin.hide_replaced() - self.update() - - def hide_invalidated(self): - f.bal_window.bal_plugin.hide_invalidated() - self.update() - - def build_transactions(self): - will = self.bal_window.prepare_will() - if will: - self.update_will(will) - - def export_json_file(self,path): - write_json_file(path, self.will) - - def export_will(self): - self.bal_window.export_will() - self.update() - - def import_will(self): - self.bal_window.import_will() - - def ask_password_and_sign_transactions(self): - self.bal_window.ask_password_and_sign_transactions(callback=self.update) - - def broadcast(self): - self.bal_window.broadcast_transactions() - self.update() - - def check(self): - self.bal_window.check_transactions(self.bal_window.willitems) - self.update() - - def invalidate_will(self): - self.bal_window.invalidate_will() - self.update() - -class PreviewDialog(BalDialog,MessageBoxMixin): - def __init__(self, bal_window, will): - self.parent = bal_window.window - BalDialog.__init__(self,bal_window = bal_window) - self.bal_plugin = bal_window.bal_plugin - self.gui_object = self.bal_plugin.gui_object - self.config = self.bal_plugin.config - self.bal_window = bal_window - self.wallet = bal_window.window.wallet - self.format_amount = bal_window.window.format_amount - self.base_unit = bal_window.window.base_unit - self.format_fiat_and_units = bal_window.window.format_fiat_and_units - self.fx = bal_window.window.fx - self.format_fee_rate = bal_window.window.format_fee_rate - self.show_address = bal_window.window.show_address - if not will: - self.will = bal_window.willitems - else: - self.will = will - self.setWindowTitle(_('Transactions Preview')) - self.setMinimumSize(1000, 200) - self.size_label = QLabel() - self.transactions_list = PreviewList(self.bal_window,self.will) - vbox = QVBoxLayout(self) - vbox.addWidget(self.size_label) - vbox.addWidget(self.transactions_list) - buttonbox = QHBoxLayout() - - b = QPushButton(_('Sign')) - b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions) - buttonbox.addWidget(b) - - b = QPushButton(_('Export Will')) - b.clicked.connect(self.transactions_list.export_will) - buttonbox.addWidget(b) - - b = QPushButton(_('Broadcast')) - b.clicked.connect(self.transactions_list.broadcast) - buttonbox.addWidget(b) - - b = QPushButton(_('Invalidate will')) - b.clicked.connect(self.transactions_list.invalidate_will) - buttonbox.addWidget(b) - - vbox.addLayout(buttonbox) - - self.update() - - - def update_will(self,will): - self.will.update(will) - self.transactions_list.update_will(will) - self.update() - - def update(self): - self.transactions_list.update() - - def is_hidden(self): - return self.isMinimized() or self.isHidden() - - def show_or_hide(self): - if self.is_hidden(): - self.bring_to_top() - else: - self.hide() - - def bring_to_top(self): - self.show() - self.raise_() - - def closeEvent(self, event): - event.accept() diff --git a/balqt/qt_resources.py b/balqt/qt_resources.py deleted file mode 100644 index 5b05954..0000000 --- a/balqt/qt_resources.py +++ /dev/null @@ -1,16 +0,0 @@ -from .. import bal_resources -import sys -try: - QT_VERSION=sys._GUI_QT_VERSION -except: - QT_VERSION=6 -if QT_VERSION == 5: - from PyQt5.QtGui import QIcon,QPixmap -else: - from PyQt6.QtGui import QIcon,QPixmap - -def read_QIcon(icon_basename: str=bal_resources.DEFAULT_ICON) -> QIcon: - return QIcon(bal_resources.icon_path(icon_basename)) -def read_QPixmap(icon_basename: str=bal_resources.DEFAULT_ICON) -> QPixmap: - return QPixmap(bal_resources.icon_path(icon_basename)) - diff --git a/balqt/willdetail.py b/balqt/willdetail.py deleted file mode 100644 index 11bc42c..0000000 --- a/balqt/willdetail.py +++ /dev/null @@ -1,199 +0,0 @@ -from functools import partial - -from . import qt_resources -if qt_resources.QT_VERSION == 5: - from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QWidget,QScrollArea) - from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, - QColor, QDesktopServices, qRgba, QPainterPath,QPalette) -else: - from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QWidget,QScrollArea) - from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, - QColor, QDesktopServices, qRgba, QPainterPath,QPalette) - - -from electrum.util import decimal_point_to_base_unit_name -from electrum.i18n import _ - -from ..bal import BalPlugin -from .. import will as Will -from .. import util as Util -from .baldialog import BalDialog - - - - - - -class WillDetailDialog(BalDialog): - - - def __init__(self, bal_window): - - self.will = bal_window.willitems - self.threshold = Util.parse_locktime_string(bal_window.will_settings['threshold']) - self.bal_window = bal_window - Will.add_willtree(self.will) - super().__init__(bal_window.window) - self.config = bal_window.window.config - self.wallet = bal_window.wallet - self.format_amount = bal_window.window.format_amount - self.base_unit = bal_window.window.base_unit - self.format_fiat_and_units = bal_window.window.format_fiat_and_units - self.fx = bal_window.window.fx - self.format_fee_rate = bal_window.window.format_fee_rate - self.decimal_point = bal_window.bal_plugin.config.get_decimal_point() - self.base_unit_name = decimal_point_to_base_unit_name(self.decimal_point) - self.setWindowTitle(_('Will Details')) - self.setMinimumSize(670,700) - self.vlayout= QVBoxLayout() - w=QWidget() - hlayout = QHBoxLayout(w) - - b = QPushButton(_('Sign')) - b.clicked.connect(self.ask_password_and_sign_transactions) - hlayout.addWidget(b) - - b = QPushButton(_('Broadcast')) - b.clicked.connect(self.broadcast_transactions) - hlayout.addWidget(b) - - b = QPushButton(_('Export')) - b.clicked.connect(self.export_will) - hlayout.addWidget(b) - """ - toggle = "Hide" - if self.bal_window.bal_plugin._hide_replaced: - toggle = "Unhide" - self.toggle_replace_button = QPushButton(_(f"{toggle} replaced")) - self.toggle_replace_button.clicked.connect(self.toggle_replaced) - hlayout.addWidget(self.toggle_replace_button) - - toggle = "Hide" - if self.bal_window.bal_plugin._hide_invalidated: - toggle = "Unhide" - - self.toggle_invalidate_button = QPushButton(_(f"{toggle} invalidated")) - self.toggle_invalidate_button.clicked.connect(self.toggle_invalidated) - hlayout.addWidget(self.toggle_invalidate_button) - """ - b = QPushButton(_('Invalidate')) - b.clicked.connect(bal_window.invalidate_will) - hlayout.addWidget(b) - self.vlayout.addWidget(w) - - self.paint_scroll_area() - #vlayout.addWidget(QLabel(_("DON'T PANIC !!! everything is fine, all possible futures are covered"))) - self.vlayout.addWidget(QLabel(_("Expiration date: ")+Util.locktime_to_str(self.threshold))) - self.vlayout.addWidget(self.scrollbox) - w=QWidget() - hlayout = QHBoxLayout(w) - hlayout.addWidget(QLabel(_("Valid Txs:")+ str(len(Will.only_valid_list(self.will))))) - hlayout.addWidget(QLabel(_("Total Txs:")+ str(len(self.will)))) - self.vlayout.addWidget(w) - self.setLayout(self.vlayout) - - def paint_scroll_area(self): - #self.scrollbox.deleteLater() - #self.willlayout.deleteLater() - #self.detailsWidget.deleteLater() - self.scrollbox = QScrollArea() - viewport = QWidget(self.scrollbox) - self.willlayout = QVBoxLayout(viewport) - self.detailsWidget = WillWidget(parent=self) - self.willlayout.addWidget(self.detailsWidget) - - self.scrollbox.setWidget(viewport) - viewport.setLayout(self.willlayout) - def ask_password_and_sign_transactions(self): - self.bal_window.ask_password_and_sign_transactions(callback=self.update) - self.update() - def broadcast_transactions(self): - self.bal_window.broadcast_transactions() - self.update() - def export_will(self): - self.bal_window.export_will() - def toggle_replaced(self): - self.bal_window.bal_plugin.hide_replaced() - toggle = _("Hide") - if self.bal_window.bal_plugin._hide_replaced: - toggle = _("Unhide") - self.toggle_replace_button.setText(f"{toggle} {_('replaced')}") - self.update() - - def toggle_invalidated(self): - self.bal_window.bal_plugin.hide_invalidated() - toggle = _("Hide") - if self.bal_window.bal_plugin._hide_invalidated: - toggle = _("Unhide") - self.toggle_invalidate_button.setText(_(f"{toggle} {_('invalidated')}")) - self.update() - - def update(self): - self.will = self.bal_window.willitems - pos = self.vlayout.indexOf(self.scrollbox) - self.vlayout.removeWidget(self.scrollbox) - self.paint_scroll_area() - self.vlayout.insertWidget(pos,self.scrollbox) - super().update() - -class WillWidget(QWidget): - def __init__(self,father=None,parent = None): - super().__init__() - vlayout = QVBoxLayout() - self.setLayout(vlayout) - self.will = parent.bal_window.willitems - self.parent = parent - for w in self.will: - if self.will[w].get_status('REPLACED') and self.parent.bal_window.bal_plugin._hide_replaced: - continue - if self.will[w].get_status('INVALIDATED') and self.parent.bal_window.bal_plugin._hide_invalidated: - continue - f = self.will[w].father - if father == f: - qwidget = QWidget() - childWidget = QWidget() - hlayout=QHBoxLayout(qwidget) - qwidget.setLayout(hlayout) - vlayout.addWidget(qwidget) - detailw=QWidget() - detaillayout=QVBoxLayout() - detailw.setLayout(detaillayout) - - willpushbutton = QPushButton(w) - - willpushbutton.clicked.connect(partial(self.parent.bal_window.show_transaction,txid=w)) - detaillayout.addWidget(willpushbutton) - locktime = Util.locktime_to_str(self.will[w].tx.locktime) - creation = Util.locktime_to_str(self.will[w].time) - def qlabel(title,value): - label = ""+_(str(title)) + f":\t{str(value)}" - return QLabel(label) - detaillayout.addWidget(qlabel("Locktime",locktime)) - detaillayout.addWidget(qlabel("Creation Time",creation)) - total_fees = self.will[w].tx.input_value() - self.will[w].tx.output_value() - decoded_fees = total_fees #Util.decode_amount(total_fees,self.parent.decimal_point) - fee_per_byte = round(total_fees/self.will[w].tx.estimated_size(),3) - fees_str = str(decoded_fees) + " ("+ str(fee_per_byte) + " sats/vbyte)" - detaillayout.addWidget(qlabel("Transaction fees:",fees_str)) - detaillayout.addWidget(qlabel("Status:",self.will[w].status)) - detaillayout.addWidget(QLabel("")) - detaillayout.addWidget(QLabel("Heirs:")) - for heir in self.will[w].heirs: - if "w!ll3x3c\"" not in heir: - decoded_amount = Util.decode_amount(self.will[w].heirs[heir][3],self.parent.decimal_point) - detaillayout.addWidget(qlabel(heir,f"{decoded_amount} {self.parent.base_unit_name}")) - if self.will[w].we: - detaillayout.addWidget(QLabel("")) - detaillayout.addWidget(QLabel(_("Willexecutor:HEIR_REAL_AMOUNT: + if len(heir) > HEIR_REAL_AMOUNT: real_amount = heir[HEIR_REAL_AMOUNT] out_amount += real_amount description += f"{name}\n" - paid_heirs[name]=heir - outputs.append(PartialTxOutput.from_address_and_value(heir[HEIR_ADDRESS], real_amount)) + paid_heirs[name] = heir + outputs.append( + PartialTxOutput.from_address_and_value( + heir[HEIR_ADDRESS], real_amount + ) + ) else: pass except Exception as e: @@ -109,35 +132,42 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): pass if int(in_amount) < int(out_amount): break - heirsvalue=out_amount + heirsvalue = out_amount change = get_change_output(wallet, in_amount, out_amount, fee) if change: outputs.append(change) - for i in range(0,100): + for i in range(0, 100): random.shuffle(outputs) print(outputs) - tx = PartialTransaction.from_io(used_utxos, outputs, locktime=Util.parse_locktime_string(locktime,wallet), version=2) - if len(description)>0: tx.description = description[:-1] - else: tx.description = "" + tx = PartialTransaction.from_io( + used_utxos, + outputs, + locktime=parse_locktime_string(locktime, wallet), + version=2, + ) + if len(description) > 0: + tx.description = description[:-1] + else: + tx.description = "" tx.heirsvalue = heirsvalue - tx.set_rbf(True) + tx.set_rbf(True) tx.remove_signatures() txid = tx.txid() if txid is None: - raise Exception("txid is none",tx) - + raise Exception("txid is none", tx) + tx.heirs = paid_heirs tx.my_locktime = locktime - txsout[txid]=tx - + txsout[txid] = tx + if change: - change_idx=tx.get_output_idxs_from_address(change.address) + change_idx = tx.get_output_idxs_from_address(change.address) prevout = TxOutpoint(txid=bfh(tx.txid()), out_idx=change_idx.pop()) txin = PartialTxInput(prevout=prevout) txin._trusted_value_sats = change.value txin.script_descriptor = change.script_descriptor - txin.is_mine=True - txin._TxInput__address=change.address + txin.is_mine = True + txin._TxInput__address = change.address txin._TxInput__scriptpubkey = change.scriptpubkey txin._TxInput__value_sats = change.value txin.utxo = tx @@ -146,67 +176,74 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): return txsout -def get_utxos_from_inputs(tx_inputs,tx,utxos): +def get_utxos_from_inputs(tx_inputs, tx, utxos): for tx_input in tx_inputs: - prevoutstr=tx_input.prevout.to_str() - utxos[prevoutstr] =utxos.get(prevoutstr,{'input':tx_input,'txs':[]}) - utxos[prevoutstr]['txs'].append(tx) + prevoutstr = tx_input.prevout.to_str() + utxos[prevoutstr] = utxos.get(prevoutstr, {"input": tx_input, "txs": []}) + utxos[prevoutstr]["txs"].append(tx) return utxos -#TODO calculate de minimum inputs to be invalidated + +# TODO calculate de minimum inputs to be invalidated def invalidate_inheritance_transactions(wallet): listids = [] utxos = {} dtxs = {} - for k,v in wallet.get_all_labels().items(): + for k, v in wallet.get_all_labels().items(): tx = None if TRANSACTION_LABEL == v: - tx=wallet.adb.get_transaction(k) + tx = wallet.adb.get_transaction(k) if tx: - dtxs[tx.txid()]=tx - get_utxos_from_inputs(tx.inputs(),tx,utxos) + dtxs[tx.txid()] = tx + get_utxos_from_inputs(tx.inputs(), tx, utxos) - for key,utxo in utxos.items(): - txid=key.split(":")[0] + for key, utxo in utxos.items(): + txid = key.split(":")[0] if txid in dtxs: - for tx in utxo['txs']: - txid =tx.txid() + for tx in utxo["txs"]: + txid = tx.txid() del dtxs[txid] - + utxos = {} - for txid,tx in dtxs.items(): - get_utxos_from_inputs(tx.inputs(),tx,utxos) + for txid, tx in dtxs.items(): + get_utxos_from_inputs(tx.inputs(), tx, utxos) - utxos = sorted(utxos.items(), key = lambda item: len(item[1])) + utxos = sorted(utxos.items(), key=lambda item: len(item[1])) - - remaining={} + remaining = {} invalidated = [] - for key,value in utxos: - for tx in value['txs']: + for key, value in utxos: + for tx in value["txs"]: txid = tx.txid() if not txid in invalidated: invalidated.append(tx.txid()) remaining[key] = value -def print_transaction(heirs,tx,locktimes,tx_fees): - jtx=tx.to_json() + +def print_transaction(heirs, tx, locktimes, tx_fees): + jtx = tx.to_json() print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}") print(f"---") for inp in jtx["inputs"]: print(f"{inp['address']}: {inp['value_sats']}") print(f"---") for out in jtx["outputs"]: - heirname="" + heirname = "" for key in heirs.keys(): - heir=heirs[key] - if heir[HEIR_ADDRESS] == out['address'] and str(heir[HEIR_LOCKTIME]) == str(jtx['locktime']): - heirname=key + heir = heirs[key] + if heir[HEIR_ADDRESS] == out["address"] and str(heir[HEIR_LOCKTIME]) == str( + jtx["locktime"] + ): + heirname = key print(f"{heirname}\t{out['address']}: {out['value_sats']}") print() size = tx.estimated_size() - print("fee: {}\texpected: {}\tsize: {}".format(tx.input_value()-tx.output_value(), size*tx_fees, size)) + print( + "fee: {}\texpected: {}\tsize: {}".format( + tx.input_value() - tx.output_value(), size * tx_fees, size + ) + ) print() try: @@ -215,30 +252,31 @@ def print_transaction(heirs,tx,locktimes,tx_fees): print("impossible to serialize") print() -def get_change_output(wallet,in_amount,out_amount,fee): + +def get_change_output(wallet, in_amount, out_amount, fee): change_amount = int(in_amount - out_amount - fee) if change_amount > wallet.dust_threshold(): change_addresses = wallet.get_change_addresses_for_new_transaction() out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount) out.is_change = True return out - -class Heirs(dict, Logger): - def __init__(self, db: 'WalletDB'): + +class Heirs(dict, Logger): + def __init__(self, db: "WalletDB"): Logger.__init__(self) self.db = db - d = self.db.get('heirs', {}) + d = self.db.get("heirs", {}) try: self.update(d) except e as Exception: return - def invalidate_transactions(self,wallet): + def invalidate_transactions(self, wallet): invalidate_inheritance_transactions(wallet) def save(self): - self.db.put('heirs', dict(self)) + self.db.put("heirs", dict(self)) def import_file(self, path): data = read_json_file(path) @@ -259,25 +297,33 @@ class Heirs(dict, Logger): self.save() return res - def get_locktimes(self,from_locktime, a=False): + def get_locktimes(self, from_locktime, a=False): locktimes = {} for key in self.keys(): - locktime = Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - if locktime > from_locktime and not a \ - or locktime <=from_locktime and a: - locktimes[int(locktime)]=None + locktime = parse_locktime_string(self[key][HEIR_LOCKTIME]) + if locktime > from_locktime and not a or locktime <= from_locktime and a: + locktimes[int(locktime)] = None return locktimes.keys() def check_locktime(self): return False - def normalize_perc(self, heir_list, total_balance, relative_balance,wallet,real=False): + def normalize_perc( + self, heir_list, total_balance, relative_balance, wallet, real=False + ): amount = 0 - for key,v in heir_list.items(): + for key, v in heir_list.items(): try: column = HEIR_AMOUNT - if real: column = HEIR_REAL_AMOUNT - value = int(math.floor(total_balance/relative_balance*self.amount_to_float(v[column]))) + if real: + column = HEIR_REAL_AMOUNT + value = int( + math.floor( + total_balance + / relative_balance + * self.amount_to_float(v[column]) + ) + ) if value > wallet.dust_threshold(): heir_list[key].insert(HEIR_REAL_AMOUNT, value) amount += value @@ -286,7 +332,7 @@ class Heirs(dict, Logger): raise e return amount - def amount_to_float(self,amount): + def amount_to_float(self, amount): try: return float(amount) except: @@ -295,92 +341,114 @@ class Heirs(dict, Logger): except: return 0.0 - def fixed_percent_lists_amount(self,from_locktime,dust_threshold,reverse = False): + def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False): fixed_heirs = {} fixed_amount = 0.0 - percent_heirs= {} + percent_heirs = {} percent_amount = 0.0 for key in self.keys(): try: - cmp= Util.parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime - if cmp<=0: + cmp = parse_locktime_string(self[key][HEIR_LOCKTIME]) - from_locktime + if cmp <= 0: continue - if Util.is_perc(self[key][HEIR_AMOUNT]): + if is_perc(self[key][HEIR_AMOUNT]): percent_amount += float(self[key][HEIR_AMOUNT][:-1]) - percent_heirs[key] =list(self[key]) + percent_heirs[key] = list(self[key]) else: heir_amount = int(math.floor(float(self[key][HEIR_AMOUNT]))) - if heir_amount>dust_threshold: + if heir_amount > dust_threshold: fixed_amount += heir_amount fixed_heirs[key] = list(self[key]) - fixed_heirs[key].insert(HEIR_REAL_AMOUNT,heir_amount) + fixed_heirs[key].insert(HEIR_REAL_AMOUNT, heir_amount) else: pass - except Exception as e: + except Exception as e: _logger.error(e) - return fixed_heirs,fixed_amount,percent_heirs,percent_amount + return fixed_heirs, fixed_amount, percent_heirs, percent_amount - - def prepare_lists(self, balance, total_fees, wallet, willexecutor = False, from_locktime = 0): - willexecutors_amount = 0 + def prepare_lists( + self, balance, total_fees, wallet, willexecutor=False, from_locktime=0 + ): + willexecutors_amount = 0 willexecutors = {} heir_list = {} onlyfixed = False newbalance = balance - total_fees - locktimes = self.get_locktimes(from_locktime); + locktimes = self.get_locktimes(from_locktime) if willexecutor: for locktime in locktimes: - if int(Util.int_locktime(locktime)) > int(from_locktime): + if int(int_locktime(locktime)) > int(from_locktime): try: - base_fee = int(willexecutor['base_fee']) + base_fee = int(willexecutor["base_fee"]) willexecutors_amount += base_fee h = [None] * 4 h[HEIR_AMOUNT] = base_fee h[HEIR_REAL_AMOUNT] = base_fee h[HEIR_LOCKTIME] = locktime - h[HEIR_ADDRESS] = willexecutor['address'] - willexecutors["w!ll3x3c\""+willexecutor['url']+"\""+str(locktime)] = h + h[HEIR_ADDRESS] = willexecutor["address"] + willexecutors[ + 'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime) + ] = h except Exception as e: - return [],False + return [], False else: - _logger.error(f"heir excluded from will locktime({locktime}){Util.int_locktime(locktime)} newbalance: - fixed_amount = self.normalize_perc(fixed_heirs,newbalance,fixed_amount,wallet) + fixed_amount = self.normalize_perc( + fixed_heirs, newbalance, fixed_amount, wallet + ) onlyfixed = True heir_list.update(fixed_heirs) - + newbalance -= fixed_amount if newbalance > 0: - perc_amount = self.normalize_perc(percent_heirs,newbalance,percent_amount,wallet) + perc_amount = self.normalize_perc( + percent_heirs, newbalance, percent_amount, wallet + ) newbalance -= perc_amount heir_list.update(percent_heirs) if newbalance > 0: newbalance += fixed_amount - fixed_amount = self.normalize_perc(fixed_heirs,newbalance,fixed_amount,wallet,real=True) + fixed_amount = self.normalize_perc( + fixed_heirs, newbalance, fixed_amount, wallet, real=True + ) newbalance -= fixed_amount heir_list.update(fixed_heirs) - - heir_list = sorted(heir_list.items(), key = lambda item: Util.parse_locktime_string(item[1][HEIR_LOCKTIME],wallet)) - + + heir_list = sorted( + heir_list.items(), + key=lambda item: parse_locktime_string(item[1][HEIR_LOCKTIME], wallet), + ) locktimes = {} for key, value in heir_list: - locktime=Util.parse_locktime_string(value[HEIR_LOCKTIME]) - if not locktime in locktimes: locktimes[locktime]={key:value} - else: locktimes[locktime][key]=value + locktime = parse_locktime_string(value[HEIR_LOCKTIME]) + if not locktime in locktimes: + locktimes[locktime] = {key: value} + else: + locktimes[locktime][key] = value return locktimes, onlyfixed - def is_perc(self,key): - return Util.is_perc(self[key][HEIR_AMOUNT]) - def buildTransactions(self,bal_plugin,wallet,tx_fees = None, utxos=None,from_locktime=0): + def is_perc(self, key): + return is_perc(self[key][HEIR_AMOUNT]) + + def buildTransactions( + self, bal_plugin, wallet, tx_fees=None, utxos=None, from_locktime=0 + ): Heirs._validate(self) - if len(self)<=0: + if len(self) <= 0: return balance = 0.0 len_utxo_set = 0 @@ -388,20 +456,21 @@ class Heirs(dict, Logger): if not utxos: utxos = wallet.get_utxos() willexecutors = Willexecutors.get_willexecutors(bal_plugin) or {} - self.decimal_point=bal_plugin.get_decimal_point() + self.decimal_point = bal_plugin.get_decimal_point() no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get() for utxo in utxos: - if utxo.value_sats()> 0*tx_fees: + if utxo.value_sats() > 0 * tx_fees: balance += utxo.value_sats() len_utxo_set += 1 available_utxos.append(utxo) - if len_utxo_set==0: return - j=-2 + if len_utxo_set == 0: + return + j = -2 willexecutorsitems = list(willexecutors.items()) willexecutorslen = len(willexecutorsitems) alltxs = {} while True: - j+=1 + j += 1 if j >= willexecutorslen: break elif 0 <= j: @@ -409,7 +478,7 @@ class Heirs(dict, Logger): if not Willexecutors.is_selected(willexecutor): continue else: - willexecutor['url']=url + willexecutor["url"] = url elif j == -1: if not no_willexecutors: continue @@ -417,78 +486,84 @@ class Heirs(dict, Logger): else: break fees = {} - i=0 + i = 0 while True: txs = {} redo = False - i+=1 - total_fees=0 + i += 1 + total_fees = 0 for fee in fees: total_fees += int(fees[fee]) - newbalance = balance - locktimes, onlyfixed = self.prepare_lists(balance, total_fees, wallet, willexecutor, from_locktime) + newbalance = balance + locktimes, onlyfixed = self.prepare_lists( + balance, total_fees, wallet, willexecutor, from_locktime + ) try: - txs = prepare_transactions(locktimes, available_utxos[:], fees, wallet) + txs = prepare_transactions( + locktimes, available_utxos[:], fees, wallet + ) if not txs: return {} except Exception as e: try: if "w!ll3x3c" in e.heirname: - Willexecutors.is_selected(willexecutors[w],False) + Willexecutors.is_selected(willexecutors[w], False) break except: raise e total_fees = 0 total_fees_real = 0 total_in = 0 - for txid,tx in txs.items(): + for txid, tx in txs.items(): tx.willexecutor = willexecutor - fee = tx.estimated_size() * tx_fees - txs[txid].tx_fees= tx_fees + fee = tx.estimated_size() * tx_fees + txs[txid].tx_fees = tx_fees total_fees += fee total_fees_real += tx.get_fee() total_in += tx.input_value() - rfee= tx.input_value()-tx.output_value() + rfee = tx.input_value() - tx.output_value() if rfee < fee or rfee > fee + wallet.dust_threshold(): redo = True - oldfees= fees.get(tx.my_locktime,0) - fees[tx.my_locktime]=fee + oldfees = fees.get(tx.my_locktime, 0) + fees[tx.my_locktime] = fee - - if balance - total_in > wallet.dust_threshold(): + if balance - total_in > wallet.dust_threshold(): redo = True if not redo: break - if i>=10: + if i >= 10: break alltxs.update(txs) - + return alltxs - def get_transactions(self,bal_plugin,wallet,tx_fees,utxos=None,from_locktime=0): - txs=self.buildTransactions(bal_plugin,wallet,tx_fees,utxos,from_locktime) + + def get_transactions( + self, bal_plugin, wallet, tx_fees, utxos=None, from_locktime=0 + ): + txs = self.buildTransactions(bal_plugin, wallet, tx_fees, utxos, from_locktime) if txs: temp_txs = {} for txid in txs: if txs[txid].available_utxos: - temp_txs.update(self.get_transactions(bal_plugin,wallet,tx_fees,txs[txid].available_utxos,txs[txid].locktime)) + temp_txs.update( + self.get_transactions( + bal_plugin, + wallet, + tx_fees, + txs[txid].available_utxos, + txs[txid].locktime, + ) + ) txs.update(temp_txs) return txs - - def resolve(self, k): if bitcoin.is_address(k): - return { - 'address': k, - 'type': 'address' - } + return {"address": k, "type": "address"} if k in self.keys(): _type, addr = self[k] - if _type == 'address': - return { - 'address': addr, - 'type': 'heir' - } + if _type == "address": + return {"address": addr, "type": "heir"} if openalias := self.resolve_openalias(k): return openalias raise AliasNotFoundException("Invalid Bitcoin address or alias", k) @@ -499,10 +574,10 @@ class Heirs(dict, Logger): if out: address, name, validated = out return { - 'address': address, - 'name': name, - 'type': 'openalias', - 'validated': validated + "address": address, + "name": name, + "type": "openalias", + "validated": validated, } return {} @@ -510,21 +585,19 @@ class Heirs(dict, Logger): for k in self.keys(): _type, addr = self[k] if addr.casefold() == name.casefold(): - return { - 'name': addr, - 'type': _type, - 'address': k - } + return {"name": addr, "type": _type, "address": k} return None - def fetch_openalias(self, config: 'SimpleConfig'): + def fetch_openalias(self, config: "SimpleConfig"): self.alias_info = None alias = config.OPENALIAS_ID if alias: alias = str(alias) + def f(): self.alias_info = self._resolve_openalias(alias) - trigger_callback('alias_received') + trigger_callback("alias_received") + t = threading.Thread(target=f) t.daemon = True t.start() @@ -532,18 +605,18 @@ class Heirs(dict, Logger): @classmethod def _resolve_openalias(cls, url: str) -> Optional[Tuple[str, str, bool]]: # support email-style addresses, per the OA standard - url = url.replace('@', '.') + url = url.replace("@", ".") try: records, validated = dnssec.query(url, dns.rdatatype.TXT) except DNSException as e: - _logger.info(f'Error resolving openalias: {repr(e)}') + _logger.info(f"Error resolving openalias: {repr(e)}") return None - prefix = 'btc' + prefix = "btc" for record in records: - string = to_string(record.strings[0], 'utf8') - if string.startswith('oa1:' + prefix): - address = cls.find_regex(string, r'recipient_address=([A-Za-z0-9]+)') - name = cls.find_regex(string, r'recipient_name=([^;]+)') + string = to_string(record.strings[0], "utf8") + if string.startswith("oa1:" + prefix): + address = cls.find_regex(string, r"recipient_address=([A-Za-z0-9]+)") + name = cls.find_regex(string, r"recipient_name=([^;]+)") if not name: name = address if not address: @@ -558,7 +631,6 @@ class Heirs(dict, Logger): except AttributeError: return None - def validate_address(address): if not bitcoin.is_address(address): raise NotAnAddress(f"not an address,{address}") @@ -566,43 +638,50 @@ class Heirs(dict, Logger): def validate_amount(amount): try: - famount = float(amount[:-1]) if Util.is_perc(amount) else float(amount) + famount = float(amount[:-1]) if is_perc(amount) else float(amount) if famount <= 0.00000001: raise AmountNotValid(f"amount have to be positive {famount} < 0") except Exception as e: raise AmountNotValid(f"amount not properly formatted, {e}") return amount - def validate_locktime(locktime,timestamp_to_check=False): + def validate_locktime(locktime, timestamp_to_check=False): try: if timestamp_to_check: - if Util.parse_locktime_string(locktime,None) < timestamp_to_check: + if parse_locktime_string(locktime, None) < timestamp_to_check: raise HeirExpiredException() except Exception as e: raise LocktimeNotValid(f"locktime string not properly formatted, {e}") return locktime - def validate_heir(k,v,timestamp_to_check=False): + def validate_heir(k, v, timestamp_to_check=False): address = Heirs.validate_address(v[HEIR_ADDRESS]) amount = Heirs.validate_amount(v[HEIR_AMOUNT]) - locktime = Heirs.validate_locktime(v[HEIR_LOCKTIME],timestamp_to_check) - return (address,amount,locktime) + locktime = Heirs.validate_locktime(v[HEIR_LOCKTIME], timestamp_to_check) + return (address, amount, locktime) - def _validate(data,timestamp_to_check=False): + def _validate(data, timestamp_to_check=False): for k, v in list(data.items()): - if k == 'heirs': + if k == "heirs": return Heirs._validate(v) try: - Heirs.validate_heir(k,v) + Heirs.validate_heir(k, v) except Exception as e: data.pop(k) return data + class NotAnAddress(ValueError): pass + + class AmountNotValid(ValueError): pass + + class LocktimeNotValid(ValueError): pass + + class HeirExpiredException(LocktimeNotValid): pass diff --git a/qt.py b/qt.py index 9ad0fea..0e09d0a 100644 --- a/qt.py +++ b/qt.py @@ -1,431 +1,338 @@ -''' +""" Bal Bitcoin after life -''' - - - - +""" import copy -from datetime import datetime -from decimal import Decimal import enum -from functools import partial import json import os import random import sys -import traceback import time -from typing import ( - TYPE_CHECKING, - Callable, - Optional, - List, - Union, - Tuple, - Mapping,Any) +import traceback import urllib.parse import urllib.request +from datetime import datetime +from decimal import Decimal +from functools import partial +from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Optional, Tuple, Union try: - QT_VERSION=sys._GUI_QT_VERSION + QT_VERSION = sys._GUI_QT_VERSION except: - QT_VERSION=6 + QT_VERSION = 6 if QT_VERSION == 5: - from PyQt5.QtGui import QStandardItemModel, QStandardItem - from PyQt5.QtCore import Qt,QPersistentModelIndex, QModelIndex - from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,QMenu) from PyQt5.QtCore import ( - QPersistentModelIndex, - QModelIndex, - Qt, - QRectF, - QRect, - QSizeF, - QUrl, - QPoint, - QSize, - QDateTime, - pyqtProperty, - pyqtSignal, - pyqtSlot, - QObject, - QEventLoop, - pyqtSignal) + QDateTime, + QEventLoop, + QModelIndex, + QObject, + QPersistentModelIndex, + QPoint, + QRect, + QRectF, + QSize, + QSizeF, + Qt, + QUrl, + pyqtProperty, + pyqtSignal, + pyqtSlot, + ) from PyQt5.QtGui import ( - QStandardItemModel, - QStandardItem, - QPalette, - QColor, - QPixmap, - QImage, - QBitmap, - QPainter, - QFontDatabase, - QPen, - QFont, - QColor, - QDesktopServices, - qRgba, - QPainterPath, - QPalette, - QPixmap, - QImage, - QBitmap, - QPainter, - QFontDatabase, - QPen, - QFont, - QIcon, - QColor, - QDesktopServices, - qRgba, - QPainterPath, - QPalette) + QBitmap, + QColor, + QDesktopServices, + QFont, + QFontDatabase, + QIcon, + QImage, + QPainter, + QPainterPath, + QPalette, + QPen, + QPixmap, + QStandardItem, + QStandardItemModel, + qRgba, + ) from PyQt5.QtWidgets import ( - QDialog, - QVBoxLayout, - QHBoxLayout, - QPushButton, - QLabel, - QMenu, - QDialog, - QVBoxLayout, - QHBoxLayout, - QPushButton, - QLabel, - QWidget, - QScrollArea, - QAbstractItemView, - QWidget, - QDateTimeEdit, - QLineEdit, - QStyle, - QStyleOptionFrame, - QSizePolicy, - QCheckBox, - QGridLayout, - QHBoxLayout, - QLabel, - QLineEdit, - QMenu, - QMenuBar, - QPushButton, - QScrollArea, - QSpacerItem, - QSizePolicy, - QSpinBox, - QVBoxLayout, - QWidget, - QStyle, - QStyleOptionFrame, - QComboBox, - QHBoxLayout, - ) -else: #QT6 + QAbstractItemView, + QCheckBox, + QComboBox, + QDateTimeEdit, + QDialog, + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QMenu, + QMenuBar, + QPushButton, + QScrollArea, + QSizePolicy, + QSpacerItem, + QSpinBox, + QStyle, + QStyleOptionFrame, + QVBoxLayout, + QWidget, + ) +else: # QT6 from PyQt6.QtCore import ( - Qt, - QDateTime, - QPersistentModelIndex, - QModelIndex, - pyqtProperty, - pyqtSignal, - pyqtSlot, - QObject, - pyqtSignal, - QSize, - Qt, - QRectF, - QRect, - QSizeF, - QUrl, - QPoint, - QSize) + QDateTime, + QModelIndex, + QObject, + QPersistentModelIndex, + QPoint, + QRect, + QRectF, + QSize, + QSizeF, + Qt, + QUrl, + pyqtProperty, + pyqtSignal, + pyqtSlot, + ) from PyQt6.QtGui import ( - QStandardItemModel, - QStandardItem, - QPalette, - QColor, - QPixmap, - QImage, - QBitmap, - QPainter, - QFontDatabase, - QPen, - QFont, - QColor, - QDesktopServices, - qRgba, - QPainterPath, - QPalette, - QPainter, - QPixmap, - QImage, - QBitmap, - QPainter, - QFontDatabase, - QPen, - QFont, - QIcon, - QColor, - QDesktopServices, - qRgba, - QPainterPath, - QPalette) - + QBitmap, + QColor, + QDesktopServices, + QFont, + QFontDatabase, + QIcon, + QImage, + QPainter, + QPainterPath, + QPalette, + QPen, + QPixmap, + QStandardItem, + QStandardItemModel, + qRgba, + ) from PyQt6.QtWidgets import ( - QDialog, - QVBoxLayout, - QHBoxLayout, - QPushButton, - QLabel, - QMenu, - QAbstractItemView, - QWidget, - QDialog, - QVBoxLayout, - QHBoxLayout, - QPushButton, - QLabel, - QWidget, - QScrollArea, - QDateTimeEdit, - QLabel, - QVBoxLayout, - QCheckBox, - QWidget, - QLabel, - QVBoxLayout, - QCheckBox, - QLineEdit, - QStyle, - QStyleOptionFrame, - QSizePolicy, - QGridLayout, - QVBoxLayout, - QHBoxLayout, - QLabel, - QPushButton, - QLineEdit, - QCheckBox, - QSpinBox, - QMenuBar, - QMenu, - QLineEdit, - QScrollArea, - QWidget, - QSpacerItem, - QComboBox, - QSizePolicy) + QAbstractItemView, + QCheckBox, + QComboBox, + QDateTimeEdit, + QDialog, + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QMenu, + QMenuBar, + QPushButton, + QScrollArea, + QSizePolicy, + QSpacerItem, + QSpinBox, + QStyle, + QStyleOptionFrame, + QVBoxLayout, + QWidget, + ) - - -from electrum import json_db -from electrum import constants +from electrum import constants, json_db from electrum.bitcoin import ( - is_address, - NLOCKTIME_MIN, - NLOCKTIME_MAX, - NLOCKTIME_BLOCKHEIGHT_MAX) + NLOCKTIME_BLOCKHEIGHT_MAX, + NLOCKTIME_MAX, + NLOCKTIME_MIN, + is_address, +) from electrum.gui.qt.amountedit import ( - BTCAmountEdit, - char_width_in_lineedit, - ColorScheme) + BTCAmountEdit, + ColorScheme, + char_width_in_lineedit, +) if TYPE_CHECKING: from electrum.gui.qt.main_window import ElectrumWindow -from electrum.gui.qt.util import ( - Buttons, - CancelButton, - char_width_in_lineedit, - CloseButton, - ColorScheme, - EnterButton, - export_meta_gui, - HelpButton, - import_meta_gui, - MessageBoxMixin, - OkButton, - read_QIcon, - TaskThread, - WindowModalDialog, - WWLabel, - read_QIcon_from_bytes, - read_QPixmap_from_bytes, - ) from electrum.gui.qt.main_window import StatusBarButton -from electrum.gui.qt.my_treeview import ( - MyTreeView, - MySortModel) -from electrum.gui.qt.transaction_dialog import TxDialog +from electrum.gui.qt.my_treeview import MySortModel, MyTreeView from electrum.gui.qt.password_dialog import PasswordDialog from electrum.gui.qt.qrtextedit import ScanQRTextEdit +from electrum.gui.qt.transaction_dialog import TxDialog +from electrum.gui.qt.util import ( + Buttons, + CancelButton, + CloseButton, + ColorScheme, + EnterButton, + HelpButton, + MessageBoxMixin, + OkButton, + TaskThread, + WindowModalDialog, + WWLabel, + char_width_in_lineedit, + export_meta_gui, + import_meta_gui, + read_QIcon, + read_QIcon_from_bytes, + read_QPixmap_from_bytes, +) from electrum.i18n import _ -from electrum.logging import get_logger,Logger from electrum.json_db import StoredDict -from electrum.network import ( - Network, - TxBroadcastError, - BestEffortRequestFailed - ) -from electrum.plugin import ( - hook, - run_hook) -from electrum.transaction import ( - SerializationError, - Transaction, - tx_from_any) +from electrum.logging import Logger, get_logger +from electrum.network import BestEffortRequestFailed, Network, TxBroadcastError +from electrum.plugin import hook, run_hook +from electrum.transaction import SerializationError, Transaction, tx_from_any from electrum.util import ( - write_json_file, - read_json_file, - make_dir, - InvalidPassword, - UserCancelled, - resource_path, - write_json_file, - read_json_file, - FileImportFailed, - bfh, - read_json_file, - write_json_file, - decimal_point_to_base_unit_name, - FileImportFailed, - DECIMAL_POINT, - FEERATE_PRECISION, - quantize_feerate, - UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE, - FileExportFailed) - + DECIMAL_POINT, + FEERATE_PRECISION, + UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE, + FileExportFailed, + FileImportFailed, + InvalidPassword, + UserCancelled, + bfh, + decimal_point_to_base_unit_name, + make_dir, + quantize_feerate, + read_json_file, + resource_path, + write_json_file, +) from .bal import BalPlugin -from .bal_resources import ( - DEFAULT_ICON, - icon_path) +from .bal_resources import DEFAULT_ICON, icon_path from .heirs import Heirs -from .util import Util +from .util import * from .will import ( - Will, - WillItem, - NoHeirsException, - NoWillExecutorNotPresent, - NotCompleteWillException, - AmountException, - HeirNotFoundException, - HeirChangeException, - WillexecutorChangeException, - WillExecutorNotPresent, - TxFeesChangedException, - WillExpiredException) - + AmountException, + HeirChangeException, + HeirNotFoundException, + NoHeirsException, + NotCompleteWillException, + NoWillExecutorNotPresent, + TxFeesChangedException, + Will, + WillexecutorChangeException, + WillExecutorNotPresent, + WillExpiredException, + WillItem, +) from .willexecutors import Willexecutors + _logger = get_logger(__name__) - - -class Plugin(BalPlugin,Logger): +class Plugin(BalPlugin, Logger): def __init__(self, parent, config, name): Logger.__init__(self) self.logger.info("INIT BALPLUGIN") print("init bal_plugin") BalPlugin.__init__(self, parent, config, name) - self.bal_windows={} - + self.bal_windows = {} @hook - def init_qt(self,gui_object): + def init_qt(self, gui_object): print("hook init qt") - self.logger.info("HOOK init qt") + self.logger.info("HOOK init qt") try: - self.gui_object=gui_object + self.gui_object = gui_object for window in gui_object.windows: wallet = window.wallet if wallet: - window.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success')) + window.show_warning( + _("Please restart Electrum to activate the BAL plugin"), + title=_("Success"), + ) return - w = BalWindow(self,window) - self.bal_windows[window.winId]= w + w = BalWindow(self, window) + self.bal_windows[window.winId] = w for child in window.children(): - if isinstance(child,QMenuBar): + if isinstance(child, QMenuBar): for menu_child in child.children(): - if isinstance(menu_child,QMenu): + if isinstance(menu_child, QMenu): try: - if menu_child.title()==_("&Tools"): + if menu_child.title() == _("&Tools"): w.init_menubar_tools(menu_child) - + except Exception as e: raise e - self.logger.error(("except:",menu_child.text())) - + self.logger.error(("except:", menu_child.text())) + except Exception as e: self.logger.error("Error loading plugini {}".format(e)) raise e - - @hook def create_status_bar(self, sb): self.logger.info("HOOK create status bar") return - b = StatusBarButton(read_QIcon_from_bytes(self.bal_plugin.read_file('bal32x32.png')), "Bal "+_("Bitcoin After Life"), - partial(self.setup_dialog, sb), sb.height()) + b = StatusBarButton( + read_QIcon_from_bytes( + self.bal_plugin.read_file(os.path.join("icons", "bal32x32.png")) + ), + "Bal " + _("Bitcoin After Life"), + partial(self.setup_dialog, sb), + sb.height(), + ) sb.addPermanentWidget(b) @hook - def init_menubar(self,window): + def init_menubar(self, window): self.logger.info("HOOK init_menubar") w = self.get_window(window) w.init_menubar_tools(window.tools_menu) @hook - def load_wallet(self,wallet, main_window): + def load_wallet(self, wallet, main_window): self.logger.info("HOOK load wallet") w = self.get_window(main_window) w.wallet = wallet w.init_will() - w.willexecutors = Willexecutors.get_willexecutors(self, update=False, bal_window=w) + w.willexecutors = Willexecutors.get_willexecutors( + self, update=False, bal_window=w + ) w.disable_plugin = False - w.ok=True + w.ok = True @hook - def close_wallet(self,wallet): + def close_wallet(self, wallet): print("HOOK close wallet") - for winid,win in self.bal_windows.items(): + for winid, win in self.bal_windows.items(): if win.wallet == wallet: win.on_close() + @hook def init_keystore(self): print("init keystore") + @hook - def daemon_wallet_loaded(self,boh,wallet): + def daemon_wallet_loaded(self, boh, wallet): print("daemon wallet loaded") - def get_window(self,window): - w = self.bal_windows.get(window.winId,None) + + def get_window(self, window): + w = self.bal_windows.get(window.winId, None) if w is None: - w=BalWindow(self,window) - self.bal_windows[window.winId]=w + w = BalWindow(self, window) + self.bal_windows[window.winId] = w return w - + def requires_settings(self): return True - + def settings_widget(self, window): + w = self.get_window(window.window) + widget = QWidget() + enterbutton = EnterButton(_("Settings"), partial(w.settings_dialog, window)) - w=self.get_window(window.window) - widget=QWidget() - enterbutton=EnterButton(_('Settings'), partial(w.settings_dialog,window)) - - widget.setLayout(Buttons(enterbutton,widget)) + widget.setLayout(Buttons(enterbutton, widget)) return widget + def password_dialog(self, msg=None, parent=None): parent = parent or self d = PasswordDialog(parent, msg) @@ -444,11 +351,12 @@ class Plugin(BalPlugin,Logger): self.extension = bool(keystore.get_passphrase(password)) return keystore.get_seed(password) - def settings_dialog(self,window,wallet): - - d = BalDialog(window,self,self.get_window_title("Settings")) + def settings_dialog(self, window, wallet): + d = BalDialog(window, self, self.get_window_title("Settings")) d.setMinimumSize(100, 200) - qicon=read_QPixmap_from_bytes(self.read_file("bal32x32.png")) + qicon = read_QPixmap_from_bytes( + self.read_file(os.path.join("icons", "bal32x32.png")) + ) lbl_logo = QLabel() lbl_logo.setPixmap(qicon) @@ -459,23 +367,61 @@ class Plugin(BalPlugin,Logger): def on_multiverse_change(): self.update_all() - #heir_enable_multiverse = bal_checkbox(self.ENABLE_MULTIVERSE,on_multiverse_change) + # heir_enable_multiverse = bal_checkbox(self.ENABLE_MULTIVERSE,on_multiverse_change) - heir_hide_replaced = bal_checkbox(self.HIDE_REPLACED,on_multiverse_change) + heir_hide_replaced = bal_checkbox(self.HIDE_REPLACED, on_multiverse_change) - heir_hide_invalidated = bal_checkbox(self.HIDE_INVALIDATED,on_multiverse_change) + heir_hide_invalidated = bal_checkbox( + self.HIDE_INVALIDATED, on_multiverse_change + ) heir_repush = QPushButton("Rebroadcast transactions") - heir_repush.clicked.connect(partial(self.broadcast_transactions,True)) - grid=QGridLayout(d) - add_widget(grid,"Hide Replaced",heir_hide_replaced, 1, "Hide replaced transactions from will detail and list") - add_widget(grid,"Hide Invalidated",heir_hide_invalidated ,2,"Hide invalidated transactions from will detail and list") - add_widget(grid,"Ping Willexecutors",heir_ping_willexecutors,3,"Ping willexecutors to get payment info before compiling will") - add_widget(grid," - Ask before",heir_ask_ping_willexecutors,4,"Ask before to ping willexecutor") - add_widget(grid,"Backup Transaction",heir_no_willexecutor,5,"Add transactions without willexecutor") - #add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ") - grid.addWidget(heir_repush,7,0) - grid.addWidget(HelpButton("Broadcast all transactions to willexecutors including those already pushed"),7,2) + heir_repush.clicked.connect(partial(self.broadcast_transactions, True)) + grid = QGridLayout(d) + add_widget( + grid, + "Hide Replaced", + heir_hide_replaced, + 1, + "Hide replaced transactions from will detail and list", + ) + add_widget( + grid, + "Hide Invalidated", + heir_hide_invalidated, + 2, + "Hide invalidated transactions from will detail and list", + ) + add_widget( + grid, + "Ping Willexecutors", + heir_ping_willexecutors, + 3, + "Ping willexecutors to get payment info before compiling will", + ) + add_widget( + grid, + " - Ask before", + heir_ask_ping_willexecutors, + 4, + "Ask before to ping willexecutor", + ) + add_widget( + grid, + "Backup Transaction", + heir_no_willexecutor, + 5, + "Add transactions without willexecutor", + ) + # add_widget(grid,"Enable Multiverse(EXPERIMENTAL/BROKEN)",heir_enable_multiverse,6,"enable multiple locktimes, will import.... ") + grid.addWidget(heir_repush, 7, 0) + grid.addWidget( + HelpButton( + "Broadcast all transactions to willexecutors including those already pushed" + ), + 7, + 2, + ) if ret := bool(d.exec()): try: @@ -485,27 +431,33 @@ class Plugin(BalPlugin,Logger): pass return False - def broadcast_transactions(self,force): - for k,w in self.bal_windows.items(): + def broadcast_transactions(self, force): + for k, w in self.bal_windows.items(): w.broadcast_transactions(force) def update_all(self): - for k,w in self.bal_windows.items(): + for k, w in self.bal_windows.items(): w.update_all() - def get_window_title(self,title): - return _('BAL - ') + _(title) -class shown_cv(): - _type= bool - def __init__(self,value): - self.value=value + def get_window_title(self, title): + return _("BAL - ") + _(title) + + +class shown_cv: + _type = bool + + def __init__(self, value): + self.value = value + def get(self): return self.value - def set(self,value): - self.value=value + + def set(self, value): + self.value = value + class BalWindow(Logger): - def __init__(self,bal_plugin: 'BalPlugin',window: 'ElectrumWindow'): + def __init__(self, bal_plugin: "BalPlugin", window: "ElectrumWindow"): Logger.__init__(self) self.bal_plugin = bal_plugin self.window = window @@ -516,18 +468,17 @@ class BalWindow(Logger): self.will_settings = None self.heirs_tab = self.create_heirs_tab() self.will_tab = self.create_will_tab() - self.ok= False + self.ok = False self.disable_plugin = True self.bal_plugin.get_decimal_point = self.window.get_decimal_point - + if self.window.wallet: self.wallet = self.window.wallet self.heirs_tab.wallet = self.wallet self.will_tab.wallet = self.wallet - - def init_menubar_tools(self,tools_menu): - self.tools_menu=tools_menu + def init_menubar_tools(self, tools_menu): + self.tools_menu = tools_menu def add_optional_tab(tabs, tab, icon, description): tab.tab_icon = icon @@ -535,28 +486,45 @@ class BalWindow(Logger): tab.tab_pos = len(tabs) if tab.is_shown_cv: tabs.addTab(tab, icon, description.replace("&", "")) + def add_toggle_action(tab): is_shown = tab.is_shown_cv.get() - tab.menu_action = self.window.view_menu.addAction(tab.tab_description, lambda: self.window.toggle_tab(tab)) + tab.menu_action = self.window.view_menu.addAction( + tab.tab_description, lambda: self.window.toggle_tab(tab) + ) tab.menu_action.setCheckable(True) tab.menu_action.setChecked(is_shown) - - - add_optional_tab(self.window.tabs, self.heirs_tab, read_QIcon_from_bytes(self.bal_plugin.read_file("heir.png")), _("&Heirs")) - add_optional_tab(self.window.tabs, self.will_tab, read_QIcon_from_bytes(self.bal_plugin.read_file("will.png")), _("&Will")) + add_optional_tab( + self.window.tabs, + self.heirs_tab, + read_QIcon_from_bytes( + self.bal_plugin.read_file(os.path.join("icons", "heir.png")) + ), + _("&Heirs"), + ) + add_optional_tab( + self.window.tabs, + self.will_tab, + read_QIcon_from_bytes( + self.bal_plugin.read_file(os.path.join("icons", "will.png")) + ), + _("&Will"), + ) tools_menu.addSeparator() - self.tools_menu.willexecutors_action = tools_menu.addAction(_("&Will-Executors"), self.show_willexecutor_dialog) + self.tools_menu.willexecutors_action = tools_menu.addAction( + _("&Will-Executors"), self.show_willexecutor_dialog + ) self.window.view_menu.addSeparator() add_toggle_action(self.heirs_tab) add_toggle_action(self.will_tab) def load_willitems(self): - self.willitems={} - for wid,w in self.will.items(): - self.willitems[wid]=WillItem(w,wallet=self.wallet) + self.willitems = {} + for wid, w in self.will.items(): + self.willitems[wid] = WillItem(w, wallet=self.wallet) if self.willitems: - self.will_list.will=self.willitems + self.will_list.will = self.willitems self.will_list.update_will(self.willitems) self.will_tab.update() @@ -564,37 +532,42 @@ class BalWindow(Logger): keys = list(self.will.keys()) for k in keys: del self.will[k] - for wid,w in self.willitems.items(): - self.will[wid]=w.to_dict() + for wid, w in self.willitems.items(): + self.will[wid] = w.to_dict() def init_will(self): self.logger.info("********************init_____will____________**********") if not self.willexecutors: - self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self) + self.willexecutors = Willexecutors.get_willexecutors( + self.bal_plugin, update=False, bal_window=self + ) if not self.heirs: self.heirs = Heirs._validate(Heirs(self.wallet.db)) if not self.will: - self.will=self.wallet.db.get_dict("will") + self.will = self.wallet.db.get_dict("will") if self.will: self.willitems = {} try: self.load_willitems() except: - self.disable_plugin=True - self.show_warning(_('Please restart Electrum to activate the BAL plugin'), title=_('Success')) + self.disable_plugin = True + self.show_warning( + _("Please restart Electrum to activate the BAL plugin"), + title=_("Success"), + ) self.close_wallet() return if not self.will_settings: - self.will_settings=self.wallet.db.get_dict("will_settings") + self.will_settings = self.wallet.db.get_dict("will_settings") self.logger.info("will_settings: {}".format(self.will_settings)) if not self.will_settings: - Util.copy(self.will_settings,self.bal_plugin.default_will_settings()) + copy(self.will_settings, self.bal_plugin.default_will_settings()) self.logger.debug("not_will_settings {}".format(self.will_settings)) self.bal_plugin.validate_will_settings(self.will_settings) self.heir_list.update_will_settings() - + def init_wizard(self): wizard_dialog = BalWizardDialog(self) wizard_dialog.exec() @@ -604,25 +577,27 @@ class BalWindow(Logger): self.willexecutor_dialog.show() def create_heirs_tab(self): - self.heir_list = l = HeirList(self,self.window) + self.heir_list = l = HeirList(self, self.window) tab = self.window.create_list_tab(l) tab.is_shown_cv = shown_cv(True) return tab def create_will_tab(self): - self.will_list = l = PreviewList(self,self.window,None) + self.will_list = l = PreviewList(self, self.window, None) tab = self.window.create_list_tab(l) tab.is_shown_cv = shown_cv(True) return tab - def new_heir_dialog(self,heir_key=None): + def new_heir_dialog(self, heir_key=None): heir = self.heirs.get(heir_key) title = "New heir" if heir: title = f"Edit: {heir_key}" - d = BalDialog(self.window, self.bal_plugin,self.bal_plugin.get_window_title(_(title))) + d = BalDialog( + self.window, self.bal_plugin, self.bal_plugin.get_window_title(_(title)) + ) vbox = QVBoxLayout(d) grid = QGridLayout() @@ -637,17 +612,20 @@ class BalWindow(Logger): heir_address.setText(str(heir[0])) heir_amount = PercAmountEdit(self.window.get_decimal_point) if heir: - heir_amount.setText(str(Util.decode_amount(heir[1],self.window.get_decimal_point()))) - heir_locktime = HeirsLockTimeEdit(self.window,0) + heir_amount.setText( + str(decode_amount(heir[1], self.window.get_decimal_point())) + ) + heir_locktime = HeirsLockTimeEdit(self.window, 0) if heir: heir_locktime.set_locktime(heir[2]) heir_is_xpub = QCheckBox() - new_heir_button=QPushButton(_("Add another heir")) - self.add_another_heir=False + new_heir_button = QPushButton(_("Add another heir")) + self.add_another_heir = False + def new_heir(): - self.add_another_heir=True + self.add_another_heir = True d.accept() new_heir_button.clicked.connect(new_heir) @@ -655,36 +633,36 @@ class BalWindow(Logger): grid.addWidget(QLabel(_("Name")), 1, 0) grid.addWidget(heir_name, 1, 1) - grid.addWidget(HelpButton(_("Unique name or description about heir")),1,2) + grid.addWidget(HelpButton(_("Unique name or description about heir")), 1, 2) grid.addWidget(QLabel(_("Address")), 2, 0) grid.addWidget(heir_address, 2, 1) - grid.addWidget(HelpButton(_("heir bitcoin address")),2,2) + grid.addWidget(HelpButton(_("heir bitcoin address")), 2, 2) - grid.addWidget(QLabel(_("Amount")),3,0) - grid.addWidget(heir_amount,3,1) - grid.addWidget(HelpButton(_("Fixed or Percentage amount if end with %")),3,2) + grid.addWidget(QLabel(_("Amount")), 3, 0) + grid.addWidget(heir_amount, 3, 1) + grid.addWidget(HelpButton(_("Fixed or Percentage amount if end with %")), 3, 2) - locktime_label=QLabel(_("Locktime")) - enable_multiverse=self.bal_plugin.ENABLE_MULTIVERSE.get() + locktime_label = QLabel(_("Locktime")) + enable_multiverse = self.bal_plugin.ENABLE_MULTIVERSE.get() if enable_multiverse: - grid.addWidget(locktime_label,4,0) - grid.addWidget(heir_locktime,4,1) - grid.addWidget(HelpButton(_("locktime")),4,2) + grid.addWidget(locktime_label, 4, 0) + grid.addWidget(heir_locktime, 4, 1) + grid.addWidget(HelpButton(_("locktime")), 4, 2) vbox.addLayout(grid) - buttons=[CancelButton(d), OkButton(d)] + buttons = [CancelButton(d), OkButton(d)] if not heir: buttons.append(new_heir_button) vbox.addLayout(Buttons(*buttons)) while d.exec(): - #TODO SAVE HEIR + # TODO SAVE HEIR heir = [ - heir_name.text(), - heir_address.text(), - Util.encode_amount(heir_amount.text(),self.window.get_decimal_point()), - str(heir_locktime.get_locktime()), - ] + heir_name.text(), + heir_address.text(), + encode_amount(heir_amount.text(), self.window.get_decimal_point()), + str(heir_locktime.get_locktime()), + ] try: self.set_heir(heir) if self.add_another_heir: @@ -693,127 +671,158 @@ class BalWindow(Logger): except Exception as e: self.show_error(str(e)) - #def export_inheritance_handler(self,path): + # def export_inheritance_handler(self,path): # txs = self.build_inheritance_transaction(ignore_duplicate=True, keep_original=False) # with open(path,"w") as f: # for tx in txs: # tx['status']+="."+BalPlugin.STATUS_EXPORTED # f.write(str(tx['tx'])) # f.write('\n') - - def set_heir(self,heir): - heir=list(heir) - if not self.bal_plugin.ENABLE_MULTIVERSE.get(): - heir[3]=self.will_settings['locktime'] - h=Heirs.validate_heir(heir[0],heir[1:]) - self.heirs[heir[0]]=h + def set_heir(self, heir): + heir = list(heir) + if not self.bal_plugin.ENABLE_MULTIVERSE.get(): + heir[3] = self.will_settings["locktime"] + + h = Heirs.validate_heir(heir[0], heir[1:]) + self.heirs[heir[0]] = h self.heir_list.update() return True - def delete_heirs(self,heirs): + def delete_heirs(self, heirs): for heir in heirs: del self.heirs[heir] self.heirs.save() self.heir_list.update() return True - - def import_heirs(self,): - import_meta_gui(self.window, _('heirs'), self.heirs.import_file, self.heir_list.update) + + def import_heirs( + self, + ): + import_meta_gui( + self.window, _("heirs"), self.heirs.import_file, self.heir_list.update + ) def export_heirs(self): - Util.export_meta_gui(self.window, _('heirs'), self.heirs.export_file) + export_meta_gui(self.window, _("heirs"), self.heirs.export_file) - def prepare_will(self, ignore_duplicate = False, keep_original = False): - will = self.build_inheritance_transaction(ignore_duplicate = ignore_duplicate, keep_original=keep_original) + def prepare_will(self, ignore_duplicate=False, keep_original=False): + will = self.build_inheritance_transaction( + ignore_duplicate=ignore_duplicate, keep_original=keep_original + ) return will - - def delete_not_valid(self,txid,s_utxo): + + def delete_not_valid(self, txid, s_utxo): raise NotImplementedError() - def update_will(self,will): - Will.update_will(self.willitems,will) + def update_will(self, will): + Will.update_will(self.willitems, will) self.willitems.update(will) - Will.normalize_will(self.willitems,self.wallet) - - def build_will(self, ignore_duplicate = True, keep_original = True ): + Will.normalize_will(self.willitems, self.wallet) + def build_will(self, ignore_duplicate=True, keep_original=True): will = {} - willtodelete=[] - willtoappend={} + willtodelete = [] + willtoappend = {} try: self.init_class_variables() - self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin, update=False, bal_window=self) - - if not self.no_willexecutor: + self.willexecutors = Willexecutors.get_willexecutors( + self.bal_plugin, update=False, bal_window=self + ) - f=False - for u,w in self.willexecutors.items(): + if not self.no_willexecutor: + f = False + for u, w in self.willexecutors.items(): if Willexecutors.is_selected(w): - f=True + f = True if not f: - raise NoWillExecutorNotPresent("No Will-Executor or backup transaction selected") - txs = self.heirs.get_transactions(self.bal_plugin,self.window.wallet,self.will_settings['baltx_fees'],None,self.date_to_check) + raise NoWillExecutorNotPresent( + "No Will-Executor or backup transaction selected" + ) + txs = self.heirs.get_transactions( + self.bal_plugin, + self.window.wallet, + self.will_settings["baltx_fees"], + None, + self.date_to_check, + ) self.logger.info(txs) creation_time = time.time() if txs: for txid in txs: - txtodelete=[] + txtodelete = [] _break = False tx = {} - tx['tx'] = txs[txid] - tx['my_locktime'] = txs[txid].my_locktime - tx['heirsvalue'] = txs[txid].heirsvalue - tx['description'] = txs[txid].description - tx['willexecutor'] = copy.deepcopy(txs[txid].willexecutor) - tx['status'] = _("New") - tx['baltx_fees'] = txs[txid].tx_fees - tx['time'] = creation_time - tx['heirs'] = copy.deepcopy(txs[txid].heirs) - tx['txchildren'] = [] - will[txid]=WillItem(tx,_id=txid,wallet=self.wallet) + tx["tx"] = txs[txid] + tx["my_locktime"] = txs[txid].my_locktime + tx["heirsvalue"] = txs[txid].heirsvalue + tx["description"] = txs[txid].description + tx["willexecutor"] = copy.deepcopy(txs[txid].willexecutor) + tx["status"] = _("New") + tx["baltx_fees"] = txs[txid].tx_fees + tx["time"] = creation_time + tx["heirs"] = copy.deepcopy(txs[txid].heirs) + tx["txchildren"] = [] + will[txid] = WillItem(tx, _id=txid, wallet=self.wallet) self.update_will(will) except Exception as e: raise e pass - return self.willitems + return self.willitems def check_will(self): - return Will.is_will_valid(self.willitems, self.block_to_check, self.date_to_check, self.will_settings['baltx_fees'],self.window.wallet.get_utxos(),heirs=self.heirs,willexecutors=self.willexecutors ,self_willexecutor=self.no_willexecutor, wallet = self.wallet, callback_not_valid_tx=self.delete_not_valid) - def show_message(self,text): + return Will.is_will_valid( + self.willitems, + self.block_to_check, + self.date_to_check, + self.will_settings["baltx_fees"], + self.window.wallet.get_utxos(), + heirs=self.heirs, + willexecutors=self.willexecutors, + self_willexecutor=self.no_willexecutor, + wallet=self.wallet, + callback_not_valid_tx=self.delete_not_valid, + ) + + def show_message(self, text): self.window.show_message(text) - def show_warning(self,text,parent =None): - self.window.show_warning(text, parent= None) - def show_error(self,text): + + def show_warning(self, text, parent=None): + self.window.show_warning(text, parent=None) + + def show_error(self, text): self.window.show_error(text) - def show_critical(self,text): + + def show_critical(self, text): self.window.show_critical(text) - def init_heirs_to_locktime(self,multiverse=False): + def init_heirs_to_locktime(self, multiverse=False): for heir in self.heirs: - h=self.heirs[heir] + h = self.heirs[heir] if not multiverse: - self.heirs[heir]=[h[0],h[1],self.will_settings['locktime']] + self.heirs[heir] = [h[0], h[1], self.will_settings["locktime"]] def init_class_variables(self): - if not self.heirs: - raise NoHeirsException() - return - try: - self.date_to_check = Util.parse_locktime_string(self.will_settings['threshold']) - found = False - self.locktime_blocks=self.bal_plugin.LOCKTIME_BLOCKS.get() - self.current_block = Util.get_current_height(self.wallet.network) - self.block_to_check = self.current_block + self.locktime_blocks - self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get() - self.willexecutors = Willexecutors.get_willexecutors(self.bal_plugin,update=True,bal_window=self,task=False) - self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get()) + if not self.heirs: + raise NoHeirsException() + return + try: + self.date_to_check = parse_locktime_string(self.will_settings["threshold"]) + found = False + self.locktime_blocks = self.bal_plugin.LOCKTIME_BLOCKS.get() + self.current_block = get_current_height(self.wallet.network) + self.block_to_check = self.current_block + self.locktime_blocks + self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get() + self.willexecutors = Willexecutors.get_willexecutors( + self.bal_plugin, update=True, bal_window=self, task=False + ) + self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get()) - except Exception as e: - self.logger.error(e) - raise e + except Exception as e: + self.logger.error(e) + raise e - def build_inheritance_transaction(self,ignore_duplicate = True, keep_original = True): + def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True): try: if self.disable_plugin: self.logger.info("plugin is disabled") @@ -823,20 +832,32 @@ class BalWindow(Logger): return self.init_class_variables() try: - Will.check_amounts(self.heirs,self.willexecutors,self.window.wallet.get_utxos(),self.date_to_check,self.window.wallet.dust_threshold()) + Will.check_amounts( + self.heirs, + self.willexecutors, + self.window.wallet.get_utxos(), + self.date_to_check, + self.window.wallet.dust_threshold(), + ) except AmountException as e: - self.show_warning(_(f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.\n{e}")) - locktime = Util.parse_locktime_string(self.will_settings['locktime']) + self.show_warning( + _( + f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.\n{e}" + ) + ) + locktime = parse_locktime_string(self.will_settings["locktime"]) if locktime < self.date_to_check: self.show_error(_("locktime is lower than threshold")) return if not self.no_willexecutor: - f=False - for k,we in self.willexecutors.items(): + f = False + for k, we in self.willexecutors.items(): if Willexecutors.is_selected(we): - f=True + f = True if not f: - self.show_error(_(" no backup transaction or willexecutor selected")) + self.show_error( + _(" no backup transaction or willexecutor selected") + ) return try: @@ -847,33 +868,40 @@ class BalWindow(Logger): except NoHeirsException: return except NotCompleteWillException as e: - self.logger.info("{}:{}".format(type(e),e)) - message = False - if isinstance(e,HeirChangeException): - message ="Heirs changed:" - elif isinstance(e,WillExecutorNotPresent): + self.logger.info("{}:{}".format(type(e), e)) + message = False + if isinstance(e, HeirChangeException): + message = "Heirs changed:" + elif isinstance(e, WillExecutorNotPresent): message = "Will-Executor not present:" - elif isinstance(e,WillexecutorChangeException): + elif isinstance(e, WillexecutorChangeException): message = "Will-Executor changed" - elif isinstance(e,TxFeesChangedException): + elif isinstance(e, TxFeesChangedException): message = "Txfees are changed" - elif isinstance(e,HeirNotFoundException): + elif isinstance(e, HeirNotFoundException): message = "Heir not found" if message: - self.show_message(f"{_(message)}:\n {e}\n{_('will have to be built')}") - + self.show_message( + f"{_(message)}:\n {e}\n{_('will have to be built')}" + ) + self.logger.info("build will") - self.build_will(ignore_duplicate,keep_original) + self.build_will(ignore_duplicate, keep_original) try: self.check_will() - for wid,w in self.willitems.items(): - self.wallet.set_label(wid,"BAL Transaction") + for wid, w in self.willitems.items(): + self.wallet.set_label(wid, "BAL Transaction") except WillExpiredException as e: self.invalidate_will() except NotCompleteWillException as e: - self.show_error("Error:{}\n {}".format(str(e),_("Please, check your heirs, locktime and threshold!"))) + self.show_error( + "Error:{}\n {}".format( + str(e), + _("Please, check your heirs, locktime and threshold!"), + ) + ) self.window.history_list.update() self.window.utxo_list.update() @@ -886,10 +914,10 @@ class BalWindow(Logger): self, tx: Transaction, *, - parent: 'ElectrumWindow', + parent: "ElectrumWindow", prompt_if_unsaved: bool = False, external_keypairs: Mapping[bytes, bytes] = None, - payment_identifier: 'PaymentIdentifier' = None, + payment_identifier: "PaymentIdentifier" = None, ): try: d = TxDialog( @@ -897,62 +925,78 @@ class BalWindow(Logger): parent=parent, prompt_if_unsaved=prompt_if_unsaved, external_keypairs=external_keypairs, - #payment_identifier=payment_identifier, + # payment_identifier=payment_identifier, + ) + d.setWindowIcon( + read_QIcon_from_bytes( + self.bal_plugin.read_file(os.path.join("icons", "bal32x32.png")) + ) ) - d.setWindowIcon(read_QIcon_from_bytes(self.bal_plugin.read_file("bal32x32.png"))) except SerializationError as e: - self.logger.error('unable to deserialize the transaction') - parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e)) + self.logger.error("unable to deserialize the transaction") + parent.show_critical( + _("Electrum was unable to deserialize the transaction:") + "\n" + str(e) + ) else: d.show() return d - def show_transaction(self,tx=None,txid=None,parent = None): + def show_transaction(self, tx=None, txid=None, parent=None): if not parent: parent = self.window - if txid !=None and txid in self.willitems: - tx=self.willitems[txid].tx + if txid != None and txid in self.willitems: + tx = self.willitems[txid].tx if not tx: raise Exception(_("no tx")) - return self.show_transaction_real(tx,parent=parent) + return self.show_transaction_real(tx, parent=parent) def invalidate_will(self): def on_success(result): if result: - self.show_message(_("Please sign and broadcast this transaction to invalidate current will")) - self.wallet.set_label(result.txid(),"BAL Invalidate") - a=self.show_transaction(result) + self.show_message( + _( + "Please sign and broadcast this transaction to invalidate current will" + ) + ) + self.wallet.set_label(result.txid(), "BAL Invalidate") + a = self.show_transaction(result) else: self.show_message(_("No transactions to invalidate")) + def on_failure(exec_info): self.show_error(f"ERROR:{exec_info}") - fee_per_byte=self.will_settings.get('baltx_fees',1) - task = partial(Will.invalidate_will,self.willitems,self.wallet,fee_per_byte) + fee_per_byte = self.will_settings.get("baltx_fees", 1) + task = partial(Will.invalidate_will, self.willitems, self.wallet, fee_per_byte) msg = _("Calculating Transactions") - self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False) + self.waiting_dialog = BalWaitingDialog( + self, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() - def sign_transactions(self,password): + def sign_transactions(self, password): try: - txs={} + txs = {} signed = None tosign = None + def get_message(): msg = "" if signed: - msg=_(f"signed: {signed}\n") + msg = _(f"signed: {signed}\n") return msg + _(f"signing: {tosign}") + for txid in Will.only_valid(self.willitems): wi = self.willitems[txid] tx = copy.deepcopy(wi.tx) - if wi.get_status('COMPLETE'): - txs[txid]=tx + if wi.get_status("COMPLETE"): + txs[txid] = tx continue - tosign=txid + tosign = txid try: self.waiting_dialog.update(get_message()) - except:pass + except: + pass for txin in tx.inputs(): prevout = txin.prevout.to_json() if prevout[0] in self.willitems: @@ -962,27 +1006,27 @@ class BalWindow(Logger): txin.script_descriptor = change.script_descriptor except: pass - txin.is_mine=True - txin._TxInput__address=change.address + txin.is_mine = True + txin._TxInput__address = change.address txin._TxInput__scriptpubkey = change.scriptpubkey txin._TxInput__value_sats = change.value - - self.wallet.sign_transaction(tx, password,ignore_warnings=True) - signed=tosign - is_complete=False + + self.wallet.sign_transaction(tx, password, ignore_warnings=True) + signed = tosign + is_complete = False if tx.is_complete(): - is_complete=True - wi.set_status('COMPLETE',True) - txs[txid]=tx + is_complete = True + wi.set_status("COMPLETE", True) + txs[txid] = tx except Exception as e: return None return txs - def get_wallet_password(self,message=None,parent=None): - parent =self.window if not parent else parent + def get_wallet_password(self, message=None, parent=None): + parent = self.window if not parent else parent password = None if self.wallet.has_keystore_encryption(): - password = self.bal_plugin.password_dialog(parent=parent,msg=message) + password = self.bal_plugin.password_dialog(parent=parent, msg=message) if password is None: return False try: @@ -995,7 +1039,7 @@ class BalWindow(Logger): def on_close(self): try: if not self.disable_plugin: - close_window=BalBuildWillDialog(self) + close_window = BalBuildWillDialog(self) close_window.build_will_task() self.save_willitems() self.heirs_tab.close() @@ -1004,15 +1048,15 @@ class BalWindow(Logger): self.window.toggle_tab(self.heirs_tab) self.window.toggle_tab(self.will_tab) self.window.tabs.update() - except Exception as e: + except Exception as e: pass - def ask_password_and_sign_transactions(self,callback=None): + def ask_password_and_sign_transactions(self, callback=None): def on_success(txs): if txs: - for txid,tx in txs.items(): - self.willitems[txid].tx=copy.deepcopy(tx) - self.will[txid]=self.willitems[txid].to_dict() + for txid, tx in txs.items(): + self.willitems[txid].tx = copy.deepcopy(tx) + self.will[txid] = self.willitems[txid].to_dict() try: self.will_list.update() except: @@ -1026,149 +1070,178 @@ class BalWindow(Logger): def on_failure(exc_info): self.logger.info("sign fail: {}".format(exc_info)) self.show_error(exc_info) - password= self.get_wallet_password() - task = partial(self.sign_transactions,password) - msg = _('Signing transactions...') - self.waiting_dialog = BalWaitingDialog(self, msg, task, on_success, on_failure,exe=False) + + password = self.get_wallet_password() + task = partial(self.sign_transactions, password) + msg = _("Signing transactions...") + self.waiting_dialog = BalWaitingDialog( + self, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() - def broadcast_transactions(self,force=False): + def broadcast_transactions(self, force=False): def on_success(sulcess): self.will_list.update() if sulcess: - self.logger.info("error, some transaction was not sent"); + self.logger.info("error, some transaction was not sent") self.show_warning(_("Some transaction was not broadcasted")) return self.logger.debug("OK, sulcess transaction was sent") - self.show_message(_("All transactions are broadcasted to respective Will-Executors")) + self.show_message( + _("All transactions are broadcasted to respective Will-Executors") + ) def on_failure(err): self.logger.error(err) - task = partial(self.push_transactions_to_willexecutors,force) - msg = _('Selecting Will-Executors') - self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False) + task = partial(self.push_transactions_to_willexecutors, force) + msg = _("Selecting Will-Executors") + self.waiting_dialog = BalWaitingDialog( + self, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() - def push_transactions_to_willexecutors(self,force=False): + def push_transactions_to_willexecutors(self, force=False): willexecutors = Willexecutors.get_willexecutor_transactions(self.willitems) + def getMsg(willexecutors): msg = "Broadcasting Transactions to Will-Executors:\n" for url in willexecutors: msg += f"{url}:\t{willexecutors[url]['broadcast_status']}\n" return msg - error=False + + error = False for url in willexecutors: willexecutor = willexecutors[url] self.waiting_dialog.update(getMsg(willexecutors)) - if 'txs' in willexecutor: + if "txs" in willexecutor: try: - if Willexecutors.push_transactions_to_willexecutor(willexecutors[url]): - for wid in willexecutors[url]['txsids']: - self.willitems[wid].set_status('PUSHED', True) - willexecutors[url]['broadcast_status'] = _("Success") + if Willexecutors.push_transactions_to_willexecutor( + willexecutors[url] + ): + for wid in willexecutors[url]["txsids"]: + self.willitems[wid].set_status("PUSHED", True) + willexecutors[url]["broadcast_status"] = _("Success") else: - for wid in willexecutors[url]['txsids']: - self.willitems[wid].set_status('PUSH_FAIL', True) - error=True - willexecutors[url]['broadcast_status'] = _("Failed") - del willexecutor['txs'] + for wid in willexecutors[url]["txsids"]: + self.willitems[wid].set_status("PUSH_FAIL", True) + error = True + willexecutors[url]["broadcast_status"] = _("Failed") + del willexecutor["txs"] except Willexecutors.AlreadyPresentException: - for wid in willexecutor['txsids']: - row = self.waiting_dialog.update("checking {} - {} : {}".format(self.willitems[wid].we['url'],wid, "Waiting")) + for wid in willexecutor["txsids"]: + row = self.waiting_dialog.update( + "checking {} - {} : {}".format( + self.willitems[wid].we["url"], wid, "Waiting" + ) + ) self.willitems[wid].check_willexecutor() - row = self.waiting_dialog.update("checked {} - {} : {}".format(self.willitems[wid].we['url'],wid,self.willitems[wid].get_status("CHECKED" ))) + row = self.waiting_dialog.update( + "checked {} - {} : {}".format( + self.willitems[wid].we["url"], + wid, + self.willitems[wid].get_status("CHECKED"), + ) + ) if error: return True - - def export_json_file(self,path): + def export_json_file(self, path): for wid in self.willitems: - self.willitems[wid].set_status('EXPORTED', True) - self.will[wid]=self.willitems[wid].to_dict() + self.willitems[wid].set_status("EXPORTED", True) + self.will[wid] = self.willitems[wid].to_dict() write_json_file(path, self.will) def export_will(self): try: - Util.export_meta_gui(self.window, _('will.json'), self.export_json_file) + export_meta_gui(self.window, _("will.json"), self.export_json_file) except Exception as e: self.show_error(str(e)) raise e - + def import_will(self): def sulcess(): self.will_list.update_will(self.willitems) - import_meta_gui(self.window, _('will'), self.import_json_file,sulcess) - def import_json_file(self,path): + import_meta_gui(self.window, _("will"), self.import_json_file, sulcess) + + def import_json_file(self, path): try: data = read_json_file(path) willitems = {} - for k,v in data.items(): - data[k]['tx']=tx_from_any(v['tx']) - willitems[k]=WillItem(data[k],_id=k) + for k, v in data.items(): + data[k]["tx"] = tx_from_any(v["tx"]) + willitems[k] = WillItem(data[k], _id=k) self.update_will(willitems) except Exception as e: raise e raise FileImportFailed(_("Invalid will file")) - def check_transactions_task(self,will): + def check_transactions_task(self, will): start = time.time() - for wid,w in will.items(): - self.waiting_dialog.update("checking transaction: {}\n willexecutor: {}".format(wid,w.we['url'])) - w.check_willexecutor() + for wid, w in will.items(): + self.waiting_dialog.update( + "checking transaction: {}\n willexecutor: {}".format(wid, w.we["url"]) + ) + w.check_willexecutor() - if time.time()-start < 3: - time.sleep(3-(time.time()-start)) + if time.time() - start < 3: + time.sleep(3 - (time.time() - start)) - def check_transactions(self,will): + def check_transactions(self, will): def on_success(result): del self.waiting_dialog self.update_all() pass + def on_failure(e): self.logger.error(f"error checking transactions {e}") pass - task = partial(self.check_transactions_task,will) - msg = _('Check Transaction') - self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False) + task = partial(self.check_transactions_task, will) + msg = _("Check Transaction") + self.waiting_dialog = BalWaitingDialog( + self, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() - def ping_willexecutors_task(self,wes): + def ping_willexecutors_task(self, wes): self.logger.info("ping willexecutots task") pinged = [] failed = [] + def get_title(): - msg = _('Ping Will-Executors:') + msg = _("Ping Will-Executors:") msg += "\n\n" for url in wes: urlstr = "{:<50}: ".format(url[:50]) if url in pinged: urlstr += "Ok" elif url in failed: - urlstr +="Ko" + urlstr += "Ko" else: urlstr += "--" - urlstr+="\n" - msg+=urlstr + urlstr += "\n" + msg += urlstr - return msg - for url,we in wes.items(): + return msg + + for url, we in wes.items(): try: self.waiting_dialog.update(get_title()) except: pass - wes[url]=Willexecutors.get_info_task(url,we) - if wes[url]['status']=='KO': + wes[url] = Willexecutors.get_info_task(url, we) + if wes[url]["status"] == "KO": failed.append(url) else: pinged.append(url) - - def ping_willexecutors(self,wes,parent=None): + + def ping_willexecutors(self, wes, parent=None): if not parent: - parent=self + parent = self + def on_success(result): del self.waiting_dialog try: @@ -1176,19 +1249,22 @@ class BalWindow(Logger): except Exception as e: _logger.error(f"error updating willexecutors {e}") pass + def on_failure(e): self.logger.error(e) pass + self.logger.info("ping willexecutors") - task = partial(self.ping_willexecutors_task,wes) - msg = _('Ping Will-Executors') - self.waiting_dialog = BalWaitingDialog(self,msg,task,on_success,on_failure,exe=False) + task = partial(self.ping_willexecutors_task, wes) + msg = _("Ping Will-Executors") + self.waiting_dialog = BalWaitingDialog( + self, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() def preview_modal_dialog(self): - self.dw=WillDetailDialog(self) + self.dw = WillDetailDialog(self) self.dw.show() - def update_all(self): self.will_list.update_will(self.willitems) @@ -1196,15 +1272,18 @@ class BalWindow(Logger): self.will_tab.update() self.will_list.update() -def add_widget(grid,label,widget,row,help_): - grid.addWidget(QLabel(_(label)),row,0) - grid.addWidget(widget,row,1) - grid.addWidget(HelpButton(help_),row,2) + +def add_widget(grid, label, widget, row, help_): + grid.addWidget(QLabel(_(label)), row, 0) + grid.addWidget(widget, row, 1) + grid.addWidget(HelpButton(help_), row, 2) + class HeirsLockTimeEdit(QWidget): valueEdited = pyqtSignal() locktime_threshold = 50000000 - def __init__(self, parent=None,default_index = 1): + + def __init__(self, parent=None, default_index=1): QWidget.__init__(self, parent) hbox = QHBoxLayout() @@ -1212,19 +1291,18 @@ class HeirsLockTimeEdit(QWidget): hbox.setContentsMargins(0, 0, 0, 0) hbox.setSpacing(0) - self.locktime_raw_e = LockTimeRawEdit(self,time_edit = self) - self.locktime_date_e = LockTimeDateEdit(self,time_edit = self) + self.locktime_raw_e = LockTimeRawEdit(self, time_edit=self) + self.locktime_date_e = LockTimeDateEdit(self, time_edit=self) self.editors = [self.locktime_raw_e, self.locktime_date_e] self.combo = QComboBox() - options = [_("Raw"),_("Date")] + options = [_("Raw"), _("Date")] self.option_index_to_editor_map = { 0: self.locktime_raw_e, 1: self.locktime_date_e, } self.combo.addItems(options) - self.editor = self.option_index_to_editor_map[default_index] self.combo.currentIndexChanged.connect(self.on_current_index_changed) self.combo.setCurrentIndex(default_index) @@ -1234,36 +1312,35 @@ class HeirsLockTimeEdit(QWidget): for w in self.editors: hbox.addWidget(w) hbox.addStretch(1) - #spacer_widget = QWidget() - #spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - #hbox.addWidget(spacer_widget) - - + # spacer_widget = QWidget() + # spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + # hbox.addWidget(spacer_widget) self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit) self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit) self.combo.currentIndexChanged.connect(self.valueEdited.emit) - def on_current_index_changed(self,i): + def on_current_index_changed(self, i): for w in self.editors: w.setVisible(False) w.setEnabled(False) prev_locktime = self.editor.get_locktime() self.editor = self.option_index_to_editor_map[i] if self.editor.is_acceptable_locktime(prev_locktime): - self.editor.set_locktime(prev_locktime,force=True) + self.editor.set_locktime(prev_locktime, force=True) self.editor.setVisible(True) self.editor.setEnabled(True) def get_locktime(self) -> Optional[str]: return self.editor.get_locktime() - def set_index(self,index): + def set_index(self, index): self.combo.setCurrentIndex(index) self.on_current_index_changed(index) - def set_locktime(self, x: Any,force=True) -> None: - self.editor.set_locktime(x,force) + def set_locktime(self, x: Any, force=True) -> None: + self.editor.set_locktime(x, force) + class _LockTimeEditor: min_allowed_value = NLOCKTIME_MIN @@ -1272,7 +1349,7 @@ class _LockTimeEditor: def get_locktime(self) -> Optional[int]: raise NotImplementedError() - def set_locktime(self, x: Any,force=True) -> None: + def set_locktime(self, x: Any, force=True) -> None: raise NotImplementedError() def is_acceptable_locktime(cls, x: Any) -> bool: @@ -1284,26 +1361,27 @@ class _LockTimeEditor: return False return cls.min_allowed_value <= x <= cls.max_allowed_value + class LockTimeRawEdit(QLineEdit, _LockTimeEditor): - def __init__(self, parent=None,time_edit=None): + def __init__(self, parent=None, time_edit=None): QLineEdit.__init__(self, parent) self.setFixedWidth(14 * char_width_in_lineedit()) self.textChanged.connect(self.numbify) self.isdays = False self.isyears = False self.isblocks = False - self.time_edit=time_edit + self.time_edit = time_edit - def replace_str(self,text): - return str(text).replace('d','').replace('y','').replace('b','') + def replace_str(self, text): + return str(text).replace("d", "").replace("y", "").replace("b", "") - def checkbdy(self,s,pos,appendix): + def checkbdy(self, s, pos, appendix): try: - charpos = pos-1 - charpos = max(0,charpos) - charpos = min(len(s)-1,charpos) - if appendix == s[charpos]: - s=self.replace_str(s)+appendix + charpos = pos - 1 + charpos = max(0, charpos) + charpos = min(len(s) - 1, charpos) + if appendix == s[charpos]: + s = self.replace_str(s) + appendix pos = charpos except Exception as e: pass @@ -1311,29 +1389,34 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor): def numbify(self): text = self.text().strip() - #chars = '0123456789bdy' removed the option to choose locktime by block - chars = '0123456789dy' + # chars = '0123456789bdy' removed the option to choose locktime by block + chars = "0123456789dy" pos = posx = self.cursorPosition() - pos = len(''.join([i for i in text[:pos] if i in chars])) - s = ''.join([i for i in text if i in chars]) + pos = len("".join([i for i in text[:pos] if i in chars])) + s = "".join([i for i in text if i in chars]) self.isdays = False self.isyears = False self.isblocks = False - pos,s = self.checkbdy(s,pos,'d') - pos,s = self.checkbdy(s,pos,'y') - pos,s = self.checkbdy(s,pos,'b') + pos, s = self.checkbdy(s, pos, "d") + pos, s = self.checkbdy(s, pos, "y") + pos, s = self.checkbdy(s, pos, "b") - if 'd' in s: self.isdays = True - if 'y' in s: self.isyears = True - if 'b' in s: self.isblocks = True + if "d" in s: + self.isdays = True + if "y" in s: + self.isyears = True + if "b" in s: + self.isblocks = True + if self.isdays: + s = self.replace_str(s) + "d" + if self.isyears: + s = self.replace_str(s) + "y" + if self.isblocks: + s = self.replace_str(s) + "b" - if self.isdays: s= self.replace_str(s) + 'd' - if self.isyears: s = self.replace_str(s) + 'y' - if self.isblocks: s= self.replace_str(s) + 'b' - - self.set_locktime(s,force=False) + self.set_locktime(s, force=False) # setText sets Modified to False. Instead we want to remember # if updates were because of user modification. self.setModified(self.hasFocus()) @@ -1345,28 +1428,29 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor): except Exception as e: return None - def set_locktime(self, x: Any,force=True) -> None: + def set_locktime(self, x: Any, force=True) -> None: out = str(x) - if 'd' in out: - out = self.replace_str(x)+'d' - elif 'y' in out: - out = self.replace_str(x)+'y' - elif 'b' in out: - out = self.replace_str(x)+'b' + if "d" in out: + out = self.replace_str(x) + "d" + elif "y" in out: + out = self.replace_str(x) + "y" + elif "b" in out: + out = self.replace_str(x) + "b" else: try: out = int(x) except Exception as e: - self.setText('') + self.setText("") return out = max(out, self.min_allowed_value) out = min(out, self.max_allowed_value) self.setText(str(out)) + class LockTimeHeightEdit(LockTimeRawEdit): max_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX - def __init__(self, parent=None,time_edit=None): + def __init__(self, parent=None, time_edit=None): LockTimeRawEdit.__init__(self, parent) self.setFixedWidth(20 * char_width_in_lineedit()) self.time_edit = time_edit @@ -1381,6 +1465,7 @@ class LockTimeHeightEdit(LockTimeRawEdit): painter.setPen(ColorScheme.GRAY.as_color()) painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height") + def get_max_allowed_timestamp() -> int: ts = NLOCKTIME_MAX # Test if this value is within the valid timestamp limits (which is platform-dependent). @@ -1388,15 +1473,16 @@ def get_max_allowed_timestamp() -> int: try: datetime.fromtimestamp(ts) except (OSError, OverflowError): - ts = 2 ** 31 - 1 # INT32_MAX + ts = 2**31 - 1 # INT32_MAX datetime.fromtimestamp(ts) # test if raises return ts + class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1 max_allowed_value = get_max_allowed_timestamp() - def __init__(self, parent=None,time_edit=None): + def __init__(self, parent=None, time_edit=None): QDateTimeEdit.__init__(self, parent) self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value)) self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value)) @@ -1408,7 +1494,7 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): locktime = int(time.mktime(dt.timetuple())) return locktime - def set_locktime(self, x: Any,force = False) -> None: + def set_locktime(self, x: Any, force=False) -> None: if not self.is_acceptable_locktime(x): self.setDateTime(QDateTime.currentDateTime()) return @@ -1420,36 +1506,39 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): dt = datetime.fromtimestamp(x) self.setDateTime(dt) + _NOT_GIVEN = object() # sentinel value + class PercAmountEdit(BTCAmountEdit): - def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN): + def __init__( + self, decimal_point, is_int=False, parent=None, *, max_amount=_NOT_GIVEN + ): super().__init__(decimal_point, is_int, parent, max_amount=max_amount) def numbify(self): text = self.text().strip() - if text == '!': + if text == "!": self.shortcut.emit() return pos = self.cursorPosition() - chars = '0123456789%' + chars = "0123456789%" chars += DECIMAL_POINT - - s = ''.join([i for i in text if i in chars]) - if '%' in s: - self.is_perc=True - s=s.replace('%','') + s = "".join([i for i in text if i in chars]) + + if "%" in s: + self.is_perc = True + s = s.replace("%", "") else: - self.is_perc=False + self.is_perc = False if DECIMAL_POINT in s: p = s.find(DECIMAL_POINT) - s = s.replace(DECIMAL_POINT, '') - s = s[:p] + DECIMAL_POINT + s[p:p+8] + s = s.replace(DECIMAL_POINT, "") + s = s[:p] + DECIMAL_POINT + s[p : p + 8] if self.is_perc: - s+='%' - + s += "%" self.setText(s) self.setModified(self.hasFocus()) @@ -1457,15 +1546,16 @@ class PercAmountEdit(BTCAmountEdit): def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]: try: - text = text.replace(DECIMAL_POINT, '.') - text = text.replace('%', '') + text = text.replace(DECIMAL_POINT, ".") + text = text.replace("%", "") return (Decimal)(text) except Exception: return None def _get_text_from_amount(self, amount): out = super()._get_text_from_amount(amount) - if self.is_perc: out+='%' + if self.is_perc: + out += "%" return out def paintEvent(self, event): @@ -1473,97 +1563,151 @@ class PercAmountEdit(BTCAmountEdit): if self.base_unit: panel = QStyleOptionFrame() self.initStyleOption(panel) - textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self) + textRect = self.style().subElementRect( + QStyle.SubElement.SE_LineEditContents, panel, self + ) textRect.adjust(2, 0, -10, 0) painter = QPainter(self) painter.setPen(ColorScheme.GRAY.as_color()) - if len(self.text())==0: - painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit() + " or perc value") + if len(self.text()) == 0: + painter.drawText( + textRect, + int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), + self.base_unit() + " or perc value", + ) + class BalDialog(WindowModalDialog): - def __init__(self,parent,bal_plugin,title=None, icon = 'bal32x32.png'): - self.parent=parent - WindowModalDialog.__init__(self,parent,title) - #WindowModalDialog.__init__(self,parent) + def __init__( + self, parent, bal_plugin, title=None, icon=os.path.join("icons", "bal32x32.png") + ): + self.parent = parent + WindowModalDialog.__init__(self, parent, title) + # WindowModalDialog.__init__(self,parent) self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon))) + class BalWizardDialog(BalDialog): - def __init__(self,bal_window: 'BalWindow' ): + def __init__(self, bal_window: "BalWindow"): assert bal_window - BalDialog.__init__(self, bal_window.window, bal_window.bal_plugin, _("Bal Wizard Setup")) + BalDialog.__init__( + self, bal_window.window, bal_window.bal_plugin, _("Bal Wizard Setup") + ) self.setMinimumSize(800, 400) self.bal_window = bal_window self.parent = bal_window.window - self.layout=QVBoxLayout(self) - self.widget = BalWizardHeirsWidget(bal_window,self,self.on_next_heir,None,self.on_cancel_heir) + self.layout = QVBoxLayout(self) + self.widget = BalWizardHeirsWidget( + bal_window, self, self.on_next_heir, None, self.on_cancel_heir + ) self.layout.addWidget(self.widget) - def next_widget(self,widget): + def next_widget(self, widget): self.layout.removeWidget(self.widget) self.widget.close() - self.widget=widget + self.widget = widget self.layout.addWidget(self.widget) - #self.update() - #self.repaint() + # self.update() + # self.repaint() def on_next_heir(self): - self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_previous_heir,self.on_cancel_heir)) + self.next_widget( + BalWizardLocktimeAndFeeWidget( + self.bal_window, + self, + self.on_next_locktimeandfee, + self.on_previous_heir, + self.on_cancel_heir, + ) + ) def on_previous_heir(self): - self.next_widget(BalWizardHeirsWidget(self.bal_window,self,self.on_next_heir,None,self.on_cancel_heir)) + self.next_widget( + BalWizardHeirsWidget( + self.bal_window, self, self.on_next_heir, None, self.on_cancel_heir + ) + ) def on_cancel_heir(self): pass + def on_next_wedonwload(self): - self.next_widget(BalWizardWEWidget(self.bal_window,self,self.on_next_we,self.on_next_locktimeandfee,self.on_cancel_heir)) + self.next_widget( + BalWizardWEWidget( + self.bal_window, + self, + self.on_next_we, + self.on_next_locktimeandfee, + self.on_cancel_heir, + ) + ) + def on_next_we(self): - close_window=BalBuildWillDialog(self.bal_window,self) + close_window = BalBuildWillDialog(self.bal_window, self) close_window.build_will_task() self.close() - #self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_next_wedonwload,self.on_next_wedonwload.on_cancel_heir)) + # self.next_widget(BalWizardLocktimeAndFeeWidget(self.bal_window,self,self.on_next_locktimeandfee,self.on_next_wedonwload,self.on_next_wedonwload.on_cancel_heir)) def on_next_locktimeandfee(self): - self.next_widget(BalWizardWEDownloadWidget(self.bal_window,self,self.on_next_wedonwload,self.on_next_heir,self.on_cancel_heir)) + self.next_widget( + BalWizardWEDownloadWidget( + self.bal_window, + self, + self.on_next_wedonwload, + self.on_next_heir, + self.on_cancel_heir, + ) + ) + def on_accept(self): pass + def on_reject(self): pass + def on_close(self): pass - def closeEvent(self,event): + + def closeEvent(self, event): self.bal_window.update_all() self.bal_window.heir_list.update_will_settings() pass + class BalWizardWidget(QWidget): title = None message = None - def __init__(self,bal_window: 'BalWindow',parent,on_next,on_previous,on_cancel): - QWidget.__init__(self,parent) - self.vbox=QVBoxLayout(self) - self.bal_window=bal_window - self.parent=parent - self.on_next=on_next - self.on_cancel=on_cancel + + def __init__( + self, bal_window: "BalWindow", parent, on_next, on_previous, on_cancel + ): + QWidget.__init__(self, parent) + self.vbox = QVBoxLayout(self) + self.bal_window = bal_window + self.parent = parent + self.on_next = on_next + self.on_cancel = on_cancel self.titleLabel = QLabel(self.title) self.vbox.addWidget(self.titleLabel) self.messageLabel = QLabel(_(self.message)) self.vbox.addWidget(self.messageLabel) self.content = self.get_content() - self.content_container= QWidget() - self.containrelayout=QVBoxLayout(self.content_container) + self.content_container = QWidget() + self.containrelayout = QVBoxLayout(self.content_container) self.containrelayout.addWidget(self.content) self.vbox.addWidget(self.content_container) spacer_widget = QWidget() - spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + spacer_widget.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding + ) self.vbox.addWidget(spacer_widget) - self.buttons=[] + self.buttons = [] if on_previous: - self.on_previous=on_previous + self.on_previous = on_previous self.previous_button = QPushButton(_("Previous")) self.previous_button.clicked.connect(self._on_previous) self.buttons.append(self.previous_button) @@ -1571,7 +1715,7 @@ class BalWizardWidget(QWidget): self.next_button = QPushButton(_("Next")) self.next_button.clicked.connect(self._on_next) self.buttons.append(self.next_button) - + self.abort_button = QPushButton(_("Cancel")) self.abort_button.clicked.connect(self._on_cancel) self.buttons.append(self.abort_button) @@ -1585,98 +1729,123 @@ class BalWizardWidget(QWidget): def _on_next(self): if self.validate(): self.on_next() - + def _on_previous(self): self.on_previous() - + def get_content(self): pass - + def validate(self): return True -class BalWizardHeirsWidget(BalWizardWidget): - title="Bitcoin After Life Heirs" - message="Please add your heirs\n remember that 100% of wallet balance will be spent" +class BalWizardHeirsWidget(BalWizardWidget): + title = "Bitcoin After Life Heirs" + message = ( + "Please add your heirs\n remember that 100% of wallet balance will be spent" + ) def get_content(self): - self.heirs_list=HeirList(self.bal_window,self.parent) - button_add=QPushButton(_("Add")) + self.heirs_list = HeirList(self.bal_window, self.parent) + button_add = QPushButton(_("Add")) button_add.clicked.connect(self.add_heir) - button_import=QPushButton(_("Import")) + button_import = QPushButton(_("Import")) button_import.clicked.connect(self.import_from_file) - button_export=QPushButton(_("Export")) + button_export = QPushButton(_("Export")) button_import.clicked.connect(self.export_to_file) - widget=QWidget() - vbox=QVBoxLayout(widget) + widget = QWidget() + vbox = QVBoxLayout(widget) vbox.addWidget(self.heirs_list) - vbox.addLayout(Buttons(button_add,button_import,button_export)) + vbox.addLayout(Buttons(button_add, button_import, button_export)) return widget - def import_from_file(self): self.bal_window.import_heirs() self.heirs_list.update() + def export_to_file(self): self.bal_window.export_heirs() + def add_heir(self): self.bal_window.new_heir_dialog() self.heirs_list.update() + def validate(self): return True + class BalWizardWEDownloadWidget(BalWizardWidget): - title=_("Bitcoin After Life Will-Executors") - message=_("Choose willexecutors download method") + title = _("Bitcoin After Life Will-Executors") + message = _("Choose willexecutors download method") def get_content(self): - question=QLabel() - self.combo=QComboBox() - self.combo.addItems([ - "Automatically download and select willexecutors", - "Only download willexecutors list", - "Import willexecutor list from file", - "Manual"]) - #heir_name.setFixedWidth(32 * char_width_in_lineedit()) + question = QLabel() + self.combo = QComboBox() + self.combo.addItems( + [ + "Automatically download and select willexecutors", + "Only download willexecutors list", + "Import willexecutor list from file", + "Manual", + ] + ) + # heir_name.setFixedWidth(32 * char_width_in_lineedit()) return self.combo def validate(self): return True - def _on_next(self): + def _on_next(self): index = self.combo.currentIndex() _logger.debug(f"selected index:{index}") if index < 3: - self.bal_window.willexecutors=Willexecutors.get_willexecutors(self.bal_window.bal_plugin) - + self.bal_window.willexecutors = Willexecutors.get_willexecutors( + self.bal_window.bal_plugin + ) + if index == 2: + def doNothing(): self.bal_window.willexecutors.update(self.willexecutors) - Willexecutors.save(self.bal_window.bal_plugin,self.bal_window.willexecutors) + Willexecutors.save( + self.bal_window.bal_plugin, self.bal_window.willexecutors + ) pass - import_meta_gui(self.bal_window.window, _('willexecutors.json'), self.import_json_file, doNothing) + + import_meta_gui( + self.bal_window.window, + _("willexecutors.json"), + self.import_json_file, + doNothing, + ) if index < 2: + def on_success(willexecutors): self.bal_window.willexecutors.update(willexecutors) self.bal_window.ping_willexecutors(self.bal_window.willexecutors) if index < 1: for we in self.bal_window.willexecutors: - if self.bal_window.willexecutors[we]['status']==200: - self.bal_window.willexecutors[we]['selected']=True - Willexecutors.save(self.bal_window.bal_plugin,self.bal_window.willexecutors) + if self.bal_window.willexecutors[we]["status"] == 200: + self.bal_window.willexecutors[we]["selected"] = True + Willexecutors.save( + self.bal_window.bal_plugin, self.bal_window.willexecutors + ) + def on_failure(fail): _logger.debug(f"Failed to download willexecutors list {fail}") pass - task = partial(Willexecutors.download_list,self.bal_window.bal_plugin) + task = partial(Willexecutors.download_list, self.bal_window.bal_plugin) msg = _("Downloading Will-Executors list") - self.waiting_dialog = BalWaitingDialog(self.bal_window, msg, task, on_success, on_failure,exe=False) + self.waiting_dialog = BalWaitingDialog( + self.bal_window, msg, task, on_success, on_failure, exe=False + ) self.waiting_dialog.exe() - + elif index == 3: - #TODO DO NOTHING + # TODO DO NOTHING pass if self.validate(): @@ -1685,97 +1854,155 @@ class BalWizardWEDownloadWidget(BalWizardWidget): def import_json_file(self, path): data = read_json_file(path) data = self._validate(data) - self.willexecutors=data + self.willexecutors = data - def _validate(self,data): + def _validate(self, data): return data + class BalWizardWEWidget(BalWizardWidget): - title=("Bitcoin After Life Will-Executors") - message=_("Configure and select your willexecutors") + title = "Bitcoin After Life Will-Executors" + message = _("Configure and select your willexecutors") def get_content(self): - widget=QWidget() - vbox=QVBoxLayout(widget) - vbox.addWidget(WillExecutorWidget(self,self.bal_window,Willexecutors.get_willexecutors(self.bal_window.bal_plugin))) + widget = QWidget() + vbox = QVBoxLayout(widget) + vbox.addWidget( + WillExecutorWidget( + self, + self.bal_window, + Willexecutors.get_willexecutors(self.bal_window.bal_plugin), + ) + ) return widget + class BalWizardLocktimeAndFeeWidget(BalWizardWidget): - title=("Bitcoin After Life Will Settings") - message=_("") + title = "Bitcoin After Life Will Settings" + message = _("") def get_content(self): - widget=QWidget() - self.heir_locktime = HeirsLockTimeEdit(widget,0) - will_settings=self.bal_window.bal_plugin.WILL_SETTINGS.get() - self.heir_locktime.set_locktime(will_settings['locktime']) + widget = QWidget() + self.heir_locktime = HeirsLockTimeEdit(widget, 0) + will_settings = self.bal_window.bal_plugin.WILL_SETTINGS.get() + self.heir_locktime.set_locktime(will_settings["locktime"]) + def on_heir_locktime(): if not self.heir_locktime.get_locktime(): - self.heir_locktime.set_locktime('1y') - self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y" + self.heir_locktime.set_locktime("1y") + self.bal_window.will_settings["locktime"] = ( + self.heir_locktime.get_locktime() + if self.heir_locktime.get_locktime() + else "1y" + ) self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) + self.heir_locktime.valueEdited.connect(on_heir_locktime) - self.heir_threshold = HeirsLockTimeEdit(widget,0) - self.heir_threshold.set_locktime(will_settings['threshold']) + self.heir_threshold = HeirsLockTimeEdit(widget, 0) + self.heir_threshold.set_locktime(will_settings["threshold"]) + def on_heir_threshold(): if not self.heir_threshold.get_locktime(): - self.heir_threshold.set_locktime('180d') + self.heir_threshold.set_locktime("180d") - self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime() + self.bal_window.will_settings["threshold"] = ( + self.heir_threshold.get_locktime() + ) self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) + self.heir_threshold.valueEdited.connect(on_heir_threshold) self.heir_tx_fees = QSpinBox(widget) self.heir_tx_fees.setMinimum(1) self.heir_tx_fees.setMaximum(10000) - self.heir_tx_fees.setValue(will_settings['baltx_fees']) + self.heir_tx_fees.setValue(will_settings["baltx_fees"]) + def on_heir_tx_fees(): if not self.heir_tx_fees.value(): self.heir_tx_fees.set_value(1) - self.bal_window.will_settings['baltx_fees'] = self.heir_tx_fees.value() + self.bal_window.will_settings["baltx_fees"] = self.heir_tx_fees.value() self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) + self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees) - - def make_hlayout(label,twidget,help_text): - tw=QWidget() - hlayout=QHBoxLayout(tw) + + def make_hlayout(label, twidget, help_text): + tw = QWidget() + hlayout = QHBoxLayout(tw) hlayout.addWidget(QLabel(label)) hlayout.addWidget(twidget) hlayout.addWidget(HelpButton(help_text)) hlayout.addStretch(1) spacer_widget = QWidget() - spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + spacer_widget.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding + ) hlayout.addWidget(spacer_widget) return tw - layout = QVBoxLayout(widget) - - layout.addWidget(make_hlayout(_("Delivery Time:"),self.heir_locktime,_("Locktime* to be used in the transaction\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - +" - d: number of days after current day(ex: 1d means tomorrow)\n" - +" - y: number of years after currrent day(ex: 1y means one year from today)\n" - +"* locktime can be anticipated to update will\n"))) - layout.addWidget(make_hlayout(_("Check Alive:"),self.heir_threshold,_("Check to ask for invalidation.\n" - +"When less then this time is missing, ask to invalidate.\n" - +"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - +" - d: number of days after current day(ex: 1d means tomorrow).\n" - +" - y: number of years after currrent day(ex: 1y means one year from today).\n\n"))) - layout.addWidget(make_hlayout(_("Fees(sats/vbyte):"),self.heir_tx_fees,("Fee to be used in the transaction"))) + + layout.addWidget( + make_hlayout( + _("Delivery Time:"), + self.heir_locktime, + _( + "Locktime* to be used in the transaction\n" + + "if you choose Raw, you can insert various options based on suffix:\n" + + " - d: number of days after current day(ex: 1d means tomorrow)\n" + + " - y: number of years after currrent day(ex: 1y means one year from today)\n" + + "* locktime can be anticipated to update will\n" + ), + ) + ) + layout.addWidget( + make_hlayout( + _("Check Alive:"), + self.heir_threshold, + _( + "Check to ask for invalidation.\n" + + "When less then this time is missing, ask to invalidate.\n" + + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n" + + "if you choose Raw, you can insert various options based on suffix:\n" + + " - d: number of days after current day(ex: 1d means tomorrow).\n" + + " - y: number of years after currrent day(ex: 1y means one year from today).\n\n" + ), + ) + ) + layout.addWidget( + make_hlayout( + _("Fees(sats/vbyte):"), + self.heir_tx_fees, + ("Fee to be used in the transaction"), + ) + ) spacer_widget = QWidget() - spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + spacer_widget.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding + ) layout.addWidget(spacer_widget) return widget + class BalWaitingDialog(BalDialog): - updatemessage=pyqtSignal([str], arguments=['message']) - def __init__(self, bal_window: 'BalWindow', message: str, task, on_success=None, on_error=None, on_cancel=None,exe=True): + updatemessage = pyqtSignal([str], arguments=["message"]) + + def __init__( + self, + bal_window: "BalWindow", + message: str, + task, + on_success=None, + on_error=None, + on_cancel=None, + exe=True, + ): assert bal_window - BalDialog.__init__(self, bal_window.window,bal_window.bal_plugin, _("Please wait")) + BalDialog.__init__( + self, bal_window.window, bal_window.bal_plugin, _("Please wait") + ) self.message_label = QLabel(message) vbox = QVBoxLayout(self) vbox.addWidget(self.message_label) @@ -1785,7 +2012,7 @@ class BalWaitingDialog(BalDialog): self.cancel_button.clicked.connect(on_cancel) vbox.addLayout(Buttons(self.cancel_button)) self.accepted.connect(self.on_accepted) - self.task=task + self.task = task self.on_success = on_success self.on_error = on_error self.on_cancel = on_cancel @@ -1801,28 +2028,32 @@ class BalWaitingDialog(BalDialog): def hello(self): pass + def finished(self): _logger.info("finished") + def wait(self): self.thread.wait() def on_accepted(self): self.thread.stop() - def update_message(self,msg): + + def update_message(self, msg): self.message_label.setText(msg) def update(self, msg): self.updatemessage.emit(msg) def getText(self): - return self.message_label.text() + return self.message_label.text() - def closeEvent(self,event): + def closeEvent(self, event): self.thread.stop() + class BalBlockingWaitingDialog(BalDialog): - def __init__(self, bal_window: 'BalWindow', message: str, task: Callable[[], Any]): - BalDialog.__init__(self, bal_window, bal_window.bal_plugin,_("Please wait")) + def __init__(self, bal_window: "BalWindow", message: str, task: Callable[[], Any]): + BalDialog.__init__(self, bal_window, bal_window.bal_plugin, _("Please wait")) self.message_label = QLabel(message) vbox = QVBoxLayout(self) vbox.addWidget(self.message_label) @@ -1839,32 +2070,37 @@ class BalBlockingWaitingDialog(BalDialog): # close popup self.accept() + class bal_checkbox(QCheckBox): - def __init__(self, variable,on_click=None): + def __init__(self, variable, on_click=None): QCheckBox.__init__(self) self.setChecked(variable.get()) - self.on_click=on_click + self.on_click = on_click + def on_check(v): variable.set(v == 2) variable.get() if self.on_click: self.on_click() + self.stateChanged.connect(on_check) + class BalBuildWillDialog(BalDialog): - updatemessage=pyqtSignal() - def __init__(self,bal_window,parent=None): + updatemessage = pyqtSignal() + + def __init__(self, bal_window, parent=None): if not parent: parent = bal_window.window - BalDialog.__init__(self,parent,babl_window.bal_plugin, "Building Will") + BalDialog.__init__(self, parent, babl_window.bal_plugin, "Building Will") self.updatemessage.connect(self.update) - self.bal_window=bal_window + self.bal_window = bal_window self.message_label = QLabel("Building Will:") self.vbox = QVBoxLayout(self) self.vbox.addWidget(self.message_label) - self.qwidget=QWidget() + self.qwidget = QWidget() self.vbox.addWidget(self.qwidget) - self.labels=[] + self.labels = [] self.check_row = None self.inval_row = None self.build_row = None @@ -1874,12 +2110,18 @@ class BalBuildWillDialog(BalDialog): self._stopping = False self.thread = TaskThread(self) self.thread.finished.connect(self.task_finished) # see #3956 + def task_finished(self): pass - + def build_will_task(self): _logger.debug("build will task to be started") - self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1) + self.thread.add( + self.task_phase1, + on_success=self.on_success_phase1, + on_done=self.on_accept, + on_error=self.on_error_phase1, + ) self.show() self.exec() @@ -1889,35 +2131,49 @@ class BalBuildWillDialog(BalDialog): self.bal_window.init_class_variables() except NoHeirsException: return False, None - self.msg_set_status("checking variables","Waiting") + self.msg_set_status("checking variables", "Waiting") try: - Will.check_amounts(self.bal_window.heirs,self.bal_window.willexecutors,self.bal_window.window.wallet.get_utxos(),self.bal_window.date_to_check,self.bal_window.window.wallet.dust_threshold()) + Will.check_amounts( + self.bal_window.heirs, + self.bal_window.willexecutors, + self.bal_window.window.wallet.get_utxos(), + self.bal_window.date_to_check, + self.bal_window.window.wallet.dust_threshold(), + ) except AmountException: - self.msg_edit_row(''+_("In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts"+"")) + self.msg_edit_row( + '' + + _( + "In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts" + + "" + ) + ) self.msg_set_checking() - have_to_build=False + have_to_build = False try: self.bal_window.check_will() - self.msg_set_checking('Ok') + self.msg_set_checking("Ok") except WillExpiredException as e: self.msg_set_checking("Expired") - fee_per_byte=self.bal_window.will_settings.get('baltx_fees',1) - return None, Will.invalidate_will(self.bal_window.willitems,self.bal_window.wallet,fee_per_byte) + fee_per_byte = self.bal_window.will_settings.get("baltx_fees", 1) + return None, Will.invalidate_will( + self.bal_window.willitems, self.bal_window.wallet, fee_per_byte + ) except NoHeirsException: self.msg_set_checking("No Heirs") except NotCompleteWillException as e: message = False - have_to_build=True - if isinstance(e,HeirChangeException): - message ="Heirs changed:" - elif isinstance(e,WillExecutorNotPresent): + have_to_build = True + if isinstance(e, HeirChangeException): + message = "Heirs changed:" + elif isinstance(e, WillExecutorNotPresent): message = "Will-Executor not present" - elif isinstance(e,WillexecutorChangeException): + elif isinstance(e, WillexecutorChangeException): message = "Will-Executor changed" - elif isinstance(e,TxFeesChangedException): + elif isinstance(e, TxFeesChangedException): message = "Txfees are changed" - elif isinstance(e,HeirNotFoundException): + elif isinstance(e, HeirNotFoundException): message = "Heir not found" if message: self.msg_set_checking(message) @@ -1930,18 +2186,18 @@ class BalBuildWillDialog(BalDialog): self.bal_window.build_will() self.bal_window.check_will() for wid in Will.only_valid(self.bal_window.willitems): - self.bal_window.wallet.set_label(wid,"BAL Transaction") + self.bal_window.wallet.set_label(wid, "BAL Transaction") self.msg_set_building("Ok") except Exception as e: self.msg_set_building(self.msg_error(e)) - return False,None + return False, None have_to_sign = False for wid in Will.only_valid(self.bal_window.willitems): if not self.bal_window.willitems[wid].get_status("COMPLETE"): have_to_sign = True break return have_to_sign, None - + def on_accept(self): pass @@ -1951,25 +2207,26 @@ class BalBuildWillDialog(BalDialog): def on_error_push(self): pass - def wait(self,secs): - wait_row=None - for i in range(secs,0,-1): + def wait(self, secs): + wait_row = None + for i in range(secs, 0, -1): if self._stopping: return - wait_row = self.msg_edit_row(f"Please wait {i}secs", wait_row) + wait_row = self.msg_edit_row(f"Please wait {i}secs", wait_row) time.sleep(1) self.msg_del_row(wait_row) - def loop_broadcast_invalidating(self,tx): + def loop_broadcast_invalidating(self, tx): self.msg_set_invalidating("Broadcasting") try: tx.add_info_from_wallet(self.bal_window.wallet) self.network.run_from_another_thread(tx.add_info_from_network(self.network)) - txid = self.network.run_from_another_thread(self.network.broadcast_transaction(tx,timeout=120),timeout=120) + txid = self.network.run_from_another_thread( + self.network.broadcast_transaction(tx, timeout=120), timeout=120 + ) self.msg_set_invalidating("Ok") if not txid: _logger.debug(f"should not be none txid: {txid}") - except TxBroadcastError as e: _logger.error(e) @@ -1982,27 +2239,46 @@ class BalBuildWillDialog(BalDialog): self.msg_set_pushing("Broadcasting") retry = False try: - willexecutors=Willexecutors.get_willexecutor_transactions(self.bal_window.willitems) - for url,willexecutor in willexecutors.items(): + willexecutors = Willexecutors.get_willexecutor_transactions( + self.bal_window.willitems + ) + for url, willexecutor in willexecutors.items(): try: - if Willexecutors.is_selected(self.bal_window.willexecutors.get(url)): + if Willexecutors.is_selected( + self.bal_window.willexecutors.get(url) + ): _logger.debug(f"{url}: {willexecutor}") - if not Willexecutors.push_transactions_to_willexecutor(willexecutor): - for wid in willexecutor['txsids']: - self.bal_window.willitems[wid].set_status('PUSH_FAIL',True) - retry=True + if not Willexecutors.push_transactions_to_willexecutor( + willexecutor + ): + for wid in willexecutor["txsids"]: + self.bal_window.willitems[wid].set_status( + "PUSH_FAIL", True + ) + retry = True else: - for wid in willexecutor['txsids']: - self.bal_window.willitems[wid].set_status('PUSHED',True) + for wid in willexecutor["txsids"]: + self.bal_window.willitems[wid].set_status( + "PUSHED", True + ) except Willexecutors.AlreadyPresentException: - for wid in willexecutor['txsids']: - row = self.msg_edit_row("checking {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid, "Waiting")) + for wid in willexecutor["txsids"]: + row = self.msg_edit_row( + "checking {} - {} : {}".format( + self.bal_window.willitems[wid].we["url"], wid, "Waiting" + ) + ) self.bal_window.willitems[wid].check_willexecutor() - row = self.msg_edit_row("checked {} - {} : {}".format(self.bal_window.willitems[wid].we['url'],wid,self.bal_window.willitems[wid].get_status("CHECKED" )),row) - - - except Exception as e: + row = self.msg_edit_row( + "checked {} - {} : {}".format( + self.bal_window.willitems[wid].we["url"], + wid, + self.bal_window.willitems[wid].get_status("CHECKED"), + ), + row, + ) + except Exception as e: _logger.error(e) raise e if retry: @@ -2014,10 +2290,9 @@ class BalBuildWillDialog(BalDialog): if not self._stopping: self.loop_push() - - def invalidate_task(self,tx,password): + def invalidate_task(self, tx, password): _logger.debug(f"invalidate tx: {tx}") - tx = self.bal_window.wallet.sign_transaction(tx,password) + tx = self.bal_window.wallet.sign_transaction(tx, password) try: if tx: if tx.is_complete(): @@ -2030,54 +2305,76 @@ class BalBuildWillDialog(BalDialog): except Exception as e: self.msg_set_invalidating("Error") raise Exception("Impossible to sign") - def on_success_invalidate(self,success): - self.thread.add(self.task_phase1,on_success=self.on_success_phase1,on_done=self.on_accept,on_error=self.on_error_phase1) - def on_error(self,error): + + def on_success_invalidate(self, success): + self.thread.add( + self.task_phase1, + on_success=self.on_success_phase1, + on_done=self.on_accept, + on_error=self.on_error_phase1, + ) + + def on_error(self, error): _logger.error(error) pass - def on_success_phase1(self,result): - self.have_to_sign,tx = list(result) + + def on_success_phase1(self, result): + self.have_to_sign, tx = list(result) _logger.debug("have to sign {}".format(self.have_to_sign)) - password=None + password = None if self.have_to_sign is None: self.msg_set_invalidating() - #need to sign invalidate and restart phase 1 + # need to sign invalidate and restart phase 1 - password = self.bal_window.get_wallet_password("Invalidate your old will",parent=self) + password = self.bal_window.get_wallet_password( + "Invalidate your old will", parent=self + ) if password is False: self.msg_set_invalidating("Aborted") self.wait(3) self.close() return - self.thread.add(partial(self.invalidate_task,tx,password),on_success=self.on_success_invalidate, on_done=self.on_accept, on_error=self.on_error) - + self.thread.add( + partial(self.invalidate_task, tx, password), + on_success=self.on_success_invalidate, + on_done=self.on_accept, + on_error=self.on_error, + ) + return - + elif self.have_to_sign: - password = self.bal_window.get_wallet_password("Sign your will",parent=self) + password = self.bal_window.get_wallet_password( + "Sign your will", parent=self + ) if password is False: - self.msg_set_signing('Aborted') + self.msg_set_signing("Aborted") else: - self.msg_set_signing('Nothing to do') - self.thread.add(partial(self.task_phase2,password),on_success=self.on_success_phase2,on_done=self.on_accept_phase2,on_error=self.on_error_phase2) + self.msg_set_signing("Nothing to do") + self.thread.add( + partial(self.task_phase2, password), + on_success=self.on_success_phase2, + on_done=self.on_accept_phase2, + on_error=self.on_error_phase2, + ) return - - def on_success_phase2(self,arg=False): + + def on_success_phase2(self, arg=False): self.thread.stop() self.bal_window.save_willitems() self.msg_edit_row("Finished") self.close() - def closeEvent(self,event): + def closeEvent(self, event): self.bal_window.update_all() - self._stopping=True + self._stopping = True self.thread.stop() - def task_phase2(self,password): + def task_phase2(self, password): if self.have_to_sign: try: - if txs:=self.bal_window.sign_transactions(password): - for txid,tx in txs.items(): + if txs := self.bal_window.sign_transactions(password): + for txid, tx in txs.items(): self.bal_window.willitems[txid].tx = copy.deepcopy(tx) self.bal_window.save_willitems() self.msg_set_signing("Ok") @@ -2087,7 +2384,7 @@ class BalBuildWillDialog(BalDialog): self.msg_set_pushing() have_to_push = False for wid in Will.only_valid(self.bal_window.willitems): - w=self.bal_window.willitems[wid] + w = self.bal_window.willitems[wid] if w.we and w.get_status("COMPLETE") and not w.get_status("PUSHED"): have_to_push = True if not have_to_push: @@ -2102,63 +2399,67 @@ class BalBuildWillDialog(BalDialog): self.msg_edit_row("Ok") self.wait(5) - def on_error_phase1(self,error): + def on_error_phase1(self, error): _logger.error(f"error phase1: {error}") - def on_error_phase2(self,error): + def on_error_phase2(self, error): _logger.error("error phase2: { error}") - - def msg_set_checking(self, status = None, row = None): + def msg_set_checking(self, status=None, row=None): row = self.check_row if row is None else row - self.check_row = self.msg_set_status("Checking your will", row, status) + self.check_row = self.msg_set_status("Checking your will", row, status) - def msg_set_invalidating(self, status = None, row = None): + def msg_set_invalidating(self, status=None, row=None): row = self.inval_row if row is None else row - self.inval_row = self.msg_set_status("Invalidating old will", self.inval_row, status) + self.inval_row = self.msg_set_status( + "Invalidating old will", self.inval_row, status + ) - def msg_set_building(self, status = None, row = None): + def msg_set_building(self, status=None, row=None): row = self.build_row if row is None else row - self.build_row = self.msg_set_status("Building your will", self.build_row, status) + self.build_row = self.msg_set_status( + "Building your will", self.build_row, status + ) - def msg_set_signing(self, status = None, row = None): + def msg_set_signing(self, status=None, row=None): row = self.sign_row if row is None else row - self.sign_row = self.msg_set_status("Signing your will", self.sign_row, status) + self.sign_row = self.msg_set_status("Signing your will", self.sign_row, status) - def msg_set_pushing(self, status = None, row = None): + def msg_set_pushing(self, status=None, row=None): row = self.push_row if row is None else row - self.push_row = self.msg_set_status("Broadcasting your will to executors", self.push_row, status) + self.push_row = self.msg_set_status( + "Broadcasting your will to executors", self.push_row, status + ) - def msg_set_waiting(self, status = None, row = None): + def msg_set_waiting(self, status=None, row=None): row = self.wait_row if row is None else row - self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row) + self.wait_row = self.msg_edit_row(f"Please wait {status}secs", self.wait_row) - def msg_error(self,e): + def msg_error(self, e): return "Error: {}".format(e) - def msg_set_status(self,msg,row,status=None): - status= "Wait" if status is None else status - line="{}:\t{}".format(_(msg), status) - return self.msg_edit_row(line,row) - - - def ask_password(self,msg=None): - self.password=self.bal_window.get_wallet_password(msg,parent=self) + def msg_set_status(self, msg, row, status=None): + status = "Wait" if status is None else status + line = "{}:\t{}".format(_(msg), status) + return self.msg_edit_row(line, row) - def msg_edit_row(self,line,row=None): + def ask_password(self, msg=None): + self.password = self.bal_window.get_wallet_password(msg, parent=self) + + def msg_edit_row(self, line, row=None): _logger.debug(f"{row},{line}") - + try: - self.labels[row]=line + self.labels[row] = line except Exception as e: self.labels.append(line) - row=len(self.labels)-1 - + row = len(self.labels) - 1 + self.updatemessage.emit() - + return row - def msg_del_row(self,row): + def msg_del_row(self, row): try: del self.labels[row] except Exception as e: @@ -2167,7 +2468,7 @@ class BalBuildWillDialog(BalDialog): def update(self): self.vbox.removeWidget(self.qwidget) - self.qwidget=QWidget(self) + self.qwidget = QWidget(self) labelsbox = QVBoxLayout(self.qwidget) for label in self.labels: labelsbox.addWidget(QLabel(label)) @@ -2175,18 +2476,20 @@ class BalBuildWillDialog(BalDialog): def get_text(self): return self.message_label.text() + pass -class HeirList(MyTreeView,MessageBoxMixin): + +class HeirList(MyTreeView, MessageBoxMixin): class Columns(MyTreeView.BaseColumnsEnum): NAME = enum.auto() ADDRESS = enum.auto() AMOUNT = enum.auto() headers = { - Columns.NAME: _('Name'), - Columns.ADDRESS: _('Address'), - Columns.AMOUNT: _('Amount'), + Columns.NAME: _("Name"), + Columns.ADDRESS: _("Address"), + Columns.AMOUNT: _("Amount"), } filter_columns = [Columns.NAME, Columns.ADDRESS] @@ -2195,16 +2498,20 @@ class HeirList(MyTreeView,MessageBoxMixin): ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 1001 key_role = ROLE_HEIR_KEY - def __init__(self, bal_window: 'BalWindow',parent): + def __init__(self, bal_window: "BalWindow", parent): super().__init__( parent=parent, main_window=bal_window.window, stretch_column=self.Columns.NAME, - editable_columns=[self.Columns.NAME,self.Columns.ADDRESS,self.Columns.AMOUNT], + editable_columns=[ + self.Columns.NAME, + self.Columns.ADDRESS, + self.Columns.AMOUNT, + ], ) self.decimal_point = bal_window.window.get_decimal_point() self.bal_window = bal_window - + try: self.setModel(QStandardItemModel(self)) self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) @@ -2216,13 +2523,13 @@ class HeirList(MyTreeView,MessageBoxMixin): self.std_model = self.model() self.update() - - def on_activated(self,idx): + + def on_activated(self, idx): self.on_double_click(idx) - def on_double_click(self,idx): - edit_key = self.get_edit_key_from_coordinate(idx.row(),idx.column()) - heir= self.bal_window.heirs.get(edit_key) + def on_double_click(self, idx): + edit_key = self.get_edit_key_from_coordinate(idx.row(), idx.column()) + heir = self.bal_window.heirs.get(edit_key) self.bal_window.new_heir_dialog(edit_key) def on_edited(self, idx, edit_key, *, text): @@ -2232,23 +2539,25 @@ class HeirList(MyTreeView,MessageBoxMixin): col = idx.column() try: if col == 2: - text = Util.encode_amount(text,self.decimal_point) + text = encode_amount(text, self.decimal_point) elif col == 0: self.bal_window.delete_heirs([edit_key]) edit_key = text - prior_name[col-1] = text - prior_name.insert(0,edit_key) + prior_name[col - 1] = text + prior_name.insert(0, edit_key) prior_name = tuple(prior_name) except Exception as e: - prior_name = (edit_key,)+prior_name[:col-1]+(text,)+prior_name[col:] - + prior_name = ( + (edit_key,) + prior_name[: col - 1] + (text,) + prior_name[col:] + ) + try: self.bal_window.set_heir(prior_name) except Exception as e: pass try: - self.bal_window.set_heir((edit_key,)+original) + self.bal_window.set_heir((edit_key,) + original) except Exception as e: self.update() @@ -2262,18 +2571,28 @@ class HeirList(MyTreeView,MessageBoxMixin): selected_keys.append(sel_key) if selected_keys and idx.isValid(): column_title = self.model().horizontalHeaderItem(column).text() - column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() - for s_idx in self.selected_in_column(column)) - menu.addAction(_("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(column_data, title=column_title)) + column_data = "\n".join( + self.model().itemFromIndex(s_idx).text() + for s_idx in self.selected_in_column(column) + ) + menu.addAction( + _("Copy {}").format(column_title), + lambda: self.place_text_on_clipboard(column_data, title=column_title), + ) if column in self.editable_columns: item = self.model().itemFromIndex(idx) if item.isEditable(): persistent = QPersistentModelIndex(idx) - menu.addAction(_("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p))) - menu.addAction(_("Delete"), lambda: self.bal_window.delete_heirs(selected_keys)) + menu.addAction( + _("Edit {}").format(column_title), + lambda p=persistent: self.edit(QModelIndex(p)), + ) + menu.addAction( + _("Delete"), lambda: self.bal_window.delete_heirs(selected_keys) + ) menu.exec(self.viewport().mapToGlobal(position)) - #def get_selected_keys(self): + # def get_selected_keys(self): # selected_keys = [] # for s_idx in self.selected_in_column(self.Columns.NAME): # sel_key = self.model().itemFromIndex(s_idx).data(0) @@ -2282,7 +2601,9 @@ class HeirList(MyTreeView,MessageBoxMixin): def update(self): if self.maybe_defer_update(): return - current_key = self.get_role_data_for_current_item(col=self.Columns.NAME, role=self.ROLE_HEIR_KEY) + current_key = self.get_role_data_for_current_item( + col=self.Columns.NAME, role=self.ROLE_HEIR_KEY + ) self.model().clear() self.update_headers(self.__class__.headers) set_current = None @@ -2291,15 +2612,15 @@ class HeirList(MyTreeView,MessageBoxMixin): labels = [""] * len(self.Columns) labels[self.Columns.NAME] = key labels[self.Columns.ADDRESS] = heir[0] - labels[self.Columns.AMOUNT] = Util.decode_amount(heir[1],self.decimal_point) + labels[self.Columns.AMOUNT] = decode_amount(heir[1], self.decimal_point) items = [QStandardItem(x) for x in labels] items[self.Columns.NAME].setEditable(True) items[self.Columns.ADDRESS].setEditable(True) items[self.Columns.AMOUNT].setEditable(True) - items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY+1) - items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY+2) - items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY+3) + items[self.Columns.NAME].setData(key, self.ROLE_HEIR_KEY + 1) + items[self.Columns.ADDRESS].setData(key, self.ROLE_HEIR_KEY + 2) + items[self.Columns.AMOUNT].setData(key, self.ROLE_HEIR_KEY + 3) self.model().insertRow(self.model().rowCount(), items) @@ -2309,7 +2630,7 @@ class HeirList(MyTreeView,MessageBoxMixin): self.set_current_idx(set_current) # FIXME refresh loses sort order; so set "default" here: self.filter() - run_hook('update_heirs_tab', self) + run_hook("update_heirs_tab", self) self.update_will_settings() def refresh_row(self, key, row): @@ -2317,63 +2638,88 @@ class HeirList(MyTreeView,MessageBoxMixin): pass def get_edit_key_from_coordinate(self, row, col): - return self.get_role_data_from_coordinate(row, col, role=self.ROLE_HEIR_KEY+col+1) + return self.get_role_data_from_coordinate( + row, col, role=self.ROLE_HEIR_KEY + col + 1 + ) def create_toolbar(self, config): - toolbar, menu = self.create_toolbar_with_menu('') + toolbar, menu = self.create_toolbar_with_menu("") menu.addAction(_("&New Heir"), self.bal_window.new_heir_dialog) menu.addAction(_("Import"), self.bal_window.import_heirs) menu.addAction(_("Export"), lambda: self.bal_window.export_heirs()) - self.heir_locktime = HeirsLockTimeEdit(self,0) + self.heir_locktime = HeirsLockTimeEdit(self, 0) + def on_heir_locktime(): if not self.heir_locktime.get_locktime(): - self.heir_locktime.set_locktime('1y') - self.bal_window.will_settings['locktime'] = self.heir_locktime.get_locktime() if self.heir_locktime.get_locktime() else "1y" + self.heir_locktime.set_locktime("1y") + self.bal_window.will_settings["locktime"] = ( + self.heir_locktime.get_locktime() + if self.heir_locktime.get_locktime() + else "1y" + ) self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) + self.heir_locktime.valueEdited.connect(on_heir_locktime) - self.heir_threshold = HeirsLockTimeEdit(self,0) + self.heir_threshold = HeirsLockTimeEdit(self, 0) + def on_heir_threshold(): if not self.heir_threshold.get_locktime(): - self.heir_threshold.set_locktime('180d') + self.heir_threshold.set_locktime("180d") - self.bal_window.will_settings['threshold'] = self.heir_threshold.get_locktime() + self.bal_window.will_settings["threshold"] = ( + self.heir_threshold.get_locktime() + ) self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) + self.heir_threshold.valueEdited.connect(on_heir_threshold) self.heir_tx_fees = QSpinBox() self.heir_tx_fees.setMinimum(1) self.heir_tx_fees.setMaximum(10000) + def on_heir_tx_fees(): if not self.heir_tx_fees.value(): self.heir_tx_fees.set_value(1) - self.bal_window.will_settings['baltx_fees'] = self.heir_tx_fees.value() + self.bal_window.will_settings["baltx_fees"] = self.heir_tx_fees.value() self.bal_window.bal_plugin.WILL_SETTINGS.set(self.bal_window.will_settings) - self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees) + self.heir_tx_fees.valueChanged.connect(on_heir_tx_fees) self.heirs_widget = QWidget() layout = QHBoxLayout() self.heirs_widget.setLayout(layout) - + layout.addWidget(QLabel(_("Delivery Time:"))) layout.addWidget(self.heir_locktime) - layout.addWidget(HelpButton(_("Locktime* to be used in the transaction\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - +" - d: number of days after current day(ex: 1d means tomorrow)\n" - +" - y: number of years after currrent day(ex: 1y means one year from today)\n" - +"* locktime can be anticipated to update will\n"))) + layout.addWidget( + HelpButton( + _( + "Locktime* to be used in the transaction\n" + + "if you choose Raw, you can insert various options based on suffix:\n" + + " - d: number of days after current day(ex: 1d means tomorrow)\n" + + " - y: number of years after currrent day(ex: 1y means one year from today)\n" + + "* locktime can be anticipated to update will\n" + ) + ) + ) layout.addWidget(QLabel(" ")) layout.addWidget(QLabel(_("Check Alive:"))) layout.addWidget(self.heir_threshold) - layout.addWidget(HelpButton(_("Check to ask for invalidation.\n" - +"When less then this time is missing, ask to invalidate.\n" - +"If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n" - +"if you choose Raw, you can insert various options based on suffix:\n" - +" - d: number of days after current day(ex: 1d means tomorrow).\n" - +" - y: number of years after currrent day(ex: 1y means one year from today).\n\n"))) + layout.addWidget( + HelpButton( + _( + "Check to ask for invalidation.\n" + + "When less then this time is missing, ask to invalidate.\n" + + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n" + + "if you choose Raw, you can insert various options based on suffix:\n" + + " - d: number of days after current day(ex: 1d means tomorrow).\n" + + " - y: number of years after currrent day(ex: 1y means one year from today).\n\n" + ) + ) + ) layout.addWidget(QLabel(" ")) layout.addWidget(QLabel(_("Fees:"))) layout.addWidget(self.heir_tx_fees) @@ -2387,11 +2733,12 @@ class HeirList(MyTreeView,MessageBoxMixin): toolbar.insertWidget(2, self.heirs_widget) return toolbar + def update_will_settings(self): try: - self.heir_locktime.set_locktime(self.bal_window.will_settings['locktime']) - self.heir_tx_fees.setValue(int(self.bal_window.will_settings['baltx_fees'])) - self.heir_threshold.set_locktime(self.bal_window.will_settings['threshold']) + self.heir_locktime.set_locktime(self.bal_window.will_settings["locktime"]) + self.heir_tx_fees.setValue(int(self.bal_window.will_settings["baltx_fees"])) + self.heir_threshold.set_locktime(self.bal_window.will_settings["threshold"]) except Exception as e: _logger.error(f"Exception update_will_settings {e}") @@ -2408,44 +2755,44 @@ class PreviewList(MyTreeView): STATUS = enum.auto() headers = { - Columns.LOCKTIME: _('Locktime'), - Columns.TXID: _('Txid'), - Columns.WILLEXECUTOR: _('Will-Executor'), - Columns.STATUS: _('Status'), + Columns.LOCKTIME: _("Locktime"), + Columns.TXID: _("Txid"), + Columns.WILLEXECUTOR: _("Will-Executor"), + Columns.STATUS: _("Status"), } ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000 key_role = ROLE_HEIR_KEY - def __init__(self, bal_window: 'BalWindow',parent,will): + def __init__(self, bal_window: "BalWindow", parent, will): super().__init__( parent=parent, stretch_column=self.Columns.TXID, ) - self.parent=parent - self.bal_window=bal_window - self.decimal_point=bal_window.window.get_decimal_point + self.parent = parent + self.bal_window = bal_window + self.decimal_point = bal_window.window.get_decimal_point self.setModel(QStandardItemModel(self)) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - if not will is None: self.will = will else: self.will = bal_window.willitems - self.wallet=bal_window.window.wallet + self.wallet = bal_window.window.wallet self.setModel(QStandardItemModel(self)) self.setSortingEnabled(True) self.std_model = self.model() self.config = bal_window.bal_plugin.config - self.bal_plugin=self.bal_window.bal_plugin + self.bal_plugin = self.bal_window.bal_plugin self.update() - def on_activated(self,idx): + + def on_activated(self, idx): self.on_double_click(idx) - def on_double_click(self,idx): + def on_double_click(self, idx): idx = self.model().index(idx.row(), self.Columns.TXID) sel_key = self.model().itemFromIndex(idx).data(0) self.show_transaction([sel_key]) @@ -2460,19 +2807,28 @@ class PreviewList(MyTreeView): selected_keys.append(sel_key) if selected_keys and idx.isValid(): column_title = self.model().horizontalHeaderItem(column).text() - column_data = '\n'.join(self.model().itemFromIndex(s_idx).text() - for s_idx in self.selected_in_column(column)) + column_data = "\n".join( + self.model().itemFromIndex(s_idx).text() + for s_idx in self.selected_in_column(column) + ) - menu.addAction(_("details").format(column_title), lambda: self.show_transaction(selected_keys)).setEnabled(len(selected_keys)<2) - menu.addAction(_("check ").format(column_title), lambda: self.check_transactions(selected_keys)) + menu.addAction( + _("details").format(column_title), + lambda: self.show_transaction(selected_keys), + ).setEnabled(len(selected_keys) < 2) + menu.addAction( + _("check ").format(column_title), + lambda: self.check_transactions(selected_keys), + ) menu.addSeparator() - menu.addAction(_("delete").format(column_title), lambda: self.delete(selected_keys)) + menu.addAction( + _("delete").format(column_title), lambda: self.delete(selected_keys) + ) menu.exec(self.viewport().mapToGlobal(position)) - - def delete(self,selected_keys): + def delete(self, selected_keys): for key in selected_keys: del self.will[key] try: @@ -2485,7 +2841,7 @@ class PreviewList(MyTreeView): pass self.update() - def check_transactions(self,selected_keys): + def check_transactions(self, selected_keys): wout = {} for k in selected_keys: wout[k] = self.will[k] @@ -2493,22 +2849,22 @@ class PreviewList(MyTreeView): self.bal_window.check_transactions(wout) self.update() - def show_transaction(self,selected_keys): + def show_transaction(self, selected_keys): for key in selected_keys: self.bal_window.show_transaction(self.will[key].tx) self.update() - def select(self,selected_keys): + def select(self, selected_keys): self.selected += selected_keys self.update() - def deselect(self,selected_keys): + def deselect(self, selected_keys): for key in selected_keys: self.selected.remove(key) self.update() - def update_will(self,will): + def update_will(self, will): self.will.update(will) self.update() @@ -2520,49 +2876,52 @@ class PreviewList(MyTreeView): finally: if self.bal_plugin.ENABLE_MULTIVERSE.get(): try: - self.importaction=self.menu.addAction(_("Import"), self.import_will) + self.importaction = self.menu.addAction( + _("Import"), self.import_will + ) except Exception as ex: pass if self.will is None: return - current_key = self.get_role_data_for_current_item(col=self.Columns.TXID, role=self.ROLE_HEIR_KEY) + current_key = self.get_role_data_for_current_item( + col=self.Columns.TXID, role=self.ROLE_HEIR_KEY + ) self.model().clear() self.update_headers(self.__class__.headers) - - - set_current = None - for txid,bal_tx in self.will.items(): - if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status('REPLACED'): + for txid, bal_tx in self.will.items(): + if self.bal_window.bal_plugin._hide_replaced and bal_tx.get_status( + "REPLACED" + ): continue - if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status('INVALIDATED'): + if self.bal_window.bal_plugin._hide_invalidated and bal_tx.get_status( + "INVALIDATED" + ): continue - if not isinstance(bal_tx,WillItem): - bal_tx=WillItem(bal_tx) + if not isinstance(bal_tx, WillItem): + bal_tx = WillItem(bal_tx) - tx=bal_tx.tx + tx = bal_tx.tx labels = [""] * len(self.Columns) - labels[self.Columns.LOCKTIME] = Util.locktime_to_str(tx.locktime) + labels[self.Columns.LOCKTIME] = locktime_to_str(tx.locktime) labels[self.Columns.TXID] = txid - we = 'None' + we = "None" if bal_tx.we: - we = bal_tx.we['url'] - labels[self.Columns.WILLEXECUTOR]=we + we = bal_tx.we["url"] + labels[self.Columns.WILLEXECUTOR] = we status = bal_tx.status if len(bal_tx.status) > 53: status = "...{}".format(status[-50:]) labels[self.Columns.STATUS] = status - - - - items=[] + + items = [] for e in labels: - if type(e)== list: + if type(e) == list: try: items.append(QStandardItem(*e)) except Exception as e: @@ -2572,26 +2931,25 @@ class PreviewList(MyTreeView): items[-1].setBackground(QColor(bal_tx.get_color())) - self.model().insertRow(self.model().rowCount(), items) if txid == current_key: idx = self.model().index(row_count, self.Columns.TXID) set_current = QPersistentModelIndex(idx) self.set_current_idx(set_current) - def create_toolbar(self, config): - toolbar, menu = self.create_toolbar_with_menu('') - menu.addAction(_("Prepare"), self.build_transactions) - menu.addAction(_("Display"), self.bal_window.preview_modal_dialog) + def create_toolbar(self, config): + toolbar, menu = self.create_toolbar_with_menu("") + menu.addAction(_("Prepare"), self.build_transactions) + menu.addAction(_("Display"), self.bal_window.preview_modal_dialog) menu.addAction(_("Sign"), self.ask_password_and_sign_transactions) menu.addAction(_("Export"), self.export_will) if self.bal_plugin.ENABLE_MULTIVERSE.get(): - self.importaction=menu.addAction(_("Import"), self.import_will) + self.importaction = menu.addAction(_("Import"), self.import_will) menu.addAction(_("Broadcast"), self.broadcast) menu.addAction(_("Check"), self.check) menu.addAction(_("Invalidate"), self.invalidate_will) - wizard=QPushButton(_("Setup Wizard")) + wizard = QPushButton(_("Setup Wizard")) wizard.clicked.connect(self.bal_window.init_wizard) display = QPushButton(_("Display")) display.clicked.connect(self.bal_window.preview_modal_dialog) @@ -2600,14 +2958,12 @@ class PreviewList(MyTreeView): hlayout = QHBoxLayout(widget) hlayout.addWidget(wizard) hlayout.addWidget(display) - toolbar.insertWidget(2,widget) + toolbar.insertWidget(2, widget) - - self.menu=menu - self.toolbar=toolbar + self.menu = menu + self.toolbar = toolbar return toolbar - def hide_replaced(self): self.bal_window.bal_plugin.hide_replaced() self.update() @@ -2621,7 +2977,7 @@ class PreviewList(MyTreeView): if will: self.update_will(will) - def export_json_file(self,path): + def export_json_file(self, path): write_json_file(path, self.will) def export_will(self): @@ -2632,8 +2988,8 @@ class PreviewList(MyTreeView): self.bal_window.import_will() def ask_password_and_sign_transactions(self): - self.bal_window.ask_password_and_sign_transactions(callback=self.update) - + self.bal_window.ask_password_and_sign_transactions(callback=self.update) + def broadcast(self): self.bal_window.broadcast_transactions() self.update() @@ -2642,7 +2998,7 @@ class PreviewList(MyTreeView): will = {} for wid, w in self.bal_window.willitems: if w.get_status("VALID"): - will[wid]=w + will[wid] = w self.bal_window.check_transactions(will) self.update() @@ -2650,10 +3006,13 @@ class PreviewList(MyTreeView): self.bal_window.invalidate_will() self.update() -class PreviewDialog(BalDialog,MessageBoxMixin): + +class PreviewDialog(BalDialog, MessageBoxMixin): def __init__(self, bal_window, will): self.parent = bal_window.window - BalDialog.__init__(self,bal_window = bal_window,bal_plugin=bal_window.bal_plugin) + BalDialog.__init__( + self, bal_window=bal_window, bal_plugin=bal_window.bal_plugin + ) self.bal_plugin = bal_window.bal_plugin self.gui_object = self.bal_plugin.gui_object self.config = self.bal_plugin.config @@ -2661,49 +3020,48 @@ class PreviewDialog(BalDialog,MessageBoxMixin): self.wallet = bal_window.window.wallet self.format_amount = bal_window.window.format_amount self.base_unit = bal_window.window.base_unit - self.format_fiat_and_units = bal_window.window.format_fiat_and_units - self.fx = bal_window.window.fx + self.format_fiat_and_units = bal_window.window.format_fiat_and_units + self.fx = bal_window.window.fx self.format_fee_rate = bal_window.window.format_fee_rate self.show_address = bal_window.window.show_address if not will: self.will = bal_window.willitems else: self.will = will - self.setWindowTitle(_('Transactions Preview')) + self.setWindowTitle(_("Transactions Preview")) self.setMinimumSize(1000, 200) self.size_label = QLabel() - self.transactions_list = PreviewList(self.bal_window,self.will) + self.transactions_list = PreviewList(self.bal_window, self.will) vbox = QVBoxLayout(self) vbox.addWidget(self.size_label) vbox.addWidget(self.transactions_list) buttonbox = QHBoxLayout() - b = QPushButton(_('Sign')) + b = QPushButton(_("Sign")) b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions) buttonbox.addWidget(b) - b = QPushButton(_('Export Will')) + b = QPushButton(_("Export Will")) b.clicked.connect(self.transactions_list.export_will) buttonbox.addWidget(b) - b = QPushButton(_('Broadcast')) + b = QPushButton(_("Broadcast")) b.clicked.connect(self.transactions_list.broadcast) buttonbox.addWidget(b) - b = QPushButton(_('Invalidate will')) + b = QPushButton(_("Invalidate will")) b.clicked.connect(self.transactions_list.invalidate_will) buttonbox.addWidget(b) vbox.addLayout(buttonbox) self.update() - - def update_will(self,will): + def update_will(self, will): self.will.update(will) self.transactions_list.update_will(will) self.update() - + def update(self): self.transactions_list.update() @@ -2722,22 +3080,23 @@ class PreviewDialog(BalDialog,MessageBoxMixin): def closeEvent(self, event): event.accept() - -def read_bal_QIcon(icon_basename: str=DEFAULT_ICON) -> QIcon: + + +def read_bal_QIcon(icon_basename: str = DEFAULT_ICON) -> QIcon: return QIcon(icon_path(icon_basename)) -def read_bal_QPixmap(icon_basename: str=DEFAULT_ICON) -> QPixmap: + +def read_bal_QPixmap(icon_basename: str = DEFAULT_ICON) -> QPixmap: return QPixmap(icon_path(icon_basename)) class WillDetailDialog(BalDialog): def __init__(self, bal_window): - self.will = bal_window.willitems - self.threshold = Util.parse_locktime_string(bal_window.will_settings['threshold']) - self.bal_window = bal_window + self.threshold = parse_locktime_string(bal_window.will_settings["threshold"]) + self.bal_window = bal_window Will.add_willtree(self.will) - super().__init__(bal_window.window,bal_window.bal_plugin) + super().__init__(bal_window.window, bal_window.bal_plugin) self.config = bal_window.window.config self.wallet = bal_window.wallet self.format_amount = bal_window.window.format_amount @@ -2747,35 +3106,39 @@ class WillDetailDialog(BalDialog): self.format_fee_rate = bal_window.window.format_fee_rate self.decimal_point = bal_window.window.get_decimal_point() self.base_unit_name = decimal_point_to_base_unit_name(self.decimal_point) - self.setWindowTitle(_('Will Details')) - self.setMinimumSize(670,700) - self.vlayout= QVBoxLayout() - w=QWidget() + self.setWindowTitle(_("Will Details")) + self.setMinimumSize(670, 700) + self.vlayout = QVBoxLayout() + w = QWidget() hlayout = QHBoxLayout(w) - b = QPushButton(_('Sign')) + b = QPushButton(_("Sign")) b.clicked.connect(self.ask_password_and_sign_transactions) hlayout.addWidget(b) - - b = QPushButton(_('Broadcast')) - b.clicked.connect(self.broadcast_transactions) - hlayout.addWidget(b) - b = QPushButton(_('Export')) + b = QPushButton(_("Broadcast")) + b.clicked.connect(self.broadcast_transactions) + hlayout.addWidget(b) + + b = QPushButton(_("Export")) b.clicked.connect(self.export_will) hlayout.addWidget(b) - b = QPushButton(_('Invalidate')) + b = QPushButton(_("Invalidate")) b.clicked.connect(bal_window.invalidate_will) hlayout.addWidget(b) self.vlayout.addWidget(w) self.paint_scroll_area() - self.vlayout.addWidget(QLabel(_("Expiration date: ")+Util.locktime_to_str(self.threshold))) + self.vlayout.addWidget( + QLabel(_("Expiration date: ") + locktime_to_str(self.threshold)) + ) self.vlayout.addWidget(self.scrollbox) - w=QWidget() + w = QWidget() hlayout = QHBoxLayout(w) - hlayout.addWidget(QLabel(_("Valid Txs:")+ str(len(Will.only_valid_list(self.will))))) - hlayout.addWidget(QLabel(_("Total Txs:")+ str(len(self.will)))) + hlayout.addWidget( + QLabel(_("Valid Txs:") + str(len(Will.only_valid_list(self.will)))) + ) + hlayout.addWidget(QLabel(_("Total Txs:") + str(len(self.will)))) self.vlayout.addWidget(w) self.setLayout(self.vlayout) @@ -2788,14 +3151,18 @@ class WillDetailDialog(BalDialog): self.scrollbox.setWidget(viewport) viewport.setLayout(self.willlayout) + def ask_password_and_sign_transactions(self): self.bal_window.ask_password_and_sign_transactions(callback=self.update) self.update() + def broadcast_transactions(self): self.bal_window.broadcast_transactions() self.update() + def export_will(self): self.bal_window.export_will() + def toggle_replaced(self): self.bal_window.bal_plugin.hide_replaced() toggle = _("Hide") @@ -2817,72 +3184,101 @@ class WillDetailDialog(BalDialog): pos = self.vlayout.indexOf(self.scrollbox) self.vlayout.removeWidget(self.scrollbox) self.paint_scroll_area() - self.vlayout.insertWidget(pos,self.scrollbox) + self.vlayout.insertWidget(pos, self.scrollbox) super().update() + class WillWidget(QWidget): - def __init__(self,father=None,parent = None): + def __init__(self, father=None, parent=None): super().__init__() vlayout = QVBoxLayout() self.setLayout(vlayout) self.will = parent.bal_window.willitems self.parent = parent for w in self.will: - if self.will[w].get_status('REPLACED') and self.parent.bal_window.bal_plugin._hide_replaced: + if ( + self.will[w].get_status("REPLACED") + and self.parent.bal_window.bal_plugin._hide_replaced + ): continue - if self.will[w].get_status('INVALIDATED') and self.parent.bal_window.bal_plugin._hide_invalidated: + if ( + self.will[w].get_status("INVALIDATED") + and self.parent.bal_window.bal_plugin._hide_invalidated + ): continue f = self.will[w].father if father == f: qwidget = QWidget() childWidget = QWidget() - hlayout=QHBoxLayout(qwidget) + hlayout = QHBoxLayout(qwidget) qwidget.setLayout(hlayout) vlayout.addWidget(qwidget) - detailw=QWidget() - detaillayout=QVBoxLayout() + detailw = QWidget() + detaillayout = QVBoxLayout() detailw.setLayout(detaillayout) willpushbutton = QPushButton(w) - willpushbutton.clicked.connect(partial(self.parent.bal_window.show_transaction,txid=w)) + willpushbutton.clicked.connect( + partial(self.parent.bal_window.show_transaction, txid=w) + ) detaillayout.addWidget(willpushbutton) - locktime = Util.locktime_to_str(self.will[w].tx.locktime) - creation = Util.locktime_to_str(self.will[w].time) - def qlabel(title,value): - label = ""+_(str(title)) + f":\t{str(value)}" + locktime = locktime_to_str(self.will[w].tx.locktime) + creation = locktime_to_str(self.will[w].time) + + def qlabel(title, value): + label = "" + _(str(title)) + f":\t{str(value)}" return QLabel(label) - detaillayout.addWidget(qlabel("Locktime",locktime)) - detaillayout.addWidget(qlabel("Creation Time",creation)) + + detaillayout.addWidget(qlabel("Locktime", locktime)) + detaillayout.addWidget(qlabel("Creation Time", creation)) try: - total_fees = self.will[w].tx.input_value() - self.will[w].tx.output_value() + total_fees = ( + self.will[w].tx.input_value() - self.will[w].tx.output_value() + ) except: total_fees = -1 - decoded_fees = total_fees - fee_per_byte = round(total_fees/self.will[w].tx.estimated_size(),3) - fees_str = str(decoded_fees) + " ("+ str(fee_per_byte) + " sats/vbyte)" - detaillayout.addWidget(qlabel("Transaction fees:",fees_str)) - detaillayout.addWidget(qlabel("Status:",self.will[w].status)) + decoded_fees = total_fees + fee_per_byte = round(total_fees / self.will[w].tx.estimated_size(), 3) + fees_str = str(decoded_fees) + " (" + str(fee_per_byte) + " sats/vbyte)" + detaillayout.addWidget(qlabel("Transaction fees:", fees_str)) + detaillayout.addWidget(qlabel("Status:", self.will[w].status)) detaillayout.addWidget(QLabel("")) detaillayout.addWidget(QLabel("Heirs:")) for heir in self.will[w].heirs: - if "w!ll3x3c\"" not in heir: - decoded_amount = Util.decode_amount(self.will[w].heirs[heir][3],self.parent.decimal_point) - detaillayout.addWidget(qlabel(heir,f"{decoded_amount} {self.parent.base_unit_name}")) + if 'w!ll3x3c"' not in heir: + decoded_amount = decode_amount( + self.will[w].heirs[heir][3], self.parent.decimal_point + ) + detaillayout.addWidget( + qlabel( + heir, f"{decoded_amount} {self.parent.base_unit_name}" + ) + ) if self.will[w].we: detaillayout.addWidget(QLabel("")) detaillayout.addWidget(QLabel(_("Willexecutor: LOCKTIME_THRESHOLD: - dt = datetime.fromtimestamp(locktime).isoformat() - return dt - - except Exception as e: - pass - return str(locktime) - - def str_to_locktime(locktime): - try: - if locktime[-1] in ('y','d','b'): - return locktime - else: return int(locktime) - except Exception as e: - pass - dt_object = datetime.fromisoformat(locktime) - timestamp = dt_object.timestamp() - return int(timestamp) - - def parse_locktime_string(locktime,w=None): - try: - return int(locktime) - - except Exception as e: - pass - try: - now = datetime.now() - if locktime[-1] == 'y': - locktime = str(int(locktime[:-1])*365) + "d" - if locktime[-1] == 'd': - return int((now + timedelta(days = int(locktime[:-1]))).replace(hour=0,minute=0,second=0,microsecond=0).timestamp()) - if locktime[-1] == 'b': - locktime = int(locktime[:-1]) - height = 0 - if w: - height = Util.get_current_height(w.network) - locktime+=int(height) - return int(locktime) - except Exception as e: - pass - return 0 - def int_locktime(seconds=0,minutes=0,hours=0, days=0, blocks = 0): - return int(seconds + minutes*60 + hours*60*60 + days*60*60*24 + blocks * 600) - - def encode_amount(amount, decimal_point): - if Util.is_perc(amount): - return amount - else: - try: - return int(float(amount)*pow(10,decimal_point)) - except: - return 0 - - def decode_amount(amount,decimal_point): - if Util.is_perc(amount): - return amount - else: - num=8-decimal_point - basestr="{{:0{}.{}f}}".format(num,num) - return "{:08.8f}".format(float(amount)/pow(10,decimal_point)) - - def is_perc(value): - try: - return value[-1] == '%' - except: - return False - - def cmp_array(heira,heirb): - try: - if not len(heira) == len(heirb): - return False - for h in range(0,len(heira)): - if not heira[h] == heirb[h]: - return False - return True - except: - return False - - def cmp_heir(heira,heirb): - if heira[0] == heirb[0] and heira[1] == heirb[1]: - return True - return False - - def cmp_willexecutor(willexecutora,willexecutorb): - if willexecutora == willexecutorb: - return True - try: - if willexecutora['url']==willexecutorb['url'] and willexecutora['address'] == willexecutorb['address'] and willexecutora['base_fee']==willexecutorb['base_fee']: - return True - except: - return False - return False - - def search_heir_by_values(heirs,heir,values): - for h,v in heirs.items(): - found = False - for val in values: - if val in v and v[val] != heir[val]: - found = True - - if not found: - return h - return False - - def cmp_heir_by_values(heira,heirb,values): - for v in values: - if heira[v] != heirb[v]: - return False - return True - - def cmp_heirs_by_values(heirsa,heirsb,values,exclude_willexecutors=False,reverse = True): - for heira in heirsa: - if (exclude_willexecutors and not "w!ll3x3c\"" in heira) or not exclude_willexecutors: - found = False - for heirb in heirsb: - if Util.cmp_heir_by_values(heirsa[heira],heirsb[heirb],values): - found=True - if not found: - return False - if reverse: - return Util.cmp_heirs_by_values(heirsb,heirsa,values,exclude_willexecutors=exclude_willexecutors,reverse=False) - else: - return True - - def cmp_heirs(heirsa,heirsb,cmp_function = lambda x,y: x[0]==y[0] and x[3]==y[3],reverse=True): - try: - for heir in heirsa: - if not "w!ll3x3c\"" in heir: - if not heir in heirsb or not cmp_function(heirsa[heir],heirsb[heir]): - if not Util.search_heir_by_values(heirsb,heirsa[heir],[0,3]): - return False - if reverse: - return Util.cmp_heirs(heirsb,heirsa,cmp_function,False) - else: - return True - except Exception as e: - raise e - return False - - def cmp_inputs(inputsa,inputsb): - if len(inputsa) != len(inputsb): - return False - for inputa in inputsa: - if not Util.in_utxo(inputa,inputsb): - return False - return True - - def cmp_outputs(outputsa,outputsb,willexecutor_output = None): - if len(outputsa) != len(outputsb): - return False - for outputa in outputsa: - if not Util.cmp_output(outputa,willexecutor_output): - if not Util.in_output(outputa,outputsb): - return False - return True - - def cmp_txs(txa,txb): - if not Util.cmp_inputs(txa.inputs(),txb.inputs()): - return False - if not Util.cmp_outputs(txa.outputs(),txb.outputs()): - return False - return True - - def get_value_amount(txa,txb): - outputsa=txa.outputs() - outputsb=txb.outputs() - value_amount = 0 - - for outa in outputsa: - same_amount,same_address = Util.in_output(outa,txb.outputs()) - if not (same_amount or same_address): - return False - if same_amount and same_address: - value_amount+=outa.value - if same_amount: - pass - if same_address: - pass - - return value_amount - - - - def chk_locktime(timestamp_to_check,block_height_to_check,locktime): - #TODO BUG: WHAT HAPPEN AT THRESHOLD? - locktime=int(locktime) - if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check: - return True - elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check: - return True - else: - return False - - def anticipate_locktime(locktime,blocks=0,hours=0,days=0): +def locktime_to_str(locktime): + try: locktime = int(locktime) - out=0 - if locktime> LOCKTIME_THRESHOLD: - seconds = blocks*600 + hours*3600 + days*86400 - dt = datetime.fromtimestamp(locktime) - dt -= timedelta(seconds=seconds) - out = dt.timestamp() - else: - blocks -= hours*6 + days*144 - out = locktime + blocks + if locktime > LOCKTIME_THRESHOLD: + dt = datetime.fromtimestamp(locktime).isoformat() + return dt - if out < 1: - out = 1 + except Exception: + pass + return str(locktime) + + +def str_to_locktime(locktime): + try: + if locktime[-1] in ("y", "d", "b"): + return locktime + else: + return int(locktime) + except Exception: + pass + dt_object = datetime.fromisoformat(locktime) + timestamp = dt_object.timestamp() + return int(timestamp) + + +def parse_locktime_string(locktime, w=None): + try: + return int(locktime) + + except Exception: + pass + try: + now = datetime.now() + if locktime[-1] == "y": + locktime = str(int(locktime[:-1]) * 365) + "d" + if locktime[-1] == "d": + return int( + (now + timedelta(days=int(locktime[:-1]))) + .replace(hour=0, minute=0, second=0, microsecond=0) + .timestamp() + ) + if locktime[-1] == "b": + locktime = int(locktime[:-1]) + height = 0 + if w: + height = get_current_height(w.network) + locktime += int(height) + return int(locktime) + except Exception: + pass + return 0 + + +def int_locktime(seconds=0, minutes=0, hours=0, days=0, blocks=0): + return int( + seconds + minutes * 60 + hours * 60 * 60 + days * 60 * 60 * 24 + blocks * 600 + ) + + +def encode_amount(amount, decimal_point): + if is_perc(amount): + return amount + else: + try: + return int(float(amount) * pow(10, decimal_point)) + except: + return 0 + + +def decode_amount(amount, decimal_point): + if is_perc(amount): + return amount + else: + num = 8 - decimal_point + basestr = "{{:0{}.{}f}}".format(num, num) + return "{:08.8f}".format(float(amount) / pow(10, decimal_point)) + + +def is_perc(value): + try: + return value[-1] == "%" + except: + return False + + +def cmp_array(heira, heirb): + try: + if not len(heira) == len(heirb): + return False + for h in range(0, len(heira)): + if not heira[h] == heirb[h]: + return False + return True + except: + return False + + +def cmp_heir(heira, heirb): + if heira[0] == heirb[0] and heira[1] == heirb[1]: + return True + return False + + +def cmp_willexecutor(willexecutora, willexecutorb): + if willexecutora == willexecutorb: + return True + try: + if ( + willexecutora["url"] == willexecutorb["url"] + and willexecutora["address"] == willexecutorb["address"] + and willexecutora["base_fee"] == willexecutorb["base_fee"] + ): + return True + except: + return False + return False + + +def search_heir_by_values(heirs, heir, values): + for h, v in heirs.items(): + found = False + for val in values: + if val in v and v[val] != heir[val]: + found = True + + if not found: + return h + return False + + +def cmp_heir_by_values(heira, heirb, values): + for v in values: + if heira[v] != heirb[v]: + return False + return True + + +def cmp_heirs_by_values( + heirsa, heirsb, values, exclude_willexecutors=False, reverse=True +): + for heira in heirsa: + if ( + exclude_willexecutors and 'w!ll3x3c"' not in heira + ) or not exclude_willexecutors: + found = False + for heirb in heirsb: + if cmp_heir_by_values(heirsa[heira], heirsb[heirb], values): + found = True + if not found: + return False + if reverse: + return cmp_heirs_by_values( + heirsb, + heirsa, + values, + exclude_willexecutors=exclude_willexecutors, + reverse=False, + ) + else: + return True + + +def cmp_heirs( + heirsa, + heirsb, + cmp_function=lambda x, y: x[0] == y[0] and x[3] == y[3], + reverse=True, +): + try: + for heir in heirsa: + if 'w!ll3x3c"' not in heir: + if heir not in heirsb or not cmp_function(heirsa[heir], heirsb[heir]): + if not search_heir_by_values(heirsb, heirsa[heir], [0, 3]): + return False + if reverse: + return cmp_heirs(heirsb, heirsa, cmp_function, False) + else: + return True + except Exception as e: + raise e + return False + + +def cmp_inputs(inputsa, inputsb): + if len(inputsa) != len(inputsb): + return False + for inputa in inputsa: + if not in_utxo(inputa, inputsb): + return False + return True + + +def cmp_outputs(outputsa, outputsb, willexecutor_output=None): + if len(outputsa) != len(outputsb): + return False + for outputa in outputsa: + if not cmp_output(outputa, willexecutor_output): + if not in_output(outputa, outputsb): + return False + return True + + +def cmp_txs(txa, txb): + if not cmp_inputs(txa.inputs(), txb.inputs()): + return False + if not cmp_outputs(txa.outputs(), txb.outputs()): + return False + return True + + +def get_value_amount(txa, txb): + outputsa = txa.outputs() + outputsb = txb.outputs() + value_amount = 0 + + for outa in outputsa: + same_amount, same_address = in_output(outa, txb.outputs()) + if not (same_amount or same_address): + return False + if same_amount and same_address: + value_amount += outa.value + if same_amount: + pass + if same_address: + pass + + return value_amount + + +def chk_locktime(timestamp_to_check, block_height_to_check, locktime): + # TODO BUG: WHAT HAPPEN AT THRESHOLD? + locktime = int(locktime) + if locktime > LOCKTIME_THRESHOLD and locktime > timestamp_to_check: + return True + elif locktime < LOCKTIME_THRESHOLD and locktime > block_height_to_check: + return True + else: + return False + + +def anticipate_locktime(locktime, blocks=0, hours=0, days=0): + locktime = int(locktime) + out = 0 + if locktime > LOCKTIME_THRESHOLD: + seconds = blocks * 600 + hours * 3600 + days * 86400 + dt = datetime.fromtimestamp(locktime) + dt -= timedelta(seconds=seconds) + out = dt.timestamp() + else: + blocks -= hours * 6 + days * 144 + out = locktime + blocks + + if out < 1: + out = 1 + return out + + +def cmp_locktime(locktimea, locktimeb): + if locktimea == locktimeb: + return 0 + strlocktimea = str(locktimea) + strlocktimeb = str(locktimeb) + intlocktimea = str_to_locktime(strlocktimea) + intlocktimeb = str_to_locktime(strlocktimeb) + if locktimea[-1] in "ydb": + if locktimeb[-1] == locktimea[-1]: + return int(strlocktimea[-1]) - int(strlocktimeb[-1]) + else: + return int(locktimea) - (locktimeb) + + +def get_lowest_valid_tx(available_utxos, will): + will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime) + for txid, willitem in will.items(): + pass + + +def get_locktimes(will): + locktimes = {} + for txid, willitem in will.items(): + locktimes[willitem["tx"].locktime] = True + return locktimes.keys() + + +def get_lowest_locktimes(locktimes): + sorted_timestamp = [] + sorted_block = [] + for l in locktimes: + l = parse_locktime_string(l) + if l < LOCKTIME_THRESHOLD: + bisect.insort(sorted_block, l) + else: + bisect.insort(sorted_timestamp, l) + + return sorted(sorted_timestamp), sorted(sorted_block) + + +def get_lowest_locktimes_from_will(will): + return get_lowest_locktimes(get_locktimes(will)) + + +def search_willtx_per_io(will, tx): + for wid, w in will.items(): + if cmp_txs(w["tx"], tx["tx"]): + return wid, w + return None, None + + +def invalidate_will(will): + raise Exception("not implemented") + + +def get_will_spent_utxos(will): + utxos = [] + for txid, willitem in will.items(): + utxos += willitem["tx"].inputs() + + return utxos + + +def utxo_to_str(utxo): + try: + return utxo.to_str() + except Exception: + pass + try: + return utxo.prevout.to_str() + except Exception: + pass + return str(utxo) + + +def cmp_utxo(utxoa, utxob): + utxoa = utxo_to_str(utxoa) + utxob = utxo_to_str(utxob) + if utxoa == utxob: + return True + else: + return False + + +def in_utxo(utxo, utxos): + for s_u in utxos: + if cmp_utxo(s_u, utxo): + return True + return False + + +def txid_in_utxo(txid, utxos): + for s_u in utxos: + if s_u.prevout.txid == txid: + return True + return False + + +def cmp_output(outputa, outputb): + return outputa.address == outputb.address and outputa.value == outputb.value + + +def in_output(output, outputs): + for s_o in outputs: + if cmp_output(s_o, output): + return True + return False + + +# check all output with the same amount if none have the same address it can be a change +# return true true same address same amount +# return true false same amount different address +# return false false different amount, different address not found + + +def din_output(out, outputs): + same_amount = [] + for s_o in outputs: + if int(out.value) == int(s_o.value): + same_amount.append(s_o) + if out.address == s_o.address: + return True, True + else: + pass + + if len(same_amount) > 0: + return True, False + else: + return False, False + + +def get_change_output(wallet, in_amount, out_amount, fee): + change_amount = int(in_amount - out_amount - fee) + if change_amount > wallet.dust_threshold(): + change_addresses = wallet.get_change_addresses_for_new_transaction() + out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount) + out.is_change = True return out - def cmp_locktime(locktimea,locktimeb): - if locktimea==locktimeb: - return 0 - strlocktime = str(locktimea) - strlocktimeb = str(locktimeb) - intlocktimea = Util.str_to_locktime(strlocktimea) - intlocktimeb = Util.str_to_locktime(strlocktimeb) - if locktimea[-1] in "ydb": - if locktimeb[-1] == locktimea[-1]: - return int(strlocktimea[-1])-int(strlocktimeb[-1]) - else: - return int(locktimea)-(locktimeb) - - def get_lowest_valid_tx(available_utxos,will): - will = sorted(will.items(),key = lambda x: x[1]['tx'].locktime) - for txid,willitem in will.items(): +def get_current_height(network: "Network"): + # if no network or not up to date, just set locktime to zero + if not network: + return 0 + chain = network.blockchain() + if chain.is_tip_stale(): + return 0 + # figure out current block height + chain_height = chain.height() # learnt from all connected servers, SPV-checked + server_height = ( + network.get_server_height() + ) # height claimed by main server, unverified + # note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork) + # - if it's lagging too much, it is the network's job to switch away + if server_height < chain_height - 10: + # the diff is suspiciously large... give up and use something non-fingerprintable + return 0 + # discourage "fee sniping" + height = min(chain_height, server_height) + return height + + +def print_var(var, name="", veryverbose=False): + print(f"---{name}---") + if var is not None: + try: + print("doc:", doc(var)) + except: + pass + try: + print("str:", str(var)) + except: + pass + try: + print("repr", repr(var)) + except: + pass + try: + print("dict", dict(var)) + except: + pass + try: + print("dir", dir(var)) + except: + pass + try: + print("type", type(var)) + except: + pass + try: + print("to_json", var.to_json()) + except: + pass + try: + print("__slotnames__", var.__slotnames__) + except: pass - def get_locktimes(will): - locktimes = {} - for txid,willitem in will.items(): - locktimes[willitem['tx'].locktime]=True - return locktimes.keys() - - def get_lowest_locktimes(locktimes): - sorted_timestamp=[] - sorted_block=[] - for l in locktimes: - l=Util.parse_locktime_string(l) - if l < LOCKTIME_THRESHOLD: - bisect.insort(sorted_block,l) - else: - bisect.insort(sorted_timestamp,l) - - return sorted(sorted_timestamp), sorted(sorted_block) - - def get_lowest_locktimes_from_will(will): - return Util.get_lowest_locktimes(Util.get_locktimes(will)) - - def search_willtx_per_io(will,tx): - for wid, w in will.items(): - if Util.cmp_txs(w['tx'],tx['tx']): - return wid,w - return None, None - - def invalidate_will(will): - raise Exception("not implemented") - - def get_will_spent_utxos(will): - utxos=[] - for txid,willitem in will.items(): - utxos+=willitem['tx'].inputs() - - return utxos - - def utxo_to_str(utxo): - try: return utxo.to_str() - except Exception as e: pass - try: return utxo.prevout.to_str() - except Exception as e: pass - return str(utxo) - - def cmp_utxo(utxoa,utxob): - utxoa=Util.utxo_to_str(utxoa) - utxob=Util.utxo_to_str(utxob) - if utxoa == utxob: - return True - else: - return False - - def in_utxo(utxo, utxos): - for s_u in utxos: - if Util.cmp_utxo(s_u,utxo): - return True - return False - - def txid_in_utxo(txid,utxos): - for s_u in utxos: - if s_u.prevout.txid == txid: - return True - return False - - def cmp_output(outputa,outputb): - return outputa.address == outputb.address and outputa.value == outputb.value - - def in_output(output,outputs): - for s_o in outputs: - if Util.cmp_output(s_o,output): - return True - return False - - #check all output with the same amount if none have the same address it can be a change - #return true true same address same amount - #return true false same amount different address - #return false false different amount, different address not found + print(f"---end {name}---") - def din_output(out,outputs): - same_amount=[] - for s_o in outputs: - if int(out.value) == int(s_o.value): - same_amount.append(s_o) - if out.address==s_o.address: - return True, True - else: - pass - - if len(same_amount)>0: - return True, False - else:return False, False +def print_utxo(utxo, name=""): + print(f"---utxo-{name}---") + print_var(utxo, name) + print_prevout(utxo.prevout, name) + print_var(utxo.script_sig, f"{name}-script-sig") + print_var(utxo.witness, f"{name}-witness") + print("_TxInput__address:", utxo._TxInput__address) + print("_TxInput__scriptpubkey:", utxo._TxInput__scriptpubkey) + print("_TxInput__value_sats:", utxo._TxInput__value_sats) + print(f"---utxo-end {name}---") - def get_change_output(wallet,in_amount,out_amount,fee): - change_amount = int(in_amount - out_amount - fee) - if change_amount > wallet.dust_threshold(): - change_addresses = wallet.get_change_addresses_for_new_transaction() - out = PartialTxOutput.from_address_and_value(change_addresses[0], change_amount) - out.is_change = True - return out +def print_prevout(prevout, name=""): + print(f"---prevout-{name}---") + print_var(prevout, f"{name}-prevout") + print_var(prevout._asdict()) + print(f"---prevout-end {name}---") - def get_current_height(network:'Network'): - #if no network or not up to date, just set locktime to zero - if not network: - return 0 - chain = network.blockchain() - if chain.is_tip_stale(): - return 0 - # figure out current block height - chain_height = chain.height() # learnt from all connected servers, SPV-checked - server_height = network.get_server_height() # height claimed by main server, unverified - # note: main server might be lagging (either is slow, is malicious, or there is an SPV-invisible-hard-fork) - # - if it's lagging too much, it is the network's job to switch away - if server_height < chain_height - 10: - # the diff is suspiciously large... give up and use something non-fingerprintable - return 0 - # discourage "fee sniping" - height = min(chain_height, server_height) - return height - - - def print_var(var,name = "",veryverbose=False): - print(f"---{name}---") - if not var is None: - try: - print("doc:",doc(var)) - except: pass - try: - print("str:",str(var)) - except: pass - try: - print("repr",repr(var)) - except:pass - try: - print("dict",dict(var)) - except:pass - try: - print("dir",dir(var)) - except:pass - try: - print("type",type(var)) - except:pass - try: - print("to_json",var.to_json()) - except: pass - try: - print("__slotnames__",var.__slotnames__) - except:pass - - print(f"---end {name}---") - - def print_utxo(utxo, name = ""): - print(f"---utxo-{name}---") - Util.print_var(utxo,name) - Util.print_prevout(utxo.prevout,name) - Util.print_var(utxo.script_sig,f"{name}-script-sig") - Util.print_var(utxo.witness,f"{name}-witness") - print("_TxInput__address:",utxo._TxInput__address) - print("_TxInput__scriptpubkey:",utxo._TxInput__scriptpubkey) - print("_TxInput__value_sats:",utxo._TxInput__value_sats) - print(f"---utxo-end {name}---") - - def print_prevout(prevout, name = ""): - print(f"---prevout-{name}---") - Util.print_var(prevout,f"{name}-prevout") - Util.print_var(prevout._asdict()) - print(f"---prevout-end {name}---") - - def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter): - filter_ = "All files (*)" - filename = getSaveFileName( - parent=electrum_window, - title=_("Select file to save your {}").format(title), - filename='BALplugin_{}'.format(title), - filter=filter_, - config=electrum_window.config, +def export_meta_gui(electrum_window: "ElectrumWindow", title, exporter): + filter_ = "All files (*)" + filename = getSaveFileName( + parent=electrum_window, + title=_("Select file to save your {}").format(title), + filename="BALplugin_{}".format(title), + filter=filter_, + config=electrum_window.config, + ) + if not filename: + return + try: + exporter(filename) + except FileExportFailed as e: + electrum_window.show_critical(str(e)) + else: + electrum_window.show_message( + _("Your {0} were exported to '{1}'").format(title, str(filename)) ) - if not filename: - return - try: - exporter(filename) - except FileExportFailed as e: - electrum_window.show_critical(str(e)) - else: - electrum_window.show_message(_("Your {0} were exported to '{1}'") - .format(title, str(filename))) - def copy(dicto,dictfrom): - for k,v in dictfrom.items(): - dicto[k]=v +def copy(dicto, dictfrom): + for k, v in dictfrom.items(): + dicto[k] = v diff --git a/will.py b/will.py index b3cd89d..76836e9 100644 --- a/will.py +++ b/will.py @@ -1,103 +1,112 @@ import copy -from .willexecutors import Willexecutors -from .util import Util - -from electrum.i18n import _ - -from electrum.transaction import TxOutpoint,PartialTxInput,tx_from_any,PartialTransaction,PartialTxOutput,Transaction -from electrum.util import bfh, decimal_point_to_base_unit_name -from electrum.util import write_json_file,read_json_file,FileImportFailed -from electrum.logging import get_logger,Logger from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX +from electrum.i18n import _ +from electrum.logging import Logger, get_logger +from electrum.transaction import ( + PartialTransaction, + PartialTxInput, + PartialTxOutput, + Transaction, + TxOutpoint, + tx_from_any, +) +from electrum.util import ( + FileImportFailed, + bfh, + decimal_point_to_base_unit_name, + read_json_file, + write_json_file, +) + +from .util import * +from .willexecutors import Willexecutors MIN_LOCKTIME = 1 MIN_BLOCK = 1 _logger = get_logger(__name__) + class Will: - #return an array with the list of children - def get_children(will,willid): + # return an array with the list of children + def get_children(will, willid): out = [] for _id in will: inputs = will[_id].tx.inputs() - for idi in range(0,len(inputs)): + for idi in range(0, len(inputs)): _input = inputs[idi] if _input.prevout.txid.hex() == willid: - out.append([_id,idi,_input.prevout.out_idx]) + out.append([_id, idi, _input.prevout.out_idx]) return out - #build a tree with parent transactions + # build a tree with parent transactions def add_willtree(will): for willid in will: - will[willid].children = Will.get_children(will,willid) + will[willid].children = Will.get_children(will, willid) for child in will[willid].children: - if not will[child[0]].father: + if not will[child[0]].father: will[child[0]].father = willid - - #return a list of will sorted by locktime + # return a list of will sorted by locktime def get_sorted_will(will): - return sorted(will.items(),key = lambda x: x[1]['tx'].locktime) - + return sorted(will.items(), key=lambda x: x[1]["tx"].locktime) def only_valid(will): - for k,v in will.items(): - if v.get_status('VALID'): + for k, v in will.items(): + if v.get_status("VALID"): yield k - def search_equal_tx(will,tx,wid): + def search_equal_tx(will, tx, wid): for w in will: - if w != wid and not tx.to_json() != will[w]['tx'].to_json(): - if will[w]['tx'].txid() != tx.txid(): - if Util.cmp_txs(will[w]['tx'],tx): - return will[w]['tx'] + if w != wid and not tx.to_json() != will[w]["tx"].to_json(): + if will[w]["tx"].txid() != tx.txid(): + if cmp_txs(will[w]["tx"], tx): + return will[w]["tx"] return False def get_tx_from_any(x): try: - a=str(x) + a = str(x) return tx_from_any(a) - + except Exception as e: raise e return x - def add_info_from_will(will,wid,wallet): - if isinstance(will[wid].tx,str): + def add_info_from_will(will, wid, wallet): + if isinstance(will[wid].tx, str): will[wid].tx = Will.get_tx_from_any(will[wid].tx) if wallet: will[wid].tx.add_info_from_wallet(wallet) for txin in will[wid].tx.inputs(): txid = txin.prevout.txid.hex() if txid in will: - #print(will[txid].tx.outputs()) - #print(txin.prevout.out_idx) + # print(will[txid].tx.outputs()) + # print(txin.prevout.out_idx) change = will[txid].tx.outputs()[txin.prevout.out_idx] txin._trusted_value_sats = change.value try: txin.script_descriptor = change.script_descriptor except: pass - txin.is_mine=True - txin._TxInput__address=change.address + txin.is_mine = True + txin._TxInput__address = change.address txin._TxInput__scriptpubkey = change.scriptpubkey txin._TxInput__value_sats = change.value txin._trusted_value_sats = change.value - def normalize_will(will,wallet = None,others_inputs = {}): + def normalize_will(will, wallet=None, others_inputs={}): to_delete = [] to_add = {} - #add info from wallet - willitems={} + # add info from wallet + willitems = {} for wid in will: - Will.add_info_from_will(will,wid,wallet) - willitems[wid]=WillItem(will[wid]) - will=willitems - errors ={} + Will.add_info_from_will(will, wid, wallet) + willitems[wid] = WillItem(will[wid]) + will = willitems + errors = {} for wid in will: - txid = will[wid].tx.txid() if txid is None: @@ -105,51 +114,55 @@ class Will: _logger.error(wid) _logger.error(will[wid]) _logger.error(will[wid].tx.to_json()) - + _logger.error("txid is none") - will[wid].set_status('ERROR',True) - errors[wid]=will[wid] + will[wid].set_status("ERROR", True) + errors[wid] = will[wid] continue if txid != wid: outputs = will[wid].tx.outputs() - ow=will[wid] + ow = will[wid] ow.normalize_locktime(others_inputs) - will[wid]=WillItem(ow.to_dict()) + will[wid] = WillItem(ow.to_dict()) - for i in range(0,len(outputs)): - Will.change_input(will,wid,i,outputs[i],others_inputs,to_delete,to_add) + for i in range(0, len(outputs)): + Will.change_input( + will, wid, i, outputs[i], others_inputs, to_delete, to_add + ) to_delete.append(wid) - to_add[ow.tx.txid()]=ow.to_dict() + to_add[ow.tx.txid()] = ow.to_dict() - for eid,err in errors.items(): + for eid, err in errors.items(): new_txid = err.tx.txid() - for k,w in to_add.items(): + for k, w in to_add.items(): will[k] = w for wid in to_delete: if wid in will: del will[wid] - def new_input(txid,idx,change): + def new_input(txid, idx, change): prevout = TxOutpoint(txid=bfh(txid), out_idx=idx) inp = PartialTxInput(prevout=prevout) inp._trusted_value_sats = change.value - inp.is_mine=True - inp._TxInput__address=change.address + inp.is_mine = True + inp._TxInput__address = change.address inp._TxInput__scriptpubkey = change.scriptpubkey inp._TxInput__value_sats = change.value return inp - def check_anticipate(ow:'WillItem',nw:'WillItem'): - anticipate = Util.anticipate_locktime(ow.tx.locktime,days=1) + def check_anticipate(ow: "WillItem", nw: "WillItem"): + anticipate = anticipate_locktime(ow.tx.locktime, days=1) if int(nw.tx.locktime) >= int(anticipate): - if Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,1],exclude_willexecutors = True): + if cmp_heirs_by_values( + ow.heirs, nw.heirs, [0, 1], exclude_willexecutors=True + ): if nw.we and ow.we: - if ow.we['url'] == nw.we['url']: - if int(ow.we['base_fee'])>int(nw.we['base_fee']): + if ow.we["url"] == nw.we["url"]: + if int(ow.we["base_fee"]) > int(nw.we["base_fee"]): return anticipate else: if int(ow.tx_fees) != int(nw.tx_fees): @@ -160,7 +173,7 @@ class Will: ow.tx.locktime else: if nw.we == ow.we: - if not Util.cmp_heirs_by_values(ow.heirs,nw.heirs,[0,3]): + if not cmp_heirs_by_values(ow.heirs, nw.heirs, [0, 3]): return anticipate else: return ow.tx.locktime @@ -168,10 +181,9 @@ class Will: return ow.tx.locktime else: return anticipate - return 4294967295+1 + return 4294967295 + 1 - - def change_input(will, otxid, idx, change,others_inputs,to_delete,to_append): + def change_input(will, otxid, idx, change, others_inputs, to_delete, to_append): ow = will[otxid] ntxid = ow.tx.txid() if otxid != ntxid: @@ -182,12 +194,15 @@ class Will: found = False old_txid = w.tx.txid() ntx = None - for i in range(0,len(inputs)): - if inputs[i].prevout.txid.hex() == otxid and inputs[i].prevout.out_idx == idx: - if isinstance(w.tx,Transaction): + for i in range(0, len(inputs)): + if ( + inputs[i].prevout.txid.hex() == otxid + and inputs[i].prevout.out_idx == idx + ): + if isinstance(w.tx, Transaction): will[wid].tx = PartialTransaction.from_tx(w.tx) will[wid].tx.set_rbf(True) - will[wid].tx._inputs[i]=Will.new_input(wid,idx,change) + will[wid].tx._inputs[i] = Will.new_input(wid, idx, change) found = True if found == True: pass @@ -195,19 +210,27 @@ class Will: new_txid = will[wid].tx.txid() if old_txid != new_txid: to_delete.append(old_txid) - to_append[new_txid]=will[wid] + to_append[new_txid] = will[wid] outputs = will[wid].tx.outputs() - for i in range(0,len(outputs)): - Will.change_input(will, wid, i, outputs[i],others_inputs,to_delete,to_append) - - def get_all_inputs(will,only_valid = False): + for i in range(0, len(outputs)): + Will.change_input( + will, + wid, + i, + outputs[i], + others_inputs, + to_delete, + to_append, + ) + + def get_all_inputs(will, only_valid=False): all_inputs = {} - for w,wi in will.items(): - if not only_valid or wi.get_status('VALID'): + for w, wi in will.items(): + if not only_valid or wi.get_status("VALID"): inputs = wi.tx.inputs() for i in inputs: prevout_str = i.prevout.to_str() - inp=[w,will[w],i] + inp = [w, will[w], i] if not prevout_str in all_inputs: all_inputs[prevout_str] = [inp] else: @@ -216,67 +239,65 @@ class Will: def get_all_inputs_min_locktime(all_inputs): all_inputs_min_locktime = {} - - for i,values in all_inputs.items(): - min_locktime = min(values,key = lambda x:x[1].tx.locktime)[1].tx.locktime + + for i, values in all_inputs.items(): + min_locktime = min(values, key=lambda x: x[1].tx.locktime)[1].tx.locktime for w in values: if w[1].tx.locktime == min_locktime: if not i in all_inputs_min_locktime: - all_inputs_min_locktime[i]=[w] + all_inputs_min_locktime[i] = [w] else: - all_inputs_min_locktime[i].append(w) + all_inputs_min_locktime[i].append(w) return all_inputs_min_locktime - - def search_anticipate_rec(will,old_inputs): + def search_anticipate_rec(will, old_inputs): redo = False to_delete = [] to_append = {} - new_inputs = Will.get_all_inputs(will,only_valid = True) - for nid,nwi in will.items(): + new_inputs = Will.get_all_inputs(will, only_valid=True) + for nid, nwi in will.items(): if nwi.search_anticipate(new_inputs) or nwi.search_anticipate(old_inputs): if nid != nwi.tx.txid(): redo = True to_delete.append(nid) to_append[nwi.tx.txid()] = nwi outputs = nwi.tx.outputs() - for i in range(0,len(outputs)): - Will.change_input(will,nid,i,outputs[i],new_inputs,to_delete,to_append) - + for i in range(0, len(outputs)): + Will.change_input( + will, nid, i, outputs[i], new_inputs, to_delete, to_append + ) for w in to_delete: try: del will[w] except: pass - for k,w in to_append.items(): - will[k]=w + for k, w in to_append.items(): + will[k] = w if redo: - Will.search_anticipate_rec(will,old_inputs) + Will.search_anticipate_rec(will, old_inputs) - - def update_will(old_will,new_will): - all_old_inputs = Will.get_all_inputs(old_will,only_valid=True) + def update_will(old_will, new_will): + all_old_inputs = Will.get_all_inputs(old_will, only_valid=True) all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_old_inputs) all_new_inputs = Will.get_all_inputs(new_will) - #check if the new input is already spent by other transaction - #if it is use the same locktime, or anticipate. - Will.search_anticipate_rec(new_will,all_old_inputs) + # check if the new input is already spent by other transaction + # if it is use the same locktime, or anticipate. + Will.search_anticipate_rec(new_will, all_old_inputs) - other_inputs = Will.get_all_inputs(old_will,{}) + other_inputs = Will.get_all_inputs(old_will, {}) try: - Will.normalize_will(new_will,others_inputs=other_inputs) + Will.normalize_will(new_will, others_inputs=other_inputs) except Exception as e: raise e - for oid in Will.only_valid(old_will): if oid in new_will: new_heirs = new_will[oid].heirs new_we = new_will[oid].we - new_will[oid]=old_will[oid] + new_will[oid] = old_will[oid] new_will[oid].heirs = new_heirs new_will[oid].we = new_we @@ -297,39 +318,45 @@ class Will: out[inp.prevout.to_str()] = inp return out - def invalidate_will(will,wallet,fees_per_byte): + def invalidate_will(will, wallet, fees_per_byte): will_only_valid = Will.only_valid_list(will) inputs = Will.get_all_inputs(will_only_valid) utxos = wallet.get_utxos() filtered_inputs = [] prevout_to_spend = [] - for prevout_str,ws in inputs.items(): + for prevout_str, ws in inputs.items(): for w in ws: - if not w[0] in filtered_inputs: + if not w[0] in filtered_inputs: filtered_inputs.append(w[0]) if not prevout_str in prevout_to_spend: prevout_to_spend.append(prevout_str) balance = 0 utxo_to_spend = [] for utxo in utxos: - utxo_str=utxo.prevout.to_str() + utxo_str = utxo.prevout.to_str() if utxo_str in prevout_to_spend: balance += inputs[utxo_str][0][2].value_sats() utxo_to_spend.append(utxo) - if len(utxo_to_spend) > 0: + if len(utxo_to_spend) > 0: change_addresses = wallet.get_change_addresses_for_new_transaction() out = PartialTxOutput.from_address_and_value(change_addresses[0], balance) out.is_change = True - locktime = Util.get_current_height(wallet.network) - tx = PartialTransaction.from_io(utxo_to_spend, [out], locktime=locktime, version=2) + locktime = get_current_height(wallet.network) + tx = PartialTransaction.from_io( + utxo_to_spend, [out], locktime=locktime, version=2 + ) tx.set_rbf(True) - fee=tx.estimated_size()*fees_per_byte - if balance -fee >0: - out = PartialTxOutput.from_address_and_value(change_addresses[0],balance - fee) - tx = PartialTransaction.from_io(utxo_to_spend,[out], locktime=locktime, version=2) + fee = tx.estimated_size() * fees_per_byte + if balance - fee > 0: + out = PartialTxOutput.from_address_and_value( + change_addresses[0], balance - fee + ) + tx = PartialTransaction.from_io( + utxo_to_spend, [out], locktime=locktime, version=2 + ) tx.set_rbf(True) - + _logger.debug(f"invalidation tx: {tx}") return tx @@ -340,71 +367,73 @@ class Will: _logger.debug("len utxo_to_spend <=0") pass - def is_new(will): - for wid,w in will.items(): - if w.get_status('VALID') and not w.get_status('COMPLETE'): + for wid, w in will.items(): + if w.get_status("VALID") and not w.get_status("COMPLETE"): return True - def search_rai (all_inputs,all_utxos,will,wallet): + def search_rai(all_inputs, all_utxos, will, wallet): will_only_valid = Will.only_valid_or_replaced_list(will) - for inp,ws in all_inputs.items(): - inutxo = Util.in_utxo(inp,all_utxos) + for inp, ws in all_inputs.items(): + inutxo = in_utxo(inp, all_utxos) for w in ws: - wi=w[1] - if wi.get_status('VALID') or wi.get_status('CONFIRMED') or wi.get_status('PENDING'): - prevout_id=w[2].prevout.txid.hex() + wi = w[1] + if ( + wi.get_status("VALID") + or wi.get_status("CONFIRMED") + or wi.get_status("PENDING") + ): + prevout_id = w[2].prevout.txid.hex() if not inutxo: if prevout_id in will: - wo=will[prevout_id] - if wo.get_status('REPLACED'): - wi.set_status('REPLACED',True) + wo = will[prevout_id] + if wo.get_status("REPLACED"): + wi.set_status("REPLACED", True) if wo.get_status("INVALIDATED"): - wi.set_status('INVALIDATED',True) - + wi.set_status("INVALIDATED", True) + else: if wallet.db.get_transaction(wi._id): - wi.set_status('CONFIRMED',True) + wi.set_status("CONFIRMED", True) else: - wi.set_status('INVALIDATED',True) - + wi.set_status("INVALIDATED", True) + for child in wi.search(all_inputs): if child.tx.locktime < wi.tx.locktime: _logger.debug("a child was found") - wi.set_status('REPLACED',True) + wi.set_status("REPLACED", True) else: pass def utxos_strs(utxos): - return [Util.utxo_to_str(u) for u in utxos] + return [utxo_to_str(u) for u in utxos] - - def set_invalidate(wid,will=[]): - will[wid].set_status("INVALIDATED",True) + def set_invalidate(wid, will=[]): + will[wid].set_status("INVALIDATED", True) if will[wid].children: for c in self.children.items(): - Will.set_invalidate(c[0],will) + Will.set_invalidate(c[0], will) def check_tx_height(tx, wallet): - info=wallet.get_tx_info(tx) + info = wallet.get_tx_info(tx) return info.tx_mined_status.height - #check if transactions are stil valid tecnically valid - def check_invalidated(willtree,utxos_list,wallet): - for wid,w in willtree.items(): + # check if transactions are stil valid tecnically valid + def check_invalidated(willtree, utxos_list, wallet): + for wid, w in willtree.items(): if not w.father: for inp in w.tx.inputs(): - inp_str = Util.utxo_to_str(inp) + inp_str = utxo_to_str(inp) if not inp_str in utxos_list: if wallet: - height= Will.check_tx_height(w.tx,wallet) + height = Will.check_tx_height(w.tx, wallet) if height < 0: - Will.set_invalidate(wid,willtree) + Will.set_invalidate(wid, willtree) elif height == 0: - w.set_status("PENDING",True) + w.set_status("PENDING", True) else: - w.set_status('CONFIRMED',True) + w.set_status("CONFIRMED", True) def reflect_to_children(treeitem): if not treeitem.get_status("VALID"): @@ -413,153 +442,190 @@ class Will: wc = willtree[child] if wc.get_status("VALID"): if treeitem.get_status("INVALIDATED"): - wc.set_status("INVALIDATED",True) + wc.set_status("INVALIDATED", True) if treeitem.get_status("REPLACED"): - wc.set_status("REPLACED",True) + wc.set_status("REPLACED", True) if wc.children: Will.reflect_to_children(wc) - def check_amounts(heirs,willexecutors,all_utxos,timestamp_to_check,dust): - fixed_heirs,fixed_amount,perc_heirs,perc_amount = heirs.fixed_percent_lists_amount(timestamp_to_check,dust,reverse=True) + def check_amounts(heirs, willexecutors, all_utxos, timestamp_to_check, dust): + fixed_heirs, fixed_amount, perc_heirs, perc_amount = ( + heirs.fixed_percent_lists_amount(timestamp_to_check, dust, reverse=True) + ) wallet_balance = 0 for utxo in all_utxos: wallet_balance += utxo.value_sats() if fixed_amount >= wallet_balance: - raise FixedAmountException(f"Fixed amount({fixed_amount}) >= {wallet_balance}") + raise FixedAmountException( + f"Fixed amount({fixed_amount}) >= {wallet_balance}" + ) if perc_amount != 100: raise PercAmountException(f"Perc amount({perc_amount}) =! 100%") - for url,wex in willexecutors.items(): + for url, wex in willexecutors.items(): if Willexecutors.is_selected(wex): - temp_balance = wallet_balance - int(wex['base_fee']) + temp_balance = wallet_balance - int(wex["base_fee"]) if fixed_amount >= temp_balance: - raise FixedAmountException(f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}") + raise FixedAmountException( + f"Willexecutor{url} excess base fee({wex['base_fee']}), {fixed_amount} >={temp_balance}" + ) - - def check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check): + def check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check): Will.add_willtree(will) - utxos_list= Will.utxos_strs(all_utxos) + utxos_list = Will.utxos_strs(all_utxos) - Will.check_invalidated(will,utxos_list,wallet) + Will.check_invalidated(will, utxos_list, wallet) - all_inputs=Will.get_all_inputs(will,only_valid = True) + all_inputs = Will.get_all_inputs(will, only_valid=True) all_inputs_min_locktime = Will.get_all_inputs_min_locktime(all_inputs) - Will.check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check) + Will.check_will_expired( + all_inputs_min_locktime, block_to_check, timestamp_to_check + ) - all_inputs=Will.get_all_inputs(will,only_valid = True) - - Will.search_rai(all_inputs,all_utxos,will,wallet) + all_inputs = Will.get_all_inputs(will, only_valid=True) - def is_will_valid(will, block_to_check, timestamp_to_check, tx_fees, all_utxos,heirs={},willexecutors={},self_willexecutor=False, wallet=False, callback_not_valid_tx=None): - - Will.check_will(will,all_utxos,wallet,block_to_check,timestamp_to_check) + Will.search_rai(all_inputs, all_utxos, will, wallet) + def is_will_valid( + will, + block_to_check, + timestamp_to_check, + tx_fees, + all_utxos, + heirs={}, + willexecutors={}, + self_willexecutor=False, + wallet=False, + callback_not_valid_tx=None, + ): + Will.check_will(will, all_utxos, wallet, block_to_check, timestamp_to_check) if heirs: - if not Will.check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,timestamp_to_check,tx_fees): + if not Will.check_willexecutors_and_heirs( + will, + heirs, + willexecutors, + self_willexecutor, + timestamp_to_check, + tx_fees, + ): raise NotCompleteWillException() + all_inputs = Will.get_all_inputs(will, only_valid=True) - all_inputs=Will.get_all_inputs(will,only_valid = True) - - _logger.info('check all utxo in wallet are spent') + _logger.info("check all utxo in wallet are spent") if all_inputs: for utxo in all_utxos: - if utxo.value_sats() > 68 * tx_fees: - if not Util.in_utxo(utxo,all_inputs.keys()): - _logger.info("utxo is not spent",utxo.to_json()) - _logger.debug(all_inputs.keys()) - raise NotCompleteWillException("Some utxo in the wallet is not included") + if utxo.value_sats() > 68 * tx_fees: + if not in_utxo(utxo, all_inputs.keys()): + _logger.info("utxo is not spent", utxo.to_json()) + _logger.debug(all_inputs.keys()) + raise NotCompleteWillException( + "Some utxo in the wallet is not included" + ) - _logger.info('will ok') + _logger.info("will ok") return True - def check_will_expired(all_inputs_min_locktime,block_to_check,timestamp_to_check): + def check_will_expired(all_inputs_min_locktime, block_to_check, timestamp_to_check): _logger.info("check if some transaction is expired") for prevout_str, wid in all_inputs_min_locktime.items(): - for w in wid: - if w[1].get_status('VALID'): + for w in wid: + if w[1].get_status("VALID"): locktime = int(wid[0][1].tx.locktime) if locktime <= NLOCKTIME_BLOCKHEIGHT_MAX: if locktime < int(block_to_check): - raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}") + raise WillExpiredException( + f"Will Expired {wid[0][0]}: {locktime}<{block_to_check}" + ) else: if locktime < int(timestamp_to_check): - raise WillExpiredException(f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}") - + raise WillExpiredException( + f"Will Expired {wid[0][0]}: {locktime}<{timestamp_to_check}" + ) + def check_all_input_spent_are_in_wallet(): _logger.info("check all input spent are in wallet or valid txs") - for inp,ws in all_inputs.items(): - if not Util.in_utxo(inp,all_utxos): + for inp, ws in all_inputs.items(): + if not in_utxo(inp, all_utxos): for w in ws: - if w[1].get_status('VALID'): + if w[1].get_status("VALID"): prevout_id = w[2].prevout.txid.hex() - parentwill = will.get(prevout_id,False) - if not parentwill or not parentwill.get_status('VALID'): - w[1].set_status('INVALIDATED',True) - + parentwill = will.get(prevout_id, False) + if not parentwill or not parentwill.get_status("VALID"): + w[1].set_status("INVALIDATED", True) def only_valid_list(will): - out={} - for wid,w in will.items(): - if w.get_status('VALID'): - out[wid]=w + out = {} + for wid, w in will.items(): + if w.get_status("VALID"): + out[wid] = w return out def only_valid_or_replaced_list(will): - out=[] - for wid,w in will.items(): + out = [] + for wid, w in will.items(): wi = w - if wi.get_status('VALID') or wi.get_status('REPLACED'): + if wi.get_status("VALID") or wi.get_status("REPLACED"): out.append(wid) return out - def check_willexecutors_and_heirs(will,heirs,willexecutors,self_willexecutor,check_date,tx_fees): + def check_willexecutors_and_heirs( + will, heirs, willexecutors, self_willexecutor, check_date, tx_fees + ): _logger.debug("check willexecutors heirs") no_willexecutor = 0 willexecutors_found = {} heirs_found = {} will_only_valid = Will.only_valid_list(will) - if len(will_only_valid)<1: + if len(will_only_valid) < 1: return False for wid in Will.only_valid_list(will): w = will[wid] if w.tx_fees != tx_fees: - raise TxFeesChangedException(f"{tx_fees}:",w.tx_fees) + raise TxFeesChangedException(f"{tx_fees}:", w.tx_fees) for wheir in w.heirs: if not 'w!ll3x3c"' == wheir[:9]: their = will[wid].heirs[wheir] - if heir := heirs.get(wheir,None): - - if heir[0] == their[0] and heir[1] == their[1] and Util.parse_locktime_string(heir[2]) >= Util.parse_locktime_string(their[2]): - count = heirs_found.get(wheir,0) - heirs_found[wheir]=count + 1 + if heir := heirs.get(wheir, None): + if ( + heir[0] == their[0] + and heir[1] == their[1] + and parse_locktime_string(heir[2]) + >= parse_locktime_string(their[2]) + ): + count = heirs_found.get(wheir, 0) + heirs_found[wheir] = count + 1 else: - _logger.debug("heir not present transaction is not valid:",wid,w) + _logger.debug( + "heir not present transaction is not valid:", wid, w + ) continue if willexecutor := w.we: - count = willexecutors_found.get(willexecutor['url'],0) - if Util.cmp_willexecutor(willexecutor,willexecutors.get(willexecutor['url'],None)): - willexecutors_found[willexecutor['url']]=count+1 + count = willexecutors_found.get(willexecutor["url"], 0) + if cmp_willexecutor( + willexecutor, willexecutors.get(willexecutor["url"], None) + ): + willexecutors_found[willexecutor["url"]] = count + 1 else: no_willexecutor += 1 count_heirs = 0 for h in heirs: - if Util.parse_locktime_string(heirs[h][2])>=check_date: - count_heirs +=1 + if parse_locktime_string(heirs[h][2]) >= check_date: + count_heirs += 1 if not h in heirs_found: _logger.debug(f"heir: {h} not found") raise HeirNotFoundException(h) if not count_heirs: raise NoHeirsException("there are not valid heirs") - if self_willexecutor and no_willexecutor ==0: + if self_willexecutor and no_willexecutor == 0: raise NoWillExecutorNotPresent("Backup tx") - for url,we in willexecutors.items(): + for url, we in willexecutors.items(): if Willexecutors.is_selected(we): if not url in willexecutors_found: _logger.debug(f"will-executor: {url} not fount") @@ -568,101 +634,105 @@ class Will: return True -class WillItem(Logger): - +class WillItem(Logger): STATUS_DEFAULT = { - 'ANTICIPATED': ['Anticipated', False], - 'BROADCASTED': ['Broadcasted', False], - 'CHECKED': ['Checked', False], - 'CHECK_FAIL': ['Check Failed',False], - 'COMPLETE': ['Signed', False], - 'CONFIRMED': ['Confirmed', False], - 'ERROR': ['Error', False], - 'EXPIRED': ['Expired', False], - 'EXPORTED': ['Exported', False], - 'IMPORTED': ['Imported', False], - 'INVALIDATED': ['Invalidated', False], - 'PENDING': ['Pending', False], - 'PUSH_FAIL': ['Push failed', False], - 'PUSHED': ['Pushed', False], - 'REPLACED': ['Replaced', False], - 'RESTORED': ['Restored', False], - 'VALID': ['Valid', True], + "ANTICIPATED": ["Anticipated", False], + "BROADCASTED": ["Broadcasted", False], + "CHECKED": ["Checked", False], + "CHECK_FAIL": ["Check Failed", False], + "COMPLETE": ["Signed", False], + "CONFIRMED": ["Confirmed", False], + "ERROR": ["Error", False], + "EXPIRED": ["Expired", False], + "EXPORTED": ["Exported", False], + "IMPORTED": ["Imported", False], + "INVALIDATED": ["Invalidated", False], + "PENDING": ["Pending", False], + "PUSH_FAIL": ["Push failed", False], + "PUSHED": ["Pushed", False], + "REPLACED": ["Replaced", False], + "RESTORED": ["Restored", False], + "VALID": ["Valid", True], } - def set_status(self,status,value=True): - _logger.debug("set status {} - {} {} -> {}".format(self._id,status,self.STATUS[status][1],value)) + + def set_status(self, status, value=True): + _logger.debug( + "set status {} - {} {} -> {}".format( + self._id, status, self.STATUS[status][1], value + ) + ) if self.STATUS[status][1] == bool(value): return None - self.status += "." +("NOT " if not value else "" + _(self.STATUS[status][0])) + self.status += "." + ("NOT " if not value else "" + _(self.STATUS[status][0])) self.STATUS[status][1] = bool(value) if value: - if status in ['INVALIDATED','REPLACED','CONFIRMED','PENDING']: - self.STATUS['VALID'][1] = False + if status in ["INVALIDATED", "REPLACED", "CONFIRMED", "PENDING"]: + self.STATUS["VALID"][1] = False - if status in ['CONFIRMED','PENDING']: - self.STATUS['INVALIDATED'][1] = False + if status in ["CONFIRMED", "PENDING"]: + self.STATUS["INVALIDATED"][1] = False - if status in ['PUSHED']: - self.STATUS['PUSH_FAIL'][1] = False - self.STATUS['CHECK_FAIL'][1] = False + if status in ["PUSHED"]: + self.STATUS["PUSH_FAIL"][1] = False + self.STATUS["CHECK_FAIL"][1] = False - - if status in ['CHECKED']: - self.STATUS['PUSHED'][1] = True - self.STATUS['PUSH_FAIL'][1] = False + if status in ["CHECKED"]: + self.STATUS["PUSHED"][1] = True + self.STATUS["PUSH_FAIL"][1] = False return value - def get_status(self,status): + def get_status(self, status): return self.STATUS[status][1] - def __init__(self,w,_id=None,wallet=None): - if isinstance(w,WillItem,): + def __init__(self, w, _id=None, wallet=None): + if isinstance( + w, + WillItem, + ): self.__dict__ = w.__dict__.copy() else: - self.tx = Will.get_tx_from_any(w['tx']) - self.heirs = w.get('heirs',None) - self.we = w.get('willexecutor',None) - self.status = w.get('status',None) - self.description = w.get('description',None) - self.time = w.get('time',None) - self.change = w.get('change',None) - self.tx_fees = w.get('baltx_fees',0) - self.father = w.get('Father',None) - self.children = w.get('Children',None) + self.tx = Will.get_tx_from_any(w["tx"]) + self.heirs = w.get("heirs", None) + self.we = w.get("willexecutor", None) + self.status = w.get("status", None) + self.description = w.get("description", None) + self.time = w.get("time", None) + self.change = w.get("change", None) + self.tx_fees = w.get("baltx_fees", 0) + self.father = w.get("Father", None) + self.children = w.get("Children", None) self.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT) for s in self.STATUS: - self.STATUS[s][1]=w.get(s,WillItem.STATUS_DEFAULT[s][1]) + self.STATUS[s][1] = w.get(s, WillItem.STATUS_DEFAULT[s][1]) if not _id: self._id = self.tx.txid() else: self._id = _id if not self._id: - self.status+="ERROR!!!" + self.status += "ERROR!!!" self.valid = False if wallet: self.tx.add_info_from_wallet(wallet) - - def to_dict(self): out = { - '_id':self._id, - 'tx':self.tx, - 'heirs':self.heirs, - 'willexecutor':self.we, - 'status':self.status, - 'description':self.description, - 'time':self.time, - 'change':self.change, - 'baltx_fees':self.tx_fees + "_id": self._id, + "tx": self.tx, + "heirs": self.heirs, + "willexecutor": self.we, + "status": self.status, + "description": self.description, + "time": self.time, + "change": self.change, + "baltx_fees": self.tx_fees, } for key in self.STATUS: try: - out[key]=self.STATUS[key][1] + out[key] = self.STATUS[key][1] except Exception as e: _logger.error(f"{key},{self.STATUS[key]} {e}") @@ -674,58 +744,58 @@ class WillItem(Logger): def __str__(self): return str(self.to_dict()) - def set_anticipate(self, ow:'WillItem'): - nl = min(ow.tx.locktime,Will.check_anticipate(ow,self)) + def set_anticipate(self, ow: "WillItem"): + nl = min(ow.tx.locktime, Will.check_anticipate(ow, self)) if int(nl) < self.tx.locktime: self.tx.locktime = int(nl) return True else: return False - - def search_anticipate(self,all_inputs): + def search_anticipate(self, all_inputs): anticipated = False for ow in self.search(all_inputs): if self.set_anticipate(ow): anticipated = True return anticipated - def search(self,all_inputs): + def search(self, all_inputs): for inp in self.tx.inputs(): prevout_str = inp.prevout.to_str() - oinps = all_inputs.get(prevout_str,[]) + oinps = all_inputs.get(prevout_str, []) for oinp in oinps: - ow=oinp[1] - if ow._id!=self._id: + ow = oinp[1] + if ow._id != self._id: yield ow - def normalize_locktime(self,all_inputs): + def normalize_locktime(self, all_inputs): outputs = self.tx.outputs() - for idx in range(0,len(outputs)): - inps = all_inputs.get(f"{self._id}:{idx}",[]) + for idx in range(0, len(outputs)): + inps = all_inputs.get(f"{self._id}:{idx}", []) _logger.debug("****check locktime***") for inp in inps: - if inp[0]!= self._id: + if inp[0] != self._id: iw = inp[1] self.set_anticipate(iw) def check_willexecutor(self): try: - if resp:=Willexecutors.check_transaction(self._id,self.we['url']): - if 'tx' in resp and resp['tx']==str(self.tx): - self.set_status('PUSHED') - self.set_status('CHECKED') - else: - self.set_status('CHECK_FAIL') - self.set_status('PUSHED',False) + if resp := Willexecutors.check_transaction(self._id, self.we["url"]): + if "tx" in resp and resp["tx"] == str(self.tx): + self.set_status("PUSHED") + self.set_status("CHECKED") + else: + self.set_status("CHECK_FAIL") + self.set_status("PUSHED", False) return True else: - self.set_status('CHECK_FAIL') - self.set_status('PUSHED',False) + self.set_status("CHECK_FAIL") + self.set_status("PUSHED", False) return False except Exception as e: _logger.error(f"exception checking transaction: {e}") - self.set_status('CHECK_FAIL') + self.set_status("CHECK_FAIL") + def get_color(self): if self.get_status("INVALIDATED"): return "#f87838" @@ -748,29 +818,54 @@ class WillItem(Logger): else: return "#ffffff" + class WillException(Exception): pass + + class WillExpiredException(WillException): pass + + class NotCompleteWillException(WillException): pass + + class HeirChangeException(NotCompleteWillException): pass + + class TxFeesChangedException(NotCompleteWillException): pass + + class HeirNotFoundException(NotCompleteWillException): pass + + class WillexecutorChangeException(NotCompleteWillException): pass + + class NoWillExecutorNotPresent(NotCompleteWillException): pass + + class WillExecutorNotPresent(NotCompleteWillException): pass + + class NoHeirsException(WillException): pass + + class AmountException(WillException): pass + + class PercAmountException(AmountException): pass + + class FixedAmountException(AmountException): pass diff --git a/willexecutors.py b/willexecutors.py index 899b483..6dabf98 100644 --- a/willexecutors.py +++ b/willexecutors.py @@ -1,45 +1,50 @@ import json from datetime import datetime from functools import partial -from aiohttp import ClientResponse -from electrum.network import Network +from aiohttp import ClientResponse from electrum import constants -from electrum.logging import get_logger from electrum.gui.qt.util import WaitingDialog from electrum.i18n import _ +from electrum.logging import get_logger +from electrum.network import Network + from .bal import BalPlugin -from .util import Util + +# from .util import * DEFAULT_TIMEOUT = 5 _logger = get_logger(__name__) -class Willexecutors: + +class Willexecutors: def save(bal_plugin, willexecutors): - aw=bal_plugin.WILLEXECUTORS.get() - aw[constants.net.NET_NAME]=willexecutors + aw = bal_plugin.WILLEXECUTORS.get() + aw[constants.net.NET_NAME] = willexecutors bal_plugin.WILLEXECUTORS.set(aw) - def get_willexecutors(bal_plugin, update = False,bal_window=False,force=False,task=True): + def get_willexecutors( + bal_plugin, update=False, bal_window=False, force=False, task=True + ): willexecutors = bal_plugin.WILLEXECUTORS.get() - willexecutors=willexecutors.get(constants.net.NET_NAME,{}) - to_del=[] + willexecutors = willexecutors.get(constants.net.NET_NAME, {}) + to_del = [] for w in willexecutors: - if not isinstance(willexecutors[w],dict): + if not isinstance(willexecutors[w], dict): to_del.append(w) continue - Willexecutors.initialize_willexecutor(willexecutors[w],w) + Willexecutors.initialize_willexecutor(willexecutors[w], w) for w in to_del: print("ERROR: WILLEXECUTOR TO DELETE:", w) del willexecutors[w] - bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME,{}) - for bal_url,bal_executor in bal.items(): + bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {}) + for bal_url, bal_executor in bal.items(): if not bal_url in willexecutors: _logger.debug(f"force add {bal_url} willexecutor") willexecutors[bal_url] = bal_executor if update: found = False - for url,we in willexecutors.items(): + for url, we in willexecutors.items(): if Willexecutors.is_selected(we): found = True if found or force: @@ -47,110 +52,133 @@ class Willexecutors: ping_willexecutors = True if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force: if bal_window: - ping_willexecutors = bal_window.window.question(_("Contact willexecutors servers to update payment informations?")) + ping_willexecutors = bal_window.window.question( + _( + "Contact willexecutors servers to update payment informations?" + ) + ) if ping_willexecutors: if task: - bal_window.ping_willexecutors(willexecutors,task) + bal_window.ping_willexecutors(willexecutors, task) else: bal_window.ping_willexecutors_task(willexecutors) - w_sorted = dict(sorted(willexecutors.items(), key=lambda w:w[1].get('sort',0),reverse=True)) + w_sorted = dict( + sorted( + willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True + ) + ) return w_sorted - def is_selected(willexecutor,value=None): + + def is_selected(willexecutor, value=None): if not willexecutor: return False if not value is None: - willexecutor['selected']=value + willexecutor["selected"] = value try: - return willexecutor['selected'] + return willexecutor["selected"] except: - willexecutor['selected']=False + willexecutor["selected"] = False return False def get_willexecutor_transactions(will, force=False): - willexecutors ={} - for wid,willitem in will.items(): - if willitem.get_status('VALID'): - if willitem.get_status('COMPLETE'): - if not willitem.get_status('PUSHED') or force: + willexecutors = {} + for wid, willitem in will.items(): + if willitem.get_status("VALID"): + if willitem.get_status("COMPLETE"): + if not willitem.get_status("PUSHED") or force: if willexecutor := willitem.we: - url=willexecutor['url'] - if willexecutor and Willexecutors.is_selected(willexecutor): + url = willexecutor["url"] + if willexecutor and Willexecutors.is_selected(willexecutor): if not url in willexecutors: - willexecutor['txs']="" - willexecutor['txsids']=[] - willexecutor['broadcast_status']= _("Waiting...") - willexecutors[url]=willexecutor - willexecutors[url]['txs']+=str(willitem.tx)+"\n" - willexecutors[url]['txsids'].append(wid) + willexecutor["txs"] = "" + willexecutor["txsids"] = [] + willexecutor["broadcast_status"] = _("Waiting...") + willexecutors[url] = willexecutor + willexecutors[url]["txs"] += str(willitem.tx) + "\n" + willexecutors[url]["txsids"].append(wid) return willexecutors def only_selected_list(willexecutors): out = {} - for url,v in willexecutors.items(): + for url, v in willexecutors.items(): if Willexecutors.is_selected(willexecutor): - out[url]=v + out[url] = v + def push_transactions_to_willexecutors(will): willexecutors = get_transactions_to_be_pushed() for url in willexecutors: willexecutor = willexecutors[url] if Willexecutors.is_selected(willexecutor): - if 'txs' in willexecutor: - Willexecutors.push_transactions_to_willexecutor(willexecutors[url]['txs'],url) + if "txs" in willexecutor: + Willexecutors.push_transactions_to_willexecutor( + willexecutors[url]["txs"], url + ) def send_request(method, url, data=None, *, timeout=10): network = Network.get_instance() if not network: - raise ErrorConnectingServer('You are offline.') - _logger.debug(f'<-- {method} {url} {data}') + raise ErrorConnectingServer("You are offline.") + _logger.debug(f"<-- {method} {url} {data}") headers = {} - headers['user-agent'] = f"BalPlugin v:{BalPlugin.version()}" - headers['Content-Type']='text/plain' + headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}" + headers["Content-Type"] = "text/plain" try: - if method == 'get': - response = Network.send_http_on_proxy(method, url, - params=data, - headers=headers, - on_finish=Willexecutors.handle_response, - timeout=timeout) - elif method == 'post': - response = Network.send_http_on_proxy(method, url, - body=data, - headers=headers, - on_finish=Willexecutors.handle_response, - timeout=timeout) + if method == "get": + response = Network.send_http_on_proxy( + method, + url, + params=data, + headers=headers, + on_finish=Willexecutors.handle_response, + timeout=timeout, + ) + elif method == "post": + response = Network.send_http_on_proxy( + method, + url, + body=data, + headers=headers, + on_finish=Willexecutors.handle_response, + timeout=timeout, + ) else: raise Exception(f"unexpected {method=!r}") except Exception as e: _logger.error(f"exception sending request {e}") raise e else: - _logger.debug(f'--> {response}') + _logger.debug(f"--> {response}") return response - async def handle_response(resp:ClientResponse): - r=await resp.text() + + async def handle_response(resp: ClientResponse): + r = await resp.text() try: - r=json.loads(r) - r['status'] = resp.status - r['selected']=Willexecutors.is_selected(willexecutor) - r['url']=url + r = json.loads(r) + r["status"] = resp.status + r["selected"] = Willexecutors.is_selected(willexecutor) + r["url"] = url except: - pass + pass return r class AlreadyPresentException(Exception): pass - def push_transactions_to_willexecutor(willexecutor): - out=True - try: + def push_transactions_to_willexecutor(willexecutor): + out = True + try: _logger.debug(f"willexecutor['txs']") - if w:=Willexecutors.send_request('post', willexecutor['url']+"/"+constants.net.NET_NAME+"/pushtxs", data=willexecutor['txs'].encode('ascii')): - willexecutor['broadcast_status'] = _("Success") + if w := Willexecutors.send_request( + "post", + willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs", + data=willexecutor["txs"].encode("ascii"), + ): + willexecutor["broadcast_status"] = _("Success") _logger.debug(f"pushed: {w}") - if w !='thx': + if w != "thx": _logger.debug(f"error: {w}") raise Exception(w) else: @@ -159,98 +187,95 @@ class Willexecutors: _logger.debug(f"error:{e}") if str(e) == "already present": raise Willexecutors.AlreadyPresentException() - out=False - willexecutor['broadcast_status'] = _("Failed") + out = False + willexecutor["broadcast_status"] = _("Failed") return out def ping_servers(willexecutors): - for url,we in willexecutors.items(): - Willexecutors.get_info_task(url,we) + for url, we in willexecutors.items(): + Willexecutors.get_info_task(url, we) - - def get_info_task(url,willexecutor): - w=None + def get_info_task(url, willexecutor): + w = None try: _logger.info("GETINFO_WILLEXECUTOR") _logger.debug(url) - netname="bitcoin" - if constants.net.NET_NAME!="mainnet": - netname=constants.net.NET_NAME - w = Willexecutors.send_request('get',url+"/"+netname+"/info") + netname = "bitcoin" + if constants.net.NET_NAME != "mainnet": + netname = constants.net.NET_NAME + w = Willexecutors.send_request("get", url + "/" + netname + "/info") - willexecutor['url']=url - willexecutor['status'] = w['status'] - willexecutor['base_fee'] = w['base_fee'] - willexecutor['address'] = w['address'] - if not willexecutor['info']: - willexecutor['info'] = w['info'] + willexecutor["url"] = url + willexecutor["status"] = w["status"] + willexecutor["base_fee"] = w["base_fee"] + willexecutor["address"] = w["address"] + if not willexecutor["info"]: + willexecutor["info"] = w["info"] _logger.debug(f"response_data {w['address']}") except Exception as e: _logger.error(f"error {e} contacting {url}: {w}") - willexecutor['status']="KO" + willexecutor["status"] = "KO" - willexecutor['last_update'] = datetime.now().timestamp() + willexecutor["last_update"] = datetime.now().timestamp() return willexecutor - def initialize_willexecutor(willexecutor,url,status=None,selected=None): - willexecutor['url']=url + def initialize_willexecutor(willexecutor, url, status=None, selected=None): + willexecutor["url"] = url if not status is None: - willexecutor['status'] = status - willexecutor['selected'] = Willexecutors.is_selected(willexecutor,selected) + willexecutor["status"] = status + willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected) def download_list(bal_plugin): try: - l = Willexecutors.send_request('get',"https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100") - del l['status'] + l = Willexecutors.send_request( + "get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100" + ) + del l["status"] for w in l: - willexecutor=l[w] - Willexecutors.initialize_willexecutor(willexecutor,w,'New',False) - #bal_plugin.WILLEXECUTORS.set(l) - #bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True) + willexecutor = l[w] + Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) + # bal_plugin.WILLEXECUTORS.set(l) + # bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True) return l except Exception as e: _logger.error(f"Failed to download willexecutors list: {e}") return {} + def get_willexecutors_list_from_json(bal_plugin): try: with open("willexecutors.json") as f: willexecutors = json.load(f) for w in willexecutors: - willexecutor=willexecutors[w] - Willexecutors.initialize_willexecutor(willexecutor,w,'New',False) - #bal_plugin.WILLEXECUTORS.set(willexecutors) + willexecutor = willexecutors[w] + Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) + # bal_plugin.WILLEXECUTORS.set(willexecutors) return h except Exception as e: _logger.error(f"error opening willexecutors json: {e}") return {} - def check_transaction(txid,url): + def check_transaction(txid, url): _logger.debug(f"{url}:{txid}") try: - w = Willexecutors.send_request('post',url+"/searchtx",data=txid.encode('ascii')) + w = Willexecutors.send_request( + "post", url + "/searchtx", data=txid.encode("ascii") + ) return w except Exception as e: _logger.error(f"error contacting {url} for checking txs {e}") raise e + class WillExecutor: - def __init__(self,url,base_fee,chain,info,version): - self.url = url - self.base_fee = base_fee - self.chain = chain - self.info = info - self.version = version + def __init__(self, url, base_fee, chain, info, version): + self.url = url + self.base_fee = base_fee + self.chain = chain + self.info = info + self.version = version def from_dict(d): - we = WillExecutor( - d['url'], - d['base_fee'], - d['chain'], - d['info'], - d['version'] - ) - - + we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])