diff --git a/.github/workflows/label-merge-conflicts.yml b/.github/workflows/label-merge-conflicts.yml index c6d4ad742d00..b804b36c5138 100644 --- a/.github/workflows/label-merge-conflicts.yml +++ b/.github/workflows/label-merge-conflicts.yml @@ -11,11 +11,12 @@ on: permissions: contents: read pull-requests: write + # issues: write is required so the action can manage labels and post comments on PRs + issues: write # Enforce other not needed permissions are off actions: none checks: none deployments: none - issues: none #metadata: read packages: none repository-projects: none @@ -27,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: check if prs are dirty - uses: eps1lon/actions-label-merge-conflict@releases/2.x + uses: eps1lon/actions-label-merge-conflict@v3.1.0 with: dirtyLabel: "needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/merge-check.yml b/.github/workflows/merge-check.yml index 7fc2e22a8705..1a3029a8bbe3 100644 --- a/.github/workflows/merge-check.yml +++ b/.github/workflows/merge-check.yml @@ -1,7 +1,10 @@ name: Check Merge Fast-Forward Only permissions: + contents: read pull-requests: write + # Required so we can apply labels to PRs (labels go through the issues API) + issues: write on: push: @@ -42,11 +45,16 @@ jobs: fi - name: add labels - uses: actions-ecosystem/action-add-labels@v1 - if: failure() + uses: actions/github-script@v8 + if: failure() && github.event.pull_request with: - labels: | - needs rebase + script: | + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: ['needs rebase'] + }); - name: comment uses: mshick/add-pr-comment@v2 diff --git a/.github/workflows/release_docker_hub.yml b/.github/workflows/release_docker_hub.yml index f4e1775a7859..fb465665cfdb 100644 --- a/.github/workflows/release_docker_hub.yml +++ b/.github/workflows/release_docker_hub.yml @@ -33,7 +33,7 @@ jobs: echo "build_tag=${TAG#v}" >> $GITHUB_OUTPUT - name: Set suffix - uses: actions/github-script@v6 + uses: actions/github-script@v8 id: suffix with: result-encoding: string diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 3ad1d1a4d93d..191a34f37381 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -12,7 +12,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v5 + - uses: amannn/action-semantic-pull-request@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/configure.ac b/configure.ac index 91beef9b689a..fb96e7d29cec 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ AC_PREREQ([2.69]) dnl Don't forget to push a corresponding tag when updating any of _CLIENT_VERSION_* numbers define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 1) -define(_CLIENT_VERSION_BUILD, 7) +define(_CLIENT_VERSION_BUILD, 8) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2026) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/contrib/containers/ci/ci-slim.Dockerfile b/contrib/containers/ci/ci-slim.Dockerfile index 5332f8504a57..8436fd14dfa3 100644 --- a/contrib/containers/ci/ci-slim.Dockerfile +++ b/contrib/containers/ci/ci-slim.Dockerfile @@ -81,7 +81,6 @@ RUN uv pip install --system --break-system-packages \ flake8==5.0.4 \ jinja2 \ lief==0.13.2 \ - multiprocess \ mypy==0.981 \ pyzmq==24.0.1 \ vulture==2.6 diff --git a/contrib/devtools/circular-dependencies.py b/contrib/devtools/circular-dependencies.py index e939ec6d45ba..a6cdc343d5cb 100755 --- a/contrib/devtools/circular-dependencies.py +++ b/contrib/devtools/circular-dependencies.py @@ -5,7 +5,7 @@ import sys import re -from multiprocess import Pool # type: ignore[import] +import multiprocessing from typing import Dict, List, Set MAPPING = { @@ -33,10 +33,32 @@ def module_name(path): return path[:-4] return None -if __name__=="__main__": - files = dict() - deps: Dict[str, Set[str]] = dict() +files = dict() +deps: Dict[str, Set[str]] = dict() + +# Defined at module level (reading the global `deps`) so it pickles by reference +# for multiprocessing.Pool; forked workers inherit the populated `deps`. +def handle_module2(module): + # Build the transitive closure of dependencies of module + closure: Dict[str, List[str]] = dict() + for dep in deps[module]: + closure[dep] = [] + while True: + old_size = len(closure) + old_closure_keys = sorted(closure.keys()) + for src in old_closure_keys: + for dep in deps[src]: + if dep not in closure: + closure[dep] = closure[src] + [src] + if len(closure) == old_size: + break + # If module is in its own transitive closure, it's a circular dependency; check if it is the shortest + if module in closure: + return [module] + closure[module] + + return None +if __name__=="__main__": RE = re.compile("^#include <(.*)>") def handle_module(arg): @@ -47,27 +69,6 @@ def handle_module(arg): files[arg] = module deps[module] = set() - def handle_module2(module): - # Build the transitive closure of dependencies of module - closure: Dict[str, List[str]] = dict() - for dep in deps[module]: - closure[dep] = [] - while True: - old_size = len(closure) - old_closure_keys = sorted(closure.keys()) - for src in old_closure_keys: - for dep in deps[src]: - if dep not in closure: - closure[dep] = closure[src] + [src] - if len(closure) == old_size: - break - # If module is in its own transitive closure, it's a circular dependency; check if it is the shortest - if module in closure: - return [module] + closure[module] - - return None - - # Iterate over files, and create list of modules for arg in sys.argv[1:]: handle_module(arg) @@ -101,8 +102,14 @@ def shortest_c_dep(): if sorted_keys is None: sorted_keys = sorted(deps.keys()) - with Pool(8) as pool: - cycles = pool.map(handle_module2, sorted_keys) + # Use fork so workers inherit the populated `deps` global without + # having to pickle it for every task. fork is unavailable on + # Windows, so fall back to a serial map there. + if "fork" in multiprocessing.get_all_start_methods(): + with multiprocessing.get_context("fork").Pool(8) as pool: + cycles = pool.map(handle_module2, sorted_keys) + else: + cycles = list(map(handle_module2, sorted_keys)) for cycle in cycles: if cycle is not None and (shortest_cycles is None or len(cycle) < len(shortest_cycles)): diff --git a/contrib/flatpak/org.dash.dash-core.metainfo.xml b/contrib/flatpak/org.dash.dash-core.metainfo.xml index 30227f6b4771..66e742659443 100644 --- a/contrib/flatpak/org.dash.dash-core.metainfo.xml +++ b/contrib/flatpak/org.dash.dash-core.metainfo.xml @@ -21,6 +21,7 @@ + diff --git a/depends/packages/freetype.mk b/depends/packages/freetype.mk index fef0beaa7b49..a97f82e7feae 100644 --- a/depends/packages/freetype.mk +++ b/depends/packages/freetype.mk @@ -4,6 +4,7 @@ $(package)_download_path=https://download.savannah.gnu.org/releases/$(package) $(package)_file_name=$(package)-$($(package)_version).tar.xz $(package)_sha256_hash=8bee39bd3968c4804b70614a0a3ad597299ad0e824bc8aad5ce8aaf48067bde7 $(package)_build_subdir=build +$(package)_patches += cmake_minimum.patch define $(package)_set_vars $(package)_config_opts := -DCMAKE_BUILD_TYPE=None -DBUILD_SHARED_LIBS=TRUE @@ -12,6 +13,10 @@ define $(package)_set_vars $(package)_config_opts += -DCMAKE_DISABLE_FIND_PACKAGE_BrotliDec=TRUE endef +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/cmake_minimum.patch +endef + define $(package)_config_cmds $($(package)_cmake) -S .. -B . endef diff --git a/depends/patches/freetype/cmake_minimum.patch b/depends/patches/freetype/cmake_minimum.patch new file mode 100644 index 000000000000..0a976f8ab8d9 --- /dev/null +++ b/depends/patches/freetype/cmake_minimum.patch @@ -0,0 +1,13 @@ +build: set minimum required CMake to 3.12 + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -97,7 +97,7 @@ + # FreeType explicitly marks the API to be exported and relies on the compiler + # to hide all other symbols. CMake supports a C_VISBILITY_PRESET property + # starting with 2.8.12. +-cmake_minimum_required(VERSION 2.8.12) ++cmake_minimum_required(VERSION 3.12) + + if (NOT CMAKE_VERSION VERSION_LESS 3.3) + # Allow symbol visibility settings also on static libraries. CMake < 3.3 diff --git a/doc/man/dash-cli.1 b/doc/man/dash-cli.1 index 2d5739f408f7..8bf92ce8161b 100644 --- a/doc/man/dash-cli.1 +++ b/doc/man/dash-cli.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-CLI "1" "June 2026" "dash-cli v23.1.7" "User Commands" +.TH DASH-CLI "1" "July 2026" "dash-cli v23.1.8" "User Commands" .SH NAME -dash-cli \- manual page for dash-cli v23.1.7 +dash-cli \- manual page for dash-cli v23.1.8 .SH SYNOPSIS .B dash-cli [\fI\,options\/\fR] \fI\, \/\fR[\fI\,params\/\fR] \fI\,Send command to Dash Core\/\fR @@ -15,7 +15,7 @@ dash-cli \- manual page for dash-cli v23.1.7 .B dash-cli [\fI\,options\/\fR] \fI\,help Get help for a command\/\fR .SH DESCRIPTION -Dash Core RPC client version v23.1.7 +Dash Core RPC client version v23.1.8 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-qt.1 b/doc/man/dash-qt.1 index d906b5960f6c..00cba193fda8 100644 --- a/doc/man/dash-qt.1 +++ b/doc/man/dash-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-QT "1" "June 2026" "dash-qt v23.1.7" "User Commands" +.TH DASH-QT "1" "July 2026" "dash-qt v23.1.8" "User Commands" .SH NAME -dash-qt \- manual page for dash-qt v23.1.7 +dash-qt \- manual page for dash-qt v23.1.8 .SH SYNOPSIS .B dash-qt [\fI\,command-line options\/\fR] [\fI\,URI\/\fR] .SH DESCRIPTION -Dash Core version v23.1.7 +Dash Core version v23.1.8 .PP Optional URI is a Dash address in BIP21 URI format. .SH OPTIONS @@ -128,13 +128,13 @@ Do not keep transactions in the mempool longer than hours (default: .HP \fB\-par=\fR .IP -Set the number of script verification threads (\fB\-14\fR to 15, 0 = auto, <0 = -leave that many cores free, default: 0) +Set the number of script verification threads (0 = auto, <0 = leave that +many cores free, max: 15, default: 0) .HP \fB\-parbls=\fR .IP -Set the number of BLS verification threads (\fB\-14\fR to 33, 0 = auto, <0 = -leave that many cores free, default: 0) +Set the number of BLS verification threads (0 = auto, <0 = leave that +many cores free, max: 33, default: 0) .HP \fB\-persistmempool\fR .IP diff --git a/doc/man/dash-tx.1 b/doc/man/dash-tx.1 index fa7fc530064b..7b587eeb9f19 100644 --- a/doc/man/dash-tx.1 +++ b/doc/man/dash-tx.1 @@ -1,7 +1,7 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-TX "1" "June 2026" "dash-tx v23.1.7" "User Commands" +.TH DASH-TX "1" "July 2026" "dash-tx v23.1.8" "User Commands" .SH NAME -dash-tx \- manual page for dash-tx v23.1.7 +dash-tx \- manual page for dash-tx v23.1.8 .SH SYNOPSIS .B dash-tx [\fI\,options\/\fR] \fI\, \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded dash transaction\/\fR @@ -9,7 +9,7 @@ dash-tx \- manual page for dash-tx v23.1.7 .B dash-tx [\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded dash transaction\/\fR .SH DESCRIPTION -Dash Core dash\-tx utility version v23.1.7 +Dash Core dash\-tx utility version v23.1.8 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-util.1 b/doc/man/dash-util.1 index f1a1e4ccf654..3a47b0b8037a 100644 --- a/doc/man/dash-util.1 +++ b/doc/man/dash-util.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-UTIL "1" "June 2026" "dash-util v23.1.7" "User Commands" +.TH DASH-UTIL "1" "July 2026" "dash-util v23.1.8" "User Commands" .SH NAME -dash-util \- manual page for dash-util v23.1.7 +dash-util \- manual page for dash-util v23.1.8 .SH SYNOPSIS .B dash-util [\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR .SH DESCRIPTION -Dash Core dash\-util utility version v23.1.7 +Dash Core dash\-util utility version v23.1.8 .SH OPTIONS .HP \-? diff --git a/doc/man/dash-wallet.1 b/doc/man/dash-wallet.1 index b942f301f180..ffeaccdb710f 100644 --- a/doc/man/dash-wallet.1 +++ b/doc/man/dash-wallet.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASH-WALLET "1" "June 2026" "dash-wallet v23.1.7" "User Commands" +.TH DASH-WALLET "1" "July 2026" "dash-wallet v23.1.8" "User Commands" .SH NAME -dash-wallet \- manual page for dash-wallet v23.1.7 +dash-wallet \- manual page for dash-wallet v23.1.8 .SH DESCRIPTION -Dash Core dash\-wallet version v23.1.7 +Dash Core dash\-wallet version v23.1.8 .PP dash\-wallet is an offline tool for creating and interacting with Dash Core wallet files. By default dash\-wallet will act on wallets in the default mainnet wallet directory in the datadir. diff --git a/doc/man/dashd.1 b/doc/man/dashd.1 index 011b448337c3..3f2e77656af2 100644 --- a/doc/man/dashd.1 +++ b/doc/man/dashd.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3. -.TH DASHD "1" "June 2026" "dashd v23.1.7" "User Commands" +.TH DASHD "1" "July 2026" "dashd v23.1.8" "User Commands" .SH NAME -dashd \- manual page for dashd v23.1.7 +dashd \- manual page for dashd v23.1.8 .SH SYNOPSIS .B dashd [\fI\,options\/\fR] \fI\,Start Dash Core\/\fR .SH DESCRIPTION -Dash Core version v23.1.7 +Dash Core version v23.1.8 .SH OPTIONS .HP \-? @@ -126,13 +126,13 @@ Do not keep transactions in the mempool longer than hours (default: .HP \fB\-par=\fR .IP -Set the number of script verification threads (\fB\-14\fR to 15, 0 = auto, <0 = -leave that many cores free, default: 0) +Set the number of script verification threads (0 = auto, <0 = leave that +many cores free, max: 15, default: 0) .HP \fB\-parbls=\fR .IP -Set the number of BLS verification threads (\fB\-14\fR to 33, 0 = auto, <0 = -leave that many cores free, default: 0) +Set the number of BLS verification threads (0 = auto, <0 = leave that +many cores free, max: 33, default: 0) .HP \fB\-persistmempool\fR .IP diff --git a/doc/release-notes.md b/doc/release-notes.md index 5e4e5e7628b3..4bd06422ada9 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,7 +1,7 @@ -# Dash Core version v23.1.7 +# Dash Core version v23.1.8 -This is a new patch version release, bringing security hardening and build fixes -for newer compiler toolchains. +This is a new patch version release, bringing further hardening of the +peer-to-peer message handlers along with networking, RPC and build fixes. This release is **recommended** for all nodes, and especially for masternodes. Please report bugs using the issue tracker at GitHub: @@ -28,32 +28,71 @@ require a reindex. ## Security -This release hardens several peer-to-peer message handlers against +This release continues the hardening of peer-to-peer message handlers against denial-of-service from remote peers. These issues do not affect consensus and do not put funds at risk, but they could be used to crash or degrade nodes - masternodes in particular - so upgrading is recommended. -- Networking: a peer whose receive buffer filled up could keep the socket-handler - thread spinning at 100% CPU for the duration of the backpressure. The thread now - falls back to its normal poll wait while such peers are paused. -- LLMQ / DKG: pushed DKG messages are now accepted only from verified masternodes, - are bounded in size, and are structurally validated before being retained; - malformed signatures can no longer trigger an assertion failure during batch - signature verification. -- BLS: verifying a DKG contribution share whose verification vector was never - received no longer dereferences a null pointer. -- InstantSend: locks with an oversized input set are now rejected before any - expensive processing, and the queues holding not-yet-verified and - awaiting-transaction locks are bounded to prevent unbounded memory growth. -- Governance: vote-sync requests carrying a bloom filter outside the permitted size - are rejected, preventing a CPU-amplification stall of P2P message processing. - -## Build - -- Fixed GCC 16 build failures in warning-enabled builds by tightening header - includes and initializing LevelDB compaction output size. - -# v23.1.7 Change log +- LLMQ / signing: the queues of not-yet-verified recovered signatures and + signature shares are now bounded, and the vectors carried by the QSIGSHARE, + QSIGSESANN, QSIGSHARESINV, QGETSIGSHARES and QBSIGSHARES messages are bounded + before any allocation or decoding takes place. The number of signing share + sessions a single peer may announce is also capped, so a peer can no longer + grow that per-peer state without limit (dash#7351). +- LLMQ / DKG: the number of encrypted contribution blobs in a DKG contribution + is now checked against the quorum's lower bound as well as its upper bound. +- LLMQ / quorum data: the verification vector and encrypted contribution + vectors in QDATA responses are validated against their expected sizes before + any BLS decoding is performed. +- Transaction relay: an oversized `notfound` message is now penalised rather + than silently ignored (dash#7348). +- ChainLocks: the cache of seen ChainLock signatures is now bounded. +- Governance: per-object vote sync requests are now throttled per peer, and + governance object and vote responses are only accepted from a peer if that + peer announced them or they were requested from it, using the net-layer + per-peer request tracker. Governance vote signatures are bounded when read + from the network and must use one of the two legitimate encodings. +- CoinJoin: the vectors carried by CoinJoin mixing messages are bounded before + allocation, and a non-participant can no longer abort another session's + signing phase. An invalid `dstx` message now carries a misbehaviour score + instead of being dropped for free (dash#7347). +- Bloom filters: filterload and filteradd payloads are bounded before + allocation. +- Sporks: spork signatures are bounded during deserialization, and malformed + spork messages now attribute misbehaviour to the sending peer. +- Compact block relay: batched hardening backported from upstream Bitcoin Core + (dash#7398), including detection of mutated blocks as a defence-in-depth + measure. + +## RPC + +- `protx listdiff` no longer reports an always-zero `platformP2PPort` / + `platformHTTPPort` for masternodes registered with extended addresses; the + live Platform ports are reported instead. + +## GUI + +- The PoSe score column is no longer hidden together with banned masternodes in + the masternode list. +- Fixed an abort when scaling widgets whose font was set in pixels rather than + points (for example by a stylesheet's `font-size: Npx`); such fonts are now + converted to a point size instead of being assumed to have one (dash#7465). + +## Build and CI + +- Fixed a CMake compatibility error when building the freetype dependency with + newer CMake (dash#7372). +- Stabilized the `-par` / `-parbls` help text (and the generated man pages) so + they no longer embed the core count of the build machine. +- Updated GitHub Actions pins for the Node 24 runtime. +- Fixed the circular-dependencies lint script under Python 3.15. + +## Tests + +- Governance inventory cache coverage moved from a functional test to unit + tests, and governance vote test fixtures are now wire-valid. + +# v23.1.8 Change log See detailed [set of changes][set-of-changes]. @@ -61,8 +100,10 @@ See detailed [set of changes][set-of-changes]. Thanks to everyone who directly contributed to this release: -- knst +- Konstantin Akimov +- PastaClaw - PastaPastaPasta +- UdjinM6 As well as everyone that submitted issues, reviewed pull requests and helped debug the release candidates. @@ -71,6 +112,7 @@ debug the release candidates. These releases are considered obsolete. Old release notes can be found here: +- [v23.1.7](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.7.md) released Jun/30/2026 - [v23.1.5](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.5.md) released Jun/19/2026 - [v23.1.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.4.md) released Jun/18/2026 - [v23.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.3.md) released May/28/2026 @@ -89,4 +131,4 @@ These releases are considered obsolete. Old release notes can be found here: - [v21.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md) released Jul/25/2024 - [v20.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.1.md) released April/3/2024 -[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.5...dashpay:v23.1.7 +[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.7...dashpay:v23.1.8 diff --git a/doc/release-notes/dash/release-notes-23.1.7.md b/doc/release-notes/dash/release-notes-23.1.7.md new file mode 100644 index 000000000000..5e4e5e7628b3 --- /dev/null +++ b/doc/release-notes/dash/release-notes-23.1.7.md @@ -0,0 +1,92 @@ +# Dash Core version v23.1.7 + +This is a new patch version release, bringing security hardening and build fixes +for newer compiler toolchains. +This release is **recommended** for all nodes, and especially for masternodes. + +Please report bugs using the issue tracker at GitHub: + + + +# Upgrading and downgrading + +## How to Upgrade + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over /Applications/Dash-Qt (on Mac) or +dashd/dash-qt (on Linux). + +## Downgrade warning + +### Downgrade to a version < v23.0.0 + +Downgrading to a version older than v23.0.0 is not supported, and will +require a reindex. + +# Release Notes + +## Security + +This release hardens several peer-to-peer message handlers against +denial-of-service from remote peers. These issues do not affect consensus and do +not put funds at risk, but they could be used to crash or degrade nodes - +masternodes in particular - so upgrading is recommended. + +- Networking: a peer whose receive buffer filled up could keep the socket-handler + thread spinning at 100% CPU for the duration of the backpressure. The thread now + falls back to its normal poll wait while such peers are paused. +- LLMQ / DKG: pushed DKG messages are now accepted only from verified masternodes, + are bounded in size, and are structurally validated before being retained; + malformed signatures can no longer trigger an assertion failure during batch + signature verification. +- BLS: verifying a DKG contribution share whose verification vector was never + received no longer dereferences a null pointer. +- InstantSend: locks with an oversized input set are now rejected before any + expensive processing, and the queues holding not-yet-verified and + awaiting-transaction locks are bounded to prevent unbounded memory growth. +- Governance: vote-sync requests carrying a bloom filter outside the permitted size + are rejected, preventing a CPU-amplification stall of P2P message processing. + +## Build + +- Fixed GCC 16 build failures in warning-enabled builds by tightening header + includes and initializing LevelDB compaction output size. + +# v23.1.7 Change log + +See detailed [set of changes][set-of-changes]. + +# Credits + +Thanks to everyone who directly contributed to this release: + +- knst +- PastaPastaPasta + +As well as everyone that submitted issues, reviewed pull requests and helped +debug the release candidates. + +# Older releases + +These releases are considered obsolete. Old release notes can be found here: + +- [v23.1.5](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.5.md) released Jun/19/2026 +- [v23.1.4](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.4.md) released Jun/18/2026 +- [v23.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.3.md) released May/28/2026 +- [v23.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.2.md) released Mar/12/2026 +- [v23.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.1.0.md) released Feb/15/2026 +- [v23.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.2.md) released Dec/4/2025 +- [v23.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-23.0.0.md) released Nov/10/2025 +- [v22.1.3](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.3.md) released Jul/15/2025 +- [v22.1.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.2.md) released Apr/15/2025 +- [v22.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.1.md) released Feb/17/2025 +- [v22.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.1.0.md) released Feb/10/2025 +- [v22.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-22.0.0.md) released Dec/12/2024 +- [v21.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.1.md) released Oct/22/2024 +- [v21.1.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.1.0.md) released Aug/8/2024 +- [v21.0.2](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.2.md) released Aug/1/2024 +- [v21.0.0](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-21.0.0.md) released Jul/25/2024 +- [v20.1.1](https://github.com/dashpay/dash/blob/master/doc/release-notes/dash/release-notes-20.1.1.md) released April/3/2024 + +[set-of-changes]: https://github.com/dashpay/dash/compare/v23.1.5...dashpay:v23.1.7 diff --git a/src/Makefile.am b/src/Makefile.am index 55ef13001b68..19f972054635 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -505,7 +505,6 @@ libbitcoin_node_a_SOURCES = \ chainlock/signing.cpp \ coinjoin/coinjoin.cpp \ coinjoin/server.cpp \ - coinjoin/walletman.cpp \ consensus/tx_verify.cpp \ dbwrapper.cpp \ deploymentstatus.cpp \ @@ -662,6 +661,7 @@ libbitcoin_wallet_a_SOURCES = \ coinjoin/client.cpp \ coinjoin/interfaces.cpp \ coinjoin/util.cpp \ + coinjoin/walletman.cpp \ wallet/bip39.cpp \ wallet/coinjoin.cpp \ wallet/coincontrol.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index dd6dda7178c3..3f750fc63b88 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -119,7 +119,9 @@ BITCOIN_TESTS =\ test/flatfile_tests.cpp \ test/fs_tests.cpp \ test/getarg_tests.cpp \ + test/governance_inv_tests.cpp \ test/governance_validators_tests.cpp \ + test/governance_vote_wire_tests.cpp \ test/coinjoin_inouts_tests.cpp \ test/coinjoin_dstxmanager_tests.cpp \ test/coinjoin_basemanager_tests.cpp \ @@ -336,6 +338,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.cpp \ test/fuzz/parse_univalue.cpp \ + test/fuzz/partially_downloaded_block.cpp \ test/fuzz/policy_estimator.cpp \ test/fuzz/policy_estimator_io.cpp \ test/fuzz/poolresource.cpp \ diff --git a/src/active/quorums.cpp b/src/active/quorums.cpp index c6c82f219bef..9103207612b9 100644 --- a/src/active/quorums.cpp +++ b/src/active/quorums.cpp @@ -152,7 +152,11 @@ MessageProcessingResult QuorumParticipant::ProcessContribQDATA(CNode& pfrom, CDa } std::vector> vecEncrypted; - vStream >> vecEncrypted; + const size_t expected_contributions{static_cast(std::ranges::count(quorum.qc->validMembers, true))}; + if (!UnserializeVectorWithMaxSize(vStream, vecEncrypted, expected_contributions) || + vecEncrypted.size() != expected_contributions) { + return MisbehavingError{100, "invalid encrypted contribution vector size"}; + } std::vector vecSecretKeys; vecSecretKeys.resize(vecEncrypted.size()); diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index b66c98e8e80c..f12b45b49354 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -54,7 +54,8 @@ ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& c if (cmpctblock.shorttxids.size() + cmpctblock.prefilledtxn.size() > MaxBlockSize() / MIN_TRANSACTION_SIZE) return READ_STATUS_INVALID; - assert(header.IsNull() && txn_available.empty()); + if (!header.IsNull() || !txn_available.empty()) return READ_STATUS_INVALID; + header = cmpctblock.header; txn_available.resize(cmpctblock.BlockTxCount()); @@ -169,14 +170,18 @@ ReadStatus PartiallyDownloadedBlock::InitData(const CBlockHeaderAndShortTxIDs& c return READ_STATUS_OK; } -bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const { - assert(!header.IsNull()); +bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const +{ + if (header.IsNull()) return false; + assert(index < txn_available.size()); return txn_available[index] != nullptr; } -ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector& vtx_missing) { - assert(!header.IsNull()); +ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector& vtx_missing) +{ + if (header.IsNull()) return READ_STATUS_INVALID; + uint256 hash = header.GetHash(); block = header; block.vtx.resize(txn_available.size()); @@ -198,15 +203,10 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector< if (vtx_missing.size() != tx_missing_offset) return READ_STATUS_INVALID; - BlockValidationState state; - if (!CheckBlock(block, state, Params().GetConsensus())) { - // TODO: We really want to just check merkle tree manually here, - // but that is expensive, and CheckBlock caches a block's - // "checked-status" (in the CBlock?). CBlock should be able to - // check its own merkle root and cache that check. - if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) - return READ_STATUS_FAILED; // Possible Short ID collision - return READ_STATUS_CHECKBLOCK_FAILED; + // Check for possible mutations early now that we have a seemingly good block + IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? m_check_block_mutated_mock : IsBlockMutated}; + if (check_mutated(/*block=*/block)) { + return READ_STATUS_FAILED; // Possible Short ID collision } LogPrint(BCLog::CMPCTBLOCK, "Successfully reconstructed block %s with %lu txn prefilled, %lu txn from mempool (incl at least %lu from extra pool) and %lu txn requested\n", hash.ToString(), prefilled_count, mempool_count, extra_count, vtx_missing.size()); diff --git a/src/blockencodings.h b/src/blockencodings.h index a19db7db2dd3..61210ef9b78f 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -7,8 +7,13 @@ #include +#include class CTxMemPool; +class BlockValidationState; +namespace Consensus { +struct Params; +}; // Transaction compression schemes for compact block relay can be introduced by writing // an actual formatter here. @@ -79,8 +84,6 @@ typedef enum ReadStatus_t READ_STATUS_OK, READ_STATUS_INVALID, // Invalid object, peer is sending bogus crap READ_STATUS_FAILED, // Failed to process object - READ_STATUS_CHECKBLOCK_FAILED, // Used only by FillBlock to indicate a - // failure in CheckBlock. } ReadStatus; class CBlockHeaderAndShortTxIDs { @@ -129,6 +132,11 @@ class PartiallyDownloadedBlock { const CTxMemPool* pool; public: CBlockHeader header; + + // Can be overriden for testing + using IsBlockMutatedFn = std::function; + IsBlockMutatedFn m_check_block_mutated_mock{nullptr}; + explicit PartiallyDownloadedBlock(CTxMemPool* poolIn) : pool(poolIn) {} // extra_txn is a list of extra transactions to look at, in form diff --git a/src/chainlock/handler.cpp b/src/chainlock/handler.cpp index d9addc2e70b2..022fdee7e637 100644 --- a/src/chainlock/handler.cpp +++ b/src/chainlock/handler.cpp @@ -44,7 +44,8 @@ ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, Chainstate m_mn_sync{mn_sync}, scheduler{std::make_unique()}, scheduler_thread{ - std::make_unique(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))} + std::make_unique(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}, + seenChainLocks{MAX_SEEN_CHAINLOCKS} { } @@ -68,9 +69,28 @@ void ChainlockHandler::Start() void ChainlockHandler::Stop() { scheduler->stop(); } bool ChainlockHandler::AlreadyHave(const CInv& inv) const +{ + { + LOCK(cs); + if (seenChainLocks.count(inv.hash) != 0) { + return true; + } + } + + chainlock::ChainLockSig clsig; + return m_chainlocks.GetChainLockByHash(inv.hash, clsig); +} + +size_t ChainlockHandler::SeenChainLockCacheSizeForTesting() const +{ + LOCK(cs); + return seenChainLocks.size(); +} + +size_t ChainlockHandler::SeenChainLockCacheMaxSizeForTesting() const { LOCK(cs); - return seenChainLocks.count(inv.hash) != 0; + return seenChainLocks.max_size(); } void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) @@ -89,13 +109,16 @@ MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, { LOCK(cs); - if (!seenChainLocks.emplace(hash, GetTime()).second) { + if (seenChainLocks.count(hash) != 0) { return {}; } + seenChainLocks.insert({hash, GetTime()}); - // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation) + // Height is checked twice: preliminary (for optimization) and inside + // UpdateBestChainlock, as this mutex is not kept during validation. if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) { - // no need to process older/same CLSIGs + // Remember the hash so AlreadyHave() suppresses repeated requests + // for stale CLSIG announcements. return {}; } } @@ -293,7 +316,9 @@ void ChainlockHandler::Cleanup() LOCK(cs); for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) { if (GetTime() - it->second >= CLEANUP_SEEN_TIMEOUT) { - it = seenChainLocks.erase(it); + const auto hash = it->first; + ++it; + seenChainLocks.erase(hash); } else { ++it; } diff --git a/src/chainlock/handler.h b/src/chainlock/handler.h index e024ca539876..979133263e07 100644 --- a/src/chainlock/handler.h +++ b/src/chainlock/handler.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_CHAINLOCK_HANDLER_H #define BITCOIN_CHAINLOCK_HANDLER_H +#include #include #include #include @@ -59,10 +60,12 @@ class ChainlockHandler final : public CValidationInterface std::atomic tryLockChainTipScheduled{false}; std::atomic isEnabled{false}; + static constexpr size_t MAX_SEEN_CHAINLOCKS{1024}; + const CBlockIndex* lastNotifyChainLockBlockIndex GUARDED_BY(cs){nullptr}; Uint256HashMap txFirstSeenTime GUARDED_BY(cs); - std::map seenChainLocks GUARDED_BY(cs); + unordered_limitedmap seenChainLocks GUARDED_BY(cs); CleanupThrottler cleanupThrottler; @@ -79,6 +82,8 @@ class ChainlockHandler final : public CValidationInterface bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs); void UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) EXCLUSIVE_LOCKS_REQUIRED(!cs); + size_t SeenChainLockCacheSizeForTesting() const EXCLUSIVE_LOCKS_REQUIRED(!cs); + size_t SeenChainLockCacheMaxSizeForTesting() const EXCLUSIVE_LOCKS_REQUIRED(!cs); [[nodiscard]] MessageProcessingResult ProcessNewChainLock(NodeId from, const chainlock::ChainLockSig& clsig, const llmq::CQuorumManager& qman, diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index be6e7b458115..4cb1c9b7b2d2 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -172,8 +172,8 @@ void CCoinJoinClientManager::ProcessMessage(CNode& peer, CChainState& active_cha if (!m_mn_sync.IsBlockchainSynced()) return; if (!CheckDiskSpace(gArgs.GetDataDirNet())) { - ResetPool(); - StopMixing(); + resetPool(); + stopMixing(); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::ProcessMessage -- Not enough disk space, disabling CoinJoin.\n"); return; } @@ -250,18 +250,17 @@ void CCoinJoinClientSession::ProcessMessage(CNode& peer, CChainState& active_cha } } -bool CCoinJoinClientManager::StartMixing() { - bool expected{false}; - return fMixing.compare_exchange_strong(expected, true); +bool CCoinJoinClientManager::startMixing() { + return m_wallet->StartMixing(); } -void CCoinJoinClientManager::StopMixing() { - fMixing = false; +void CCoinJoinClientManager::stopMixing() { + m_wallet->StopMixing(); } -bool CCoinJoinClientManager::IsMixing() const +bool CCoinJoinClientManager::isMixing() const { - return fMixing; + return m_wallet->IsMixing(); } void CCoinJoinClientSession::ResetPool() @@ -272,7 +271,7 @@ void CCoinJoinClientSession::ResetPool() WITH_LOCK(cs_coinjoin, SetNull()); } -void CCoinJoinClientManager::ResetPool() +void CCoinJoinClientManager::resetPool() { nCachedLastSuccessBlock = 0; AssertLockNotHeld(cs_deqsessions); @@ -354,7 +353,7 @@ bilingual_str CCoinJoinClientSession::GetStatus(bool fWaitForBlock) const } } -std::vector CCoinJoinClientManager::GetStatuses() const +std::vector CCoinJoinClientManager::getSessionStatuses() const { AssertLockNotHeld(cs_deqsessions); @@ -368,7 +367,7 @@ std::vector CCoinJoinClientManager::GetStatuses() const return ret; } -std::string CCoinJoinClientManager::GetSessionDenoms() +std::string CCoinJoinClientManager::getSessionDenoms() const { std::string strSessionDenoms; @@ -441,7 +440,7 @@ void CCoinJoinClientManager::CheckTimeout() { AssertLockNotHeld(cs_deqsessions); - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return; LOCK(cs_deqsessions); for (auto& session : deqSessions) { @@ -719,7 +718,7 @@ bool CCoinJoinClientManager::WaitForAnotherBlock() const bool CCoinJoinClientManager::CheckAutomaticBackup() { - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return false; // We don't need auto-backups for descriptor wallets if (!m_wallet->IsLegacy()) return true; @@ -728,7 +727,7 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() case 0: strAutoDenomResult = _("Automatic backups disabled") + Untranslated(", ") + _("no mixing available."); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - StopMixing(); + stopMixing(); m_wallet->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" return false; case -1: @@ -754,7 +753,7 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() m_wallet->nKeysLeftSinceAutoBackup); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); // It's getting really dangerous, stop mixing - StopMixing(); + stopMixing(); return false; } else if (m_wallet->nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_WARNING) { // Low number of keys left, but it's still more or less safe to continue @@ -976,7 +975,7 @@ bool CCoinJoinClientSession::DoAutomaticDenominating(ChainstateManager& chainman bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman, CConnman& connman, const CTxMemPool& mempool, bool fDryRun) { - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return false; if (!m_mn_sync.IsBlockchainSynced()) { strAutoDenomResult = _("Can't mix while sync in progress."); @@ -1873,10 +1872,10 @@ void CCoinJoinClientSession::GetJsonInfo(UniValue& obj) const obj.pushKV("entries_count", GetEntriesCount()); } -void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const +UniValue CCoinJoinClientManager::getJsonInfo() const { - assert(obj.isObject()); - obj.pushKV("running", IsMixing()); + UniValue obj(UniValue::VOBJ); + obj.pushKV("running", isMixing()); UniValue arrSessions(UniValue::VARR); AssertLockNotHeld(cs_deqsessions); @@ -1889,6 +1888,7 @@ void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const } } obj.pushKV("sessions", arrSessions); + return obj; } CoinJoinWalletManager::CoinJoinWalletManager(ChainstateManager& chainman, CDeterministicMNManager& dmnman, @@ -1934,16 +1934,3 @@ void CoinJoinWalletManager::Remove(const std::string& name) { m_wallet_manager_map.erase(name); } -void CoinJoinWalletManager::Flush(const std::string& name) -{ - auto clientman = Assert(Get(name)); - clientman->ResetPool(); - clientman->StopMixing(); -} - -CCoinJoinClientManager* CoinJoinWalletManager::Get(const std::string& name) const -{ - LOCK(cs_wallet_manager_map); - auto it = m_wallet_manager_map.find(name); - return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr; -} diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 2b1933841c71..0aba5675b521 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,7 +16,6 @@ #include #include -#include #include #include #include @@ -88,9 +88,6 @@ class CoinJoinWalletManager { void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); void Remove(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - void Flush(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - - CCoinJoinClientManager* Get(const std::string& name) const EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); template void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) @@ -108,6 +105,18 @@ class CoinJoinWalletManager { return ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(pair.second); }); }; + //! Execute func under the wallet manager lock for the client identified by name. + //! Returns true if the client was found and func was called, false otherwise. + template + bool DoForClient(const std::string& name, Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) + { + LOCK(cs_wallet_manager_map); + auto it = m_wallet_manager_map.find(name); + if (it == m_wallet_manager_map.end()) return false; + func(*it->second); + return true; + }; + private: ChainstateManager& m_chainman; CDeterministicMNManager& m_dmnman; @@ -240,7 +249,7 @@ class CCoinJoinClientQueueManager : public CCoinJoinBaseManager /** Used to keep track of current status of mixing pool */ -class CCoinJoinClientManager +class CCoinJoinClientManager : public interfaces::CoinJoin::Client { private: const std::shared_ptr m_wallet; @@ -254,8 +263,6 @@ class CCoinJoinClientManager // TODO: or map ?? std::deque deqSessions GUARDED_BY(cs_deqsessions); - std::atomic fMixing{false}; - int nCachedLastSuccessBlock{0}; int nMinBlocksToWait{1}; // how many blocks to wait for after one successful mixing tx in non-multisession mode bilingual_str strAutoDenomResult; @@ -263,15 +270,15 @@ class CCoinJoinClientManager // Keep track of current block height int nCachedBlockHeight{0}; + int nCachedNumBlocks{std::numeric_limits::max()}; // used for the overview screen + bool fCreateAutoBackups{true}; // builtin support for automatic backups + bool WaitForAnotherBlock() const; // Make sure we have enough keys since last backup bool CheckAutomaticBackup(); public: - int nCachedNumBlocks{std::numeric_limits::max()}; // used for the overview screen - bool fCreateAutoBackups{true}; // builtin support for automatic backups - CCoinJoinClientManager() = delete; CCoinJoinClientManager(const CCoinJoinClientManager&) = delete; CCoinJoinClientManager& operator=(const CCoinJoinClientManager&) = delete; @@ -283,14 +290,6 @@ class CCoinJoinClientManager void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - bool StartMixing(); - void StopMixing(); - bool IsMixing() const; - void ResetPool() EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - - std::vector GetStatuses() const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - std::string GetSessionDenoms() EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - bool GetMixingMasternodesInfo(std::vector& vecDmnsRet) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); /// Passively run mixing in the background according to the configuration in settings @@ -314,7 +313,18 @@ class CCoinJoinClientManager void DoMaintenance(ChainstateManager& chainman, CConnman& connman, const CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - void GetJsonInfo(UniValue& obj) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + // interfaces::CoinJoin::Client overrides + void resetCachedBlocks() override { nCachedNumBlocks = std::numeric_limits::max(); } + int getCachedBlocks() const override { return nCachedNumBlocks; } + void setCachedBlocks(int nCachedBlocks) override { nCachedNumBlocks = nCachedBlocks; } + void disableAutobackups() override { fCreateAutoBackups = false; } + void resetPool() override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + UniValue getJsonInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + std::vector getSessionStatuses() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + std::string getSessionDenoms() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + bool isMixing() const override; + bool startMixing() override; + void stopMixing() override; }; #endif // BITCOIN_COINJOIN_CLIENT_H diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index 33fbc43d6b0e..f18a991722be 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -92,7 +92,7 @@ bool CCoinJoinBroadcastTx::IsValidStructure() const if (tx->vin.size() < size_t(CoinJoin::GetMinPoolParticipants())) { return false; } - if (tx->vin.size() > CoinJoin::GetMaxPoolParticipants() * COINJOIN_ENTRY_MAX_SIZE) { + if (tx->vin.size() > CoinJoin::GetMaxPoolInputOutputCount()) { return false; } return ranges::all_of(tx->vout, [] (const auto& txOut){ diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index 6e176ebfe302..2c0fbc0ed875 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,15 @@ static constexpr int COINJOIN_SIGNING_TIMEOUT = 15; static constexpr size_t COINJOIN_ENTRY_MAX_SIZE = 9; +namespace CoinJoin { +/// Get the minimum/maximum number of participants for the pool +int GetMinPoolParticipants(); +int GetMaxPoolParticipants(); + +/// Maximum number of inputs or outputs across a full pool +inline size_t GetMaxPoolInputOutputCount() { return size_t(GetMaxPoolParticipants()) * COINJOIN_ENTRY_MAX_SIZE; } +} // namespace CoinJoin + // pool responses enum PoolMessage : int32_t { ERR_ALREADY_HAVE, @@ -164,9 +174,25 @@ class CCoinJoinEntry { } - SERIALIZE_METHODS(CCoinJoinEntry, obj) + template + void Serialize(Stream& s) const + { + s << vecTxDSIn << txCollateral << vecTxOut; + } + + template + void Unserialize(Stream& s) { - READWRITE(obj.vecTxDSIn, obj.txCollateral, obj.vecTxOut); + const size_t max_count{CoinJoin::GetMaxPoolInputOutputCount()}; + if (!UnserializeVectorWithMaxSize(s, vecTxDSIn, max_count)) { + throw std::ios_base::failure("CCoinJoinEntry::vecTxDSIn size too large"); + } + + s >> txCollateral; + + if (!UnserializeVectorWithMaxSize(s, vecTxOut, max_count)) { + throw std::ios_base::failure("CCoinJoinEntry::vecTxOut size too large"); + } } bool AddScriptSig(const CTxIn& txin); @@ -355,10 +381,6 @@ namespace CoinJoin { bilingual_str GetMessageByID(PoolMessage nMessageID); - /// Get the minimum/maximum number of participants for the pool - int GetMinPoolParticipants(); - int GetMaxPoolParticipants(); - constexpr CAmount GetMaxPoolAmount() { return COINJOIN_ENTRY_MAX_SIZE * vecStandardDenominations.front(); } /// If the collateral is valid given by a client diff --git a/src/coinjoin/interfaces.cpp b/src/coinjoin/interfaces.cpp index 8dd645025561..5ddb127f4217 100644 --- a/src/coinjoin/interfaces.cpp +++ b/src/coinjoin/interfaces.cpp @@ -20,60 +20,6 @@ using wallet::CWallet; namespace coinjoin { namespace { -class CoinJoinClientImpl : public interfaces::CoinJoin::Client -{ - CCoinJoinClientManager& m_clientman; - -public: - explicit CoinJoinClientImpl(CCoinJoinClientManager& clientman) - : m_clientman(clientman) {} - - void resetCachedBlocks() override - { - m_clientman.nCachedNumBlocks = std::numeric_limits::max(); - } - void resetPool() override - { - m_clientman.ResetPool(); - } - void disableAutobackups() override - { - m_clientman.fCreateAutoBackups = false; - } - int getCachedBlocks() override - { - return m_clientman.nCachedNumBlocks; - } - void getJsonInfo(UniValue& obj) override - { - return m_clientman.GetJsonInfo(obj); - } - std::string getSessionDenoms() override - { - return m_clientman.GetSessionDenoms(); - } - std::vector getSessionStatuses() override - { - return m_clientman.GetStatuses(); - } - void setCachedBlocks(int nCachedBlocks) override - { - m_clientman.nCachedNumBlocks = nCachedBlocks; - } - bool isMixing() override - { - return m_clientman.IsMixing(); - } - bool startMixing() override - { - return m_clientman.StartMixing(); - } - void stopMixing() override - { - m_clientman.StopMixing(); - } -}; - class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader { private: @@ -82,37 +28,32 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader return *Assert(m_node.cj_walletman); } - interfaces::WalletLoader& wallet_loader() - { - return *Assert(m_node.wallet_loader); - } - public: explicit CoinJoinLoaderImpl(NodeContext& node) : m_node(node) { - // Enablement will be re-evaluated when a wallet is added or removed - CCoinJoinClientOptions::SetEnabled(false); + CCoinJoinClientOptions::SetEnabled(gArgs.GetBoolArg("-enablecoinjoin", true)); } void AddWallet(const std::shared_ptr& wallet) override { manager().addWallet(wallet); - g_wallet_init_interface.InitCoinJoinSettings(*this, wallet_loader()); + if (!CCoinJoinClientOptions::IsEnabled()) return; + manager().doForClient(wallet->GetName(), [](CCoinJoinClientManager& mgr) { + g_wallet_init_interface.InitCoinJoinSettings(mgr); + }); } void RemoveWallet(const std::string& name) override { manager().removeWallet(name); - g_wallet_init_interface.InitCoinJoinSettings(*this, wallet_loader()); } void FlushWallet(const std::string& name) override { manager().flushWallet(name); } - std::unique_ptr GetClient(const std::string& name) override + bool WithClient(const std::string& name, const std::function& func) override { - auto clientman = manager().getClient(name); - return clientman ? std::make_unique(*clientman) : nullptr; + return manager().doForClient(name, [&](CCoinJoinClientManager& mgr) { func(mgr); }); } NodeContext& m_node; diff --git a/src/coinjoin/server.cpp b/src/coinjoin/server.cpp index e431e1d9632b..af31e11251fd 100644 --- a/src/coinjoin/server.cpp +++ b/src/coinjoin/server.cpp @@ -16,6 +16,7 @@ #include #include #include