diff --git a/.gitignore b/.gitignore index a987342..472a39c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,13 @@ node_modules/ *.swo *.bak +# Local tooling / scratch files (not part of the plugin) +TODO_LIST.md +opencode.json +package.json +package-lock.json +pyrightconfig.json + # Debug / scratch files debug.py init.ol diff --git a/bal/core/heirs.py b/bal/core/heirs.py index 101559b..809fc36 100644 --- a/bal/core/heirs.py +++ b/bal/core/heirs.py @@ -21,6 +21,8 @@ Will-executor "heirs" are synthetic entries whose key starts with the ``w!ll3x3c"`` marker; they are skipped by most heir comparisons. """ +import asyncio +import inspect import math import random import re @@ -31,6 +33,7 @@ from typing import ( Dict, Optional, Tuple, + cast, ) import dns @@ -65,6 +68,19 @@ if TYPE_CHECKING: _logger = get_logger(__name__) + +def _query_txt_records(url: str) -> Tuple[Any, bool]: + """Resolve TXT records with DNSSEC validation. + + ``electrum.dnssec.query`` became an ``async`` function in Electrum 4.7.2, + so adapt the call for this synchronous context while staying compatible + with older synchronous implementations. + """ + query = dnssec.query + if inspect.iscoroutinefunction(query): + return asyncio.run(query(url, dns.rdatatype.TXT)) + return cast(Tuple[Any, bool], query(url, dns.rdatatype.TXT)) + # Column layout of a stored heir list. These indices are part of the on-disk # wallet format and are relied upon all over the codebase, so they must NEVER # be reordered. @@ -520,8 +536,8 @@ class Heirs(dict, Logger): ) def prepare_lists( - self, balance, total_fees, wallet, willexecutor=False, from_locktime=0, - max_fee=None, + self, balance, total_fees, wallet, willexecutor: Optional[dict] = None, + from_locktime=0, max_fee=None, ): if balance 0 * tx_fees: + if utxo.value_sats() > 0: balance += utxo.value_sats() len_utxo_set += 1 available_utxos.append(utxo) @@ -685,11 +701,12 @@ class Heirs(dict, Logger): elif j == -1: if not no_willexecutors: continue - url = willexecutor = False + url = willexecutor = None else: break fees = {} i = 0 + txs = {} while i < 10: txs = {} redo = False @@ -829,7 +846,7 @@ class Heirs(dict, Logger): # support email-style addresses, per the OA standard url = url.replace("@", ".") try: - records, validated = dnssec.query(url, dns.rdatatype.TXT) + records, validated = _query_txt_records(url) except DNSException as e: _logger.info(f"Error resolving openalias: {repr(e)}") return None @@ -838,15 +855,15 @@ class Heirs(dict, Logger): string = to_string(record.strings[0], "utf8") if string.startswith("oa1:" + prefix): address = cls.find_regex(string, r"recipient_address=([A-Za-z0-9]+)") + if not address: + continue name = cls.find_regex(string, r"recipient_name=([^;]+)") if not name: name = address - if not address: - continue return address, name, validated @staticmethod - def find_regex(haystack, needle): + def find_regex(haystack, needle) -> Optional[str]: regex = re.compile(needle) try: return regex.search(haystack).groups()[0] diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py index 6da82c9..8ade25b 100644 --- a/bal/core/plugin_base.py +++ b/bal/core/plugin_base.py @@ -108,7 +108,7 @@ def get_will(x): try: # Electrum >= 4.8.0 - from electrum.stored_dict import register_name as _electrum_register_name + from electrum.stored_dict import register_name as _electrum_register_name # pyright: ignore[reportMissingImports] def _register_will_dict(name, method, _type=None): """Register a plugin dict in the wallet DB (Electrum >= 4.8.0 API).""" @@ -118,7 +118,7 @@ except ImportError: # Electrum <= 4.7.2 def _register_will_dict(name, method, _type=None): """Register a plugin dict in the wallet DB (Electrum <= 4.7.2 API).""" - json_db.register_dict(name, method, _type) + json_db.register_dict(name, method, _type) # pyright: ignore[reportAttributeAccessIssue] _register_will_dict("heirs", tuple) @@ -405,7 +405,7 @@ class BalPlugin(BasePlugin): """Fill in any missing will-setting with its default value.""" defaults = BalPlugin.default_will_settings() if not will_settings: - will_settings = [] + will_settings = {} if int(will_settings.get("baltx_fees", 0)) < 1: will_settings["baltx_fees"] = defaults['baltx_fees'] if not will_settings.get("threshold"): @@ -427,7 +427,7 @@ class BalPlugin(BasePlugin): @staticmethod def default_will_settings(): """Default will settings: a fee rate plus absolute threshold/locktime.""" - will_settings = {"baltx_fees": 20} + will_settings: dict[str, float] = {"baltx_fees": 20} will_settings.update(BalPlugin.default_will_settings_absolute()) return will_settings @@ -460,10 +460,12 @@ class BalTimestamp: * an integer -> an absolute UNIX timestamp (``unit is None``) """ - value = None - unit = None + value: int + unit: str | None def __init__(self, value): + self.value = 1 + self.unit = None str_value = str(value) if str_value and str_value[-1].lower() in ("y", "d"): self.value = int(str_value[:-1]) diff --git a/bal/core/will.py b/bal/core/will.py index 0e0695d..ea3d3aa 100644 --- a/bal/core/will.py +++ b/bal/core/will.py @@ -443,11 +443,12 @@ class Will: for wid in will: wtx = will[wid].tx found = False + inp = None for inp in wtx.inputs(): if inp.prevout.txid.hex() in will: found = True break - if not found: + if not found and inp is not None: out[inp.prevout.to_str()] = inp return out diff --git a/bal/core/willexecutors.py b/bal/core/willexecutors.py index cea2e8d..8de78b7 100644 --- a/bal/core/willexecutors.py +++ b/bal/core/willexecutors.py @@ -17,6 +17,7 @@ interaction is handled by the Qt layer. import json import time from datetime import datetime +from typing import Any from aiohttp import ClientResponse from electrum import bitcoin, constants @@ -154,7 +155,7 @@ class Willexecutors: @staticmethod def get_willexecutors( - bal_plugin, update=False, bal_window=False, force=False, task=True + bal_plugin, update=False, bal_window: Any = None, force=False, task=True ): willexecutors = bal_plugin.WILLEXECUTORS.get() willexecutors = willexecutors.get(chainname, {}) @@ -205,15 +206,11 @@ class Willexecutors: return w_sorted @staticmethod - def is_selected(willexecutor, value=None, max_fee=None): + def is_selected(willexecutor, value=None): if not willexecutor: return False if value is not None: willexecutor["selected"] = value - if max_fee is not None: - base_fee = willexecutor.get("base_fee", 0) - if int(base_fee) >= max_fee: - return False try: return willexecutor["selected"] except Exception: @@ -228,9 +225,9 @@ class Willexecutors: if not address or not bitcoin.is_address(address, net=constants.net): return False base_fee = int(willexecutor.get("base_fee", 0)) - if dust is not None and base_fee <= dust: + if dust is not None and base_fee < dust: return False - if max_fee is not None and base_fee >= max_fee: + if max_fee is not None and base_fee > max_fee: return False return True @@ -426,7 +423,7 @@ class Willexecutors: else: base_fee = w.get("base_fee") try: - base_fee = int(base_fee) + base_fee = int(base_fee or 0) if base_fee < 0: raise ValueError("negative fee") if base_fee > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN: diff --git a/bal/gui/qt/calendar.py b/bal/gui/qt/calendar.py index 2bef8ee..e6552a6 100644 --- a/bal/gui/qt/calendar.py +++ b/bal/gui/qt/calendar.py @@ -194,30 +194,15 @@ class BalCalendar: @staticmethod def ical_escape(text: str) -> str: # escape per RFC5545: backslash, ; , newlines - text = text.encode("utf-8") text = ( - text.replace(b"\\", b"\\\\") - .replace(b";", b"\\;") - .replace(b",", b"\\,") + text.replace("\\", "\\\\") + .replace(";", "\\;") + .replace(",", "\\,") + ) + return "\r\n".join( + BalCalendar.fold_ical_line(line) + for line in text.split("\r\n") ) - out ="" - temp=text.split(b"\r\n") - for s in temp: - encoded= s - cut =0 - while len(encoded) >75: - cut+=5 - encoded=f"{s[:len(s)-cut]}" - if encoded[-1]==b"\\" and encoded[-2]!=b"\\\\": - cut += 1 - encoded=f"{s[:len(s)-cut]}" - encoded=f"{encoded}...\r\n".encode("utf-8") - if cut>0: - out+=str(f"{s[:len(s)-cut].decode()}...\r\n") - else: - out+=str(f"{s.decode()}\r\n") - - return out[:-2] @staticmethod def fold_ical_line(line: str, limit: int = 75) -> str: diff --git a/bal/gui/qt/dialogs.py b/bal/gui/qt/dialogs.py index 80fee88..066915f 100644 --- a/bal/gui/qt/dialogs.py +++ b/bal/gui/qt/dialogs.py @@ -17,6 +17,8 @@ the few list classes they reference are imported lazily inside the methods that use them (see ``lists`` imports below). """ +from typing import TYPE_CHECKING + from .calendar import BalCalendar, BalCalendarButton from .common import * from .common import _, _logger # underscore names are not re-exported by "import *" @@ -27,6 +29,9 @@ from .widgets import ( compute_reminder_offsets, ) +if TYPE_CHECKING: + from .window import BalWindow + # NOTE: list views (HeirListWidget, PreviewList, WillExecutorWidget) are # imported lazily where needed to avoid a dialogs<->lists import cycle. @@ -467,7 +472,6 @@ class BalWaitingDialog(BalDialog): 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) # IMPORTANT: keep the *application-modal* exec() of the original code. # This dialog is driven by a TaskThread whose result (on_success, e.g. @@ -482,9 +486,6 @@ class BalWaitingDialog(BalDialog): def hello(self): pass - def finished(self): - pass - def on_accepted(self): pass @@ -1147,7 +1148,6 @@ class BalBuildWillDialog(BalDialog): for url, we in willexecutors.items() if Willexecutors.is_selected( self.bal_window.willexecutors.get(url), - max_fee=self.bal_window.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), ) and Willexecutors.is_valid( self.bal_window.willexecutors.get(url), max_fee=self.bal_window.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), diff --git a/bal/gui/qt/lists.py b/bal/gui/qt/lists.py index d8fce0e..78a8d15 100644 --- a/bal/gui/qt/lists.py +++ b/bal/gui/qt/lists.py @@ -14,6 +14,8 @@ construction) for all business actions, so the heavy logic stays in ``window`` and ``dialogs``. """ +from typing import TYPE_CHECKING + from PyQt6.QtWidgets import QLineEdit as _QLineEdit from PyQt6.QtWidgets import QMessageBox, QStyledItemDelegate @@ -22,6 +24,9 @@ from .common import _, _logger # underscore names are not re-exported by "impor from .dialogs import BalBuildWillDialog, BalDialog from .widgets import BalCheckBox, WillSettingsWidget +if TYPE_CHECKING: + from .window import BalWindow + def _can_sign(will_item): """True if a will transaction may still be signed (not fully signed).""" @@ -127,7 +132,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): self.bal_window.new_heir_dialog(edit_key) def on_edited(self, idx, edit_key, *, text): - original = prior_name = self.bal_window.heirs.get(edit_key) + prior_name = self.bal_window.heirs.get(edit_key) if not prior_name: return col = idx.column() @@ -148,12 +153,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): try: self.bal_window.set_heir(prior_name) except Exception: - pass - - try: - self.bal_window.set_heir((edit_key,) + original) - except Exception: - self.update() + self.update() def delete_heirs(self, selected_keys): self.bal_window.delete_heirs(selected_keys) @@ -938,6 +938,7 @@ class WillExecutorListWidget(MyTreeView): idx = self.indexAt(position) column = idx.column() or self.Columns.URL selected_keys = [] + sel_key = None for s_idx in self.selected_in_column(self.Columns.URL): item = self.model().itemFromIndex(s_idx) # Use the FULL url stored in the key role, NOT item.data(0): the @@ -1079,7 +1080,7 @@ class WillExecutorListWidget(MyTreeView): # are shown unchanged. display_url = url if len(url) <= 40 else url[:37] + "\u2026" labels[self.Columns.URL] = display_url - if Willexecutors.is_selected(value, max_fee=float("inf")): + if Willexecutors.is_selected(value): labels[self.Columns.SELECTED] = [ read_QIcon_from_bytes( @@ -1368,6 +1369,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): add_another_btn.clicked.connect(add_another) else: self._add_another = False + add_another_btn = None row = 0 grid.addWidget(QLabel(_("URL")), row, 0) diff --git a/bal/gui/qt/widgets.py b/bal/gui/qt/widgets.py index 63d97ed..5abf45d 100644 --- a/bal/gui/qt/widgets.py +++ b/bal/gui/qt/widgets.py @@ -18,10 +18,15 @@ Contents: * WillWidget - single will-tx box """ +from typing import TYPE_CHECKING + from .calendar import BalCalendar, BalCalendarButton from .common import * from .common import _, _logger # underscore names are not re-exported by "import *" +if TYPE_CHECKING: + from .window import BalWindow + def compute_reminder_offsets(days, count): """Return the reminder offsets (in days BEFORE the deadline) for an .ics event. diff --git a/bal/gui/qt/window.py b/bal/gui/qt/window.py index 7b79405..afbfa58 100644 --- a/bal/gui/qt/window.py +++ b/bal/gui/qt/window.py @@ -385,7 +385,7 @@ class BalWindow: f = False for _u, w in self.willexecutors.items(): if Willexecutors.is_selected( - w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get() + w ) and Willexecutors.is_valid( w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), dust=self.window.wallet.dust_threshold() @@ -521,8 +521,8 @@ class BalWindow: 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_warning(self, text, parent=None, title=None): + self.window.show_warning(text, parent=parent, title=title) def show_error(self, text): self.window.show_error(text) @@ -703,7 +703,7 @@ class BalWindow: f = False for _k, we in self.willexecutors.items(): if Willexecutors.is_selected( - we, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get() + we ) and Willexecutors.is_valid( we, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), dust=self.window.wallet.dust_threshold() @@ -903,7 +903,7 @@ class BalWindow: self.show_message(_("No transactions to invalidate")) def on_failure(exec_info): - log_error(exec_info, self.bal_window) + log_error(exec_info, self) willitems = will if will is not None else self.willitems fee_per_byte = self.will_settings.get("baltx_fees", 1) @@ -1080,7 +1080,7 @@ class BalWindow: raise e def on_failure(exec_info): - log_error(exec_info, self.bal_window) + log_error(exec_info, self) password = self.get_wallet_password() task = partial(self.sign_transactions, password, will=will, txids=txids) @@ -1106,7 +1106,7 @@ class BalWindow: ) def on_failure(exec_info): - log_error(exec_info, self.bal_window) + log_error(exec_info, self) # a,b,c = err # _logger.error(f"fail to broadcast transactions:{err}") # _logger.error(f"error: {b}") @@ -1246,6 +1246,13 @@ class BalWindow: valid/invalidated/replaced statuses (no server contact, no expiry raise). """ + # The reference timestamp is normally set by init_class_variables(), + # which the merge flow does not run (Merge -> file import can be the + # very first action in a session). Fall back to "now" so the local + # validity check and the trailing update_all() always have it. + if not hasattr(self, "date_to_check") or self.date_to_check is None: + self.date_to_check = datetime.now().timestamp() + for wid, wi in imported.items(): if wid in self.willitems: live = self.willitems[wid] @@ -1298,7 +1305,7 @@ class BalWindow: ) Will.check_signatures(self.willitems, self.wallet) except Exception as e: - log_error(e, self.bal_window) + log_error(e, self) self.save_willitems() self.update_all() @@ -1726,14 +1733,15 @@ class BalWindow: Willexecutors.ping_servers_parallel(wes, on_each=on_each, on_tick=on_tick) def ping_willexecutors(self, wes, fn_on_success, fn_on_failure=None): + if not fn_on_failure: + fn_on_failure = log_error + def on_success(result): fn_on_success(result) def on_failure(exec_info): fn_on_failure(exec_info) - if not fn_on_failure: - fn_on_failure = log_error _logger.info("ping willexecutors") task = partial(self.ping_willexecutors_task, wes) msg = _("Ping Will-Executors") diff --git a/opencode.json b/opencode.json deleted file mode 100644 index 9cc8361..0000000 --- a/opencode.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "lsp": true -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0bf2724..0000000 --- a/package-lock.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "bal-electrum-plugin", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "pyright": "^1.1.411" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/pyright": { - "version": "1.1.411", - "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.411.tgz", - "integrity": "sha512-03S/vmS5lF1S/tVbKc2WNXCMq8JWCwta/qIYjj1jvqbQhoy+N3NgBzHTSmUlbYD6DJwqQ5XHf108QujoqeURvw==", - "license": "MIT", - "bin": { - "pyright": "index.js", - "pyright-langserver": "langserver.index.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 4def390..0000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "pyright": "^1.1.411" - } -} diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index 19c875f..0000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "python.analysis": { - "extraPaths": ["../electrum"] - } -} - diff --git a/tests/karen7 b/tests/karen7 index 2ff4f4b..08b8700 100644 --- a/tests/karen7 +++ b/tests/karen7 @@ -3187,7 +3187,9 @@ "lightning_preimages": {}, "lightning_xprv": "vprv9HMaVA1cGK7XCUCiTrrdD9kGgiSeiwpqDDYfAafTZoSXyZtmQYnQ4CUvsZggS4fWrF3kve47MFWjrWLJ6t4uXjzs2XagtmBeUqoaRRFpJGF", "notes_text": "", - "num_parents": {}, + "num_parents": { + "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": 4 + }, "onchain_channel_backups": {}, "payment_requests": { "6e7ca1969b": { @@ -3260,7 +3262,6 @@ "a001edc1d43b5b41adc5a4c5ce9b6edd9dcad9fa3e50cca287d256a23cca9d4e:0": 50000, "a1aee934c5dda700d15934667507f90db1ce36d7dd3068165b54159df320cd02:0": 50000, "aa407bf21c31fa3ebe8bf074767bfec17df01644da94b3b709c3d3551f3210ca:0": 50000, - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:0": 1000, "b28b20fb9089f2d021d812dc34ce461fdcd800137434edaa09160a395c182c8c:0": 50000, "b6e993fb2b0584bb35b8eefcfbe6ba7c9b9b7875e69389063c96f78dd1fca9e1:0": 50000, "bdaa76d37810e4d4ad1d2e7ac6e198ad96c48683e552e874901a7af3d8ae4e50:0": 50000, @@ -3718,9 +3719,6 @@ "5fb4dbd05379842b11a048ee73467fff68ef04500bf59e0e1a0388474b0e259a": { "af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e:0": 1250000000 }, - "61d6068a9000df4a2d0c919d0845ceb7b9f690e609fc27238e38facecbe38d31": { - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:2": 316818514 - }, "6232f0790f4bc993b8bd042828a1f626b0ae3ac20b1dcf5a479be57c1035de29": { "316b0ac8e6516b59633a46dd5f4fa19da180f799f871a57085f6a7e99bb561e7:0": 1250000000 }, @@ -3811,11 +3809,6 @@ "8cebd66ae223c28c1db0fbd9638668ae9a28c757b164c97e93a22c9b2ef9fb8c:0": 610351, "b3e3d7cba32180e7a72a2e66126d6b2bee9e386788ee099c5f032efaa8772fe3:0": 751351 }, - "7d1852bfc82939c14ac37a01ad558273fcdc304c50eb3dc87272643bb2ce36b6": { - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:1": 40001, - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:3": 336619671, - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:4": 346520250 - }, "8017a32067302648ad152b7a87fdb1c2c4f19c174a5240e88666ba987063163c": { "1d30124dd0acb5c11a2cb8e607c50b5f402a7be178cafb264747fb0dab37c8cd:0": 1250000000 }, @@ -4387,9 +4380,6 @@ "2af047c4a003fb7b90ce2c8fc49bcdb3eaee0fff213b14dc1645b9fb18b8f0f6": { "0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308" }, - "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": { - "0": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91" - }, "2c6d3a41eb0f5589e2586138b2bee1cf453144fe48dc73c2a3b0f4d0a3bfd596": { "0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15" }, @@ -5179,7 +5169,7 @@ "2": "8cebb4ea3a63829a2787c3a473f14b4f73ced07552f73ff8c2b1152169042550" } }, - "stored_height": 2336, + "stored_height": 2381, "submarine_swaps": {}, "transactions": { "00fda0d8fdc53b1f95410bafca884c5dff8ac1e5c168ace5f09a32d872b7413a": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402460100ffffffff02807c814a00000000160014b51c529851d6140f1a37f2aa46bafe171a549f210000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", @@ -5360,7 +5350,6 @@ "ab1012eb232c0070b566b6620a8b839e8fa78831cf8216337c1db71e35b89953": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402d70100ffffffff0240be402500000000160014ddc0da8880db383448d588868cd34ab79779dd7c0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "ab74079fca87f186df5676c62de63d8b21e98746c8c8888f108310bc7849e70e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04021e0100ffffffff0200f9029500000000160014178787a67beeadb99e36f6c5513fe8e2b02e800b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "ad3f3d67b74bb4b9b4457f1ccb99ea3dad02ad24c0f4d2383ff1fd12f7c0a684": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402f20200ffffffff02902f500900000000160014ea779f0f8e203034ae25cd71e52c1a81ac488e2b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": "cHNidP8BAM4CAAAAAUxP/9q+TSeosGYYSSpf6Cg0VVGz2oEHuMraX0UplWUsAAAAAAD9////BegDAAAAAAAAFgAUHJ2Hucc2GI5sonbAeh3TaBHHeApBnAAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8UkTiEgAAAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnZdoEBQAAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7y6eqcUAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8wKJObAABAR+2yJo7AAAAABYAFH77QT1b74D8A9KpXC1rH06KZ0UgAQC/AgAAAAABAd8W8LQ4gBPr4BMdIIFgxnlK/0pm2Hv7VRK0tdMKsspOAAAAAAD9////AbbImjsAAAAAFgAUfvtBPVvvgPwD0qlcLWsfTopnRSACRzBEAiBiDWmocrmu+1Hip22YEAiBWHdkSIwuaYOqolHI5yjlJwIgaVVzZF7VDoudB+M8a9n375i5hXF+w+RnR2RYs+oKnUgBIQMAYJEwf6yH90elPJ15yFBJZBFMuZDDwEIcyvu85pUc5uYIAAAiBgMlkmiTZPOkeW/DWh8YN5HrfF2yXm82+g8ekmVO1nnHkRBZSzQGAAAAgAEAAAARAAAAAAAAAAAA", "ae8e62c0ec616466782a4f861d108ad06e593af6c8a4fe3d2c9ca1386fd559bd": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402200100ffffffff0200f9029500000000160014d2b8a4b410689315e81b354969e36584451f85530000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04023a0100ffffffff02807c814a00000000160014b2da19ab366b4a2f09d6dd233192d9339444ca980000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "b00e9f65ca32f0ab7a05666f21a78b461623c43d1297a731f09a807f2c9dc497": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402850300ffffffff02aa19a8040000000016001439690f010cb6ee0ab0180e01d1a9f6b3417af06b0000000000000000266a24aa21a9edb6b7b0b5bded40786dbf759cf9af7726000596503854971f2cc19c3fed02f4a10120000000000000000000000000000000000000000000000000000000000000000000000000", @@ -6352,11 +6341,6 @@ false, 1 ], - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": [ - 234, - true, - 1 - ], "ae8e62c0ec616466782a4f861d108ad06e593af6c8a4fe3d2c9ca1386fd559bd": [ null, false, @@ -7395,11 +7379,6 @@ "88a56990a69eb1a7c11595b7a88097d799fb6070bb2f77ea34e7f2fe8d54d867:1": 100000 } }, - "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": { - "bcrt1q0ma5z02ma7q0cq7j49wz66clf69xw3fq2zl05n": { - "2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c:0": 999999670 - } - }, "b020ccbf483abab08d6553b7fec974ce9f8ab43654863757e3ec7694c399f86c": { "bcrt1qazle627r46apscly8lj4q5cxfxgrtuew879jde": { "335d73d9f9295b283a07187652a587830eb1eb2ee2829ef566dde8445946e474:1": 68897247707 @@ -13151,21 +13130,21 @@ "BROADCASTED": false, "CHECKED": false, "CHECK_FAIL": false, - "COMPLETE": false, + "COMPLETE": true, "CONFIRMED": false, "ERROR": false, "EXPIRED": false, "EXPORTED": false, "IMPORTED": false, - "INVALIDATED": true, + "INVALIDATED": false, "MEMPOOL": false, "PARTIALLY_SIGNED": false, - "PUSHED": false, + "PUSHED": true, "PUSH_FAIL": false, "REPLACED": false, "RESTORED": false, "UPDATED": false, - "VALID": false, + "VALID": true, "_id": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91", "baltx_fees": 1, "change": null, @@ -13205,25 +13184,30 @@ }, "sigs_have": 0, "sigs_required": 1, - "status": "New.Invalidated", - "time": 1785562346.7776835, - "tx": "cHNidP8BAM4CAAAAAUxP/9q+TSeosGYYSSpf6Cg0VVGz2oEHuMraX0UplWUsAAAAAAD9////BegDAAAAAAAAFgAUHJ2Hucc2GI5sonbAeh3TaBHHeApBnAAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8UkTiEgAAAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnZdoEBQAAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7y6eqcUAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8wKJObAABAR+2yJo7AAAAABYAFH77QT1b74D8A9KpXC1rH06KZ0UgAQC/AgAAAAABAd8W8LQ4gBPr4BMdIIFgxnlK/0pm2Hv7VRK0tdMKsspOAAAAAAD9////AbbImjsAAAAAFgAUfvtBPVvvgPwD0qlcLWsfTopnRSACRzBEAiBiDWmocrmu+1Hip22YEAiBWHdkSIwuaYOqolHI5yjlJwIgaVVzZF7VDoudB+M8a9n375i5hXF+w+RnR2RYs+oKnUgBIQMAYJEwf6yH90elPJ15yFBJZBFMuZDDwEIcyvu85pUc5uYIAAAiBgMlkmiTZPOkeW/DWh8YN5HrfF2yXm82+g8ekmVO1nnHkRBZSzQGAAAAgAEAAAARAAAAAAAAAAAA", + "status": "New.Firmato.Pushed", + "time": 1785620332.2813325, + "tx": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c", "willexecutor": { "address": "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", "balance": 90000, "base_fee": 1000, + "broadcast_status": "Riuscito", "chain": "regtest", "count_win": 0, "id": 66, "info": "BAL devel willexecutor server", "last_block": 0, - "last_update": 1785557736.334134, + "last_update": 1785592563.693952, "onion_url": null, "points": 0, "promo_code": null, "selected": true, "status": 200, "tld": "localhost", + "txs": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c\n", + "txsids": [ + "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91" + ], "unconfirmed_balance": 0, "url": "http://localhost:9133", "version": "0.3.2" diff --git a/tests/samanta7 b/tests/samanta7 index 4effc3f..ef5b7f1 100644 --- a/tests/samanta7 +++ b/tests/samanta7 @@ -54,6 +54,10 @@ [ "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07", 2249 + ], + [ + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a", + 0 ] ], "bcrt1qqzheazqw004kt0379qszs66xm6ha5m9uwn84hu": [], @@ -64,7 +68,12 @@ "bcrt1qw3ljqdscx64qk9lxke3evz8scukj4zf8l5mptt": [], "bcrt1qw3m44vlmx08xzpnus22yfa8k5exnq9wk9uncrl": [], "bcrt1qwhjyltkpkyqj2k8zd6zjq2uu49tpu03f4sm7j0": [], - "bcrt1qygktltj8sjkus96k0atzgr8srcy658axnpj82x": [], + "bcrt1qygktltj8sjkus96k0atzgr8srcy658axnpj82x": [ + [ + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a", + 0 + ] + ], "bcrt1qyjpmlr7qehqedzhkzsx6xrvr9ey6455yur2pqx": [], "bcrt1qyx3e4qwguyr70g9wdva4dgpp2pdpauj9dht5ga": [] }, @@ -123,22 +132,22 @@ "aaaa": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", "34%", - 1813204800 + "1y" ], "lucia": [ "bcrt1q0cv672t0yhgf9u36pssgsglxtjq69tuakdzy2j", "32%", - 1813204800 + "1y" ], "mario": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", "35%", - 1813204800 + "1y" ], "mario2": [ "bcrt1q3c0u8xtpc4rc0lwhkh8nukwqjkuhxnaurd45ju", 40000, - 1813204800 + "1y" ] }, "imported_channel_backups": {}, @@ -159,6 +168,7 @@ "5833dd0e77cc9e22d981b92e71e5f5804deb5294dfa4dc666cfe4fa015afee8d": "BAL Inheritance transaction", "5ca07e947509bea249d18591358c13a9c55e02b897390bab7ce0d2c6b2b394ec": "BAL Inheritance transaction", "6ad1383bb5d358c4b7bcebbf009d642e7a78b923cf3b764b196904c68ffa116d": "BAL Inheritance transaction", + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a": "BAL Invalidate transaction", "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": "BAL Invalidate transaction", "7e45b6093570bdaa73cd2001acb7443492b0048bf4be821ac3182787cf3a9141": "BAL Inheritance transaction", "a45935c22968fe56dfdc329420336682cebfce0691ed764ab5891199ed91a158": "BAL Inheritance transaction", @@ -172,9 +182,7 @@ "lightning_preimages": {}, "lightning_xprv": "vprv9HoQk9nhHxLPsMH1eASbcw5VpZbn9GqWUM1xXUurrM9ZyPJc64mL6NTaZ9TYZ4wYgKBkYJNieJ2GuQsSRh7aMnJYVDwsYuskKiuLGyronwJ", "notes_text": "", - "num_parents": { - "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": 4 - }, + "num_parents": {}, "onchain_channel_backups": {}, "payment_requests": { "168d59e09c": { @@ -196,6 +204,9 @@ }, "plugin_data": {}, "prevouts_by_scripthash": { + "11842fac1d86ffab05359a3003b9a3fc8ab3359b437fc587b5083a158d4368a3": { + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a:0": 207263913381 + }, "3cacf6c872b2a8819f9d8a06367fd75cfe6dfe82f64106e049be669a20b9dde0": { "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07:0": 207263913491 }, @@ -249,6 +260,9 @@ "68a99483af54769940b80095e83d95d607f9c32c2d03c0b89a86cd4143d65eb9": { "0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308" }, + "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": { + "0": "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a" + }, "83ff9433dece51bf3a37f60c81e196fac96cef88c587a2f97fb44be490482536": { "0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308" }, @@ -286,10 +300,11 @@ "0": "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07" } }, - "stored_height": 2336, + "stored_height": 2381, "submarine_swaps": {}, "transactions": { "4c955222d31ed19318841b7f3dd0e95edd6198ebddc0dbea1c249c38c609aeac": "0200000000010108d37b8e201da3203b47c7cc33fdf21a55b9cf776fdf5d1a028cd250e953d9d20000000000fdffffff01ef56e44130000000160014f0df9afc9fdd8ec20c46eb6af56d3c4934063343024730440220625a4a9944af68bd2b4c4146ae9db95f052725b8ca4fc8e8fca541d1c863d37a022004a705b8974e5fea3707acd0d8cc5f9a9c898a508fd0ae348b81043c4a86ad0b0121021e753044de0b2d3894751fae6e62c0627d379752809e522a5d0d4e3689e4ba9fa9080000", + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a": "0200000000010107fe970943ecade0dfb178ccbbf0ccbcef3a295634943654d9b56d0a919fb47c0000000000fdffffff01a555e44130000000160014222cbfae4784adc817567f56240cf01e09aa1fa6024730440220532df5e6921268d3905d9349f8d0e98714fc34fa70fdd20b75541f0144b4738e02207d2d930f296d19caf0e572d5d3340ea133326985f05455e5f8f10f04f7526b72012102c5e7ef3fc3902156154cd1a2238e80bc15f1bb253f3c973b3b87f9ed6fe1c7b74d090000", "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": "02000000000101aa7e002d508e8824c501e071e6d5cfda450520b7f6a58c420b82f64b6a6c4ff50000000000fdffffff011356e44130000000160014016f7731814d74eeef645de5dad2bf7d65156f6602473044022027783d9e8034114e991ad98213256ec109227243cb6de4490e3272eeee39f3670220584eb51bf984552aa026ce0e4e82e8faff3bec7b782d8ca4539cbae03dc2738d01210222328d50fb0839195043813aa0cba1d5ba0477071ebd68e33e57a70b4a54b10dc8080000", "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308": "02000000000115b6c695030bd73c8b3aea0506cea28dabb05225d68ce8437dc1d2b0c41855b8020000000000fdfffffff87bbb460cdbe6674149a0d3afb30151b0aa0833d87ef0ba344af3e2473c2d0b0000000000fdffffffd1b26eb7abb46c8a85b601a936359286351385935c54acc9bbd3e4b5c08be8130000000000fdffffff0309509b3522105cae2417551e05c32e203938c0962f2bd67b1f3ea133145c1c0000000000fdfffffffb05df2d04e81695d4b2cd68c14ed5d578efd885d1bc32c57b72ee8895cd352a0000000000fdfffffff6f0b818fbb94516dc143b21ff0feeeab3cd9bc48f2cce907bfb03a0c447f02a0000000000fdffffffb4b3eca0a6d31464e6674f4df43b010e4e81ad42f5f2ef83dbb7827f6275df410000000000fdffffffaf6a60ca203190b28e94f5c4c9a900f532534d29d52202ff05bf75fb14af1b560000000000fdffffffd52b06e1225de87a38c7546f54c16efed5e06bdfa4de3f11ccff4318fbdc13590000000000fdffffff345bd148bb030f1cc6c2911cd4e12d8a9f411e28a5f8db88ba36fc3432a7bd610000000000fdffffffb95ed64341cd869ab8c0032d2cc3f907d6953de89500b840997654af8394a9680000000000fdffffff36254890e44bb47ff9a287c588ef6cc9fa96e1810cf6373abf51cede3394ff830000000000fdffffff8cfbf92e9b2ca2937ec964b157c7289aae688663d9fbb01d8cc223e26ad6eb8c0000000000fdffffff87bd5f1e375ace44b25f835b975c7fcd22e3ebeaa2d15438dc277b0efb2b7ea00000000000fdffffffe32f77a8fa2e035f9c09ee8867389eee2b6b6d12662e2aa7e78021a3cbd7e3b30000000000fdffffffa4038a80fc7030c7a6cac366622f015b9be11938d92c0d54bf1eba0c829c6db60000000000fdffffff1b2dd18816b43df16b51cb6c69356faf33eebf9ffc6bf5237fb0e147107ecebc0000000000fdffffff512da9cd097002fcd566136faaf4b7c877737dc45c17cb8f2dacc1cc97c339ca0000000000fdffffff139aa6bd471ed4314e5a794013f1f572037927ecd6ceae760a494b3dbf79f0d00000000000fdffffffcb83d59c573d638ea2221954850be80b02bf701acc2e131f65af3ebebbc9e5e00000000000fdffffff1e143b686c377ed8946bd41abfa66cb9a3436b744ca81941435d56844d01cfe10000000000fdffffff015d57e44130000000160014b1a7091cc4ddad5df6f9ed0643fd01ef1eea340802473044022027d5185098efa3028827ed90b7683171f083603c1883739ec67ce111d473cdfc022005397d10360d5b598122b1cd78b7df3a000eca6ddfe3a1de5f0c5722fa0d080c012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc10247304402202d78c07e4f1c9c8e8eaca2c7809b5fe1a73db9993f060ae4cd60775855a91ce0022012a8de65f16c38c3e8c75e05606de33613cd85e96905e5b20e02d5723a8701a0012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc1024730440220083513295fe195da9877b6d7e76142bef71e785da1dacb479b73cf3366cb6abc02203952e9dc1fac1269fa70566466b0d2a521bd0089932da04772b2e866539ca3760121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a40247304402204caad583efd6b0a6b37a37d7e2a1c90c11c37cadc88f9a80ff2081c223becc8302204fcaf0d69032e3d6ac59e658185a6692b55e201019025eba21b84bf4c431a1380121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a4024730440220662d06d0f99c85ea43596c5b79dd89fa044c5d0be07353369515c35eb4bb2be602201975d288dc734d6c8e8f015fcc2dee2f92c7175d7c69bf700e2e3b85d246dedb012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc10247304402204849e76859906905bc8c62d4e25cf845fc6acabe8515f7c4bb34b5d6d752b27a02202df5b5cad977fa1e6cb8c9b55a89cb78899ab1cafed20d931e281f869aadcdc90121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a40247304402200f12572612ebfc2d43d08e9adcb122bfcc577456840aa75ad3ed7e8beb7acdb70220365bf198c128b2fd55a312aad5ce7b26b92ef2e6c8ef1182c8c7589dae3a6337012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc102473044022077772c17fe4fe66dd65c39847c6ec9fdd15b64a6c5aeb6b7136834c48e50f14d0220592e941e817a7e5a7113ef1b379d020de7ab4af06906096aecd2dd734f791daf012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc102473044022027676aef0665989b3c3a25a68e5a3bad5bcc0f74f942f92373a2b812f4285a3602205739c9931b9d54cfa788f89390bf562fa4b4e67f78de24c0fb996fb0d4ed62c4012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc10247304402207d0351783b4730072920d4ac6a4f7182e47521d1660a931e014a5058cf4a725802203372274efd412f97c4701f09770a15f1a54fcc4871a84da6862e347ddf7df16d012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc10247304402200c7ad4a07fa88edb1341ae4927d9dc9ea5fa2f8758b1dbfc3ff40a50ad4517ae0220715cec02054171e5ca063e86fe47573a045510f661b9a696b6b0369d0712f6e1012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc1024730440220303e378e0f4872bfb94ba4f8333362cfd731c3b579265673ecbfa50b13e7d2a502200989208e927c71296bbe4a97b08e32fe3e42baa4e95efcdda48431285dcf79f901210210a66339c361622bb927cdb3d187d38eece517c3ea8d8d7688bfc60b7e52ce750247304402201db739cb8f2ed20adba605a521eec61f8c45ad5d989cea4d55de34e29b4035d902205e868a9391c996048bc449cccb64491ef957636add19703ba87cc44c63afd53c012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc1024730440220602eff893bcbbf8c974baad847994e0f7b48a868989077877b2e911a2711952402205588895c5a79329df79c36568e16eab06ffefe2c037e9f8efecb04dec7a290710121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a40247304402201c38e73911a4ba93c51a30b459c0930f79d8a0133413bbede6fff6217135a1680220437f944c75af8b158c4b81ccfad0e92a280a1caa50844f802289473b6c580d46012102c6b17fc402c319be45aac0087c55d2d29a95ab543c37df5d3576343efdcd0dc1024730440220639e2ffa1ac8685ee8b52f37991a2b170956c20c0fb2ac2b93520f78318c2d4802206180a8ba3f5c8dade3ecce522bee12ed1d319906ccfecf535f5d5456732869340121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a402473044022009b4163a1635cb35fbaaf2d3e6a5905e0af154958bbb1aaabeb39a2eed06ed79022002e140ef602c43dc13faea0ac0089d5b7cb05be9db60609285c96baceea7515c0121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a40247304402206904b8be3d962d7b4fdfa62ece322a8f20438b1e7061db104892ecbde0258d2002207644efbd762c698be7eed0741ddce8517616ab4265b81c06b263ffa428b45d820121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a40247304402207a799297d99c8a38f3d052f76ccccb70838de3e7d29d0059e755d3c711154fff02206e7a7b4979292254da6db728783f78633f6304a551a72faa0a1d30dea13ff6630121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a402473044022058b9ddd500c7cb5b4fe64dd7b593b8a1222ce3448a46fe07eacea121eea0d42b0220782809c1e0fc02dfbb48b3dfc729d5ccddfb9b4bbf7dcd2b00417c5f7a5c96710121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a402473044022038865ea9230f31b2f01b43e7d34209c18b94702de2a78accc8c386c32214953802203f7153984448b92ea72b094d0d2df87ee2dd624cb1da8b5f0a725923ba205c730121039baadaf427e032e46ac70529ba1476d8edeea7b35271f65e46b96ea6ce2530a4a9080000", "f54f6c6a4bf6820b428ca5f6b7200545dacfd5e671e001c524888e502d007eaa": "02000000000101acae09c6389c241ceadbc0ddeb9861dd5ee9d03d7f1b841893d11ed32252954c0000000000fdffffff018156e4413000000016001442b0a33571c38858ba8162d1daab078981b9e7b6024730440220648cab92f3eab55746ed6b241e4f07f9c3ed0eefa280c77e1a947b3704d777dd022007d7e342b5a2e4ec659c88697d4b1f14ba71f754fdff8f83da33af41052c660a01210224ab09dc547a0d281a6561016d2b0d857e25e0fad7bf3fb3bcb79bbcd8a4161aae080000" @@ -301,6 +316,11 @@ true, 1 ], + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a": [ + 110, + false, + 1 + ], "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": [ 110, true, @@ -323,6 +343,11 @@ "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308:0": 207263913821 } }, + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a": { + "bcrt1qq9hhwvvpf46wammythja454l04j32mmxezkevz": { + "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07:0": 207263913491 + } + }, "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": { "bcrt1qg2c2xdt3cwy93w5pvtga42c83xqmneakmantm2": { "f54f6c6a4bf6820b428ca5f6b7200545dacfd5e671e001c524888e502d007eaa:0": 207263913601 @@ -343,6 +368,14 @@ ] } }, + "7987fe64108796009704a9caa5c846c7c80809b029d26c7229759fff0992638a": { + "bcrt1qygktltj8sjkus96k0atzgr8srcy658axnpj82x": { + "0": [ + 207263913381, + false + ] + } + }, "7cb49f910a6db5d95436943456293aefbcccf0bbcc78b1dfe0adec430997fe07": { "bcrt1qq9hhwvvpf46wammythja454l04j32mmxezkevz": { "0": [ @@ -410,6 +443,7 @@ "IMPORTED": false, "INVALIDATED": false, "MEMPOOL": false, + "PARTIALLY_SIGNED": false, "PUSHED": true, "PUSH_FAIL": false, "REPLACED": false, @@ -453,6 +487,8 @@ 1000 ] }, + "sigs_have": 0, + "sigs_required": 0, "status": "New.Firmato.Pushed.Checked", "time": 1785536273.0165532, "tx": "0200000000010107fe970943ecade0dfb178ccbbf0ccbcef3a295634943654d9b56d0a919fb47c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc63851a4a0f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db92dbc3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbce4010db9100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402201ce57302608a734f3de1b0d3b0eca08efb78f0d886180e7adf4897bfe4b4a49302202120e6f4308cef294094fa347ae7369eb0b0bb9b1bb64c8f03f50b349f01d7e1012102c5e7ef3fc3902156154cd1a2238e80bc15f1bb253f3c973b3b87f9ed6fe1c7b7404f136c", @@ -495,6 +531,7 @@ "IMPORTED": false, "INVALIDATED": true, "MEMPOOL": false, + "PARTIALLY_SIGNED": false, "PUSHED": true, "PUSH_FAIL": false, "REPLACED": false, @@ -538,6 +575,8 @@ 1000 ] }, + "sigs_have": 0, + "sigs_required": 0, "status": "New.Firmato.Pushed.Checked.Invalidated", "time": 1785440069.9933782, "tx": "02000000000101aa7e002d508e8824c501e071e6d5cfda450520b7f6a58c420b82f64b6a6c4ff50000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc86851a4a0f0000001600147e19af296f25d092f23a0c208823e65c81a2af9dde2dbc3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0a020db9100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205f58eee70a433d110e24e618fb84baf245f480a41d12a788ed73484ef3766d0002200f0cbc2af37e101fc77b692eda8b560cdac3e0b488e0e9d42f80bdc46b00246e01210222328d50fb0839195043813aa0cba1d5ba0477071ebd68e33e57a70b4a54b10dc0cdd073", @@ -580,6 +619,7 @@ "IMPORTED": false, "INVALIDATED": false, "MEMPOOL": false, + "PARTIALLY_SIGNED": false, "PUSHED": true, "PUSH_FAIL": false, "REPLACED": true, @@ -623,6 +663,8 @@ 1000 ] }, + "sigs_have": 0, + "sigs_required": 0, "status": "New.Firmato.Pushed.Checked.Replaced", "time": 1785449918.3222272, "tx": "0200000000010107fe970943ecade0dfb178ccbbf0ccbcef3a295634943654d9b56d0a919fb47c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc63851a4a0f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db92dbc3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbce4010db9100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402200b71f679baef994767c58cea234f2a2baa83cc029f64dae4ff6fe451aaffc369022051b2a70fd5c33fcc175d6dca4b052b4a7bc168c761434139560bee66ed965b18012102c5e7ef3fc3902156154cd1a2238e80bc15f1bb253f3c973b3b87f9ed6fe1c7b7c0cdd073", @@ -665,6 +707,7 @@ "IMPORTED": false, "INVALIDATED": false, "MEMPOOL": false, + "PARTIALLY_SIGNED": false, "PUSHED": true, "PUSH_FAIL": false, "REPLACED": true, @@ -708,6 +751,8 @@ 1000 ] }, + "sigs_have": 0, + "sigs_required": 0, "status": "New.Firmato.Pushed.Checked.Replaced", "time": 1785529939.6807153, "tx": "0200000000010107fe970943ecade0dfb178ccbbf0ccbcef3a295634943654d9b56d0a919fb47c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc63851a4a0f0000001600147e19af296f25d092f23a0c208823e65c81a2af9db92dbc3e100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbce4010db9100000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402203e830a47f218826e2d1e4b708fdf4dc4a8826ac5c8a37a79fec5fbf1c6aa287002205df204067685f8874211ad092b0dad17a725f82a51e16f5c52db778135ecfbab012102c5e7ef3fc3902156154cd1a2238e80bc15f1bb253f3c973b3b87f9ed6fe1c7b740514d6c", diff --git a/tests/test_anticipate_manual_locktime.py b/tests/test_anticipate_manual_locktime.py index 75a6c53..9187870 100644 --- a/tests/test_anticipate_manual_locktime.py +++ b/tests/test_anticipate_manual_locktime.py @@ -33,7 +33,7 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -import pytest # noqa: E402 +import pytest # noqa: E402 # pyright: ignore[reportMissingImports] from bal.core.will import ( # noqa: E402 NotCompleteWillException, diff --git a/tests/test_core_heirs_extra.py b/tests/test_core_heirs_extra.py index 322278a..46d988d 100644 --- a/tests/test_core_heirs_extra.py +++ b/tests/test_core_heirs_extra.py @@ -140,6 +140,7 @@ def test_prepare_lists_mixed_dust_continues(): "dust": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", "1%", "30d"], }) raised = False + result = None try: result, _onlyfixed = h.prepare_lists(5200, 100, wallet) except HeirAmountIsDustException: @@ -166,6 +167,7 @@ def test_prepare_lists_multi_locktime_continues(): "late_valid": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", 5000, "60d"], }) raised = False + result = None try: result, _onlyfixed = h.prepare_lists(5200, 100, wallet) except HeirAmountIsDustException: diff --git a/tests/test_core_util.py b/tests/test_core_util.py index c58fd7d..979a2d6 100644 --- a/tests/test_core_util.py +++ b/tests/test_core_util.py @@ -14,7 +14,7 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -import pytest +import pytest # pyright: ignore[reportMissingImports] from bal.core.util import Util diff --git a/tests/test_group_e_karen7_invalidate.py b/tests/test_group_e_karen7_invalidate.py index 4fd9a3f..58359dc 100644 --- a/tests/test_group_e_karen7_invalidate.py +++ b/tests/test_group_e_karen7_invalidate.py @@ -26,7 +26,7 @@ import json import os import sys -import pytest +import pytest # pyright: ignore[reportMissingImports] # ------------------------------------------------------------------ # # Electrum regtest mode (replaces mocking bitcoin.is_address) diff --git a/tests/test_group_e_mock_karen7.py b/tests/test_group_e_mock_karen7.py index 00d86c3..22363ed 100644 --- a/tests/test_group_e_mock_karen7.py +++ b/tests/test_group_e_mock_karen7.py @@ -30,7 +30,7 @@ import time import traceback from unittest.mock import MagicMock, patch -import pytest +import pytest # pyright: ignore[reportMissingImports] # Make the plugin package importable when run directly (tests/ is one level # below the repo root that contains the ``bal`` package). @@ -274,7 +274,7 @@ def test_e2_karen7_add_remove_heir(): heirs["charlie"] = ["addr_charlie", "20000", "30d"] assert "charlie" in heirs - assert "charlie" in wallet.db.get("heirs", {}) + assert "charlie" in (wallet.db.get("heirs") or {}) removed = heirs.pop("alice") assert removed is not None @@ -622,6 +622,7 @@ def test_e5_build_with_real_wallet_heirs_and_utxos(): "bal.core.heirs.PartialTransaction.from_io", side_effect=_fake_from_io, ): + result = None try: result = h.buildTransactions( bal_plugin, wallet, tx_fees=1, utxos=utxos diff --git a/tests/test_group_f_heir_change_rebuild.py b/tests/test_group_f_heir_change_rebuild.py index ad45aac..f08f155 100644 --- a/tests/test_group_f_heir_change_rebuild.py +++ b/tests/test_group_f_heir_change_rebuild.py @@ -113,5 +113,5 @@ def test_same_heirs_empty(): if __name__ == "__main__": - import pytest + import pytest # pyright: ignore[reportMissingImports] raise SystemExit(pytest.main([__file__, "-v"])) diff --git a/tests/test_import_will_details.py b/tests/test_import_will_details.py index 2f6846b..7c9d5e6 100644 --- a/tests/test_import_will_details.py +++ b/tests/test_import_will_details.py @@ -444,6 +444,46 @@ def test_merge_will_new_ids_added_wholesale(): assert new_id in fake.will +def test_merge_will_missing_date_to_check_defaults_to_now(): + # Regression: merge_will read self.date_to_check (which is only set by + # init_class_variables) and crashed with AttributeError when merging a + # will file was the first action of a session. + wid = "missing_dtc" + imported_tx = _make_partial_tx(locktime=1000, signed=True) + imported = {wid: _make_willitem_with_tx(imported_tx, key=wid)} + fake, calls = _make_merge_fake({}) + del fake.date_to_check + + window_mod.BalWindow.merge_will(fake, imported) + + assert isinstance(fake.date_to_check, (int, float)), \ + "date_to_check must fall back to a timestamp" + assert wid in fake.will + assert "update_all" in calls + + +def test_merge_will_validity_error_logs_without_crashing(): + # Regression: the except handler passed log_error(e, self.bal_window); + # BalWindow has no such attribute, so a validity failure raised a second + # AttributeError that masked the original error. + from unittest.mock import patch + + wid = "validity_err" + imported_tx = _make_partial_tx(locktime=1000, signed=True) + imported = {wid: _make_willitem_with_tx(imported_tx, key=wid)} + fake, calls = _make_merge_fake({}) + fake.show_error = lambda msg: calls.append(("show_error", msg)) + + with patch.object( + window_mod.Util, "get_available_utxos", side_effect=RuntimeError("boom") + ): + window_mod.BalWindow.merge_will(fake, imported) + + assert any(c[0] == "show_error" for c in calls), \ + "the validity error must be surfaced via show_error" + assert "update_all" in calls, "merge must complete after the error" + + def test_merge_will_from_file_invalid_file_raises(): from bal.gui.qt.common import FileImportFailed diff --git a/tests/test_no_willexecutor_karen7.py b/tests/test_no_willexecutor_karen7.py index de37d43..1fd4792 100644 --- a/tests/test_no_willexecutor_karen7.py +++ b/tests/test_no_willexecutor_karen7.py @@ -35,11 +35,13 @@ import sys import time from unittest.mock import patch -import pytest +import pytest # pyright: ignore[reportMissingImports] from electrum import constants constants.net = constants.BitcoinRegtest +_VALID_REG_ADDR = "bcrt1q0567jspgutk84axs4l7sm04u86yjkzg27dv6fk" + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from electrum import bitcoin @@ -250,7 +252,11 @@ class FakeBalWindow: f = False for _u, w in self.willexecutors.items(): if Willexecutors.is_selected( - w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get() + w + ) and Willexecutors.is_valid( + w, + max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), + dust=self.wallet.dust_threshold(), ): f = True if not f: @@ -508,47 +514,70 @@ class TestNoWillexecutorKaren7: "selected": True, "base_fee": base_fee, "url": "https://we.example.com", + "address": self.wallet._CHANGE_ADDR, "sort": 0, } } self.bal_plugin._willexecutors.set({"regtest": we_data}) # ------------------------------------------------------------------ # - # E. is_selected with max_fee + # E. is_selected / is_valid semantics # ------------------------------------------------------------------ # - def test_is_selected_fee_below_max_is_selected(self): - """is_selected returns True when base_fee < max_fee.""" - we = {"selected": True, "base_fee": 1000} - assert Willexecutors.is_selected(we, max_fee=500000) is True - - def test_is_selected_fee_equal_max_is_not_selected(self): - """is_selected returns False when base_fee == max_fee.""" - we = {"selected": True, "base_fee": 500000} - assert Willexecutors.is_selected(we, max_fee=500000) is False - - def test_is_selected_fee_above_max_is_not_selected(self): - """is_selected returns False when base_fee > max_fee.""" + def test_is_selected_only_checks_selected_flag(self): + """is_selected ignores the fee entirely; it only reflects the + selected flag. Fee-range enforcement lives in is_valid.""" we = {"selected": True, "base_fee": 600000} - assert Willexecutors.is_selected(we, max_fee=500000) is False - - def test_is_selected_without_max_fee_ignores_fee(self): - """is_selected without max_fee only checks the selected flag.""" - we = {"selected": True, "base_fee": 500000} assert Willexecutors.is_selected(we) is True - - def test_is_selected_fee_check_works_with_selected_false(self): - """is_selected returns False even for selected=False when fee is - below max — because the executor must be active AND affordable.""" we = {"selected": False, "base_fee": 1000} - assert Willexecutors.is_selected(we, max_fee=500000) is False + assert Willexecutors.is_selected(we) is False - def test_is_selected_fee_too_high_still_raises(self): - """A selected will-executor with base_fee >= MAX_WILLEXECUTOR_FEE - is treated as NOT selected, so build_will still raises.""" + def test_is_selected_setter_sets_flag(self): + """is_selected(value) acts as a setter for the selected flag.""" + we = {"selected": False} + assert Willexecutors.is_selected(we, True) is True + assert we["selected"] is True + + def test_is_selected_missing_flag_defaults_to_false(self): + """A dict without the 'selected' key is treated as not selected.""" + assert Willexecutors.is_selected({}) is False + + def test_is_valid_fee_equal_max_is_valid(self): + """is_valid allows the boundary value base_fee == max_fee.""" + we = {"selected": True, "base_fee": 500000, + "address": _VALID_REG_ADDR} + assert Willexecutors.is_valid(we, max_fee=500000, dust=546) is True + + def test_is_valid_fee_above_max_is_invalid(self): + """is_valid rejects base_fee > max_fee.""" + we = {"selected": True, "base_fee": 600000, + "address": _VALID_REG_ADDR} + assert Willexecutors.is_valid(we, max_fee=500000, dust=546) is False + + def test_is_valid_fee_equal_dust_is_valid(self): + """is_valid allows the boundary value base_fee == dust.""" + we = {"selected": True, "base_fee": 546, + "address": _VALID_REG_ADDR} + assert Willexecutors.is_valid(we, max_fee=500000, dust=546) is True + + def test_is_valid_fee_below_dust_is_invalid(self): + """is_valid rejects base_fee < dust.""" + we = {"selected": True, "base_fee": 545, + "address": _VALID_REG_ADDR} + assert Willexecutors.is_valid(we, max_fee=500000, dust=546) is False + + def test_is_valid_requires_valid_address(self): + """is_valid rejects executors whose address is missing or invalid.""" + we = {"selected": True, "base_fee": 1000} + assert Willexecutors.is_valid(we, max_fee=500000, dust=546) is False + + def test_build_will_fee_above_max_still_raises(self): + """A selected will-executor whose fee is outside the valid range + (base_fee > MAX_WILLEXECUTOR_FEE) is not considered valid, so + build_will still raises NoWillExecutorNotPresent.""" self.bal_window.init_class_variables() - # Add a selected executor with fee >= max (500000) + # Add a selected executor with fee > max (500000) self._add_selected_willexecutor(base_fee=600000) with pytest.raises(NoWillExecutorNotPresent):