import json from datetime import datetime import time from aiohttp import ClientResponse from electrum import constants from electrum.i18n import _ from electrum.logging import get_logger from electrum.network import Network from .bal import BalPlugin DEFAULT_TIMEOUT = 5 _logger = get_logger(__name__) chainname = constants.net.NET_NAME if constants.net.NET_NAME != "mainnet" else "bitcoin" class Willexecutors: def save(bal_plugin, willexecutors): _logger.debug(f"save {willexecutors},{chainname}") aw = bal_plugin.WILLEXECUTORS.get() aw[chainname] = willexecutors bal_plugin.WILLEXECUTORS.set(aw) _logger.debug(f"saved: {aw}") # bal_plugin.WILLEXECUTORS.set(willexecutors) def get_willexecutors( bal_plugin, update=False, bal_window=False, force=False, task=True ): willexecutors = bal_plugin.WILLEXECUTORS.get() willexecutors = willexecutors.get(chainname, {}) 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:{} {}".format( type(willexecutors[w]), w ) ) del willexecutors[w] bal = bal_plugin.WILLEXECUTORS.default.get(chainname, {}) for bal_url, bal_executor in bal.items(): if bal_url not 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 value is not None: willexecutor["selected"] = value try: return willexecutor["selected"] except Exception: 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 url not 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(url): # out[url] = v # def push_transactions_to_willexecutors(will): # willexecutors = 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( bal_plugin,method, url, data=None, *, timeout=10, handle_response=None, count_reply=0 ): network = Network.get_instance() if not network: raise Exception("You are offline.") _logger.debug(f"<-- {method} {url} {data}") headers = {} headers["user-agent"] = f"BalPlugin v:{bal_plugin.version()}" headers["Content-Type"] = "text/plain" if not handle_response: handle_response = Willexecutors.handle_response try: if method == "get": response = Network.send_http_on_proxy( method, url, params=data, headers=headers, on_finish=handle_response, timeout=timeout, ) elif method == "post": response = Network.send_http_on_proxy( method, url, body=data, headers=headers, on_finish=handle_response, timeout=timeout, ) else: raise Exception(f"unexpected {method=!r}") except TimeoutError: if count_reply < 10: _logger.debug(f"timeout({count_reply}) error: retry in 3 sec...") time.sleep(3) return Willexecutors.send_request( bal_plugin, method, url, data, timeout=timeout, handle_response=handle_response, count_reply=count_reply + 1, ) else: _logger.debug(f"Too many timeouts: {count_reply}") except Exception as e: raise e else: _logger.debug(f"--> {response}") return response def get_we_url_from_response(resp): url_slices = str(resp.url).split("/") if len(url_slices) > 2: url_slices = url_slices[:-2] return "/".join(url_slices) async def handle_response(resp: ClientResponse): r = await resp.text() try: r = json.loads(r) # url = Willexecutors.get_we_url_from_response(resp) # r["url"]= url # r["status"]=resp.status except Exception as e: _logger.debug(f"error handling response:{e}") pass return r class AlreadyPresentException(Exception): pass def push_transactions_to_willexecutor(bal_plugin, willexecutor): out = True try: _logger.debug(f"{willexecutor['url']}: {willexecutor['txs']}") if w := Willexecutors.send_request( bal_plugin, "post", willexecutor["url"] + "/" + chainname + "/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(bal_plugin, willexecutors): for url, we in willexecutors.items(): Willexecutors.get_info_task(bal_plugin,url, we) def get_info_task(bal_plugin,url, willexecutor): w = None try: _logger.info("GETINFO_WILLEXECUTOR") _logger.debug(url) w = Willexecutors.send_request(bal_plugin,"get", url + "/" + chainname + "/info") if isinstance(w, dict): willexecutor["url"] = url willexecutor["status"] = 200 willexecutor["base_fee"] = w["base_fee"] willexecutor["address"] = w["address"] willexecutor["info"] = w["info"] _logger.debug(f"response_data {w}") 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, old_willexecutor={}): willexecutor["url"] = url if status is not None: willexecutor["status"] = status else: willexecutor["status"] = old_willexecutor.get("status",willexecutor.get("status","Ko")) willexecutor["selected"]=Willexecutors.is_selected(old_willexecutor) or willexecutor.get("selected",False) willexecutor["address"]=old_willexecutor.get("address",willexecutor.get("address","")) willexecutor["promo_code"]=old_willexecutor.get("promo_code",willexecutor.get("promo_code")) def download_list(bal_plugin,old_willexecutors): try: willexecutors = Willexecutors.send_request( bal_plugin, "get", f"https://welist.bitcoin-after.life/data/{chainname}?page=0&limit=100", ) # del willexecutors["status"] for w in willexecutors: if w not in ("status", "url"): Willexecutors.initialize_willexecutor( willexecutors[w], w, None, old_willexecutors.get(w,{}) ) # bal_plugin.WILLEXECUTORS.set(l) # bal_plugin.config.set_key(bal_plugin.WILLEXECUTORS,l,save=True) return willexecutors 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 willexecutors except Exception as e: _logger.error(f"error opening willexecutors json: {e}") return {} def check_transaction(bal_plugin,txid, url): _logger.debug(f"{url}:{txid}") try: w = Willexecutors.send_request( bal_plugin, "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 def compute_id(willexecutor): return "{}-{}".format(willexecutor.get("url"), willexecutor.get("chain")) class WillExecutor: def __init__( self, url, base_fee, chain, info, version, status, is_selected=False, promo_code="", ): self.url = url self.base_fee = base_fee self.chain = chain self.info = info self.version = version self.status = status self.promo_code = promo_code self.is_selected = is_selected self.id = self.compute_id() def from_dict(d): return WillExecutor( url=d.get("url", "http://localhost:8000"), base_fee=d.get("base_fee", 1000), chain=d.get("chain", chainname), info=d.get("info", ""), version=d.get("version", 0), status=d.get("status", "Ko"), is_selected=d.get("is_selected", "False"), promo_code=d.get("promo_code", ""), ) def to_dict(self): return { "url": self.url, "base_fee": self.base_fee, "chain": self.chain, "info": self.info, "version": self.version, "promo_code": self.promo_code, } def compute_id(self): return f"{self.url}-{self.chain}"