forked from MudwoodLabs/pyrxd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
430 lines (387 loc) · 14.1 KB
/
Copy pathpyproject.toml
File metadata and controls
430 lines (387 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "pyrxd"
version = "0.9.0"
description = "Python SDK for the Radiant (RXD) blockchain — transactions, HD wallets, Glyph tokens (NFT/FT/dMint), Gravity cross-chain atomic swaps, SPV, and ElectrumX."
authors = [
{name = "Mudwood Labs", email = "opensource@mudwoodlabs.com"}
]
license = "Apache-2.0"
readme = "README.md"
requires-python = ">=3.10,<4.0"
keywords = ["radiant", "rxd", "blockchain", "glyph", "nft", "ft", "dmint", "gravity", "atomic-swap", "bip32", "bip39", "bip44", "electrumx"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Security :: Cryptography",
"Topic :: Software Development :: Libraries :: Python Modules",
]
[project.urls]
Homepage = "https://github.com/Radiant-Core/pyrxd"
Documentation = "https://mudwoodlabs.github.io/pyrxd/"
Source = "https://github.com/Radiant-Core/pyrxd"
Tracker = "https://github.com/Radiant-Core/pyrxd/issues"
Changelog = "https://github.com/Radiant-Core/pyrxd/blob/main/CHANGELOG.md"
[project.scripts]
pyrxd = "pyrxd.cli.main:run"
pyrxd-miner = "pyrxd.contrib.miner.cli:main"
[tool.poetry]
packages = [{include = "pyrxd", from = "src"}]
include = [
{ path = "src/pyrxd/gravity/artifacts/*.json", format = ["sdist", "wheel"] },
{ path = "src/pyrxd/hd/wordlist/*.txt", format = ["sdist", "wheel"] },
{ path = "src/pyrxd/py.typed", format = ["sdist", "wheel"] },
{ path = "src/pyrxd/contrib/miner/README.md", format = ["sdist", "wheel"] },
]
[tool.taskipy.tasks]
# `task lint` runs the same set CI runs: ruff (style + safety) and
# bandit (security smell detection on src/). Catch failures locally
# before pushing.
lint = "ruff check src tests examples && bandit -r src/ -c pyproject.toml --quiet"
format = "ruff format src tests examples"
format-check = "ruff format --check src tests examples"
test = "pytest"
typecheck = "mypy src/pyrxd/security/"
coverage-security = 'pytest tests/security/ -o "addopts=" --cov=pyrxd.security --cov-fail-under=100'
coverage-overall = 'pytest tests/ -o "addopts=" --cov=pyrxd --cov-fail-under=85'
# `task check-private-links` ensures tracked docs (and staged additions) don't
# link to gitignored paths. Catches leaks where a public doc references a
# private design doc — broken links + leaked filenames in commit history.
check-private-links = "python3 scripts/check-no-private-links.py"
# `task ci` runs the full set of checks GitHub Actions runs (.github/workflows/{lint,ci}.yml)
# plus local-only guardrails (private-link check). Run before pushing to avoid
# the push-fail-fix-push loop. ~3-5 min.
# For a faster pre-commit pass, just run `task lint && task format-check && task test`.
ci = "task lint && task format-check && task test && task coverage-security && task coverage-overall && task typecheck && task check-private-links"
docs = "sphinx-build -b html docs docs/_build/html"
docs-clean = "rm -rf docs/_build"
docs-serve = "sphinx-autobuild docs docs/_build/html --open-browser"
[tool.poetry.dependencies]
aiohttp = ">=3.14.1"
coincurve = "^21.0.0"
base58 = "^2.1.1"
pycryptodomex = "^3.23.0"
# Required for X25519 ECDH + HKDF-SHA256 used by Glyph TIMELOCK / ENCRYPTED
# (pyrxd.crypto.kem). OpenSSL-backed, well-audited, typically already a
# transitive dep in most environments.
cryptography = ">=49.0.0"
websockets = ">=16.0,<17.0.0"
cbor2 = "^6.1"
click = "^8.4"
# tomllib backport for Python 3.10 (stdlib tomllib lands in 3.11).
# pyrxd.cli.config falls back to this when running on 3.10.
tomli = {version = "^2.4", python = "<3.11"}
[tool.poetry.group.dev.dependencies]
taskipy = "^1.14.1"
bandit = {version = "^1.9.4", extras = ["toml"]}
pip-audit = "^2.10.1"
detect-secrets = "^1.5.0"
mypy = "^2.1.0"
ruff = "^0.15.17"
hypothesis = "^6.155.2"
pytest-asyncio = ">=1.4.0"
[tool.poetry.group.test.dependencies]
bandit = {version = "^1.9.4", extras = ["toml"]}
check-manifest = "0.51"
pre-commit = "4.6.0"
pytest-cov = "7.1.0"
pytest-mock = "<3.10.1"
pytest-runner = "*"
pytest = ">=9.1.0"
pytest-github-actions-annotate-failures = "*"
shellcheck-py = "0.11.0.1"
[tool.bandit]
exclude_dirs = ["tests"]
skips = []
[tool.mypy]
strict = true
python_version = "3.10"
exclude = ["tests/"]
[tool.ruff]
target-version = "py310"
line-length = 120
extend-exclude = ["docs/_build"]
[tool.ruff.lint]
# Enabled rule families:
# E, W pycodestyle errors / warnings
# F pyflakes (unused imports/vars, undefined names)
# I isort (import ordering, including the required `from __future__ import annotations`)
# B flake8-bugbear (likely bugs)
# UP pyupgrade (modern Python idioms)
# SIM flake8-simplify (clearer rewrites)
# RUF ruff-specific rules
# S flake8-bandit subset (security smell detection)
# FA flake8-future-annotations (require `from __future__ import annotations` so X|Y unions work on 3.10)
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "S", "FA"]
ignore = [
"E501", # line-too-long: ruff format handles wrapping; long URLs/strings are OK
"E741", # ambiguous variable name 'l' / 'I' — sometimes the natural choice in math
"E702", # multiple statements on one line with semicolon — used in compact byte parsers
"S101", # assert: legitimate in src/ where used as runtime invariants
"S105", # hardcoded password string false positives on BIP39 passphrase=""
"S110", # try/except/pass — flagged when the exception is intentional swallow with explanatory comment
"S324", # SHA1 used in BIP32 (per spec, not for security)
"SIM105", # contextlib.suppress: explicit try/except is fine and avoids an import
"SIM108", # if/else as ternary — sometimes the long form is clearer
"SIM117", # combine nested with: not always readable
"B008", # function call in default arg: used intentionally in PrivateKey() / dataclass defaults
"B904", # raise-without-from-inside-except: chain suppression often intentional in this codebase
"B905", # zip without explicit strict — codebase predates Python 3.10's strict argument
"RUF001", # ambiguous unicode in strings (em dash, en dash) — intentional in error messages
"RUF002", # ambiguous unicode in docstrings — intentional in writing
"RUF003", # ambiguous unicode in comments — intentional in writing
"RUF012", # mutable class default — accepted pattern for dataclass defaults_factory
"RUF013", # implicit Optional from = None — codebase uses this style consistently
"RUF043", # pytest-raises-ambiguous-pattern — flagged on regex backslashes that are correct
]
[tool.ruff.lint.per-file-ignores]
"tests/**" = [
"S", # whole bandit family — secrets, hardcoded values, etc. are expected in tests
"B011", # assert False: legit in tests
"B017", # assert raises Exception: tests intentionally check broad failure modes
"E402", # module-level imports below other code: tests group imports near related test classes
]
"tests/test_spv_covenant_differential_regtest.py" = [
"F811", # reuses the shared `node` regtest fixture from test_htlc_regtest_e2e; pytest's
# fixture-injection (each test's `node` param) reads as a redefinition to ruff
]
"tests/test_soulbound_covenant_regtest.py" = [
"F811", # same: reuses the shared `node` regtest fixture from test_htlc_regtest_e2e
]
"tests/test_dmint_v1_regtest_e2e.py" = [
"F811", # same: reuses the shared `node` regtest fixture from test_htlc_regtest_e2e
]
"tests/test_dmint_v2_regtest_e2e.py" = [
"F811", # same: reuses the shared `node` regtest fixture from test_htlc_regtest_e2e
]
"examples/**" = [
"S", # examples use test keys / hardcoded WIFs intentionally
"E402", # examples may reorder imports for narrative clarity
]
"scripts/**" = [
"S603", # subprocess call with non-shell args: dev scripts trust their own argv
"S607", # partial executable path: `git`, `python3` etc. resolved via PATH at dev time
]
"src/pyrxd/devnet.py" = [
"S603", # the module's whole purpose is shelling the docker CLI with fully-controlled list-form argv
"S607", # `docker` resolved via PATH (guarded by shutil.which up front)
]
"docs/conf.py" = ["E", "F", "I"]
[tool.ruff.lint.isort]
required-imports = ["from __future__ import annotations"]
[tool.ruff.format]
# Match the existing 120-char line length. ruff format is a black-compatible
# formatter; output is byte-identical to black for ~99.9% of code.
line-ending = "lf"
[tool.coverage.run]
branch = true
omit = [
# ETH counter-chain leg, NETWORK I/O ONLY. These modules talk to a live web3 node (rpc.py is
# 100% transport; htlc_leg/private_submit's on-chain-call paths build+sign+broadcast txs) and
# are exercised by the Phase-4 Anvil/Sepolia integration suite (tests/test_eth_*anvil*.py,
# `-m integration`), which CI does not yet run. The PURE, security-critical logic in these
# files IS unit-tested offline and SHOULD stay measured once the integration suite runs in CI:
# keys.py (88%) secret.py (90%) locator.py (71%) — measured here via test_eth_leg.py,
# test_finality_verdict.py, test_eth_private_submit.py; provenance / finality-verdict /
# locator-equality / Flashbots header-signing logic is covered, only the network calls are not.
# counter_chain_leg.py (the pure ABC) is intentionally NOT omitted — it is 100% covered offline.
# Narrow this to rpc.py + the on-chain-call methods (via `# pragma: no cover`) when Anvil runs in CI.
"src/pyrxd/eth_wallet/*",
]
[tool.coverage.report]
fail_under = 85
[tool.pyright]
include = ["src"]
exclude = [
"**/node_modules",
"**/__pycache__",
]
venv = "env"
reportMissingImports = true
reportMissingTypeStubs = false
pythonVersion = "3.10"
pythonPlatform = "Linux"
executionEnvironments = [
{ root = "src" }
]
[tool.pytest.ini_options]
addopts = "--cov-report xml:coverage.xml --cov src --cov-fail-under 0 --cov-append -m 'not integration'"
pythonpath = [
"src"
]
testpaths = "tests"
junit_family = "xunit2"
asyncio_mode = "auto"
markers = [
"integration: marks as integration test",
"notebooks: marks as notebook test",
"gpu: marks as gpu test",
"slow: marks tests as slow",
"unit: fast offline tests",
]
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py, integration
[testenv]
commands =
pytest -m "not integration" {posargs}
[testenv:integration]
commands =
pytest -m "integration" {posargs}
"""
[tool.pylint]
extension-pkg-whitelist= [
"numpy",
"torch",
"cv2",
"pyodbc",
"pydantic",
"ciso8601",
"netcdf4",
"scipy"
]
ignore="CVS"
ignore-patterns="test.*?py,conftest.py"
init-hook='import sys; sys.setrecursionlimit(8 * sys.getrecursionlimit())'
jobs=0
limit-inference-results=100
persistent="yes"
suggestion-mode="yes"
unsafe-load-any-extension="no"
[tool.pylint.'MESSAGES CONTROL']
enable="c-extension-no-member"
[tool.pylint.'REPORTS']
evaluation="10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)"
output-format="text"
reports="no"
score="yes"
[tool.pylint.'REFACTORING']
max-nested-blocks=5
never-returning-functions="sys.exit"
[tool.pylint.'BASIC']
argument-naming-style="snake_case"
attr-naming-style="snake_case"
bad-names= [
"foo",
"bar"
]
class-attribute-naming-style="any"
class-naming-style="PascalCase"
const-naming-style="UPPER_CASE"
docstring-min-length=-1
function-naming-style="snake_case"
good-names= [
"i",
"j",
"k",
"ex",
"Run",
"_"
]
include-naming-hint="yes"
inlinevar-naming-style="any"
method-naming-style="snake_case"
module-naming-style="any"
no-docstring-rgx="^_"
property-classes="abc.abstractproperty"
variable-naming-style="snake_case"
[tool.pylint.'FORMAT']
ignore-long-lines="^\\s*(# )?.*['\"]?<?https?://\\S+>?"
indent-after-paren=4
indent-string=' '
max-line-length=120
max-module-lines=1000
single-line-class-stmt="no"
single-line-if-stmt="no"
[tool.pylint.'LOGGING']
logging-format-style="old"
logging-modules="logging"
[tool.pylint.'MISCELLANEOUS']
notes= [
"FIXME",
"XXX",
"TODO"
]
[tool.pylint.'SIMILARITIES']
ignore-comments="yes"
ignore-docstrings="yes"
ignore-imports="yes"
min-similarity-lines=7
[tool.pylint.'SPELLING']
max-spelling-suggestions=4
spelling-store-unknown-words="no"
[tool.pylint.'STRING']
check-str-concat-over-line-jumps="no"
[tool.pylint.'TYPECHECK']
contextmanager-decorators="contextlib.contextmanager"
generated-members="numpy.*,np.*"
ignore-mixin-members="yes"
ignore-none="yes"
ignore-on-opaque-inference="yes"
ignored-classes="optparse.Values,thread._local,_thread._local,numpy,torch,swagger_client"
ignored-modules="numpy,torch,swagger_client,netCDF4,scipy"
missing-member-hint="yes"
missing-member-hint-distance=1
missing-member-max-choices=1
[tool.pylint.'VARIABLES']
additional-builtins="dbutils"
allow-global-unused-variables="yes"
callbacks= [
"cb_",
"_cb"
]
dummy-variables-rgx="_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_"
ignored-argument-names="_.*|^ignored_|^unused_"
init-import="no"
redefining-builtins-modules="six.moves,past.builtins,future.builtins,builtins,io"
[tool.pylint.'CLASSES']
defining-attr-methods= [
"__init__",
"__new__",
"setUp",
"__post_init__"
]
exclude-protected= [
"_asdict",
"_fields",
"_replace",
"_source",
"_make"
]
valid-classmethod-first-arg="cls"
valid-metaclass-classmethod-first-arg="cls"
[tool.pylint.'DESIGN']
max-args=5
max-attributes=7
max-bool-expr=5
max-branches=12
max-locals=15
max-parents=7
max-public-methods=20
max-returns=6
max-statements=50
min-public-methods=2
[tool.pylint.'IMPORTS']
allow-wildcard-with-all="no"
analyse-fallback-blocks="no"
deprecated-modules="optparse,tkinter.tix"
[tool.pylint.'EXCEPTIONS']
overgeneral-exceptions= [
"BaseException",
"Exception"
]
[tool.poetry.group.docs.dependencies]
sphinx = "^8.1.3"
furo = "^2025.12"
myst-parser = "^4.0.1"
sphinx-copybutton = "^0.5.2"
sphinx-autobuild = "^2024.10.3"