Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ jobs:
name: python ${{ matrix.python-version }}, bitcoind ${{ matrix.bitcoind-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-13, ubuntu-latest]
python-version: ["3.9", "3.12"]
bitcoind-version: ["28.3", "29.2"]
os:
- macos-13
- ubuntu-22.04
- ubuntu-24.04
python-version:
- "3.9"
- "3.12"
- "3.13"
bitcoind-version:
- "28.3"
- "29.2"

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Download the latest [release](https://github.com/Joinmarket-Org/joinmarket-clien

Make sure to validate the signature on the tar/zip file provided with the [release](https://github.com/Joinmarket-Org/joinmarket-clientserver/releases) (or check the signature in git if you install that way using `git log --show-signature`).

JoinMarket requires Python >=3.8, <3.13 installed.
JoinMarket requires Python >=3.8, <3.14 installed.

(**macOS users**: Make sure that you have Homebrew and Apple's Command Line Tools installed.)

Expand Down
2 changes: 1 addition & 1 deletion docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* [Installation on Windows](#installation-on-windows)
* [Alternative/custom installation](#alternativecustom-installation)

JoinMarket requires Python >=3.8, <3.13.
JoinMarket requires Python >=3.8, <3.14.

### Notes on upgrading, binaries and compatibility

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ name = "joinmarket"
version = "0.9.12dev"
description = "Joinmarket client library for Bitcoin coinjoins"
readme = "README.md"
requires-python = ">=3.9,<3.13"
requires-python = ">=3.9,<3.14"
license = {file = "LICENSE"}
dependencies = [
"chromalog==1.0.5",
"cryptography==42.0.4",
"service-identity==21.1.0",
"twisted==24.7.0",
"twisted==24.11.0",
"txtorcon==23.11.0",
]

Expand Down
1 change: 1 addition & 0 deletions test/jmclient/test_wallet_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ def successful_refresh_response_handler(self, response):
def failed_refresh_response_handler(
self, response, *, message=None, error_description=None
):
jlog.debug(f"failed_refresh_response_handler '{message}' ({error_description})")
Copy link
Author

@3nprob 3nprob Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this line was added, response.code 200 in CI here (but only for python3.12 on linux - not for 3.9 or 3.13), causing the test to fail:

After, they all passed: https://github.com/JoinMarket-Org/joinmarket-clientserver/actions/runs/18824722441?pr=1802

Currently I don't have an explanation for why this happened and if it's actually related to the twisted upgrade. The failures only appearing for 3.12 but not 3.13 or 3.9 may or may not be a red herring given the small sample size but it does look consistent. Some form of race? Related to buffer flushing...?

It's concerning if this is indicative of potential auth bypass...

Is anyone able to reproduce the test assert failure in test/jmclient/test_wallet_rpc.py locally?

Copy link
Author

@3nprob 3nprob Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subsequent failing runs with the log (also python3.12) indicating it's the unsupported_grant_type case being triggered:

https://github.com/3nprob/joinmarket-clientserver/actions/runs/18824977574/job/53706175110#step:10:176

Details

=================================== FAILURES ===================================
_________________ TrialTestWRPC_JWT.test_refresh_token_request _________________

self = <test_wallet_rpc.TrialTestWRPC_JWT testMethod=test_refresh_token_request>
response = <twisted.web._newclient.Response object at 0x7f5849081cd0>

    @defer.inlineCallbacks
    def failed_refresh_response_handler(
        self, response, *, message=None, error_description=None
    ):
        jlog.debug(f"failed_refresh_response_handler '{message}' ({error_description})")
>       assert response.code == 400
E       AssertionError: assert 200 == 400
E        +  where 200 = <twisted.web._newclient.Response object at 0x7f5849081cd0>.code

test/jmclient/test_wallet_rpc.py:850: AssertionError
----------------------------- Captured stdout call -----------------------------
User data location: .
2025-10-26 23:31:53,679 [DEBUG]  rpc: getblockchaininfo []
2025-10-26 23:31:53,680 [DEBUG]  rpc: listwallets []
2025-10-26 23:31:53,681 [DEBUG]  rpc: getwalletinfo []
2025-10-26 23:31:53,746 [DEBUG]  rpc: getnewaddress []
2025-10-26 23:31:53,748 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:53,762 [DEBUG]  rpc: sendtoaddress ['bcrt1qe0zcwq57ky4d8uql4nujrhpkkxpktpedl0e0n0', 2.0]
2025-10-26 23:31:53,959 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:53,967 [DEBUG]  rpc: sendtoaddress ['bcrt1qupdzgzv79qdhkcylqfs2jkjxpv3j0h4ff6spuu', 2.0]
2025-10-26 23:31:54,164 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:54,170 [DEBUG]  rpc: sendtoaddress ['bcrt1qy7626rmvgxmdx6wj30qwgke7w8zxd2vstej5nx', 2.0]
2025-10-26 23:31:54,368 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:54,374 [DEBUG]  rpc: sendtoaddress ['bcrt1q8xd2lhe6sx5ses85jdjwvv5ja8lpweskqc78gd', 2.0]
2025-10-26 23:31:54,573 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:54,575 [DEBUG]  rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
2025-10-26 23:31:54,576 [DEBUG]  rpc: listaddressgroupings []
2025-10-26 23:31:54,724 [INFO]  Detected new wallet, performing initial import
2025-10-26 23:31:54,725 [DEBUG]  requesting detailed wallet history
2025-10-26 23:31:54,725 [DEBUG]  rpc: listlabels []
2025-10-26 23:31:54,728 [DEBUG]  rpc: listunspent [0]
2025-10-26 23:31:54,964 [DEBUG]  bitcoind sync_unspent took 0.23537540435791016sec
2025-10-26 23:31:54,976 [DEBUG]  failed_refresh_response_handler 'invalid_request' (None)
2025-10-26 23:31:54,979 [DEBUG]  failed_refresh_response_handler 'unsupported_grant_type' (None)
----------------------------- Captured stderr call -----------------------------
Unhandled error in Deferred:

Traceback (most recent call last):
  File "/home/runner/work/joinmarket-clientserver/joinmarket-clientserver/jmvenv/lib/python3.12/site-packages/twisted/internet/defer.py", line 2017, in _inlineCallbacks
    result = context.run(gen.send, result)
  File "/home/runner/work/joinmarket-clientserver/joinmarket-clientserver/test/jmclient/test_wallet_rpc.py", line 850, in failed_refresh_response_handler
    assert response.code == 400
builtins.AssertionError: assert 200 == 400
 +  where 200 = <twisted.web._newclient.Response object at 0x7f5849081cd0>.code

------------------------------ Captured log call -------------------------------
DEBUG    joinmarket:blockchaininterface.py:431 rpc: getblockchaininfo []
DEBUG    joinmarket:blockchaininterface.py:431 rpc: listwallets []
DEBUG    joinmarket:blockchaininterface.py:431 rpc: getwalletinfo []
DEBUG    joinmarket:blockchaininterface.py:431 rpc: getnewaddress []
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: sendtoaddress ['bcrt1qe0zcwq57ky4d8uql4nujrhpkkxpktpedl0e0n0', 2.0]
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: sendtoaddress ['bcrt1qupdzgzv79qdhkcylqfs2jkjxpv3j0h4ff6spuu', 2.0]
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: sendtoaddress ['bcrt1qy7626rmvgxmdx6wj30qwgke7w8zxd2vstej5nx', 2.0]
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: sendtoaddress ['bcrt1q8xd2lhe6sx5ses85jdjwvv5ja8lpweskqc78gd', 2.0]
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: generatetoaddress [1, 'bcrt1qxt62fdlsh8dhe4mj5cs4eldxccyv89sk0wm3jk']
DEBUG    joinmarket:blockchaininterface.py:431 rpc: listaddressgroupings []
INFO     joinmarket:wallet_service.py:560 Detected new wallet, performing initial import
DEBUG    joinmarket:wallet_service.py:755 requesting detailed wallet history
DEBUG    joinmarket:blockchaininterface.py:431 rpc: listlabels []
DEBUG    joinmarket:blockchaininterface.py:431 rpc: listunspent [0]
DEBUG    joinmarket:wallet_service.py:868 bitcoind sync_unspent took 0.23537540435791016sec
DEBUG    joinmarket:test_wallet_rpc.py:849 failed_refresh_response_handler 'invalid_request' (None)
DEBUG    joinmarket:test_wallet_rpc.py:849 failed_refresh_response_handler 'unsupported_grant_type' (None)
=========================== short test summary info ============================
FAILED test/jmclient/test_wallet_rpc.py::TrialTestWRPC_JWT::test_refresh_token_request
================== 1 failed, 425 passed in 301.58s (0:05:01) ===================
+ local success=1
+ [[ -f ./joinmarket.cfg ]]
+ unlink ./joinmarket.cfg
+ '[' -f /dev/shm/jm_test_home-runner/.bitcoin/bitcoind.pid ']'
+ [[ '' == true ]]
+ rm -rf /dev/shm/jm_test_home-runner/.bitcoin
+ return 1

Headers and response body: https://github.com/3nprob/joinmarket-clientserver/actions/runs/18826006246/job/53708752813

[DEBUG]  headers: (Headers({b'Server': [b'TwistedWeb/24.11.0'], b'Date': [b'Mon, 27 Oct 2025 00:35:58 GMT'], b'Content-Type': [b'application/json'], b'Access-Control-Allow-Origin': [b'*'], b'Cache-Control': [b'no-cache, no-store, must-revalidate'], b'Pragma': [b'no-cache'], b'Expires': [b'Sat, 26 Jul 1997 05:00:00 GMT']}))
[DEBUG]  reason: 'b'OK''

assert response.code == 400
body = yield readBody(response)
json_body = json.loads(body.decode("utf-8"))
Expand Down
Loading