forked from bitcoinafterlife/bal-electrum-plugin
docs(willexecutors.py): Add comprehensive documentation for all classes and methods
This commit is contained in:
190
willexecutors.py
190
willexecutors.py
@@ -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}"
|
||||||
|
|||||||
Reference in New Issue
Block a user