Files
bal-electrum-plugin/willexecutors.py
2026-01-24 19:50:41 -04:00

283 lines
10 KiB
Python

import json
from datetime import datetime
from functools import partial
from aiohttp import ClientResponse
from electrum import constants
from electrum.gui.qt.util import WaitingDialog
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.network import Network
from .bal import BalPlugin
from .util import Util
DEFAULT_TIMEOUT = 5
_logger = get_logger(__name__)
class Willexecutors:
def save(bal_plugin, willexecutors):
aw = bal_plugin.WILLEXECUTORS.get()
aw[constants.net.NET_NAME] = willexecutors
bal_plugin.WILLEXECUTORS.set(aw)
def get_willexecutors(
bal_plugin, update=False, bal_window=False, force=False, task=True
):
willexecutors = bal_plugin.WILLEXECUTORS.get()
willexecutors = willexecutors.get(constants.net.NET_NAME, {})
to_del = []
for w in willexecutors:
if not isinstance(willexecutors[w], dict):
to_del.append(w)
continue
Willexecutors.initialize_willexecutor(willexecutors[w], w)
for w in to_del:
_logger.error("error Willexecutor to delete type:{} ", type(willexecutor[w]),w)
del willexecutors[w]
bal = bal_plugin.WILLEXECUTORS.default.get(constants.net.NET_NAME, {})
for bal_url, bal_executor in bal.items():
if not bal_url in willexecutors:
_logger.debug(f"force add {bal_url} willexecutor")
willexecutors[bal_url] = bal_executor
if update:
found = False
for url, we in willexecutors.items():
if Willexecutors.is_selected(we):
found = True
if found or force:
if bal_plugin.PING_WILLEXECUTORS.get() or force:
ping_willexecutors = True
if bal_plugin.ASK_PING_WILLEXECUTORS.get() and not force:
if bal_window:
ping_willexecutors = bal_window.window.question(
_(
"Contact willexecutors servers to update payment informations?"
)
)
if ping_willexecutors:
if task:
bal_window.ping_willexecutors(willexecutors, task)
else:
bal_window.ping_willexecutors_task(willexecutors)
w_sorted = dict(
sorted(
willexecutors.items(), key=lambda w: w[1].get("sort", 0), reverse=True
)
)
return w_sorted
def is_selected(willexecutor, value=None):
if not willexecutor:
return False
if not value is None:
willexecutor["selected"] = value
try:
return willexecutor["selected"]
except:
willexecutor["selected"] = False
return False
def get_willexecutor_transactions(will, force=False):
willexecutors = {}
for wid, willitem in will.items():
if willitem.get_status("VALID"):
if willitem.get_status("COMPLETE"):
if not willitem.get_status("PUSHED") or force:
if willexecutor := willitem.we:
url = willexecutor["url"]
if willexecutor and Willexecutors.is_selected(willexecutor):
if not url in willexecutors:
willexecutor["txs"] = ""
willexecutor["txsids"] = []
willexecutor["broadcast_status"] = _("Waiting...")
willexecutors[url] = willexecutor
willexecutors[url]["txs"] += str(willitem.tx) + "\n"
willexecutors[url]["txsids"].append(wid)
return willexecutors
def only_selected_list(willexecutors):
out = {}
for url, v in willexecutors.items():
if Willexecutors.is_selected(willexecutor):
out[url] = v
def push_transactions_to_willexecutors(will):
willexecutors = get_transactions_to_be_pushed()
for url in willexecutors:
willexecutor = willexecutors[url]
if Willexecutors.is_selected(willexecutor):
if "txs" in willexecutor:
Willexecutors.push_transactions_to_willexecutor(
willexecutors[url]["txs"], url
)
def send_request(method, url, data=None, *, timeout=10):
network = Network.get_instance()
if not network:
raise ErrorConnectingServer("You are offline.")
_logger.debug(f"<-- {method} {url} {data}")
headers = {}
headers["user-agent"] = f"BalPlugin v:{BalPlugin.version()}"
headers["Content-Type"] = "text/plain"
try:
if method == "get":
response = Network.send_http_on_proxy(
method,
url,
params=data,
headers=headers,
on_finish=Willexecutors.handle_response,
timeout=timeout,
)
elif method == "post":
response = Network.send_http_on_proxy(
method,
url,
body=data,
headers=headers,
on_finish=Willexecutors.handle_response,
timeout=timeout,
)
else:
raise Exception(f"unexpected {method=!r}")
except Exception as e:
_logger.error(f"exception sending request {e}")
raise e
else:
_logger.debug(f"--> {response}")
return response
async def handle_response(resp: ClientResponse):
r = await resp.text()
try:
r = json.loads(r)
r["status"] = resp.status
r["selected"] = Willexecutors.is_selected(willexecutor)
r["url"] = url
except:
pass
return r
class AlreadyPresentException(Exception):
pass
def push_transactions_to_willexecutor(willexecutor):
out = True
try:
_logger.debug(f"willexecutor['txs']")
if w := Willexecutors.send_request(
"post",
willexecutor["url"] + "/" + constants.net.NET_NAME + "/pushtxs",
data=willexecutor["txs"].encode("ascii"),
):
willexecutor["broadcast_status"] = _("Success")
_logger.debug(f"pushed: {w}")
if w != "thx":
_logger.debug(f"error: {w}")
raise Exception(w)
else:
raise Exception("empty reply from:{willexecutor['url']}")
except Exception as e:
_logger.debug(f"error:{e}")
if str(e) == "already present":
raise Willexecutors.AlreadyPresentException()
out = False
willexecutor["broadcast_status"] = _("Failed")
return out
def ping_servers(willexecutors):
for url, we in willexecutors.items():
Willexecutors.get_info_task(url, we)
def get_info_task(url, willexecutor):
w = None
try:
_logger.info("GETINFO_WILLEXECUTOR")
_logger.debug(url)
netname = "bitcoin"
if constants.net.NET_NAME != "mainnet":
netname = constants.net.NET_NAME
w = Willexecutors.send_request("get", url + "/" + netname + "/info")
willexecutor["url"] = url
willexecutor["status"] = w["status"]
willexecutor["base_fee"] = w["base_fee"]
willexecutor["address"] = w["address"]
if not willexecutor["info"]:
willexecutor["info"] = w["info"]
_logger.debug(f"response_data {w['address']}")
except Exception as e:
_logger.error(f"error {e} contacting {url}: {w}")
willexecutor["status"] = "KO"
willexecutor["last_update"] = datetime.now().timestamp()
return willexecutor
def initialize_willexecutor(willexecutor, url, status=None, selected=None):
willexecutor["url"] = url
if not status is None:
willexecutor["status"] = status
willexecutor["selected"] = Willexecutors.is_selected(willexecutor, selected)
def download_list(bal_plugin):
try:
l = Willexecutors.send_request(
"get", "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
)
del l["status"]
for w in l:
willexecutor = l[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(l)
# bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True)
return l
except Exception as e:
_logger.error(f"Failed to download willexecutors list: {e}")
return {}
def get_willexecutors_list_from_json(bal_plugin):
try:
with open("willexecutors.json") as f:
willexecutors = json.load(f)
for w in willexecutors:
willexecutor = willexecutors[w]
Willexecutors.initialize_willexecutor(willexecutor, w, "New", False)
# bal_plugin.WILLEXECUTORS.set(willexecutors)
return h
except Exception as e:
_logger.error(f"error opening willexecutors json: {e}")
return {}
def check_transaction(txid, url):
_logger.debug(f"{url}:{txid}")
try:
w = Willexecutors.send_request(
"post", url + "/searchtx", data=txid.encode("ascii")
)
return w
except Exception as e:
_logger.error(f"error contacting {url} for checking txs {e}")
raise e
class WillExecutor:
def __init__(self, url, base_fee, chain, info, version):
self.url = url
self.base_fee = base_fee
self.chain = chain
self.info = info
self.version = version
def from_dict(d):
we = WillExecutor(d["url"], d["base_fee"], d["chain"], d["info"], d["version"])