From 94ba43a3d1a9f34eae1dab3e98374307503e804f Mon Sep 17 00:00:00 2001 From: daywalker90 <8257956+daywalker90@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:19:06 +0100 Subject: [PATCH 1/3] sauron: use urllib instead of requests requests was constantly hanging and when a timeout is used it very often hit that timeout but still gave a 200 OK response --- sauron/pyproject.toml | 2 +- sauron/sauron.py | 124 ++++++++++++++++++++++++++++++------------ sauron/uv.lock | 84 +++++++++++++++------------- 3 files changed, 134 insertions(+), 76 deletions(-) diff --git a/sauron/pyproject.toml b/sauron/pyproject.toml index eda155d01..7304e53f2 100644 --- a/sauron/pyproject.toml +++ b/sauron/pyproject.toml @@ -5,7 +5,7 @@ description = "A Bitcoin backend plugin relying on Esplora" readme = "README.md" requires-python = ">=3.9.2" -dependencies = ["pyln-client>=24.11", "requests[socks]>=2.23.0"] +dependencies = ["pyln-client>=24.11"] [dependency-groups] dev = [ diff --git a/sauron/sauron.py b/sauron/sauron.py index df90b6742..bc896c642 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -4,16 +4,16 @@ # requires-python = ">=3.9.2" # dependencies = [ # "pyln-client>=24.11", -# "requests[socks]>=2.23.0", # ] # /// -import requests import sys import time +import json -from requests.packages.urllib3.util.retry import Retry -from requests.adapters import HTTPAdapter +import urllib +import urllib.request +import urllib.error from art import sauron_eye from pyln.client import Plugin @@ -27,25 +27,71 @@ class SauronError(Exception): pass -def fetch(url): +def fetch(plugin, url): """Fetch this {url}, maybe through a pre-defined proxy.""" # FIXME: Maybe try to be smart and renew circuit to broadcast different # transactions ? Hint: lightningd will agressively send us the same # transaction a certain amount of times. - session = requests.session() - session.proxies = plugin.sauron_socks_proxies - retry_strategy = Retry( - backoff_factor=1, - total=10, - status_forcelist=[429, 500, 502, 503, 504], - allowed_methods=["HEAD", "GET", "OPTIONS"], - ) - adapter = HTTPAdapter(max_retries=retry_strategy) - - session.mount("https://", adapter) - session.mount("http://", adapter) - - return session.get(url) + max_retries = 10 + backoff_factor = 1 + status_forcelist = [429, 500, 502, 503, 504] + + class SimpleResponse: + def __init__(self, content, status_code, headers): + self.content = content + self.status_code = status_code + self.headers = headers + try: + self.text = content.decode('utf-8') + except: + self.text = str(content) + + def json(self): + return json.loads(self.text) + + for attempt in range(max_retries + 1): + try: + start = time.time() + with urllib.request.urlopen(url, timeout=10) as response: + elapsed = time.time() - start + plugin.log(f"Request took {elapsed:.3f}s", level="debug") + + data = response.read() + status = response.status + headers = dict(response.headers) + + return SimpleResponse(data, status, headers) + + except urllib.error.HTTPError as e: + # HTTP error responses (4xx, 5xx) + plugin.log(f"HTTP {e.code} for {url}", level="debug") + data = e.read() if e.fp else b'' + headers = dict(e.headers) if e.headers else {} + + # Retry on specific status codes + if e.code in status_forcelist and attempt < max_retries: + wait_time = backoff_factor * (2 ** attempt) + plugin.log(f"Retrying in {wait_time}s (attempt {attempt + 1}/{max_retries})", level="debug") + time.sleep(wait_time) + continue + + # Return error response (don't raise) + return SimpleResponse(data, e.code, headers) + + except (urllib.error.URLError, OSError, ConnectionError) as e: + # Network errors (DNS, connection refused, timeout, etc.) + if attempt < max_retries: + wait_time = backoff_factor * (2 ** attempt) + plugin.log(f"Network error, retrying in {wait_time}s (attempt {attempt + 1}/{max_retries}): {e}", level="debug") + time.sleep(wait_time) + continue + else: + plugin.log(f"Failed after {max_retries} retries: {e}", level="error") + raise + + except Exception as e: + plugin.log(f"Failed: {e}", level="error") + raise @plugin.init() @@ -80,7 +126,7 @@ def getchaininfo(plugin, **kwargs): "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6": "signet", } - genesis_req = fetch(blockhash_url) + genesis_req = fetch(plugin, blockhash_url) if not genesis_req.status_code == 200: raise SauronError( "Endpoint at {} returned {} ({}) when trying to " @@ -89,7 +135,7 @@ def getchaininfo(plugin, **kwargs): ) ) - blockcount_req = fetch(blockcount_url) + blockcount_req = fetch(plugin, blockcount_url) if not blockcount_req.status_code == 200: raise SauronError( "Endpoint at {} returned {} ({}) when trying to " "get blockcount.".format( @@ -113,7 +159,7 @@ def getchaininfo(plugin, **kwargs): @plugin.method("getrawblockbyheight") def getrawblock(plugin, height, **kwargs): blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) - blockhash_req = fetch(blockhash_url) + blockhash_req = fetch(plugin, blockhash_url) if blockhash_req.status_code != 200: return { "blockhash": None, @@ -122,7 +168,7 @@ def getrawblock(plugin, height, **kwargs): block_url = "{}/block/{}/raw".format(plugin.api_endpoint, blockhash_req.text) while True: - block_req = fetch(block_url) + block_req = fetch(plugin, block_url) if block_req.status_code != 200: return { "blockhash": None, @@ -147,35 +193,41 @@ def getrawblock(plugin, height, **kwargs): @plugin.method("sendrawtransaction") -def sendrawtx(plugin, tx, **kwargs): +def sendrawtx(plugin, tx, **kwargs): sendtx_url = "{}/tx".format(plugin.api_endpoint) - sendtx_req = requests.post(sendtx_url, data=tx) - if sendtx_req.status_code != 200: + try: + req = urllib.request.Request( + sendtx_url, + data=tx.encode() if isinstance(tx, str) else tx, + method='POST' + ) + + with urllib.request.urlopen(req, timeout=10) as _response: + return { + "success": True, + "errmsg": "", + } + + except Exception as e: return { "success": False, - "errmsg": sendtx_req.text, + "errmsg": str(e), } - return { - "success": True, - "errmsg": "", - } - - @plugin.method("getutxout") def getutxout(plugin, txid, vout, **kwargs): gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) - gettx_req = fetch(gettx_url) + gettx_req = fetch(plugin, gettx_url) if not gettx_req.status_code == 200: raise SauronError( "Endpoint at {} returned {} ({}) when trying to " "get transaction.".format( gettx_url, gettx_req.status_code, gettx_req.text ) ) - status_req = fetch(status_url) + status_req = fetch(plugin, status_url) if not status_req.status_code == 200: raise SauronError( "Endpoint at {} returned {} ({}) when trying to " "get utxo status.".format( @@ -200,7 +252,7 @@ def getutxout(plugin, txid, vout, **kwargs): def estimatefees(plugin, **kwargs): feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) - feerate_req = fetch(feerate_url) + feerate_req = fetch(plugin, feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() if plugin.sauron_network in ["test", "signet"]: diff --git a/sauron/uv.lock b/sauron/uv.lock index 21c840403..9e68cbee5 100644 --- a/sauron/uv.lock +++ b/sauron/uv.lock @@ -590,14 +590,14 @@ wheels = [ [[package]] name = "importlib-metadata" -version = "8.7.0" +version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] @@ -635,14 +635,14 @@ wheels = [ [[package]] name = "jaraco-functools" -version = "4.3.0" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] [[package]] @@ -812,28 +812,30 @@ wheels = [ [[package]] name = "psutil" -version = "7.1.3" +version = "7.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, + { url = "https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d", size = 129624, upload-time = "2025-12-29T08:26:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49", size = 130132, upload-time = "2025-12-29T08:26:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc", size = 180612, upload-time = "2025-12-29T08:26:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf", size = 183201, upload-time = "2025-12-29T08:26:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f", size = 139081, upload-time = "2025-12-29T08:26:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672", size = 134767, upload-time = "2025-12-29T08:26:14.528Z" }, + { url = "https://files.pythonhosted.org/packages/05/c2/5fb764bd61e40e1fe756a44bd4c21827228394c17414ade348e28f83cd79/psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679", size = 129716, upload-time = "2025-12-29T08:26:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d2/935039c20e06f615d9ca6ca0ab756cf8408a19d298ffaa08666bc18dc805/psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f", size = 130133, upload-time = "2025-12-29T08:26:18.009Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/19f1eb0e01d24c2b3eacbc2f78d3b5add8a89bf0bb69465bc8d563cc33de/psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129", size = 181518, upload-time = "2025-12-29T08:26:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6d/7e18b1b4fa13ad370787626c95887b027656ad4829c156bb6569d02f3262/psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a", size = 184348, upload-time = "2025-12-29T08:26:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/60/1672114392dd879586d60dd97896325df47d9a130ac7401318005aab28ec/psutil-7.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79", size = 140400, upload-time = "2025-12-29T08:26:23.993Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7b/d0e9d4513c46e46897b46bcfc410d51fc65735837ea57a25170f298326e6/psutil-7.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266", size = 135430, upload-time = "2025-12-29T08:26:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, ] [[package]] @@ -939,20 +941,20 @@ wheels = [ [[package]] name = "pyln-client" -version = "25.9.3" +version = "25.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyln-bolt7" }, { name = "pyln-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/1f/3eb7980e4e79f01091f6f7826987d20e411a7cf51643ace5a7af5f786603/pyln_client-25.9.3.tar.gz", hash = "sha256:bb7ce16e23c4ce28e8853702ba2390a2d4e73bda12d6476e98424f147f9c80df", size = 92045, upload-time = "2025-11-07T01:53:35.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/c3/6375d73950df8cda594ca7ee1069f63a817190b08ea8cb57c9ce65019664/pyln_client-25.12.tar.gz", hash = "sha256:9a0435436dea7ce471e096aac9c4e3ede704c305b862b0a3b5e410279b54d8d6", size = 92109, upload-time = "2025-12-04T00:25:55.808Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/2d/4c99428c7ff11607c2a62f4f8a0d0ce364fc4eaaab7e70704270cee21fdf/pyln_client-25.9.3-py3-none-any.whl", hash = "sha256:2653f5562d18ab3fadb46ce5a44c3d5d79ea4ff53b70481d90f57e203fa3edab", size = 37545, upload-time = "2025-11-07T01:53:34.643Z" }, + { url = "https://files.pythonhosted.org/packages/04/e7/76f13dc3174b58f91aecf7890efc174ef12721dc015601a200ac40044356/pyln_client-25.12-py3-none-any.whl", hash = "sha256:9e4ff323acb71cbc4522d160dd931599f59a4c167076db016ec5d63e6a002f86", size = 37618, upload-time = "2025-12-04T00:25:54.365Z" }, ] [[package]] name = "pyln-proto" -version = "25.9.3" +version = "25.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "base58" }, @@ -961,14 +963,14 @@ dependencies = [ { name = "cryptography" }, { name = "pysocks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/94/b55d3ad680f947ebc0aebf5bcdc0fee3e619b9b49ad97aa288ed3056feff/pyln_proto-25.9.3.tar.gz", hash = "sha256:beb56848b569e7a2dba15de18650d9909a5198b05c73bc99aed7d04435f3cbc4", size = 44861, upload-time = "2025-11-07T01:53:33.877Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/0f/f3cd0d81be57b7268e90e618bef33fec11255238613e81de188b52a4a39d/pyln_proto-25.12.tar.gz", hash = "sha256:b7bea173b923aa2cfc158b4a4901630bc04ba920048af4d9684624534b2c298d", size = 44860, upload-time = "2025-12-04T00:25:53.417Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/40/02d2ec15ae3c3e7d24eed02e64d8f62f5f1e262d3f67c5c1a49fe1db860f/pyln_proto-25.9.3-py3-none-any.whl", hash = "sha256:c550bc0ae3abed09c1c9a03c2780e151cde9480afa7875da6526ce936ac71876", size = 31829, upload-time = "2025-11-07T01:53:32.961Z" }, + { url = "https://files.pythonhosted.org/packages/1a/66/e57f6024da268ae229b3bf9da3c2c7205ffb7a6bf34a4cc70f1edb598dd7/pyln_proto-25.12-py3-none-any.whl", hash = "sha256:8827e83835decb89f85efe3ca7e754fd918c1950615fa14526384b2156999b00", size = 31822, upload-time = "2025-12-04T00:25:52.358Z" }, ] [[package]] name = "pyln-testing" -version = "25.9.3" +version = "25.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cheroot" }, @@ -982,9 +984,9 @@ dependencies = [ { name = "python-bitcoinlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/af/dbad64bc36ae358cddf4cffe16f9281e1a45d6c027f500025fffff9d4687/pyln_testing-25.9.3.tar.gz", hash = "sha256:555c267ed48256db138cc44778f8966bb816ea8224719786d3b3d676597f547f", size = 49956, upload-time = "2025-11-07T01:53:36.752Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/4d/1c170b648d22fa63209fdb778753cf3d0c8ab0f5f839db72b5865b2528ab/pyln_testing-25.12.tar.gz", hash = "sha256:9c43019abb7df5e491bfdeac9ea9bfc23c2a0fc3f1cbab4da7bd072133a70ce8", size = 51322, upload-time = "2025-12-04T00:25:53.798Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/41/f5fb467ea7076ad7b48215fbf71a73fcfdbc68096192f6e2ca1cd2f4a910/pyln_testing-25.9.3-py3-none-any.whl", hash = "sha256:2cc8b3adf109b036d274cd05b53c9388441ccb554469245c1a77b0d202b1b0a7", size = 51794, upload-time = "2025-11-07T01:53:35.451Z" }, + { url = "https://files.pythonhosted.org/packages/9c/4b/5ee750ce4c92069c020af6ecb83f6bce173b69039c6d437138be65ff2450/pyln_testing-25.12-py3-none-any.whl", hash = "sha256:6e8a51302dfdb3af03aa7785723d3fcdba5b2932096fdddc18a171cf925080fe", size = 53065, upload-time = "2025-12-04T00:25:55.057Z" }, ] [[package]] @@ -1392,6 +1394,9 @@ wheels = [ name = "sauron" version = "0.1.0" source = { virtual = "." } +dependencies = [ + { name = "pyln-client" }, +] [package.dev-dependencies] dev = [ @@ -1404,6 +1409,7 @@ dev = [ ] [package.metadata] +requires-dist = [{ name = "pyln-client", specifier = ">=24.11" }] [package.metadata.requires-dev] dev = [ @@ -1475,11 +1481,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] From 4e5b5504d0a9738c8f2c4e402412389966a65791 Mon Sep 17 00:00:00 2001 From: daywalker90 <8257956+daywalker90@users.noreply.github.com> Date: Fri, 2 Jan 2026 17:21:41 +0100 Subject: [PATCH 2/3] sauron: format using ruff --- sauron/sauron.py | 55 ++++++++++--------- sauron/tests/test_sauron_esplora_bitcoin.py | 4 +- sauron/tests/test_sauron_esplora_signet.py | 8 ++- sauron/tests/test_sauron_esplora_testnet.py | 7 ++- sauron/tests/test_sauron_esplora_tor_proxy.py | 1 + .../tests/test_sauron_mempoolspace_signet.py | 3 + 6 files changed, 48 insertions(+), 30 deletions(-) diff --git a/sauron/sauron.py b/sauron/sauron.py index bc896c642..ac5debc74 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -35,60 +35,66 @@ def fetch(plugin, url): max_retries = 10 backoff_factor = 1 status_forcelist = [429, 500, 502, 503, 504] - + class SimpleResponse: def __init__(self, content, status_code, headers): self.content = content self.status_code = status_code self.headers = headers try: - self.text = content.decode('utf-8') + self.text = content.decode("utf-8") except: self.text = str(content) - + def json(self): return json.loads(self.text) - + for attempt in range(max_retries + 1): try: start = time.time() with urllib.request.urlopen(url, timeout=10) as response: elapsed = time.time() - start plugin.log(f"Request took {elapsed:.3f}s", level="debug") - + data = response.read() status = response.status headers = dict(response.headers) - + return SimpleResponse(data, status, headers) - + except urllib.error.HTTPError as e: # HTTP error responses (4xx, 5xx) plugin.log(f"HTTP {e.code} for {url}", level="debug") - data = e.read() if e.fp else b'' + data = e.read() if e.fp else b"" headers = dict(e.headers) if e.headers else {} - + # Retry on specific status codes if e.code in status_forcelist and attempt < max_retries: - wait_time = backoff_factor * (2 ** attempt) - plugin.log(f"Retrying in {wait_time}s (attempt {attempt + 1}/{max_retries})", level="debug") + wait_time = backoff_factor * (2**attempt) + plugin.log( + f"Retrying in {wait_time}s (attempt {attempt + 1}/{max_retries})", + level="debug", + ) time.sleep(wait_time) continue - + # Return error response (don't raise) return SimpleResponse(data, e.code, headers) - + except (urllib.error.URLError, OSError, ConnectionError) as e: # Network errors (DNS, connection refused, timeout, etc.) if attempt < max_retries: - wait_time = backoff_factor * (2 ** attempt) - plugin.log(f"Network error, retrying in {wait_time}s (attempt {attempt + 1}/{max_retries}): {e}", level="debug") + wait_time = backoff_factor * (2**attempt) + plugin.log( + f"Network error, retrying in {wait_time}s (attempt {attempt + 1}/{max_retries}): {e}", + level="debug", + ) time.sleep(wait_time) continue else: plugin.log(f"Failed after {max_retries} retries: {e}", level="error") raise - + except Exception as e: plugin.log(f"Failed: {e}", level="error") raise @@ -138,7 +144,7 @@ def getchaininfo(plugin, **kwargs): blockcount_req = fetch(plugin, blockcount_url) if not blockcount_req.status_code == 200: raise SauronError( - "Endpoint at {} returned {} ({}) when trying to " "get blockcount.".format( + "Endpoint at {} returned {} ({}) when trying to get blockcount.".format( blockcount_url, blockcount_req.status_code, blockcount_req.text ) ) @@ -193,28 +199,27 @@ def getrawblock(plugin, height, **kwargs): @plugin.method("sendrawtransaction") -def sendrawtx(plugin, tx, **kwargs): +def sendrawtx(plugin, tx, **kwargs): sendtx_url = "{}/tx".format(plugin.api_endpoint) try: req = urllib.request.Request( - sendtx_url, - data=tx.encode() if isinstance(tx, str) else tx, - method='POST' + sendtx_url, data=tx.encode() if isinstance(tx, str) else tx, method="POST" ) - + with urllib.request.urlopen(req, timeout=10) as _response: return { "success": True, "errmsg": "", } - + except Exception as e: return { "success": False, "errmsg": str(e), } + @plugin.method("getutxout") def getutxout(plugin, txid, vout, **kwargs): gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) @@ -223,14 +228,14 @@ def getutxout(plugin, txid, vout, **kwargs): gettx_req = fetch(plugin, gettx_url) if not gettx_req.status_code == 200: raise SauronError( - "Endpoint at {} returned {} ({}) when trying to " "get transaction.".format( + "Endpoint at {} returned {} ({}) when trying to get transaction.".format( gettx_url, gettx_req.status_code, gettx_req.text ) ) status_req = fetch(plugin, status_url) if not status_req.status_code == 200: raise SauronError( - "Endpoint at {} returned {} ({}) when trying to " "get utxo status.".format( + "Endpoint at {} returned {} ({}) when trying to get utxo status.".format( status_url, status_req.status_code, status_req.text ) ) diff --git a/sauron/tests/test_sauron_esplora_bitcoin.py b/sauron/tests/test_sauron_esplora_bitcoin.py index 5b8137b2e..2f8b8f1da 100644 --- a/sauron/tests/test_sauron_esplora_bitcoin.py +++ b/sauron/tests/test_sauron_esplora_bitcoin.py @@ -9,6 +9,7 @@ pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): pyln.testing.utils.TEST_NETWORK = "bitcoin" @@ -74,6 +75,7 @@ def test_esplora_bitcoin_getrawblockbyheight(node_factory): } assert response == expected_response + @pytest.mark.skip(reason="testing_theory") def test_esplora_bitcoin_sendrawtransaction_invalid(node_factory): """ @@ -141,8 +143,6 @@ def test_esplora_bitcoin_estimatefees(node_factory): # } response = ln_node.rpc.call("estimatefees") - - expected_response_keys = [ "opening", "mutual_close", diff --git a/sauron/tests/test_sauron_esplora_signet.py b/sauron/tests/test_sauron_esplora_signet.py index 6c7ab5e16..e49b7787a 100644 --- a/sauron/tests/test_sauron_esplora_signet.py +++ b/sauron/tests/test_sauron_esplora_signet.py @@ -9,6 +9,7 @@ pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD + class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): pyln.testing.utils.TEST_NETWORK = "signet" @@ -74,6 +75,7 @@ def test_esplora_signet_getrawblockbyheight(node_factory): } assert response == expected_response + @pytest.mark.skip(reason="testing_theory") def test_esplora_signet_sendrawtransaction_invalid(node_factory): """ @@ -82,13 +84,15 @@ def test_esplora_signet_sendrawtransaction_invalid(node_factory): ln_node = node_factory.get_node() expected_error_substring = "RPC error" - + response = ln_node.rpc.call( "sendrawtransaction", {"tx": "invalid-raw-tx"}, ) - assert expected_error_substring in response.get("errmsg", ""), "Expected 'RPC error' in errmsg field" + assert expected_error_substring in response.get("errmsg", ""), ( + "Expected 'RPC error' in errmsg field" + ) assert response.get("success") is False, "Expected success to be False" diff --git a/sauron/tests/test_sauron_esplora_testnet.py b/sauron/tests/test_sauron_esplora_testnet.py index 2aab29a04..a7425fe2e 100644 --- a/sauron/tests/test_sauron_esplora_testnet.py +++ b/sauron/tests/test_sauron_esplora_testnet.py @@ -2,12 +2,14 @@ import os import pyln +import pytest from pyln.testing import utils from pyln.testing.fixtures import * # noqa: F403 from util import LightningD pyln.testing.fixtures.network_daemons["testnet"] = utils.BitcoinD + class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): pyln.testing.utils.TEST_NETWORK = "testnet" @@ -73,6 +75,7 @@ def test_esplora_testnet_getrawblockbyheight(node_factory): } assert response == expected_response + @pytest.mark.skip(reason="testing_theory") def test_esplora_testnet_sendrawtransaction_invalid(node_factory): """ @@ -87,7 +90,9 @@ def test_esplora_testnet_sendrawtransaction_invalid(node_factory): {"tx": "invalid-raw-tx"}, ) - assert expected_error_substring in response.get("errmsg", ""), "Expected 'RPC error' in errmsg field" + assert expected_error_substring in response.get("errmsg", ""), ( + "Expected 'RPC error' in errmsg field" + ) assert response.get("success") is False, "Expected success to be False" diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index ffdf95a48..e138ccbd2 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -9,6 +9,7 @@ pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): pyln.testing.utils.TEST_NETWORK = "bitcoin" diff --git a/sauron/tests/test_sauron_mempoolspace_signet.py b/sauron/tests/test_sauron_mempoolspace_signet.py index 8502b874f..d3ad10ce6 100644 --- a/sauron/tests/test_sauron_mempoolspace_signet.py +++ b/sauron/tests/test_sauron_mempoolspace_signet.py @@ -9,6 +9,7 @@ pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD + class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): pyln.testing.utils.TEST_NETWORK = "signet" @@ -37,6 +38,7 @@ def __init__(self, *args, **kwargs): def set_feerates(self, feerates, wait_for_effect=True): return None + @pytest.fixture def node_cls(monkeypatch): monkeypatch.setenv("TEST_NETWORK", "signet") @@ -73,6 +75,7 @@ def test_mempoolspace_signet_getrawblockbyheight(node_factory): } assert response == expected_response + @pytest.mark.skip(reason="testing_theory") def test_mempoolspace_signet_sendrawtransaction_invalid(node_factory): """ From be4d4ecddd7a46cbd0fcc663106bbdd444e223f9 Mon Sep 17 00:00:00 2001 From: daywalker90 <8257956+daywalker90@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:21:16 +0100 Subject: [PATCH 3/3] delete_me: debug network stuff --- sauron/conftest.py | 22 ++++++++++++++++++++++ sauron/sauron.py | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 sauron/conftest.py diff --git a/sauron/conftest.py b/sauron/conftest.py new file mode 100644 index 000000000..dd32fbfb3 --- /dev/null +++ b/sauron/conftest.py @@ -0,0 +1,22 @@ +import socket +import pytest + +# Store original getaddrinfo +_original_getaddrinfo = socket.getaddrinfo + + +def getaddrinfo_ipv4_only(host, port, family=0, type=0, proto=0, flags=0): + """Force IPv4 only by filtering out IPv6 addresses""" + results = _original_getaddrinfo(host, port, socket.AF_INET, type, proto, flags) + if results: + ip = results[0][4][0] + print(f"Connecting to {host} via IPv4: {ip}") + return results + + +@pytest.fixture(scope="session", autouse=True) +def force_ipv4(): + """Force all network connections to use IPv4""" + socket.getaddrinfo = getaddrinfo_ipv4_only + yield + socket.getaddrinfo = _original_getaddrinfo diff --git a/sauron/sauron.py b/sauron/sauron.py index ac5debc74..5a59520e9 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -10,10 +10,12 @@ import sys import time import json +import socket import urllib import urllib.request import urllib.error +from contextlib import contextmanager from art import sauron_eye from pyln.client import Plugin @@ -26,6 +28,18 @@ class SauronError(Exception): pass +original_getaddrinfo = socket.getaddrinfo + +def ipv4_only_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): + return original_getaddrinfo(host, port, socket.AF_INET, type, proto, flags) + +@contextmanager +def force_ipv4(): + socket.getaddrinfo = ipv4_only_getaddrinfo + try: + yield + finally: + socket.getaddrinfo = original_getaddrinfo def fetch(plugin, url): """Fetch this {url}, maybe through a pre-defined proxy.""" @@ -52,15 +66,23 @@ def json(self): for attempt in range(max_retries + 1): try: start = time.time() - with urllib.request.urlopen(url, timeout=10) as response: - elapsed = time.time() - start - plugin.log(f"Request took {elapsed:.3f}s", level="debug") - - data = response.read() - status = response.status - headers = dict(response.headers) - - return SimpleResponse(data, status, headers) + with force_ipv4(): + plugin.log(f"Opening URL: {url}") + # Resolve the host manually to see what address it's using + host = urllib.parse.urlparse(url).hostname + port = urllib.parse.urlparse(url).port or 443 + addr_info = socket.getaddrinfo(host, port) + for family, type, proto, canonname, sockaddr in addr_info[:3]: # Show first few + plugin.log(f"Resolved {host}:{port} -> {sockaddr[0]} (family: {'IPv4' if family == socket.AF_INET else 'IPv6' if family == socket.AF_INET6 else family})") + with urllib.request.urlopen(url, timeout=3) as response: + elapsed = time.time() - start + plugin.log(f"Request took {elapsed:.3f}s", level="debug") + + data = response.read() + status = response.status + headers = dict(response.headers) + + return SimpleResponse(data, status, headers) except urllib.error.HTTPError as e: # HTTP error responses (4xx, 5xx)