From 9cf64202d64e7b482a8d392d8bfbbd34c609ffd7 Mon Sep 17 00:00:00 2001 From: Nazim Date: Thu, 18 Jun 2026 15:12:27 +0000 Subject: [PATCH 1/4] fix: constrain CI dependencies --- pyproject.toml | 5 +++-- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 64fcf0a..bb92bb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,5 +54,6 @@ test = [ "mock", "black", "pre-commit", - "bdkpython" -] \ No newline at end of file + "Werkzeug<3", + "bdkpython==0.32.1" +] diff --git a/requirements.txt b/requirements.txt index b236612..f3a2a8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ embit>=0.6.1 -Flask>=2.1.1 +Flask>=2.1.1,<2.3 Flask-SQLAlchemy==2.5.1 sqlalchemy>=1.4.42,<2.0 psycopg2-binary From cac0efe9505fb7ea6a7e1cb4426b12b68a0f9687 Mon Sep 17 00:00:00 2001 From: al-munazzim Date: Sun, 28 Jun 2026 19:36:08 +0000 Subject: [PATCH 2/4] ci: wait for Nigiri RPC before integration tests --- .github/workflows/nigiri-infra.yml | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.github/workflows/nigiri-infra.yml b/.github/workflows/nigiri-infra.yml index 5e6285d..8d5308a 100644 --- a/.github/workflows/nigiri-infra.yml +++ b/.github/workflows/nigiri-infra.yml @@ -32,6 +32,47 @@ jobs: uses: vulpemventures/nigiri-github-action@v1 with: use_liquid: false + - name: Wait for Bitcoin RPC + run: | + python3 - <<'PY' + import json + import time + import urllib.error + import urllib.request + + url = "http://localhost:18443" + password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None, url, "admin1", "123") + opener = urllib.request.build_opener( + urllib.request.HTTPBasicAuthHandler(password_mgr) + ) + payload = json.dumps({ + "jsonrpc": "2.0", + "id": "ci-ready", + "method": "getblockchaininfo", + "params": [], + }).encode() + + deadline = time.time() + 120 + last_error = None + while time.time() < deadline: + try: + request = urllib.request.Request( + url, + data=payload, + headers={"content-type": "application/json"}, + ) + with opener.open(request, timeout=5) as response: + result = json.loads(response.read())["result"] + print("Bitcoin RPC ready:", result["chain"], result["blocks"]) + raise SystemExit(0) + except Exception as exc: + last_error = exc + print("Waiting for Bitcoin RPC:", exc) + time.sleep(2) + + raise SystemExit(f"Bitcoin RPC did not become ready: {last_error}") + PY - name: Run integration tests run: | pytest tests/integration/basics.py From 12dac2c61797f8253491d6ef82b0e58074d9e4e4 Mon Sep 17 00:00:00 2001 From: al-munazzim Date: Sun, 28 Jun 2026 19:38:13 +0000 Subject: [PATCH 3/4] fix: support Bitcoin Core JSON-RPC responses without error field --- src/cryptoadvance/spectrum/util_specter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cryptoadvance/spectrum/util_specter.py b/src/cryptoadvance/spectrum/util_specter.py index 462072d..4d4effa 100644 --- a/src/cryptoadvance/spectrum/util_specter.py +++ b/src/cryptoadvance/spectrum/util_specter.py @@ -295,7 +295,9 @@ def trace_call_after(cls, url, payload, timestamp): def __getattr__(self, method): def fn(*args, **kwargs): r = self.multi([(method, *args)], **kwargs)[0] - if r["error"] is not None: + # Bitcoin Core 30.0 omits the JSON-RPC "error" field from successful + # responses instead of returning "error": null. + if r.get("error") is not None: raise Exception( f"Request error for method {method}{args}: {r['error']['message']}", r, From 82dfa8cf50c64ca9af6fbb1a040e16706d481a3d Mon Sep 17 00:00:00 2001 From: al-munazzim Date: Sun, 28 Jun 2026 19:40:45 +0000 Subject: [PATCH 4/4] test: relax Electrum ping timeout in integration test --- tests/integration/elsock_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/elsock_test.py b/tests/integration/elsock_test.py index 6788b7e..5b5dbde 100644 --- a/tests/integration/elsock_test.py +++ b/tests/integration/elsock_test.py @@ -35,7 +35,7 @@ def callback(something): port=50002, callback=callback, use_ssl=True, - call_timeout=1, + call_timeout=10, ) ts = elsock.ping() logger.info(f"First working ping in {ts} ms") @@ -80,7 +80,7 @@ def callback(something): logger.info(elsock._socket) ts = elsock.ping() logger.info(f"second working ping in {ts} ms") - assert ts < 1 + assert ts < 10 assert caplog.text.count("ElectrumSocket Status changed") == 9 assert (