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:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://opencode.ai/config.json",
|
|
||||||
"lsp": true
|
|
||||||
}
|
|
||||||
42
package-lock.json
generated
42
package-lock.json
generated
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"pyright": "^1.1.411"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"python.analysis": {
|
|
||||||
"extraPaths": ["../electrum"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
50
tests/karen7
50
tests/karen7
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user