Skip to content
This repository was archived by the owner on Apr 27, 2026. It is now read-only.
Open
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
33 changes: 27 additions & 6 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@ name: Python package
on: [push, pull_request]

jobs:
shellcheck:
name: lint shell code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
version: v0.11.0
lint:
name: lint python code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/ruff-action@v3
with:
src: "./src"
format:
name: format python code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/ruff-action@v3
with:
args: "format --check --diff"
src: "./src"
build:
name: python ${{ matrix.python-version }}, bitcoind ${{ matrix.bitcoind-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: [shellcheck, lint, format]
strategy:
matrix:
os: [macos-13, ubuntu-latest]
Expand All @@ -14,10 +41,6 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
version: v0.9.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand All @@ -35,8 +58,6 @@ jobs:
run: |
bash -x ./install.sh --develop --with-qt
./jmvenv/bin/python -m pip install --upgrade pip
- name: Lint with flake8
run: source ./jmvenv/bin/activate && ./test/lint/lint-python.sh
- name: Cache bitcoind
uses: actions/cache@v3
env:
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ See [architecture-notes.md](docs/architecture-notes.md).

Instructions for developers for testing [here](docs/TESTING.md). If you want to help improve the project, please have a read of [this todo list](docs/TODO.md) and the [Help Wanted tag](https://github.com/JoinMarket-Org/joinmarket-clientserver/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) on the issue tracker.

### CODE FORMATTING

This project uses [Ruff](https://docs.astral.sh/ruff/) for code formatting and linting. The configuration is defined in `pyproject.toml`. To format your code before submitting a pull request, run:

```bash
ruff format ./src
```

To check for linting issues:

```bash
ruff check
```

### Community

+ IRC: `#joinmarket` on irc.libera.chat https://kiwiirc.com/nextclient/irc.libera.chat#joinmarket (logs can be found [here](https://gnusha.org/joinmarket/))
Expand Down
41 changes: 39 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ services = [
test = [
"joinmarket[services]",
"coverage==5.2.1",
"flake8",
"freezegun",
"mock",
"pexpect",
"pytest-cov>=2.4.0,<2.6",
"pytest==6.2.5",
"python-coveralls",
"ruff",
]
gui = [
"joinmarket[services]",
Expand All @@ -76,4 +76,41 @@ where = ["src"]
exclude = ["*.test"]

[tool.pytest.ini_options]
testpaths = ["test"]
testpaths = ["test"]

[tool.ruff]
exclude = [
".svn",
"CVS",
".bzr",
".hg",
".git",
"__pycache__",
".tox",
"__init__.py",
"src/jmqtui",
]

line-length = 79

[tool.ruff.format]
quote-style = "preserve"

[tool.ruff.lint]
# should be extended once existing issues are fixed
select = [
"F", # pyflakes
"E9", # pycodestyle errors
"W6", # pycodestyle warnings
"C901" # complex-structure (mccabe complexity)
]

# those cause many errors, need to be fixed eventually
ignore = [
"F403", # star imports
"F405", # name may be undefined due to star import
"F841" # local variable assigned but never used
]

[tool.ruff.lint.mccabe]
max-complexity = 15
2 changes: 1 addition & 1 deletion scripts/add-utxo.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def generate_single_podle_sig(u, priv, i):
ecs[u]['reveal'][j] = {'P2':P2, 's':s, 'e':e}
add_external_commitments(ecs)

def main():
def main(): # noqa: C901
parser = OptionParser(
usage=
'usage: %prog [options] [txid:n]',
Expand Down
6 changes: 3 additions & 3 deletions scripts/joinmarket-qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@ def cleanUp(self):
self.tumbler_options = None
self.tumbler_destaddrs = None

def validateSingleSend(self):
def validateSingleSend(self): # noqa: C901
if not mainWindow.wallet_service:
JMQtMessageBox(self,
"There is no wallet loaded.",
Expand Down Expand Up @@ -1542,7 +1542,7 @@ def openAddressQRCodePopup(self, address):
bip21_uri = bip21_uri.upper()
self.openQRCodePopup(address, bip21_uri)

def updateWalletInfo(self, walletinfo=None):
def updateWalletInfo(self, walletinfo=None): # noqa: C901
max_mixdepth_count = jm_single().config.getint("GUI", "max_mix_depth")

previous_expand_states = []
Expand Down Expand Up @@ -1815,7 +1815,7 @@ def showAboutDialog(self):
lyt.addWidget(btnbox)
msgbox.exec_()

def exportPrivkeysJson(self):
def exportPrivkeysJson(self): # noqa: C901
if not self.wallet_service:
JMQtMessageBox(self,
"No wallet loaded.",
Expand Down
2 changes: 1 addition & 1 deletion scripts/obwatch/ob-watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ def create_sybil_resistance_page(self, btc_unit: str) -> Tuple[str, str]:

return heading2, mainbody

def create_orderbook_table(self, btc_unit: str, rel_unit: str) -> Tuple[int, str]:
def create_orderbook_table(self, btc_unit: str, rel_unit: str) -> Tuple[int, str]: # noqa: C901
result = ''
try:
self.taker.dblock.acquire(True)
Expand Down
2 changes: 1 addition & 1 deletion scripts/sendpayment.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def pick_order(orders, n): #pragma: no cover
return orders[pickedOrderIndex]
pickedOrderIndex = -1

def main():
def main(): # noqa: C901
parser = get_sendpayment_parser()
(options, args) = parser.parse_args()
load_program_config(config_path=options.datadir)
Expand Down
2 changes: 1 addition & 1 deletion scripts/snicker/snicker-recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def get_pubs_and_indices_of_ancestor_inputs(txin, wallet_service, ours):
tx = wallet_service.get_transaction(txin.prevout.hash[::-1])
return get_pubs_and_indices_of_inputs(tx, wallet_service, ours=ours)

def main():
def main(): # noqa: C901
parser = OptionParser(
usage=
'usage: %prog [options] walletname',
Expand Down
2 changes: 1 addition & 1 deletion scripts/tumbler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

log = get_log()

def main():
def main(): # noqa: C901
(options, args) = get_tumbler_parser().parse_args()
options_org = options
options = vars(options)
Expand Down
18 changes: 0 additions & 18 deletions setup.cfg

This file was deleted.

23 changes: 20 additions & 3 deletions src/jmbase/bigstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@


def split_string(x, size):
return list(x[i*size:(i+1)*size] for i in range((len(x)+size-1)//size))
return list(
x[i * size : (i + 1) * size]
for i in range((len(x) + size - 1) // size)
)


class StringList(amp.Argument):
def fromBox(self, name, strings, objects, proto):
nk = amp._wireNameToPythonIdentifier(name)
objects[nk] = list(itertools.takewhile(bool, (strings.pop(b'%s.%d' % (name, i), None) for i in itertools.count())))
objects[nk] = list(
itertools.takewhile(
bool,
(
strings.pop(b'%s.%d' % (name, i), None)
for i in itertools.count()
),
)
)

def toBox(self, name, strings, objects, proto):
for i, elem in enumerate(objects.pop(name)):
strings[b'%s.%d' % (name, i)] = elem


class BigString(StringList):
"""
A byte-string amp.Argument with no 65,535 length limit.
Expand All @@ -24,16 +37,20 @@ class BigString(StringList):
number of key/value pairs that are given automatic key
names by prefixing this Argument's key name to a counter.
"""

def fromBox(self, name, strings, objects, proto):
nk = amp._wireNameToPythonIdentifier(name)
StringList.fromBox(self, name, strings, objects, proto)
objects[nk] = b''.join((elem) for elem in objects[nk]).decode('utf-8')

def toBox(self, name, strings, objects, proto):
obj = self.retrieve(objects, amp._wireNameToPythonIdentifier(name), proto).encode('utf-8')
obj = self.retrieve(
objects, amp._wireNameToPythonIdentifier(name), proto
).encode('utf-8')
objects[name] = split_string(obj, amp.MAX_VALUE_LENGTH)
StringList.toBox(self, name, strings, objects, proto)


class BigUnicode(BigString):
def toString(self, inObject):
return BigString.toString(self, inObject.encode('utf-8'))
Expand Down
3 changes: 2 additions & 1 deletion src/jmbase/bytesprod.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from twisted.web.iweb import IBodyProducer
from zope.interface import implementer


@implementer(IBodyProducer)
class BytesProducer(object):
def __init__(self, body):
Expand All @@ -16,4 +17,4 @@ def pauseProducing(self):
pass

def stopProducing(self):
pass
pass
Loading
Loading