docs(willexecutors.py): Add comprehensive documentation for all classes and methods

This commit is contained in:
2026-04-10 01:32:20 +00:00
parent 6fb82dc8d0
commit 06c32268a7

View File

@@ -1,3 +1,13 @@
"""Willexecutors module for BAL Electrum Plugin.
This module provides functionality to manage will executor servers that
broadcast timelocked transactions at the appropriate locktime.
Classes:
Willexecutors: Static class for managing executor list and communication
WillExecutor: Data class representing a single will executor
"""
import json import json
from datetime import datetime from datetime import datetime
import time import time
@@ -18,8 +28,19 @@ chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "
class Willexecutors: class Willexecutors:
"""Static class managing will executor servers.
Provides methods to save/load configurations, communicate via HTTP,
push transactions, and download executor lists from remote sources.
"""
def save(bal_plugin, willexecutors): def save(bal_plugin, willexecutors):
"""Save will executor configuration to plugin settings.
Args:
bal_plugin: BAL plugin instance
willexecutors: Dictionary of executor configs keyed by URL
"""
_logger.debug(f"save {willexecutors},{chainname}") _logger.debug(f"save {willexecutors},{chainname}")
aw = bal_plugin.WILLEXECUTORS.get() aw = bal_plugin.WILLEXECUTORS.get()
aw[chainname] = willexecutors aw[chainname] = willexecutors
@@ -30,6 +51,18 @@ class Willexecutors:
def get_willexecutors( def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True bal_plugin, update=False, bal_window=False, force=False, task=True
): ):
"""Retrieve and update the list of available will executors.
Args:
bal_plugin: BAL plugin instance
update: If True, ping servers to refresh data
bal_window: GUI window for user prompts
force: Force update regardless of cached data age
task: Run as background task if True
Returns:
dict: Sorted dictionary of executor configurations
"""
willexecutors = bal_plugin.WILLEXECUTORS.get() willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(chainname, {}) willexecutors = willexecutors.get(chainname, {})
to_del = [] to_del = []
@@ -79,6 +112,15 @@ class Willexecutors:
return w_sorted return w_sorted
def is_selected(willexecutor, value=None): def is_selected(willexecutor, value=None):
"""Check or set the selection status of an executor.
Args:
willexecutor: Executor configuration dictionary
value: Optional boolean to set selection status
Returns:
bool: Current selection status
"""
if not willexecutor: if not willexecutor:
return False return False
if value is not None: if value is not None:
@@ -90,6 +132,15 @@ class Willexecutors:
return False return False
def get_willexecutor_transactions(will, force=False): def get_willexecutor_transactions(will, force=False):
"""Collect transactions grouped by executor for valid, complete wills.
Args:
will: Dictionary of will items keyed by ID
force: Include already-pushed transactions
Returns:
dict: Executors with their aggregated transactions
"""
willexecutors = {} willexecutors = {}
for wid, willitem in will.items(): for wid, willitem in will.items():
if willitem.get_status("VALID"): if willitem.get_status("VALID"):
@@ -127,6 +178,22 @@ class Willexecutors:
def send_request( def send_request(
method, url, data=None, *, timeout=10, handle_response=None, count_reply=0 method, url, data=None, *, timeout=10, handle_response=None, count_reply=0
): ):
"""Send HTTP request to a will executor server.
Args:
method: HTTP method (get/post)
url: Target server URL
data: Request payload
timeout: Timeout in seconds
handle_response: Response processing function
count_reply: Retry counter for timeouts
Returns:
Server response object
Raises:
Exception: On connection errors or invalid method
"""
network = Network.get_instance() network = Network.get_instance()
if not network: if not network:
raise Exception("You are offline.") raise Exception("You are offline.")
@@ -178,12 +245,28 @@ class Willexecutors:
return response return response
def get_we_url_from_response(resp): def get_we_url_from_response(resp):
"""Extract base URL from response object.
Args:
resp: Response object with url attribute
Returns:
str: Base URL without trailing paths
"""
url_slices = str(resp.url).split("/") url_slices = str(resp.url).split("/")
if len(url_slices) > 2: if len(url_slices) > 2:
url_slices = url_slices[:-2] url_slices = url_slices[:-2]
return "/".join(url_slices) return "/".join(url_slices)
async def handle_response(resp: ClientResponse): async def handle_response(resp: ClientResponse):
"""Parse JSON response from executor server.
Args:
resp: aiohttp ClientResponse object
Returns:
Parsed JSON data or raw text on parse failure
"""
r = await resp.text() r = await resp.text()
try: try:
@@ -197,9 +280,21 @@ class Willexecutors:
return r return r
class AlreadyPresentException(Exception): class AlreadyPresentException(Exception):
"""Raised when transactions already exist on executor server."""
pass pass
def push_transactions_to_willexecutor(willexecutor): def push_transactions_to_willexecutor(willexecutor):
"""Push timelocked transactions to an executor server for broadcast.
Args:
willexecutor: Dict containing url and txs keys
Returns:
bool: True on success, False on failure
Raises:
AlreadyPresentException: If transactions already exist
"""
out = True out = True
try: try:
_logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}") _logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}")
@@ -225,10 +320,24 @@ class Willexecutors:
return out return out
def ping_servers(willexecutors): def ping_servers(willexecutors):
"""Ping all executor servers to update their information.
Args:
willexecutors: Dictionary of executor configurations
"""
for url, we in willexecutors.items(): for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we) Willexecutors.get_info_task(url, we)
def get_info_task(url, willexecutor): def get_info_task(url, willexecutor):
"""Fetch current information from a single executor server.
Args:
url: Executor server URL
willexecutor: Configuration dict to update
Returns:
Updated willexecutor dict with status, base_fee, address
"""
w = None w = None
try: try:
_logger.info("GETINFO_WILLEXECUTOR") _logger.info("GETINFO_WILLEXECUTOR")
@@ -249,6 +358,14 @@ class Willexecutors:
return willexecutor return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}): def initialize_willexecutor(willexecutor, url, status=None, old_willexecutor={}):
"""Initialize or merge executor configuration preserving user settings.
Args:
willexecutor: New executor configuration dict
url: Executor server URL
status: Optional status override
old_willexecutor: Previous config to preserve user preferences
"""
willexecutor["url"] = url willexecutor["url"] = url
if status is not None: if status is not None:
willexecutor["status"] = status willexecutor["status"] = status
@@ -261,6 +378,14 @@ class Willexecutors:
def download_list(old_willexecutors): def download_list(old_willexecutors):
"""Download latest executor list from remote source.
Args:
old_willexecutors: Existing configs to merge with new list
Returns:
dict: Merged executor configurations
"""
try: try:
willexecutors = Willexecutors.send_request( willexecutors = Willexecutors.send_request(
"get", "get",
@@ -281,6 +406,11 @@ class Willexecutors:
return {} return {}
def get_willexecutors_list_from_json(): def get_willexecutors_list_from_json():
"""Load executor list from local willexecutors.json file.
Returns:
dict: Executor configurations from JSON file
"""
try: try:
with open("willexecutors.json") as f: with open("willexecutors.json") as f:
willexecutors = json.load(f) willexecutors = json.load(f)
@@ -295,6 +425,15 @@ class Willexecutors:
return {} return {}
def check_transaction(txid, url): def check_transaction(txid, url):
"""Check if a transaction exists on executor server.
Args:
txid: Transaction ID string
url: Executor server URL
Returns:
Server response about transaction existence
"""
_logger.debug(f"{url}:{txid}") _logger.debug(f"{url}:{txid}")
try: try:
w = Willexecutors.send_request( w = Willexecutors.send_request(
@@ -306,10 +445,31 @@ class Willexecutors:
raise e raise e
def compute_id(willexecutor): def compute_id(willexecutor):
"""Compute unique identifier for an executor.
Args:
willexecutor: Executor configuration dict
Returns:
str: Unique ID combining URL and chain name
"""
return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain")) return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain"))
class WillExecutor: class WillExecutor:
"""Data class representing a single will executor server.
Attributes:
url: Executor server URL
base_fee: Fixed fee in satoshis
chain: Bitcoin chain name
info: Additional executor information
version: Plugin version compatibility
status: Connection status
is_selected: User selection flag
promo_code: Promotional discount code
"""
def __init__( def __init__(
self, self,
url, url,
@@ -321,6 +481,18 @@ class WillExecutor:
is_selected=False, is_selected=False,
promo_code="", promo_code="",
): ):
"""Initialize a new WillExecutor instance.
Args:
url: Executor server URL
base_fee: Fixed fee in satoshis
chain: Bitcoin chain name
info: Additional executor information
version: Plugin version compatibility
status: Connection status (OK/Ko)
is_selected: Whether user has selected this executor
promo_code: Promotional discount code
"""
self.url = url self.url = url
self.base_fee = base_fee self.base_fee = base_fee
self.chain = chain self.chain = chain
@@ -332,6 +504,14 @@ class WillExecutor:
self.id = self.compute_id() self.id = self.compute_id()
def from_dict(d): def from_dict(d):
"""Create WillExecutor instance from a dictionary.
Args:
d: Dictionary containing executor data fields
Returns:
WillExecutor: New instance with defaults for missing fields
"""
return WillExecutor( return WillExecutor(
url=d.get("url", "http://localhost:8000"), url=d.get("url", "http://localhost:8000"),
base_fee=d.get("base_fee", 1000), base_fee=d.get("base_fee", 1000),
@@ -344,6 +524,11 @@ class WillExecutor:
) )
def to_dict(self): def to_dict(self):
"""Convert WillExecutor to dictionary representation.
Returns:
dict: Serializable representation excluding computed fields
"""
return { return {
"url": self.url, "url": self.url,
"base_fee": self.base_fee, "base_fee": self.base_fee,
@@ -354,4 +539,9 @@ class WillExecutor:
} }
def compute_id(self): def compute_id(self):
"""Generate unique identifier for this executor.
Returns:
str: Unique ID from URL and chain name
"""
return f"{self.url}-{self.chain}" return f"{self.url}-{self.chain}"