willexecutors: fee-bound is_valid (extremes allowed), is_selected flag-only; fix merge_will crash on missing date_to_check; DNSSEC async, ical folding, pyright/ruff cleanup; refresh karen7/samanta7 fixtures

This commit is contained in:
2026-08-01 22:08:53 -04:00
parent 06d61d78d0
commit 693479b0da
24 changed files with 272 additions and 204 deletions

7
.gitignore vendored
View File

@@ -18,6 +18,13 @@ node_modules/
*.swo *.swo
*.bak *.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 / scratch files
debug.py debug.py
init.ol init.ol

View File

@@ -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. ``w!ll3x3c"`` marker; they are skipped by most heir comparisons.
""" """
import asyncio
import inspect
import math import math
import random import random
import re import re
@@ -31,6 +33,7 @@ from typing import (
Dict, Dict,
Optional, Optional,
Tuple, Tuple,
cast,
) )
import dns import dns
@@ -65,6 +68,19 @@ if TYPE_CHECKING:
_logger = get_logger(__name__) _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 # 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 # wallet format and are relied upon all over the codebase, so they must NEVER
# be reordered. # be reordered.
@@ -520,8 +536,8 @@ class Heirs(dict, Logger):
) )
def prepare_lists( def prepare_lists(
self, balance, total_fees, wallet, willexecutor=False, from_locktime=0, self, balance, total_fees, wallet, willexecutor: Optional[dict] = None,
max_fee=None, from_locktime=0, max_fee=None,
): ):
if balance<total_fees or balance < wallet.dust_threshold(): if balance<total_fees or balance < wallet.dust_threshold():
raise BalanceTooLowException(balance,wallet.dust_threshold(),total_fees) raise BalanceTooLowException(balance,wallet.dust_threshold(),total_fees)
@@ -541,7 +557,7 @@ class Heirs(dict, Logger):
willexecutor, max_fee willexecutor, max_fee
) )
willexecutors_amount += base_fee willexecutors_amount += base_fee
h = [None] * 4 h: list = [None] * 4
h[HEIR_AMOUNT] = base_fee h[HEIR_AMOUNT] = base_fee
h[HEIR_REAL_AMOUNT] = base_fee h[HEIR_REAL_AMOUNT] = base_fee
h[HEIR_LOCKTIME] = locktime h[HEIR_LOCKTIME] = locktime
@@ -661,7 +677,7 @@ class Heirs(dict, Logger):
self.decimal_point = bal_plugin.get_decimal_point() self.decimal_point = bal_plugin.get_decimal_point()
no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get() no_willexecutors = bal_plugin.NO_WILLEXECUTOR.get()
for utxo in utxos: for utxo in utxos:
if utxo.value_sats() > 0 * tx_fees: if utxo.value_sats() > 0:
balance += utxo.value_sats() balance += utxo.value_sats()
len_utxo_set += 1 len_utxo_set += 1
available_utxos.append(utxo) available_utxos.append(utxo)
@@ -685,11 +701,12 @@ class Heirs(dict, Logger):
elif j == -1: elif j == -1:
if not no_willexecutors: if not no_willexecutors:
continue continue
url = willexecutor = False url = willexecutor = None
else: else:
break break
fees = {} fees = {}
i = 0 i = 0
txs = {}
while i < 10: while i < 10:
txs = {} txs = {}
redo = False redo = False
@@ -829,7 +846,7 @@ class Heirs(dict, Logger):
# support email-style addresses, per the OA standard # support email-style addresses, per the OA standard
url = url.replace("@", ".") url = url.replace("@", ".")
try: try:
records, validated = dnssec.query(url, dns.rdatatype.TXT) records, validated = _query_txt_records(url)
except DNSException as e: except DNSException as e:
_logger.info(f"Error resolving openalias: {repr(e)}") _logger.info(f"Error resolving openalias: {repr(e)}")
return None return None
@@ -838,15 +855,15 @@ class Heirs(dict, Logger):
string = to_string(record.strings[0], "utf8") string = to_string(record.strings[0], "utf8")
if string.startswith("oa1:" + prefix): if string.startswith("oa1:" + prefix):
address = cls.find_regex(string, r"recipient_address=([A-Za-z0-9]+)") address = cls.find_regex(string, r"recipient_address=([A-Za-z0-9]+)")
if not address:
continue
name = cls.find_regex(string, r"recipient_name=([^;]+)") name = cls.find_regex(string, r"recipient_name=([^;]+)")
if not name: if not name:
name = address name = address
if not address:
continue
return address, name, validated return address, name, validated
@staticmethod @staticmethod
def find_regex(haystack, needle): def find_regex(haystack, needle) -> Optional[str]:
regex = re.compile(needle) regex = re.compile(needle)
try: try:
return regex.search(haystack).groups()[0] return regex.search(haystack).groups()[0]

View File

