From a022c413cc4c4077b2f6b8211febf6d355d25da9 Mon Sep 17 00:00:00 2001 From: svatantrya Date: Tue, 17 Mar 2026 02:34:01 -0400 Subject: [PATCH] willexecutor manager improved --- VERSION | 2 +- bal.py | 13 +- heirs.py | 73 ++++++----- manifest.json | 2 +- qt.py | 115 +++++++++-------- util.py | 82 ++++++------ wallet_util/bal_wallet_utils.py | 8 +- wallet_util/bal_wallet_utils_qt.py | 12 +- will.py | 86 ++++++------- willexecutors.py | 194 ++++++++++++++++++++--------- 10 files changed, 329 insertions(+), 258 deletions(-) diff --git a/VERSION b/VERSION index 3a4036f..53a75d6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.5 +0.2.6 diff --git a/bal.py b/bal.py index 06ce197..047bad0 100644 --- a/bal.py +++ b/bal.py @@ -57,7 +57,7 @@ class BalPlugin(BasePlugin): with open("VERSION", "r") as fi: f = str(fi.readline()) return f - except: + except Exception: return "unknown" SIZE = (159, 97) @@ -111,7 +111,16 @@ class BalPlugin(BasePlugin): "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", "selected": True, } - } + }, + "testnet": { + "https://we.bitcoin-after.life": { + "base_fee": 100000, + "status": "New", + "info": "Bitcoin After Life Will Executor", + "address": "bcrt1qa5cntu4hgadw8zd3n6sq2nzjy34sxdtd9u0gp7", + "selected": True, + } + }, }, ) self.WILL_SETTINGS = BalConfig( diff --git a/heirs.py b/heirs.py index 8368976..996252c 100644 --- a/heirs.py +++ b/heirs.py @@ -1,23 +1,37 @@ -import datetime -import json +# import datetime +# import json import math import random import re import threading -import urllib.parse -import urllib.request -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple + +# import urllib.parse +# import urllib.request +from typing import ( + TYPE_CHECKING, + Any, + Dict, + # List, + Optional, + # Sequence, + Tuple, +) import dns from dns.exception import DNSException -from electrum import bitcoin, constants, descriptor, dnssec +from electrum import ( + bitcoin, + constants, + # descriptor, + dnssec, +) from electrum.logging import Logger, get_logger from electrum.transaction import ( PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint, - TxOutput, + # TxOutput, ) from electrum.util import ( bfh, @@ -30,11 +44,11 @@ from electrum.util import ( from .util import Util from .willexecutors import Willexecutors from electrum.util import BitcoinException -from electrum import constants if TYPE_CHECKING: from .simple_config import SimpleConfig - from .wallet_db import WalletDB + + # from .wallet_db import WalletDB _logger = get_logger(__name__) @@ -87,7 +101,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): x.value_sats(), x.prevout.txid, x.prevout.out_idx ), ) - total_used_utxos = [] + # total_used_utxos = [] txsout = {} locktime, _ = Util.get_lowest_locktimes(locktimes) if not locktime: @@ -105,7 +119,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): outputs = [] paid_heirs = {} for name, heir in heirs.items(): - if len(heir) > HEIR_REAL_AMOUNT and not "DUST" in str( + if len(heir) > HEIR_REAL_AMOUNT and "DUST" not in str( heir[HEIR_REAL_AMOUNT] ): try: @@ -200,7 +214,7 @@ def get_utxos_from_inputs(tx_inputs, tx, utxos): # TODO calculate de minimum inputs to be invalidated def invalidate_inheritance_transactions(wallet): - listids = [] + # listids = [] utxos = {} dtxs = {} for k, v in wallet.get_all_labels().items(): @@ -229,7 +243,7 @@ def invalidate_inheritance_transactions(wallet): for key, value in utxos: for tx in value["txs"]: txid = tx.txid() - if not txid in invalidated: + if txid not in invalidated: invalidated.append(tx.txid()) remaining[key] = value @@ -237,10 +251,10 @@ def invalidate_inheritance_transactions(wallet): def print_transaction(heirs, tx, locktimes, tx_fees): jtx = tx.to_json() print(f"TX: {tx.txid()}\t-\tLocktime: {jtx['locktime']}") - print(f"---") + print("---") for inp in jtx["inputs"]: print(f"{inp['address']}: {inp['value_sats']}") - print(f"---") + print("---") for out in jtx["outputs"]: heirname = "" for key in heirs.keys(): @@ -262,7 +276,7 @@ def print_transaction(heirs, tx, locktimes, tx_fees): print() try: print(tx.serialize_to_network()) - except: + except Exception: print("impossible to serialize") print() @@ -285,7 +299,7 @@ class Heirs(dict, Logger): d = self.db.get("heirs", {}) try: self.update(d) - except e as Exception: + except Exception: return def invalidate_transactions(self, wallet): @@ -357,10 +371,10 @@ class Heirs(dict, Logger): def amount_to_float(self, amount): try: return float(amount) - except: + except Exception: try: return float(amount[:-1]) - except: + except Exception: return 0.0 def fixed_percent_lists_amount(self, from_locktime, dust_threshold, reverse=False): @@ -376,7 +390,7 @@ class Heirs(dict, Logger): ) if cmp <= 0: _logger.debug( - "cmp < 0 {} {} {} ".format( + "cmp < 0 {} {} {} {}".format( cmp, key, self[key][HEIR_LOCKTIME], from_locktime ) ) @@ -430,7 +444,7 @@ class Heirs(dict, Logger): willexecutors[ 'w!ll3x3c"' + willexecutor["url"] + '"' + str(locktime) ] = h - except Exception as e: + except Exception: return [], False else: _logger.error( @@ -440,9 +454,6 @@ class Heirs(dict, Logger): newbalance -= willexecutors_amount if newbalance < 0: raise WillExecutorFeeException(willexecutor) - a = list( - self.fixed_percent_lists_amount(from_locktime, wallet.dust_threshold()) - ) ( fixed_heirs, fixed_amount, @@ -484,7 +495,7 @@ class Heirs(dict, Logger): locktimes = {} for key, value in heir_list: locktime = Util.parse_locktime_string(value[HEIR_LOCKTIME]) - if not locktime in locktimes: + if locktime not in locktimes: locktimes[locktime] = {key: value} else: locktimes[locktime][key] = value @@ -545,12 +556,12 @@ class Heirs(dict, Logger): total_fees = 0 for fee in fees: total_fees += int(fees[fee]) - newbalance = balance + # newbalance = balance try: locktimes, onlyfixed = self.prepare_lists( balance, total_fees, wallet, willexecutor, from_locktime ) - except WillExecutorFeeException as e: + except WillExecutorFeeException: i = 10 continue if locktimes: @@ -566,9 +577,11 @@ class Heirs(dict, Logger): ) try: if "w!ll3x3c" in e.heirname: - Willexecutors.is_selected(willexecutors[w], False) + Willexecutors.is_selected( + e.heirname[len("w!ll3x3c") :], False + ) break - except: + except Exception: raise e total_fees = 0 total_fees_real = 0 @@ -583,7 +596,7 @@ class Heirs(dict, Logger): 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) + # oldfees = fees.get(tx.my_locktime, 0) fees[tx.my_locktime] = fee if balance - total_in > wallet.dust_threshold(): diff --git a/manifest.json b/manifest.json index 80a6690..e4824b1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "BAL", "fullname": "Bitcoin After Life", - "description": "Provides free and decentralized inheritance support
Version: 0.2.4", + "description": "Provides free and decentralized inheritance support
Version: 0.2.5", "author":"Svatantrya", "available_for": ["qt"], "icon":"icons/bal32x32.png" diff --git a/qt.py b/qt.py index 4ec4d9f..1560f32 100644 --- a/qt.py +++ b/qt.py @@ -31,10 +31,8 @@ if QT_VERSION == 5: ) from PyQt5.QtGui import ( QColor, - QIcon, QPainter, QPalette, - QPixmap, QStandardItem, QStandardItemModel, ) @@ -68,10 +66,8 @@ else: # QT6 ) from PyQt6.QtGui import ( QColor, - QIcon, QPainter, QPalette, - QPixmap, QStandardItem, QStandardItemModel, ) @@ -124,7 +120,6 @@ from electrum.gui.qt.util import ( WindowModalDialog, char_width_in_lineedit, import_meta_gui, - read_QIcon, read_QIcon_from_bytes, read_QPixmap_from_bytes, ) @@ -144,7 +139,6 @@ from electrum.util import ( ) from .bal import BalPlugin -from .bal_resources import DEFAULT_ICON, icon_path from .heirs import Heirs, HEIR_REAL_AMOUNT, HEIR_DUST_AMOUNT from .util import Util from .will import ( @@ -197,8 +191,10 @@ class Plugin(BalPlugin, Logger): w.init_menubar_tools(menu_child) except Exception as e: + self.logger.error( + ("init_qt except:", menu_child.text()) + ) raise e - self.logger.error(("except:", menu_child.text())) except Exception as e: self.logger.error("Error loading plugini {}".format(e)) @@ -297,7 +293,7 @@ class Plugin(BalPlugin, Logger): # heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS) # heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS) - heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR) + # heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR) def on_multiverse_change(): self.update_all() @@ -481,7 +477,7 @@ class BalWindow(Logger): self.willitems = {} try: self.load_willitems() - except: + except Exception: self.disable_plugin = True self.show_warning( _("Please restart Electrum to activate the BAL plugin"), @@ -511,15 +507,15 @@ class BalWindow(Logger): self.willexecutor_dialog.show() def create_heirs_tab(self): - self.heir_list = l = HeirList(self, self.window) + self.heir_list = HeirList(self, self.window) - tab = self.window.create_list_tab(l) + tab = self.window.create_list_tab(self.heir_list) tab.is_shown_cv = shown_cv(False) return tab def create_will_tab(self): - self.will_list = l = PreviewList(self, self.window, None) - tab = self.window.create_list_tab(l) + self.will_list = PreviewList(self, self.window, None) + tab = self.window.create_list_tab(self.will_list) tab.is_shown_cv = shown_cv(True) return tab @@ -553,7 +549,7 @@ class BalWindow(Logger): if heir: self.heir_locktime.set_locktime(heir[2]) - heir_is_xpub = QCheckBox() + # heir_is_xpub = QCheckBox() new_heir_button = QPushButton(_("Add another heir")) self.add_another_heir = False @@ -661,8 +657,8 @@ class BalWindow(Logger): def build_will(self, ignore_duplicate=True, keep_original=True): _logger.debug("building will...") will = {} - willtodelete = [] - willtoappend = {} + # willtodelete = [] + # willtoappend = {} try: self.init_class_variables() self.willexecutors = Willexecutors.get_willexecutors( @@ -691,7 +687,7 @@ class BalWindow(Logger): creation_time = time.time() if txs: for txid in txs: - txtodelete = [] + # txtodelete = [] _break = False tx = {} tx["tx"] = txs[txid] @@ -758,7 +754,7 @@ class BalWindow(Logger): self.date_to_check = Util.parse_locktime_string( self.will_settings["threshold"] ) - found = False + # 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 @@ -769,7 +765,7 @@ class BalWindow(Logger): self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get()) except Exception as e: - self.logger.error(e) + self.logger.error(f"init_class_variables: {e}") raise e def build_inheritance_transaction(self, ignore_duplicate=True, keep_original=True): @@ -892,7 +888,7 @@ class BalWindow(Logger): def show_transaction(self, tx=None, txid=None, parent=None): if not parent: parent = self.window - if txid != None and txid in self.willitems: + if txid is not None and txid in self.willitems: tx = self.willitems[txid].tx if not tx: raise Exception(_("no tx")) @@ -907,7 +903,7 @@ class BalWindow(Logger): ) ) self.wallet.set_label(result.txid(), "BAL Invalidate") - a = self.show_transaction(result) + self.show_transaction(result) else: self.show_message(_("No transactions to invalidate")) @@ -943,7 +939,7 @@ class BalWindow(Logger): tosign = txid try: self.waiting_dialog.update(get_message()) - except: + except Exception: pass for txin in tx.inputs(): prevout = txin.prevout.to_json() @@ -952,7 +948,7 @@ class BalWindow(Logger): txin._trusted_value_sats = change.value try: txin.script_descriptor = change.script_descriptor - except: + except Exception: pass txin.is_mine = True txin._TxInput__address = change.address @@ -961,9 +957,9 @@ class BalWindow(Logger): self.wallet.sign_transaction(tx, password, ignore_warnings=True) signed = tosign - is_complete = False + # is_complete = False if tx.is_complete(): - is_complete = True + # is_complete = True wi.set_status("COMPLETE", True) txs[txid] = tx except Exception: @@ -1040,7 +1036,7 @@ class BalWindow(Logger): ) def on_failure(err): - self.logger.error(err) + self.logger.error(f"fail to broadcast transactions:{err}") task = partial(self.push_transactions_to_willexecutors, force) msg = _("Selecting Will-Executors") @@ -1195,11 +1191,14 @@ class BalWindow(Logger): try: parent.willexecutor_list.update() except Exception as e: - _logger.error(f"error updating willexecutors {e}") + pass + try: + parent.willexecutors_list.update() + except Exception as e: pass def on_failure(e): - self.logger.error(e) + self.logger.error(f"fail to ping willexecutors: {e}") pass self.logger.info("ping willexecutors") @@ -1762,7 +1761,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget): import_meta_gui( self.bal_window.window, - _("willexecutors.json"), + _("willexecutors"), self.import_json_file, doNothing, ) @@ -1786,7 +1785,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget): _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,self.bal_window.willexecutors) msg = _("Downloading Will-Executors list") self.waiting_dialog = BalWaitingDialog( self.bal_window, msg, task, on_success, on_failure, exe=False @@ -2045,11 +2044,11 @@ class BalBuildWillDialog(BalDialog): def __init__(self, bal_window, parent=None): if not parent: parent = bal_window.window - BalDialog.__init__(self, parent, bal_window.bal_plugin, "Building Will") + BalDialog.__init__(self, parent, bal_window.bal_plugin, _("Building Will")) self.parent = parent self.updatemessage.connect(self.update) self.bal_window = bal_window - self.message_label = QLabel("Building Will:") + self.message_label = QLabel(_("Building Will:")) self.vbox = QVBoxLayout(self) self.vbox.addWidget(self.message_label) self.qwidget = QWidget() @@ -2130,15 +2129,15 @@ class BalBuildWillDialog(BalDialog): message = False have_to_build = True if isinstance(e, HeirChangeException): - message = "Heirs changed:" + message = _("Heirs changed:") elif isinstance(e, WillExecutorNotPresent): - message = "Will-Executor not present" + message = _("Will-Executor not present") elif isinstance(e, WillexecutorChangeException): - message = "Will-Executor changed" + message = _("Will-Executor changed") elif isinstance(e, TxFeesChangedException): - message = "Txfees are changed" + message = _("Txfees are changed") elif isinstance(e, HeirNotFoundException): - message = "Heir not found" + message = _("Heir not found") if message: _logger.debug(f"message: {message}") self.msg_set_checking(message) @@ -2160,7 +2159,7 @@ class BalBuildWillDialog(BalDialog): for wid in Will.only_valid(self.bal_window.willitems): self.bal_window.wallet.set_label(wid, "BAL Transaction") self.msg_set_building(self.msg_ok()) - except WillExecutorNotPresent as e: + except WillExecutorNotPresent: self.msg_set_status( _("Will-Executor excluded"), None, _("Skipped"), self.COLOR_ERROR ) @@ -2169,7 +2168,7 @@ class BalBuildWillDialog(BalDialog): self.msg_set_building(self.msg_error(e)) return False, None - excluded_heirs = [] + # excluded_heirs = [] for wid in Will.only_valid(self.bal_window.willitems): heirs = self.bal_window.willitems[wid].heirs for hid, heir in heirs.items(): @@ -2202,7 +2201,7 @@ class BalBuildWillDialog(BalDialog): 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) @@ -2219,14 +2218,14 @@ class BalBuildWillDialog(BalDialog): _logger.debug(f"should not be none txid: {txid}") except TxBroadcastError as e: - _logger.error(e) + _logger.error(f"fail to broadcast transaction:{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)) def loop_push(self): - self.msg_set_pushing("Broadcasting") + self.msg_set_pushing(_("Broadcasting")) retry = False try: willexecutors = Willexecutors.get_willexecutor_transactions( @@ -2270,7 +2269,7 @@ class BalBuildWillDialog(BalDialog): except Exception as e: - _logger.error(e) + _logger.error(f"loop push error:{e}") raise e if retry: raise Exception("retry") @@ -2283,7 +2282,7 @@ class BalBuildWillDialog(BalDialog): def invalidate_task(self, password, bal_window, tx): _logger.debug(f"invalidate tx: {tx}") - fee_per_byte = bal_window.will_settings.get("baltx_fees", 1) + # fee_per_byte = bal_window.will_settings.get("baltx_fees", 1) tx = self.bal_window.wallet.sign_transaction(tx, password) try: if tx: @@ -2321,10 +2320,10 @@ class BalBuildWillDialog(BalDialog): # need to sign invalidate and restart phase 1 password = self.bal_window.get_wallet_password( - "Invalidate your old will", parent=self + _("Invalidate your old will"), parent=self ) if password is False: - self.msg_set_invalidating("Aborted") + self.msg_set_invalidating(_("Aborted")) self.wait(3) self.close() return @@ -2339,12 +2338,12 @@ class BalBuildWillDialog(BalDialog): elif self.have_to_sign: password = self.bal_window.get_wallet_password( - "Sign your will", parent=self + _("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.msg_set_signing(_("Nothing to do")) self.thread.add( partial(self.task_phase2, password), on_success=self.on_success_phase2, @@ -2356,7 +2355,7 @@ class BalBuildWillDialog(BalDialog): def on_success_phase2(self, arg=False): self.thread.stop() self.bal_window.save_willitems() - self.msg_edit_row("Finished") + self.msg_edit_row(_("Finished")) self.close() def closeEvent(self, event): @@ -2382,7 +2381,7 @@ class BalBuildWillDialog(BalDialog): 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") + self.msg_set_pushing(_("Nothing to do")) else: try: self.loop_push() @@ -2397,16 +2396,16 @@ class BalBuildWillDialog(BalDialog): _logger.error(f"error phase1: {error}") def on_error_phase2(self, error): - _logger.error("error phase2: { error}") + _logger.error(f"error phase2: { error}") def msg_set_checking(self, status="Waiting", 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): row = self.inval_row if row is None else row self.inval_row = self.msg_set_status( - "Invalidating old will", self.inval_row, status + _("Invalidating old will"), self.inval_row, status ) def msg_set_building(self, status=None, row=None): @@ -3456,7 +3455,6 @@ class WillExecutorList(MyTreeView): pass def update(self): - if self.parent.willexecutors_list is None: return try: @@ -3541,7 +3539,8 @@ class WillExecutorList(MyTreeView): self.parent.save_willexecutors() except Exception as e: - _logger.error(e) + _logger.error(f"error updating willexcutor {e}") + raise e class WillExecutorWidget(QWidget, MessageBoxMixin): @@ -3609,7 +3608,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): self.willexecutor_list.update() def download_list(self): - self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin)) + self.willexecutors_list.update(Willexecutors.download_list(self.bal_plugin,self.bal_window.willexecutors)) self.willexecutor_list.update() def export_file(self, path): @@ -3623,7 +3622,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): def import_file(self): import_meta_gui( self.bal_window.window, - _("willexecutors.json"), + _("willexecutors"), self.import_json_file, self.willexecutors_list.update, ) diff --git a/util.py b/util.py index f40a565..5e1aa6b 100644 --- a/util.py +++ b/util.py @@ -1,12 +1,10 @@ import bisect -import urllib.parse -import urllib.request from datetime import datetime, timedelta from electrum.gui.qt.util import getSaveFileName from electrum.i18n import _ from electrum.transaction import PartialTxOutput -from electrum.util import FileExportFailed, FileImportFailed, write_json_file +from electrum.util import FileExportFailed LOCKTIME_THRESHOLD = 500000000 @@ -19,7 +17,7 @@ class Util: dt = datetime.fromtimestamp(locktime).isoformat() return dt - except Exception as e: + except Exception: pass return str(locktime) @@ -29,7 +27,7 @@ class Util: return locktime else: return int(locktime) - except Exception as e: + except Exception: pass dt_object = datetime.fromisoformat(locktime) timestamp = dt_object.timestamp() @@ -39,7 +37,7 @@ class Util: try: return int(locktime) - except Exception as e: + except Exception: pass try: now = datetime.now() @@ -58,7 +56,7 @@ class Util: height = Util.get_current_height(w.network) locktime += int(height) return int(locktime) - except Exception as e: + except Exception: pass return 0 @@ -77,7 +75,7 @@ class Util: else: try: return int(float(amount) * pow(10, decimal_point)) - except: + except Exception: return 0 def decode_amount(amount, decimal_point): @@ -87,24 +85,24 @@ class Util: basestr = "{{:0.{}f}}".format(decimal_point) try: return basestr.format(float(amount) / pow(10, decimal_point)) - except: + except Exception: return str(amount) def is_perc(value): try: return value[-1] == "%" - except: + except Exception: return False def cmp_array(heira, heirb): try: - if not len(heira) == len(heirb): + if len(heira) != len(heirb): return False for h in range(0, len(heira)): - if not heira[h] == heirb[h]: + if heira[h] != heirb[h]: return False return True - except: + except Exception: return False def cmp_heir(heira, heirb): @@ -122,7 +120,7 @@ class Util: and willexecutora["base_fee"] == willexecutorb["base_fee"] ): return True - except: + except Exception: return False return False @@ -148,7 +146,7 @@ class Util: ): for heira in heirsa: if ( - exclude_willexecutors and not 'w!ll3x3c"' in heira + exclude_willexecutors and 'w!ll3x3c"' not in heira ) or not exclude_willexecutors: found = False for heirb in heirsb: @@ -175,8 +173,8 @@ class Util: ): try: for heir in heirsa: - if not 'w!ll3x3c"' in heir: - if not heir in heirsb or not cmp_function( + if 'w!ll3x3c"' not in heir: + if heir not in heirsb or not cmp_function( heirsa[heir], heirsb[heir] ): if not Util.search_heir_by_values(heirsb, heirsa[heir], [0, 3]): @@ -215,7 +213,7 @@ class Util: def get_value_amount(txa, txb): outputsa = txa.outputs() - outputsb = txb.outputs() + # outputsb = txb.outputs() value_amount = 0 for outa in outputsa: @@ -260,10 +258,10 @@ class Util: def cmp_locktime(locktimea, locktimeb): if locktimea == locktimeb: return 0 - strlocktime = str(locktimea) + strlocktimea = str(locktimea) strlocktimeb = str(locktimeb) - intlocktimea = Util.str_to_locktime(strlocktimea) - intlocktimeb = Util.str_to_locktime(strlocktimeb) + # 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]) @@ -284,12 +282,12 @@ class Util: 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) + for locktime in locktimes: + locktime = Util.parse_locktime_string(locktime) + if locktime < LOCKTIME_THRESHOLD: + bisect.insort(sorted_block, locktime) else: - bisect.insort(sorted_timestamp, l) + bisect.insort(sorted_timestamp, locktime) return sorted(sorted_timestamp), sorted(sorted_block) @@ -315,11 +313,11 @@ class Util: def utxo_to_str(utxo): try: return utxo.to_str() - except Exception as e: + except Exception: pass try: return utxo.prevout.to_str() - except Exception as e: + except Exception: pass return str(utxo) @@ -382,7 +380,7 @@ class Util: out.is_change = True return out - def get_current_height(network: "Network"): + def get_current_height(network): # if no network or not up to date, just set locktime to zero if not network: return 0 @@ -405,38 +403,34 @@ class Util: def print_var(var, name="", veryverbose=False): print(f"---{name}---") - if not var is None: - try: - print("doc:", doc(var)) - except: - pass + if var is not None: try: print("str:", str(var)) - except: + except Exception: pass try: print("repr", repr(var)) - except: + except Exception: pass try: print("dict", dict(var)) - except: + except Exception: pass try: print("dir", dir(var)) - except: + except Exception: pass try: print("type", type(var)) - except: + except Exception: pass try: print("to_json", var.to_json()) - except: + except Exception: pass try: print("__slotnames__", var.__slotnames__) - except: + except Exception: pass print(f"---end {name}---") @@ -458,11 +452,11 @@ class Util: Util.print_var(prevout._asdict()) print(f"---prevout-end {name}---") - def export_meta_gui(electrum_window: "ElectrumWindow", title, exporter): + def export_meta_gui(electrum_window, title, exporter): filter_ = "All files (*)" filename = getSaveFileName( parent=electrum_window, - title=_("Select file to save your {}").format(title), + title=_("Select file to save your {}".format(title)), filename="BALplugin_{}".format(title), filter=filter_, config=electrum_window.config, @@ -475,7 +469,7 @@ class Util: electrum_window.show_critical(str(e)) else: electrum_window.show_message( - _("Your {0} were exported to '{1}'").format(title, str(filename)) + _("Your {0} were exported to '{1}'".format(title, str(filename))) ) def copy(dicto, dictfrom): diff --git a/wallet_util/bal_wallet_utils.py b/wallet_util/bal_wallet_utils.py index 2c4e0a9..f97b162 100755 --- a/wallet_util/bal_wallet_utils.py +++ b/wallet_util/bal_wallet_utils.py @@ -1,7 +1,7 @@ #!env/bin/python3 from electrum.storage import WalletStorage -from electrum.util import MyEncoder import json +from electrum.util import MyEncoder import sys import getpass import os @@ -47,12 +47,12 @@ def save(json_wallet, storage): def read_wallet(path, password=False): storage = WalletStorage(path) if storage.is_encrypted(): - if password == False: + if not password: password = getpass.getpass("Enter wallet password: ", stream=None) storage.decrypt(password) data = storage.read() json_wallet = json.loads("[" + data + "]")[0] - return json_wallet + return json_wallet, storage if __name__ == "__main__": @@ -65,7 +65,7 @@ if __name__ == "__main__": exit(1) command = sys.argv[1] path = sys.argv[2] - json_wallet = read_wallet(path) + json_wallet, storage = read_wallet(path) have_to_save = False if command == "fix": have_to_save = fix_will_settings_tx_fees(json_wallet) diff --git a/wallet_util/bal_wallet_utils_qt.py b/wallet_util/bal_wallet_utils_qt.py index 0efe732..e3744ee 100755 --- a/wallet_util/bal_wallet_utils_qt.py +++ b/wallet_util/bal_wallet_utils_qt.py @@ -15,10 +15,8 @@ from PyQt6.QtWidgets import ( QGroupBox, QTextEdit, ) -from PyQt6.QtCore import Qt from electrum.storage import WalletStorage -from electrum.util import MyEncoder -from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, read_wallet, save +from bal_wallet_utils import fix_will_settings_tx_fees, uninstall_bal, save class WalletUtilityGUI(QMainWindow): @@ -190,14 +188,6 @@ class WalletUtilityGUI(QMainWindow): def main(): app = QApplication(sys.argv) - # Check if dependencies are available - try: - from electrum.storage import WalletStorage - from electrum.util import MyEncoder - except ImportError as e: - print(f"ERROR: Cannot import Electrum dependencies: {str(e)}") - return 1 - window = WalletUtilityGUI() window.show() diff --git a/will.py b/will.py index 78c7f56..6e8beb3 100644 --- a/will.py +++ b/will.py @@ -12,11 +12,7 @@ from electrum.transaction import ( tx_from_any, ) from electrum.util import ( - FileImportFailed, bfh, - decimal_point_to_base_unit_name, - read_json_file, - write_json_file, ) from .util import Util @@ -85,7 +81,7 @@ class Will: txin._trusted_value_sats = change.value try: txin.script_descriptor = change.script_descriptor - except: + except Exception: pass txin.is_mine = True txin._TxInput__address = change.address @@ -132,8 +128,8 @@ class Will: to_delete.append(wid) to_add[ow.tx.txid()] = ow.to_dict() - for eid, err in errors.items(): - new_txid = err.tx.txid() + # for eid, err in errors.items(): + # new_txid = err.tx.txid() for k, w in to_add.items(): will[k] = w @@ -201,7 +197,7 @@ class Will: outputs = w.tx.outputs() found = False old_txid = w.tx.txid() - ntx = None + # ntx = None for i in range(0, len(inputs)): if ( inputs[i].prevout.txid.hex() == otxid @@ -212,7 +208,7 @@ class Will: will[wid].tx.set_rbf(True) will[wid].tx._inputs[i] = Will.new_input(wid, idx, change) found = True - if found == True: + if found: pass new_txid = will[wid].tx.txid() @@ -239,7 +235,7 @@ class Will: for i in inputs: prevout_str = i.prevout.to_str() inp = [w, will[w], i] - if not prevout_str in all_inputs: + if prevout_str not in all_inputs: all_inputs[prevout_str] = [inp] else: all_inputs[prevout_str].append(inp) @@ -252,7 +248,7 @@ class Will: 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: + if i not in all_inputs_min_locktime: all_inputs_min_locktime[i] = [w] else: all_inputs_min_locktime[i].append(w) @@ -290,7 +286,7 @@ class Will: for w in to_delete: try: del will[w] - except: + except Exception: pass for k, w in to_append.items(): will[k] = w @@ -300,8 +296,8 @@ class Will: 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) + # 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) @@ -345,9 +341,9 @@ class Will: prevout_to_spend = [] for prevout_str, ws in inputs.items(): for w in ws: - if not w[0] in filtered_inputs: + if w[0] not in filtered_inputs: filtered_inputs.append(w[0]) - if not prevout_str in prevout_to_spend: + if prevout_str not in prevout_to_spend: prevout_to_spend.append(prevout_str) balance = 0 utxo_to_spend = [] @@ -391,7 +387,7 @@ class Will: return True def search_rai(all_inputs, all_utxos, will, wallet): - will_only_valid = Will.only_valid_or_replaced_list(will) + # 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 w in ws: @@ -429,7 +425,7 @@ class Will: def set_invalidate(wid, will=[]): will[wid].set_status("INVALIDATED", True) if will[wid].children: - for c in self.children.items(): + for c in will[wid].children.items(): Will.set_invalidate(c[0], will) def check_tx_height(tx, wallet): @@ -446,7 +442,7 @@ class Will: ): for inp in w.tx.inputs(): inp_str = Util.utxo_to_str(inp) - if not inp_str in utxos_list: + if inp_str not in utxos_list: if wallet: height = Will.check_tx_height(w.tx, wallet) if height < 0: @@ -456,18 +452,18 @@ class Will: else: w.set_status("CONFIRMED", True) - def reflect_to_children(treeitem): - if not treeitem.get_status("VALID"): - _logger.debug(f"{tree:item._id} status not valid looking for children") - for child in treeitem.children: - wc = willtree[child] - if wc.get_status("VALID"): - if treeitem.get_status("INVALIDATED"): - wc.set_status("INVALIDATED", True) - if treeitem.get_status("REPLACED"): - wc.set_status("REPLACED", True) - if wc.children: - Will.reflect_to_children(wc) + # def reflect_to_children(treeitem): + # if not treeitem.get_status("VALID"): + # _logger.debug(f"{tree:item._id} status not valid looking for children") + # for child in treeitem.children: + # wc = willtree[child] + # if wc.get_status("VALID"): + # if treeitem.get_status("INVALIDATED"): + # wc.set_status("INVALIDATED", True) + # if treeitem.get_status("REPLACED"): + # 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, fixed_amount_with_dust = ( @@ -565,16 +561,16 @@ class Will: 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 w in ws: - 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) + # 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 w in ws: + # 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) def only_valid_list(will): out = {} @@ -637,7 +633,7 @@ class Will: if Util.parse_locktime_string(heirs[h][2]) >= check_date: count_heirs += 1 - if not h in heirs_found: + if h not in heirs_found: _logger.debug(f"heir: {h} not found") raise HeirNotFoundException(h) if not count_heirs: @@ -646,7 +642,7 @@ class Will: raise NoWillExecutorNotPresent("Backup tx") for url, we in willexecutors.items(): if Willexecutors.is_selected(we): - if not url in willexecutors_found: + if url not in willexecutors_found: _logger.debug(f"will-executor: {url} not fount") raise WillExecutorNotPresent(url) _logger.info("will is coherent with heirs and will-executors") @@ -888,7 +884,3 @@ class PercAmountException(AmountException): class FixedAmountException(AmountException): pass - - -def test_check_invalidated(): - Will.check_invalidated(will, utxos_list, wallet) diff --git a/willexecutors.py b/willexecutors.py index d567aaf..9ee820f 100644 --- a/willexecutors.py +++ b/willexecutors.py @@ -1,33 +1,37 @@ import json from datetime import datetime -from functools import partial +import time from aiohttp import ClientResponse from electrum import constants -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 DEFAULT_TIMEOUT = 5 _logger = get_logger(__name__) +chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin" + + class Willexecutors: def save(bal_plugin, willexecutors): + _logger.debug(f"save {willexecutors},{chainname}") aw = bal_plugin.WILLEXECUTORS.get() - aw[constants.net.NET_NAME] = willexecutors + aw[chainname] = willexecutors bal_plugin.WILLEXECUTORS.set(aw) + _logger.debug(f"saved: {aw}") + # bal_plugin.WILLEXECUTORS.set(willexecutors) 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, {}) + willexecutors = willexecutors.get(chainname, {}) to_del = [] for w in willexecutors: if not isinstance(willexecutors[w], dict): @@ -36,12 +40,14 @@ class Willexecutors: Willexecutors.initialize_willexecutor(willexecutors[w], w) for w in to_del: _logger.error( - "error Willexecutor to delete type:{} ", type(willexecutor[w]), w + "error Willexecutor to delete type:{} {}".format( + type(willexecutors[w]), w + ) ) del willexecutors[w] - bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {}) + bal = bal_plugin.WILLEXECUTORS.default.get(chainname, {}) for bal_url, bal_executor in bal.items(): - if not bal_url in willexecutors: + if bal_url not in willexecutors: _logger.debug(f"force add {bal_url} willexecutor") willexecutors[bal_url] = bal_executor # if update: @@ -75,11 +81,11 @@ class Willexecutors: def is_selected(willexecutor, value=None): if not willexecutor: return False - if not value is None: + if value is not None: willexecutor["selected"] = value try: return willexecutor["selected"] - except: + except Exception: willexecutor["selected"] = False return False @@ -92,7 +98,7 @@ class Willexecutors: if willexecutor := willitem.we: url = willexecutor["url"] if willexecutor and Willexecutors.is_selected(willexecutor): - if not url in willexecutors: + if url not in willexecutors: willexecutor["txs"] = "" willexecutor["txsids"] = [] willexecutor["broadcast_status"] = _("Waiting...") @@ -102,23 +108,25 @@ class Willexecutors: return willexecutors - def only_selected_list(willexecutors): - out = {} - for url, v in willexecutors.items(): - if Willexecutors.is_selected(willexecutor): - out[url] = v + # def only_selected_list(willexecutors): + # out = {} + # for url, v in willexecutors.items(): + # if Willexecutors.is_selected(url): + # 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 - ) + # def push_transactions_to_willexecutors(will): + # willexecutors = 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 + # ) - def send_request(method, url, data=None, *, timeout=10): + def send_request( + method, url, data=None, *, timeout=10, handle_response=None, count_reply=0 + ): network = Network.get_instance() if not network: raise Exception("You are offline.") @@ -126,7 +134,8 @@ class Willexecutors: headers = {} headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}" headers["Content-Type"] = "text/plain" - + if not handle_response: + handle_response = Willexecutors.handle_response try: if method == "get": response = Network.send_http_on_proxy( @@ -134,7 +143,7 @@ class Willexecutors: url, params=data, headers=headers, - on_finish=Willexecutors.handle_response, + on_finish=handle_response, timeout=timeout, ) elif method == "post": @@ -143,26 +152,47 @@ class Willexecutors: url, body=data, headers=headers, - on_finish=Willexecutors.handle_response, + on_finish=handle_response, timeout=timeout, ) else: raise Exception(f"unexpected {method=!r}") + except TimeoutError: + if count_reply < 10: + _logger.debug(f"timeout({count_reply}) error: retry in 3 sec...") + time.sleep(3) + return Willexecutors.send_request( + method, + url, + data, + timeout=timeout, + handle_response=handle_response, + count_reply=count_reply + 1, + ) + else: + _logger.debug(f"Too many timeouts: {count_reply}") except Exception as e: - _logger.error(f"exception sending request {e}") raise e else: _logger.debug(f"--> {response}") return response + def get_we_url_from_response(resp): + url_slices = str(resp.url).split("/") + if len(url_slices) > 2: + url_slices = url_slices[:-2] + return "/".join(url_slices) + 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 - except: + # url = Willexecutors.get_we_url_from_response(resp) + # r["url"]= url + # r["status"]=resp.status + except Exception as e: + _logger.debug(f"error handling response:{e}") pass return r @@ -175,7 +205,7 @@ class Willexecutors: _logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}") if w := Willexecutors.send_request( "post", - willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs", + willexecutor["url"] + "/" + chainname + "/pushtxs", data=willexecutor["txs"].encode("ascii"), ): willexecutor["broadcast_status"] = _("Success") @@ -203,18 +233,14 @@ class Willexecutors: 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") - - willexecutor["url"] = url - willexecutor["status"] = w["status"] - willexecutor["base_fee"] = w["base_fee"] - willexecutor["address"] = w["address"] - if not willexecutor["info"]: + w = Willexecutors.send_request("get", url + "/" + chainname + "/info") + if isinstance(w, dict): + willexecutor["url"] = url + willexecutor["status"] = 200 + willexecutor["base_fee"] = w["base_fee"] + willexecutor["address"] = w["address"] willexecutor["info"] = w["info"] - _logger.debug(f"response_data {w['address']}") + _logger.debug(f"response_data {w}") except Exception as e: _logger.error(f"error {e} contacting {url}: {w}") willexecutor["status"] = "KO" @@ -222,24 +248,33 @@ class Willexecutors: willexecutor["last_update"] = datetime.now().timestamp() return willexecutor - def initialize_willexecutor(willexecutor, url, status=None, selected=None): + def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}): willexecutor["url"] = url - if not status is None: + if status is not None: willexecutor["status"] = status - willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected) + else: + willexecutor["status"] = old_willexecutor.get("status",willexecutor.get("status","Ko")) + willexecutor["selected"]=Willexecutors.is_selected(old_willexecutor) or willexecutor.get("selected",False) + willexecutor["address"]=old_willexecutor.get("address",willexecutor.get("address","")) + willexecutor["promo_code"]=old_willexecutor.get("promo_code",willexecutor.get("promo_code")) - def download_list(bal_plugin): + + + def download_list(bal_plugin,old_willexecutors): try: - l = Willexecutors.send_request( - "get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100" + willexecutors = Willexecutors.send_request( + "get", + f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100", ) - del l["status"] - for w in l: - willexecutor = l[w] - Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) + # del willexecutors["status"] + for w in willexecutors: + if w not in ("status", "url"): + Willexecutors.initialize_willexecutor( + willexecutors[w], w, None, old_willexecutors.get(w,{}) + ) # bal_plugin.WILLEXECUTORS.set(l) # bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True) - return l + return willexecutors except Exception as e: _logger.error(f"Failed to download willexecutors list: {e}") @@ -253,7 +288,7 @@ class Willexecutors: willexecutor = willexecutors[w] Willexecutors.initialize_willexecutor(willexecutor, w, "New", False) # bal_plugin.WILLEXECUTORS.set(willexecutors) - return h + return willexecutors except Exception as e: _logger.error(f"error opening willexecutors json: {e}") @@ -270,14 +305,53 @@ class Willexecutors: _logger.error(f"error contacting {url} for checking txs {e}") raise e + def compute_id(willexecutor): + return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain")) + class WillExecutor: - def __init__(self, url, base_fee, chain, info, version): + def __init__( + self, + url, + base_fee, + chain, + info, + version, + status, + is_selected=False, + promo_code="", + ): self.url = url self.base_fee = base_fee self.chain = chain self.info = info self.version = version + self.status = status + self.promo_code = promo_code + self.is_selected = is_selected + self.id = self.compute_id() def from_dict(d): - we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"]) + return WillExecutor( + url=d.get("url", "http://localhost:8000"), + base_fee=d.get("base_fee", 1000), + chain=d.get("chain", chainname), + info=d.get("info", ""), + version=d.get("version", 0), + status=d.get("status", "Ko"), + is_selected=d.get("is_selected", "False"), + promo_code=d.get("promo_code", ""), + ) + + def to_dict(self): + return { + "url": self.url, + "base_fee": self.base_fee, + "chain": self.chain, + "info": self.info, + "version": self.version, + "promo_code": self.promo_code, + } + + def compute_id(self): + return f"{self.url}-{self.chain}"