@@ -108,7 +108,7 @@ def get_will(x):
try: try:
# Electrum >= 4.8.0 # 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): def _register_will_dict(name, method, _type=None):
"""Register a plugin dict in the wallet DB (Electrum >= 4.8.0 API).""" """Register a plugin dict in the wallet DB (Electrum >= 4.8.0 API)."""
@@ -118,7 +118,7 @@ except ImportError:
# Electrum <= 4.7.2 # Electrum <= 4.7.2
def _register_will_dict(name, method, _type=None): def _register_will_dict(name, method, _type=None):
"""Register a plugin dict in the wallet DB (Electrum <= 4.7.2 API).""" """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) _register_will_dict("heirs", tuple)
@@ -405,7 +405,7 @@ class BalPlugin(BasePlugin):
"""Fill in any missing will-setting with its default value.""" """Fill in any missing will-setting with its default value."""
defaults = BalPlugin.default_will_settings() defaults = BalPlugin.default_will_settings()
if not will_settings: if not will_settings:
will_settings = [] will_settings = {}
if int(will_settings.get("baltx_fees", 0)) < 1: if int(will_settings.get("baltx_fees", 0)) < 1:
will_settings["baltx_fees"] = defaults['baltx_fees'] will_settings["baltx_fees"] = defaults['baltx_fees']
if not will_settings.get("threshold"): if not will_settings.get("threshold"):
@@ -427,7 +427,7 @@ class BalPlugin(BasePlugin):
@staticmethod @staticmethod
def default_will_settings(): def default_will_settings():
"""Default will settings: a fee rate plus absolute threshold/locktime.""" """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()) will_settings.update(BalPlugin.default_will_settings_absolute())
return will_settings return will_settings
@@ -460,10 +460,12 @@ class BalTimestamp:
* an integer -> an absolute UNIX timestamp (``unit is None``) * an integer -> an absolute UNIX timestamp (``unit is None``)
""" """
value = None value: int
unit = None unit: str | None
def __init__(self, value): def __init__(self, value):
self.value = 1
self.unit = None
str_value = str(value) str_value = str(value)
if str_value and str_value[-1].lower() in ("y", "d"): if str_value and str_value[-1].lower() in ("y", "d"):
self.value = int(str_value[:-1]) self.value = int(str_value[:-1])

View File

@@ -443,11 +443,12 @@ class Will:
for wid in will: for wid in will:
wtx = will[wid].tx wtx = will[wid].tx
found = False found = False
inp = None
for inp in wtx.inputs(): for inp in wtx.inputs():
if inp.prevout.txid.hex() in will: if inp.prevout.txid.hex() in will:
found = True found = True
break break
if not found: if not found and inp is not None:
out[inp.prevout.to_str()] = inp out[inp.prevout.to_str()] = inp
return out return out

View File

@@ -17,6 +17,7 @@ interaction is handled by the Qt layer.
import json import json
import time import time
from datetime import datetime from datetime import datetime
from typing import Any
from aiohttp import ClientResponse from aiohttp import ClientResponse
from electrum import bitcoin, constants from electrum import bitcoin, constants
@@ -154,7 +155,7 @@ class Willexecutors:
@staticmethod @staticmethod
def get_willexecutors( 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 = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(chainname, {}) willexecutors = willexecutors.get(chainname, {})
@@ -205,15 +206,11 @@ class Willexecutors:
return w_sorted return w_sorted
@staticmethod @staticmethod
def is_selected(willexecutor, value=None, max_fee=None): def is_selected(willexecutor, value=None):
if not willexecutor: if not willexecutor:
return False return False
if value is not None: if value is not None:
willexecutor["selected"] = value 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: try:
return willexecutor["selected"] return willexecutor["selected"]
except Exception: except Exception:
@@ -228,9 +225,9 @@ class Willexecutors:
if not address or not bitcoin.is_address(address, net=constants.net): if not address or not bitcoin.is_address(address, net=constants.net):
return False return False
base_fee = int(willexecutor.get("base_fee", 0)) 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 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 False
return True return True
@@ -426,7 +423,7 @@ class Willexecutors:
else: else:
base_fee = w.get("base_fee") base_fee = w.get("base_fee")
try: try:
base_fee = int(base_fee) base_fee = int(base_fee or 0)
if base_fee < 0: if base_fee < 0:
raise ValueError("negative fee") raise ValueError("negative fee")
if base_fee > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN: if base_fee > TOTAL_COIN_SUPPLY_LIMIT_IN_BTC * COIN:

View File

@@ -194,30 +194,15 @@ class BalCalendar:
@staticmethod @staticmethod
def ical_escape(text: str) -> str: def ical_escape(text: str) -> str:
# escape per RFC5545: backslash, ; , newlines # escape per RFC5545: backslash, ; , newlines
text = text.encode("utf-8")
text = ( text = (
text.replace(b"\\", b"\\\\") text.replace("\\", "\\\\")
.replace(b";", b"\\;") .replace(";", "\\;")
.replace(b",", b"\\,") .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 @staticmethod
def fold_ical_line(line: str, limit: int = 75) -> str: def fold_ical_line(line: str, limit: int = 75) -> str:

View File

@@ -17,6 +17,8 @@ the few list classes they reference are imported lazily inside the methods that
use them (see ``lists`` imports below). use them (see ``lists`` imports below).
""" """
from typing import TYPE_CHECKING
from .calendar import BalCalendar, BalCalendarButton from .calendar import BalCalendar, BalCalendarButton
from .common import * from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *" from .common import _, _logger # underscore names are not re-exported by "import *"
@@ -27,6 +29,9 @@ from .widgets import (
compute_reminder_offsets, compute_reminder_offsets,
) )
if TYPE_CHECKING:
from .window import BalWindow
# NOTE: list views (HeirListWidget, PreviewList, WillExecutorWidget) are # NOTE: list views (HeirListWidget, PreviewList, WillExecutorWidget) are
# imported lazily where needed to avoid a dialogs<->lists import cycle. # imported lazily where needed to avoid a dialogs<->lists import cycle.
@@ -467,7 +472,6 @@ class BalWaitingDialog(BalDialog):
def exe(self): def exe(self):
self.thread = TaskThread(self) self.thread = TaskThread(self)
self.thread.finished.connect(self.deleteLater) # see #3956 self.thread.finished.connect(self.deleteLater) # see #3956
self.thread.finished.connect(self.finished)
self.thread.add(self.task, self.on_success, self.accept, self.on_error) self.thread.add(self.task, self.on_success, self.accept, self.on_error)
# IMPORTANT: keep the *application-modal* exec() of the original code. # IMPORTANT: keep the *application-modal* exec() of the original code.
# This dialog is driven by a TaskThread whose result (on_success, e.g. # This dialog is driven by a TaskThread whose result (on_success, e.g.
@@ -482,9 +486,6 @@ class BalWaitingDialog(BalDialog):
def hello(self): def hello(self):
pass pass
def finished(self):
pass
def on_accepted(self): def on_accepted(self):
pass pass
@@ -1147,7 +1148,6 @@ class BalBuildWillDialog(BalDialog):
for url, we in willexecutors.items() for url, we in willexecutors.items()
if Willexecutors.is_selected( if Willexecutors.is_selected(
self.bal_window.willexecutors.get(url), self.bal_window.willexecutors.get(url),
max_fee=self.bal_window.bal_plugin.MAX_WILLEXECUTOR_FEE.get(),
) and Willexecutors.is_valid( ) and Willexecutors.is_valid(
self.bal_window.willexecutors.get(url), self.bal_window.willexecutors.get(url),
max_fee=self.bal_window.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), max_fee=self.bal_window.bal_plugin.MAX_WILLEXECUTOR_FEE.get(),

View File

@@ -14,6 +14,8 @@ construction) for all business actions, so the heavy logic stays in ``window``
and ``dialogs``. and ``dialogs``.
""" """
from typing import TYPE_CHECKING
from PyQt6.QtWidgets import QLineEdit as _QLineEdit from PyQt6.QtWidgets import QLineEdit as _QLineEdit
from PyQt6.QtWidgets import QMessageBox, QStyledItemDelegate 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 .dialogs import BalBuildWillDialog, BalDialog
from .widgets import BalCheckBox, WillSettingsWidget from .widgets import BalCheckBox, WillSettingsWidget
if TYPE_CHECKING:
from .window import BalWindow
def _can_sign(will_item): def _can_sign(will_item):
"""True if a will transaction may still be signed (not fully signed).""" """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) self.bal_window.new_heir_dialog(edit_key)
def on_edited(self, idx, edit_key, *, text): 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: if not prior_name:
return return
col = idx.column() col = idx.column()
@@ -148,12 +153,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
try: try:
self.bal_window.set_heir(prior_name) self.bal_window.set_heir(prior_name)
except Exception: except Exception:
pass self.update()
try:
self.bal_window.set_heir((edit_key,) + original)
except Exception:
self.update()
def delete_heirs(self, selected_keys): def delete_heirs(self, selected_keys):
self.bal_window.delete_heirs(selected_keys) self.bal_window.delete_heirs(selected_keys)
@@ -938,6 +938,7 @@ class WillExecutorListWidget(MyTreeView):
idx = self.indexAt(position) idx = self.indexAt(position)
column = idx.column() or self.Columns.URL column = idx.column() or self.Columns.URL
selected_keys = [] selected_keys = []
sel_key = None
for s_idx in self.selected_in_column(self.Columns.URL): for s_idx in self.selected_in_column(self.Columns.URL):
item = self.model().itemFromIndex(s_idx) item = self.model().itemFromIndex(s_idx)
# Use the FULL url stored in the key role, NOT item.data(0): the # Use the FULL url stored in the key role, NOT item.data(0): the
@@ -1079,7 +1080,7 @@ class WillExecutorListWidget(MyTreeView):
# are shown unchanged. # are shown unchanged.
display_url = url if len(url) <= 40 else url[:37] + "\u2026" display_url = url if len(url) <= 40 else url[:37] + "\u2026"
labels[self.Columns.URL] = display_url labels[self.Columns.URL] = display_url
if Willexecutors.is_selected(value, max_fee=float("inf")): if Willexecutors.is_selected(value):
labels[self.Columns.SELECTED] = [ labels[self.Columns.SELECTED] = [
read_QIcon_from_bytes( read_QIcon_from_bytes(
@@ -1368,6 +1369,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin):
add_another_btn.clicked.connect(add_another) add_another_btn.clicked.connect(add_another)
else: else:
self._add_another = False self._add_another = False
add_another_btn = None
row = 0 row = 0
grid.addWidget(QLabel(_("URL")), row, 0) grid.addWidget(QLabel(_("URL")), row, 0)

View File

@@ -18,10 +18,15 @@ Contents:
* WillWidget - single will-tx box * WillWidget - single will-tx box
""" """
from typing import TYPE_CHECKING
from .calendar import BalCalendar, BalCalendarButton from .calendar import BalCalendar, BalCalendarButton
from .common import * from .common import *
from .common import _, _logger # underscore names are not re-exported by "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): def compute_reminder_offsets(days, count):
"""Return the reminder offsets (in days BEFORE the deadline) for an .ics event. """Return the reminder offsets (in days BEFORE the deadline) for an .ics event.

View File

@@ -385,7 +385,7 @@ class BalWindow:
f = False f = False
for _u, w in self.willexecutors.items(): for _u, w in self.willexecutors.items():
if Willexecutors.is_selected( if Willexecutors.is_selected(
w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get() w
) and Willexecutors.is_valid( ) and Willexecutors.is_valid(
w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), w, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(),
dust=self.window.wallet.dust_threshold() dust=self.window.wallet.dust_threshold()
@@ -521,8 +521,8 @@ class BalWindow:
def show_message(self, text): def show_message(self, text):
self.window.show_message(text) self.window.show_message(text)
def show_warning(self, text, parent=None): def show_warning(self, text, parent=None, title=None):
self.window.show_warning(text, parent=None) self.window.show_warning(text, parent=parent, title=title)
def show_error(self, text): def show_error(self, text):
self.window.show_error(text) self.window.show_error(text)
@@ -703,7 +703,7 @@ class BalWindow:
f = False f = False
for _k, we in self.willexecutors.items(): for _k, we in self.willexecutors.items():
if Willexecutors.is_selected( if Willexecutors.is_selected(
we, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get() we
) and Willexecutors.is_valid( ) and Willexecutors.is_valid(
we, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(), we, max_fee=self.bal_plugin.MAX_WILLEXECUTOR_FEE.get(),
dust=self.window.wallet.dust_threshold() dust=self.window.wallet.dust_threshold()
@@ -903,7 +903,7 @@ class BalWindow:
self.show_message(_("No transactions to invalidate")) self.show_message(_("No transactions to invalidate"))
def on_failure(exec_info): 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 willitems = will if will is not None else self.willitems
fee_per_byte = self.will_settings.get("baltx_fees", 1) fee_per_byte = self.will_settings.get("baltx_fees", 1)
@@ -1080,7 +1080,7 @@ class BalWindow:
raise e raise e
def on_failure(exec_info): def on_failure(exec_info):
log_error(exec_info, self.bal_window) log_error(exec_info, self)
password = self.get_wallet_password() password = self.get_wallet_password()
task = partial(self.sign_transactions, password, will=will, txids=txids) task = partial(self.sign_transactions, password, will=will, txids=txids)
@@ -1106,7 +1106,7 @@ class BalWindow:
) )
def on_failure(exec_info): def on_failure(exec_info):
log_error(exec_info, self.bal_window) log_error(exec_info, self)
# a,b,c = err # a,b,c = err
# _logger.error(f"fail to broadcast transactions:{err}") # _logger.error(f"fail to broadcast transactions:{err}")
# _logger.error(f"error: {b}") # _logger.error(f"error: {b}")
@@ -1246,6 +1246,13 @@ class BalWindow:
valid/invalidated/replaced statuses (no server contact, no expiry valid/invalidated/replaced statuses (no server contact, no expiry
raise). 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(): for wid, wi in imported.items():
if wid in self.willitems: if wid in self.willitems:
live = self.willitems[wid] live = self.willitems[wid]
@@ -1298,7 +1305,7 @@ class BalWindow:
) )
Will.check_signatures(self.willitems, self.wallet) Will.check_signatures(self.willitems, self.wallet)
except Exception as e: except Exception as e:
log_error(e, self.bal_window) log_error(e, self)
self.save_willitems() self.save_willitems()
self.update_all() self.update_all()
@@ -1726,14 +1733,15 @@ class BalWindow:
Willexecutors.ping_servers_parallel(wes, on_each=on_each, on_tick=on_tick) 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): 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): def on_success(result):
fn_on_success(result) fn_on_success(result)
def on_failure(exec_info): def on_failure(exec_info):
fn_on_failure(exec_info) fn_on_failure(exec_info)
if not fn_on_failure:
fn_on_failure = log_error
_logger.info("ping willexecutors") _logger.info("ping willexecutors")
task = partial(self.ping_willexecutors_task, wes) task = partial(self.ping_willexecutors_task, wes)
msg = _("Ping Will-Executors") msg = _("Ping Will-Executors")

View File

@@ -1,4 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"lsp": true
}

42
package-lock.json generated
View File

@@ -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"
}
}
}
}

View File

@@ -1,5 +0,0 @@
{
"dependencies": {
"pyright": "^1.1.411"
}
}

View File

@@ -1,6 +0,0 @@
{
"python.analysis": {
"extraPaths": ["../electrum"]
}
}

View File

@@ -3187,7 +3187,9 @@
"lightning_preimages": {}, "lightning_preimages": {},
"lightning_xprv": "vprv9HMaVA1cGK7XCUCiTrrdD9kGgiSeiwpqDDYfAafTZoSXyZtmQYnQ4CUvsZggS4fWrF3kve47MFWjrWLJ6t4uXjzs2XagtmBeUqoaRRFpJGF", "lightning_xprv": "vprv9HMaVA1cGK7XCUCiTrrdD9kGgiSeiwpqDDYfAafTZoSXyZtmQYnQ4CUvsZggS4fWrF3kve47MFWjrWLJ6t4uXjzs2XagtmBeUqoaRRFpJGF",
"notes_text": "", "notes_text": "",
"num_parents": {}, "num_parents": {
"2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": 4
},
"onchain_channel_backups": {}, "onchain_channel_backups": {},
"payment_requests": { "payment_requests": {
"6e7ca1969b": { "6e7ca1969b": {
@@ -3260,7 +3262,6 @@
"a001edc1d43b5b41adc5a4c5ce9b6edd9dcad9fa3e50cca287d256a23cca9d4e:0": 50000, "a001edc1d43b5b41adc5a4c5ce9b6edd9dcad9fa3e50cca287d256a23cca9d4e:0": 50000,
"a1aee934c5dda700d15934667507f90db1ce36d7dd3068165b54159df320cd02:0": 50000, "a1aee934c5dda700d15934667507f90db1ce36d7dd3068165b54159df320cd02:0": 50000,
"aa407bf21c31fa3ebe8bf074767bfec17df01644da94b3b709c3d3551f3210ca:0": 50000, "aa407bf21c31fa3ebe8bf074767bfec17df01644da94b3b709c3d3551f3210ca:0": 50000,
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:0": 1000,
"b28b20fb9089f2d021d812dc34ce461fdcd800137434edaa09160a395c182c8c:0": 50000, "b28b20fb9089f2d021d812dc34ce461fdcd800137434edaa09160a395c182c8c:0": 50000,
"b6e993fb2b0584bb35b8eefcfbe6ba7c9b9b7875e69389063c96f78dd1fca9e1:0": 50000, "b6e993fb2b0584bb35b8eefcfbe6ba7c9b9b7875e69389063c96f78dd1fca9e1:0": 50000,
"bdaa76d37810e4d4ad1d2e7ac6e198ad96c48683e552e874901a7af3d8ae4e50:0": 50000, "bdaa76d37810e4d4ad1d2e7ac6e198ad96c48683e552e874901a7af3d8ae4e50:0": 50000,
@@ -3718,9 +3719,6 @@
"5fb4dbd05379842b11a048ee73467fff68ef04500bf59e0e1a0388474b0e259a": { "5fb4dbd05379842b11a048ee73467fff68ef04500bf59e0e1a0388474b0e259a": {
"af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e:0": 1250000000 "af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e:0": 1250000000
}, },
"61d6068a9000df4a2d0c919d0845ceb7b9f690e609fc27238e38facecbe38d31": {
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:2": 316818514
},
"6232f0790f4bc993b8bd042828a1f626b0ae3ac20b1dcf5a479be57c1035de29": { "6232f0790f4bc993b8bd042828a1f626b0ae3ac20b1dcf5a479be57c1035de29": {
"316b0ac8e6516b59633a46dd5f4fa19da180f799f871a57085f6a7e99bb561e7:0": 1250000000 "316b0ac8e6516b59633a46dd5f4fa19da180f799f871a57085f6a7e99bb561e7:0": 1250000000
}, },
@@ -3811,11 +3809,6 @@
"8cebd66ae223c28c1db0fbd9638668ae9a28c757b164c97e93a22c9b2ef9fb8c:0": 610351, "8cebd66ae223c28c1db0fbd9638668ae9a28c757b164c97e93a22c9b2ef9fb8c:0": 610351,
"b3e3d7cba32180e7a72a2e66126d6b2bee9e386788ee099c5f032efaa8772fe3:0": 751351 "b3e3d7cba32180e7a72a2e66126d6b2bee9e386788ee099c5f032efaa8772fe3:0": 751351
}, },
"7d1852bfc82939c14ac37a01ad558273fcdc304c50eb3dc87272643bb2ce36b6": {
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:1": 40001,
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:3": 336619671,
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91:4": 346520250
},
"8017a32067302648ad152b7a87fdb1c2c4f19c174a5240e88666ba987063163c": { "8017a32067302648ad152b7a87fdb1c2c4f19c174a5240e88666ba987063163c": {
"1d30124dd0acb5c11a2cb8e607c50b5f402a7be178cafb264747fb0dab37c8cd:0": 1250000000 "1d30124dd0acb5c11a2cb8e607c50b5f402a7be178cafb264747fb0dab37c8cd:0": 1250000000
}, },
@@ -4387,9 +4380,6 @@
"2af047c4a003fb7b90ce2c8fc49bcdb3eaee0fff213b14dc1645b9fb18b8f0f6": { "2af047c4a003fb7b90ce2c8fc49bcdb3eaee0fff213b14dc1645b9fb18b8f0f6": {
"0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308" "0": "d2d953e950d28c021a5ddf6f77cfb9551af2fd33ccc7473b20a31d208e7bd308"
}, },
"2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c": {
"0": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91"
},
"2c6d3a41eb0f5589e2586138b2bee1cf453144fe48dc73c2a3b0f4d0a3bfd596": { "2c6d3a41eb0f5589e2586138b2bee1cf453144fe48dc73c2a3b0f4d0a3bfd596": {
"0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15" "0": "5c6d7a19cfb35a98949a25c5fede1dd798a7e1509e0067d000319126c34f8c15"
}, },
@@ -5179,7 +5169,7 @@
"2": "8cebb4ea3a63829a2787c3a473f14b4f73ced07552f73ff8c2b1152169042550" "2": "8cebb4ea3a63829a2787c3a473f14b4f73ced07552f73ff8c2b1152169042550"
} }
}, },
"stored_height": 2336, "stored_height": 2381,
"submarine_swaps": {}, "submarine_swaps": {},
"transactions": { "transactions": {
"00fda0d8fdc53b1f95410bafca884c5dff8ac1e5c168ace5f09a32d872b7413a": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402460100ffffffff02807c814a00000000160014b51c529851d6140f1a37f2aa46bafe171a549f210000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "00fda0d8fdc53b1f95410bafca884c5dff8ac1e5c168ace5f09a32d872b7413a": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402460100ffffffff02807c814a00000000160014b51c529851d6140f1a37f2aa46bafe171a549f210000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
@@ -5360,7 +5350,6 @@
"ab1012eb232c0070b566b6620a8b839e8fa78831cf8216337c1db71e35b89953": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402d70100ffffffff0240be402500000000160014ddc0da8880db383448d588868cd34ab79779dd7c0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "ab1012eb232c0070b566b6620a8b839e8fa78831cf8216337c1db71e35b89953": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402d70100ffffffff0240be402500000000160014ddc0da8880db383448d588868cd34ab79779dd7c0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"ab74079fca87f186df5676c62de63d8b21e98746c8c8888f108310bc7849e70e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04021e0100ffffffff0200f9029500000000160014178787a67beeadb99e36f6c5513fe8e2b02e800b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "ab74079fca87f186df5676c62de63d8b21e98746c8c8888f108310bc7849e70e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04021e0100ffffffff0200f9029500000000160014178787a67beeadb99e36f6c5513fe8e2b02e800b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"ad3f3d67b74bb4b9b4457f1ccb99ea3dad02ad24c0f4d2383ff1fd12f7c0a684": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402f20200ffffffff02902f500900000000160014ea779f0f8e203034ae25cd71e52c1a81ac488e2b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "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", "ae8e62c0ec616466782a4f861d108ad06e593af6c8a4fe3d2c9ca1386fd559bd": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402200100ffffffff0200f9029500000000160014d2b8a4b410689315e81b354969e36584451f85530000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04023a0100ffffffff02807c814a00000000160014b2da19ab366b4a2f09d6dd233192d9339444ca980000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", "af9e9fbb8e65d724bbf483ed4b7577ff183b0e8ddba91ca40456d43b3135f81e": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04023a0100ffffffff02807c814a00000000160014b2da19ab366b4a2f09d6dd233192d9339444ca980000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"b00e9f65ca32f0ab7a05666f21a78b461623c43d1297a731f09a807f2c9dc497": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402850300ffffffff02aa19a8040000000016001439690f010cb6ee0ab0180e01d1a9f6b3417af06b0000000000000000266a24aa21a9edb6b7b0b5bded40786dbf759cf9af7726000596503854971f2cc19c3fed02f4a10120000000000000000000000000000000000000000000000000000000000000000000000000", "b00e9f65ca32f0ab7a05666f21a78b461623c43d1297a731f09a807f2c9dc497": "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402850300ffffffff02aa19a8040000000016001439690f010cb6ee0ab0180e01d1a9f6b3417af06b0000000000000000266a24aa21a9edb6b7b0b5bded40786dbf759cf9af7726000596503854971f2cc19c3fed02f4a10120000000000000000000000000000000000000000000000000000000000000000000000000",
@@ -6352,11 +6341,6 @@
false, false,
1 1
], ],
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": [
234,
true,
1
],
"ae8e62c0ec616466782a4f861d108ad06e593af6c8a4fe3d2c9ca1386fd559bd": [ "ae8e62c0ec616466782a4f861d108ad06e593af6c8a4fe3d2c9ca1386fd559bd": [
null, null,
false, false,
@@ -7395,11 +7379,6 @@
"88a56990a69eb1a7c11595b7a88097d799fb6070bb2f77ea34e7f2fe8d54d867:1": 100000 "88a56990a69eb1a7c11595b7a88097d799fb6070bb2f77ea34e7f2fe8d54d867:1": 100000
} }
}, },
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91": {
"bcrt1q0ma5z02ma7q0cq7j49wz66clf69xw3fq2zl05n": {
"2c659529455fdacab80781dab351553428e85f2a491866b0a8274dbedaff4f4c:0": 999999670
}
},
"b020ccbf483abab08d6553b7fec974ce9f8ab43654863757e3ec7694c399f86c": { "b020ccbf483abab08d6553b7fec974ce9f8ab43654863757e3ec7694c399f86c": {
"bcrt1qazle627r46apscly8lj4q5cxfxgrtuew879jde": { "bcrt1qazle627r46apscly8lj4q5cxfxgrtuew879jde": {
"335d73d9f9295b283a07187652a587830eb1eb2ee2829ef566dde8445946e474:1": 68897247707 "335d73d9f9295b283a07187652a587830eb1eb2ee2829ef566dde8445946e474:1": 68897247707
@@ -13151,21 +13130,21 @@
"BROADCASTED": false, "BROADCASTED": false,
"CHECKED": false, "CHECKED": false,
"CHECK_FAIL": false, "CHECK_FAIL": false,
"COMPLETE": false, "COMPLETE": true,
"CONFIRMED": false, "CONFIRMED": false,
"ERROR": false, "ERROR": false,
"EXPIRED": false, "EXPIRED": false,
"EXPORTED": false, "EXPORTED": false,
"IMPORTED": false, "IMPORTED": false,
"INVALIDATED": true, "INVALIDATED": false,
"MEMPOOL": false, "MEMPOOL": false,
"PARTIALLY_SIGNED": false, "PARTIALLY_SIGNED": false,
"PUSHED": false, "PUSHED": true,
"PUSH_FAIL": false, "PUSH_FAIL": false,
"REPLACED": false, "REPLACED": false,
"RESTORED": false, "RESTORED": false,
"UPDATED": false, "UPDATED": false,
"VALID": false, "VALID": true,
"_id": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91", "_id": "adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91",
"baltx_fees": 1, "baltx_fees": 1,
"change": null, "change": null,
@@ -13205,25 +13184,30 @@
}, },
"sigs_have": 0, "sigs_have": 0,
"sigs_required": 1, "sigs_required": 1,
"status": "New.Invalidated", "status": "New.Firmato.Pushed",
"time": 1785562346.7776835, "time": 1785620332.2813325,
"tx": "cHNidP8BAM4CAAAAAUxP/9q+TSeosGYYSSpf6Cg0VVGz2oEHuMraX0UplWUsAAAAAAD9////BegDAAAAAAAAFgAUHJ2Hucc2GI5sonbAeh3TaBHHeApBnAAAAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8UkTiEgAAAAAWABR+Ga8pbyXQkvI6DCCII+ZcgaKvnZdoEBQAAAAAFgAUjh/DmWHFR4f917XPPlnAlblzT7y6eqcUAAAAABYAFI4fw5lhxUeH/de1zz5ZwJW5c0+8wKJObAABAR+2yJo7AAAAABYAFH77QT1b74D8A9KpXC1rH06KZ0UgAQC/AgAAAAABAd8W8LQ4gBPr4BMdIIFgxnlK/0pm2Hv7VRK0tdMKsspOAAAAAAD9////AbbImjsAAAAAFgAUfvtBPVvvgPwD0qlcLWsfTopnRSACRzBEAiBiDWmocrmu+1Hip22YEAiBWHdkSIwuaYOqolHI5yjlJwIgaVVzZF7VDoudB+M8a9n375i5hXF+w+RnR2RYs+oKnUgBIQMAYJEwf6yH90elPJ15yFBJZBFMuZDDwEIcyvu85pUc5uYIAAAiBgMlkmiTZPOkeW/DWh8YN5HrfF2yXm82+g8ekmVO1nnHkRBZSzQGAAAAgAEAAAARAAAAAAAAAAAA", "tx": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c",
"willexecutor": { "willexecutor": {
"address": "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk", "address": "bcrt1qrjwc0ww8xcvgum9zwmq858wndqguw7q27ek3sk",
"balance": 90000, "balance": 90000,
"base_fee": 1000, "base_fee": 1000,
"broadcast_status": "Riuscito",
"chain": "regtest", "chain": "regtest",
"count_win": 0, "count_win": 0,
"id": 66, "id": 66,
"info": "BAL devel willexecutor server", "info": "BAL devel willexecutor server",
"last_block": 0, "last_block": 0,
"last_update": 1785557736.334134, "last_update": 1785592563.693952,
"onion_url": null, "onion_url": null,
"points": 0, "points": 0,
"promo_code": null, "promo_code": null,
"selected": true, "selected": true,
"status": 200, "status": 200,
"tld": "localhost", "tld": "localhost",
"txs": "020000000001014c4fffdabe4d27a8b06618492a5fe828345551b3da8107b8cada5f452995652c0000000000fdffffff05e8030000000000001600141c9d87b9c736188e6ca276c07a1dd36811c7780a419c0000000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc5244e212000000001600147e19af296f25d092f23a0c208823e65c81a2af9d97681014000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbcba7aa714000000001600148e1fc39961c54787fdd7b5cf3e59c095b9734fbc0247304402205b777b06e8392136b30177d8a353bde7238a5aa5235eaf973a298c0eb974b4750220783b0b1c7093c93a2aee94829c6e0f6288cecd9e8eba94c61b9c54c2c84f4d2c0121032592689364f3a4796fc35a1f183791eb7c5db25e6f36fa0f1e92654ed679c791c0a24e6c\n",
"txsids": [
"adb13f7887359732c227e0d0523090de7d6cef7e7c73fb3b0a8b67844efbcd91"
],
"unconfirmed_balance": 0, "unconfirmed_balance": 0,
"url": "http://localhost:9133", "url": "http://localhost:9133",
"version": "0.3.2" "version": "0.3.2"

File diff suppressed because one or more lines are too long

View File

@@ -33,7 +33,7 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) 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 from bal.core.will import ( # noqa: E402
NotCompleteWillException, NotCompleteWillException,

View File

@@ -140,6 +140,7 @@ def test_prepare_lists_mixed_dust_continues():
"dust": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", "1%", "30d"], "dust": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", "1%", "30d"],
}) })
raised = False raised = False
result = None
try: try:
result, _onlyfixed = h.prepare_lists(5200, 100, wallet) result, _onlyfixed = h.prepare_lists(5200, 100, wallet)
except HeirAmountIsDustException: except HeirAmountIsDustException:
@@ -166,6 +167,7 @@ def test_prepare_lists_multi_locktime_continues():
"late_valid": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", 5000, "60d"], "late_valid": ["bcrt1q08z5t4x74u2883sx2qwsmzk2hj8e5n7z83e4vy", 5000, "60d"],
}) })
raised = False raised = False
result = None
try: try:
result, _onlyfixed = h.prepare_lists(5200, 100, wallet) result, _onlyfixed = h.prepare_lists(5200, 100, wallet)
except HeirAmountIsDustException: except HeirAmountIsDustException:

View File

@@ -14,7 +14,7 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) 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 from bal.core.util import Util

View File

@@ -26,7 +26,7 @@ import json
import os import os
import sys import sys
import pytest import pytest # pyright: ignore[reportMissingImports]
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# Electrum regtest mode (replaces mocking bitcoin.is_address) # Electrum regtest mode (replaces mocking bitcoin.is_address)

View File

@@ -30,7 +30,7 @@ import time
import traceback import traceback
from unittest.mock import MagicMock, patch 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 # Make the plugin package importable when run directly (tests/ is one level
# below the repo root that contains the ``bal`` package). # 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"] heirs["charlie"] = ["addr_charlie", "20000", "30d"]
assert "charlie" in heirs assert "charlie" in heirs
assert "charlie" in wallet.db.get("heirs", {}) assert "charlie" in (wallet.db.get("heirs") or {})
removed = heirs.pop("alice") removed = heirs.pop("alice")
assert removed is not None 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", "bal.core.heirs.PartialTransaction.from_io",
side_effect=_fake_from_io, side_effect=_fake_from_io,
): ):
result = None
try: try:
result = h.buildTransactions( result = h.buildTransactions(
bal_plugin, wallet, tx_fees=1, utxos=utxos bal_plugin, wallet, tx_fees=1, utxos=utxos

View File

@@ -113,5 +113,5 @@ def test_same_heirs_empty():
if __name__ == "__main__": if __name__ == "__main__":
import pytest import pytest # pyright: ignore[reportMissingImports]
raise SystemExit(pytest.main([__file__, "-v"])) raise SystemExit(pytest.main([__file__, "-v"]))

View File

@@ -444,6 +444,46 @@ def test_merge_will_new_ids_added_wholesale():
assert new_id in fake.will 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(): def test_merge_will_from_file_invalid_file_raises():
from bal.gui.qt.common import FileImportFailed from bal.gui.qt.common import FileImportFailed

View File

@@ -35,11 +35,13 @@ import sys
import time import time
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest # pyright: ignore[reportMissingImports]
from electrum import constants from electrum import constants
constants.net = constants.BitcoinRegtest constants.net = constants.BitcoinRegtest
_VALID_REG_ADDR = "bcrt1q0567jspgutk84axs4l7sm04u86yjkzg27dv6fk"
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from electrum import bitcoin from electrum import bitcoin
@@ -250,7 +252,11 @@ class FakeBalWindow:
f = False f = False
for _u, w in self.willexecutors.items(): for _u, w in self.willexecutors.items():
if Willexecutors.is_selected( 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 f = True
if not f: if not f:
@@ -508,47 +514,70 @@ class TestNoWillexecutorKaren7:
"selected": True, "selected": True,
"base_fee": base_fee, "base_fee": base_fee,
"url": "https://we.example.com", "url": "https://we.example.com",
"address": self.wallet._CHANGE_ADDR,
"sort": 0, "sort": 0,
} }
} }
self.bal_plugin._willexecutors.set({"regtest": we_data}) 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): def test_is_selected_only_checks_selected_flag(self):
"""is_selected returns True when base_fee < max_fee.""" """is_selected ignores the fee entirely; it only reflects the
we = {"selected": True, "base_fee": 1000} selected flag. Fee-range enforcement lives in is_valid."""
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."""
we = {"selected": True, "base_fee": 600000} 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 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} 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): def test_is_selected_setter_sets_flag(self):
"""A selected will-executor with base_fee >= MAX_WILLEXECUTOR_FEE """is_selected(value) acts as a setter for the selected flag."""
is treated as NOT selected, so build_will still raises.""" 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() 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) self._add_selected_willexecutor(base_fee=600000)
with pytest.raises(NoWillExecutorNotPresent): with pytest.raises(NoWillExecutorNotPresent):