Compare commits
37 Commits
ddd851dc11
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| d52550a6fc | |||
| d159e72086 | |||
| adf2bacdbd | |||
| 1ec3dbfee3 | |||
| a568ab628e | |||
| 693d2290e0 | |||
| 389c8c7383 | |||
| 4e3c0f8f6f | |||
| 2e54d9fb4d | |||
| 4238da9bea | |||
| 4e67e687d7 | |||
| 762e25294f | |||
| 3bb4eb3a5a | |||
| 7914fca0f2 | |||
| 810bd6712f | |||
| bf5b066a8d | |||
| 19e1ce6f00 | |||
| dc45e7d904 | |||
| 1ed468e040 | |||
| 53b1fe332b | |||
| 9c6ccbb726 | |||
| 34432e5848 | |||
| 4caf2fc68f | |||
| 84aefb5475 | |||
| 1f2b109d95 | |||
| 78ea2aac5b | |||
| 2419ed7bf7 | |||
| a9b1b4085f | |||
| f763e3f1e7 | |||
| f8f13f9027 | |||
| bf1b4cffe0 | |||
| 82d77344d2 | |||
| 1673cfb6dc | |||
| 2f3f320d28 | |||
| 2b011d6ee2 | |||
|
|
d77088c1f2 | ||
| faa3e925cd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -175,3 +175,4 @@ src/dragonx-tx
|
|||||||
src/dragonxd.exe
|
src/dragonxd.exe
|
||||||
src/dragonx-cli.exe
|
src/dragonx-cli.exe
|
||||||
src/dragonx-tx.exe
|
src/dragonx-tx.exe
|
||||||
|
doc/relnotes/
|
||||||
4
AUTHORS
4
AUTHORS
@@ -1,3 +1,7 @@
|
|||||||
|
# The DragonX Developers
|
||||||
|
|
||||||
|
Dan S https://git.dragonx.is/dan
|
||||||
|
|
||||||
# The Hush Developers
|
# The Hush Developers
|
||||||
|
|
||||||
Duke Leto https://git.hush.is/duke https://github.com/leto
|
Duke Leto https://git.hush.is/duke https://github.com/leto
|
||||||
|
|||||||
1
COPYING
1
COPYING
@@ -1,3 +1,4 @@
|
|||||||
|
Copyright (c) 2024-2026 The DragonX developers
|
||||||
Copyright (c) 2018-2025 The Hush developers
|
Copyright (c) 2018-2025 The Hush developers
|
||||||
Copyright (c) 2009-2017 The Bitcoin Core developers
|
Copyright (c) 2009-2017 The Bitcoin Core developers
|
||||||
Copyright (c) 2009-2018 Bitcoin Developers
|
Copyright (c) 2009-2018 Bitcoin Developers
|
||||||
|
|||||||
26
LICENSE
26
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
GENERAL GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
@@ -7,15 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GENERAL General Public License is a free, copyleft license for
|
The GNU General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GENERAL General Public License is intended to guarantee your freedom to
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
GENERAL General Public License for most of our software; it applies also to
|
GNU General Public License for most of our software; it applies also to
|
||||||
any other work released this way by its authors. You can apply it to
|
any other work released this way by its authors. You can apply it to
|
||||||
your programs, too.
|
your programs, too.
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ freedoms that you received. You must make sure that they, too, receive
|
|||||||
or can get the source code. And you must show them these terms so they
|
or can get the source code. And you must show them these terms so they
|
||||||
know their rights.
|
know their rights.
|
||||||
|
|
||||||
Developers that use the GENERAL GPL protect your rights with two steps:
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GENERAL General Public License.
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GENERAL Affero General Public License.
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GENERAL Affero General Public License into a single
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GENERAL Affero General Public License,
|
but the special requirements of the GNU Affero General Public License,
|
||||||
section 13, concerning interaction through a network will apply to the
|
section 13, concerning interaction through a network will apply to the
|
||||||
combination as such.
|
combination as such.
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GENERAL General Public License from time to time. Such new versions will
|
the GNU General Public License from time to time. Such new versions will
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GENERAL General
|
Program specifies that a certain numbered version of the GNU General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GENERAL General Public License, you may choose any version ever published
|
GNU General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GENERAL General Public License can be used, that proxy's
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -27,6 +27,21 @@ the entire history of Hush transactions; depending on the speed of your
|
|||||||
computer and network connection, it will likely take a few hours at least, but
|
computer and network connection, it will likely take a few hours at least, but
|
||||||
some people report full nodes syncing in less than 1.5 hours.
|
some people report full nodes syncing in less than 1.5 hours.
|
||||||
|
|
||||||
|
# Fastest way to sync (bootstrap)
|
||||||
|
|
||||||
|
The quickest way to get a fully-synced node is the signed bootstrap snapshot, which
|
||||||
|
installs a pre-built blockchain so you skip re-validating the whole chain from genesis:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Stop dragonxd first if it is running, then:
|
||||||
|
./util/bootstrap-dragonx.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script preserves your `wallet.dat` and `DRAGONX.conf`, verifies the download's
|
||||||
|
checksums and (once a release key is published) its cryptographic signature, then starts
|
||||||
|
you near the chain tip. If you prefer to sync from the network instead, a larger
|
||||||
|
`-dbcache` (e.g. `-dbcache=2048`) noticeably speeds up the initial block download.
|
||||||
|
|
||||||
# Banned by GitHub
|
# Banned by GitHub
|
||||||
|
|
||||||
In working on this release, Duke Leto was suspended from Github, which gave Hush developers
|
In working on this release, Duke Leto was suspended from Github, which gave Hush developers
|
||||||
@@ -106,19 +121,32 @@ apt-get install -y gcc-7 g++-7 && \
|
|||||||
|
|
||||||
# Build on Mac
|
# Build on Mac
|
||||||
|
|
||||||
```
|
Install Xcode Command Line Tools and [Homebrew](https://brew.sh/), then install dependencies:
|
||||||
sudo port update
|
|
||||||
sudo port upgrade outdated
|
```sh
|
||||||
sudo port install qt5
|
xcode-select --install
|
||||||
|
brew install gcc autoconf automake pkgconf libtool cmake curl
|
||||||
|
|
||||||
|
# Install Rust (needed for librustzcash on macOS Sequoia+)
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
|
||||||
# clone git repo
|
# clone git repo
|
||||||
git clone https://git.hush.is/hush/hush3
|
git clone https://git.hush.is/hush/hush3
|
||||||
cd hush3
|
cd hush3
|
||||||
# Build
|
# Build (uses 3 build processes, you need 2GB of RAM for each)
|
||||||
# This uses 3 build processes, you need 2GB of RAM for each.
|
# Make sure libtool gnubin and cargo are on PATH
|
||||||
|
export PATH="$HOME/.cargo/bin:/usr/local/opt/libtool/libexec/gnubin:$PATH"
|
||||||
./build.sh -j3
|
./build.sh -j3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a release build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export PATH="$HOME/.cargo/bin:/usr/local/opt/libtool/libexec/gnubin:$PATH"
|
||||||
|
./build.sh --mac-release -j$(sysctl -n hw.ncpu)
|
||||||
|
```
|
||||||
|
|
||||||
# Installing Hush binaries
|
# Installing Hush binaries
|
||||||
|
|
||||||
1. [Download the release](https://git.hush.is/hush/hush3/releases) with a .deb file extension.
|
1. [Download the release](https://git.hush.is/hush/hush3/releases) with a .deb file extension.
|
||||||
|
|||||||
20
build.sh
20
build.sh
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
VERSION="1.0.2"
|
VERSION="1.0.3"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
RELEASE_DIR="$SCRIPT_DIR/release"
|
RELEASE_DIR="$SCRIPT_DIR/release"
|
||||||
|
|
||||||
@@ -80,8 +80,12 @@ package_release() {
|
|||||||
echo "Packaging release for $platform..."
|
echo "Packaging release for $platform..."
|
||||||
mkdir -p "$release_subdir"
|
mkdir -p "$release_subdir"
|
||||||
|
|
||||||
# Copy bootstrap script
|
# Copy bootstrap script (platform-appropriate)
|
||||||
|
if [ "$platform" = "win64" ]; then
|
||||||
|
cp "$SCRIPT_DIR/util/bootstrap-dragonx.bat" "$release_subdir/"
|
||||||
|
else
|
||||||
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$release_subdir/"
|
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$release_subdir/"
|
||||||
|
fi
|
||||||
|
|
||||||
# Copy common files
|
# Copy common files
|
||||||
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$release_subdir/" 2>/dev/null || true
|
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$release_subdir/" 2>/dev/null || true
|
||||||
@@ -171,21 +175,21 @@ if [ $BUILD_LINUX_COMPAT -eq 1 ] || [ $BUILD_LINUX_RELEASE -eq 1 ] || [ $BUILD_W
|
|||||||
if [ $BUILD_LINUX_RELEASE -eq 1 ]; then
|
if [ $BUILD_LINUX_RELEASE -eq 1 ]; then
|
||||||
echo "=== Building Linux release ==="
|
echo "=== Building Linux release ==="
|
||||||
clean_for_platform linux
|
clean_for_platform linux
|
||||||
./util/build.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
package_release linux-amd64
|
package_release linux-amd64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $BUILD_WIN_RELEASE -eq 1 ]; then
|
if [ $BUILD_WIN_RELEASE -eq 1 ]; then
|
||||||
echo "=== Building Windows release ==="
|
echo "=== Building Windows release ==="
|
||||||
clean_for_platform windows
|
clean_for_platform windows
|
||||||
./util/build-win.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
package_release win64
|
package_release win64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $BUILD_MAC_RELEASE -eq 1 ]; then
|
if [ $BUILD_MAC_RELEASE -eq 1 ]; then
|
||||||
echo "=== Building macOS release ==="
|
echo "=== Building macOS release ==="
|
||||||
clean_for_platform macos
|
clean_for_platform macos
|
||||||
./util/build-mac.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
package_release macos
|
package_release macos
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -197,11 +201,11 @@ fi
|
|||||||
|
|
||||||
# Standard build (auto-detect OS)
|
# Standard build (auto-detect OS)
|
||||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||||
./util/build.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
./util/build-mac.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
elif [[ "$OSTYPE" == "msys"* ]]; then
|
elif [[ "$OSTYPE" == "msys"* ]]; then
|
||||||
./util/build-win.sh --disable-tests "${REMAINING_ARGS[@]}"
|
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||||
else
|
else
|
||||||
echo "Unable to detect your OS. What are you using?"
|
echo "Unable to detect your OS. What are you using?"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ AC_PREREQ([2.60])
|
|||||||
define(_CLIENT_VERSION_MAJOR, 1)
|
define(_CLIENT_VERSION_MAJOR, 1)
|
||||||
dnl Must be kept in sync with src/clientversion.h , ugh!
|
dnl Must be kept in sync with src/clientversion.h , ugh!
|
||||||
define(_CLIENT_VERSION_MINOR, 0)
|
define(_CLIENT_VERSION_MINOR, 0)
|
||||||
define(_CLIENT_VERSION_REVISION, 2)
|
define(_CLIENT_VERSION_REVISION, 3)
|
||||||
define(_CLIENT_VERSION_BUILD, 50)
|
define(_CLIENT_VERSION_BUILD, 50)
|
||||||
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
||||||
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
|
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
dragonx (1.0.3) stable; urgency=medium
|
||||||
|
|
||||||
|
* IBD/sync speedups: parallel RandomX pre-verification, adaptive -dbcache, P2P download fixes
|
||||||
|
* Fix Sapling witness desync and parallelize witness cache rebuild
|
||||||
|
* Opt-in bulk block streaming (-bulkblocksync) for faster initial sync
|
||||||
|
* BIP39 seed phrases (SilentDragonXLite-compatible) and HD transparent keys
|
||||||
|
* Remove assumeutxo / UTXO-snapshot feature
|
||||||
|
|
||||||
|
-- DragonX <dan-s-dev@proton.me> Tue, 07 Jul 2026 05:49:59 +0200
|
||||||
|
|
||||||
|
dragonx (1.0.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release of DragonX, forked from Hush Full Node
|
||||||
|
* Full legal-compliant rebrand: binaries, config, documentation
|
||||||
|
* RandomX proof-of-work, 36-second block time, fully shielded transactions
|
||||||
|
* New binary names: dragonxd, dragonx-cli, dragonx-tx
|
||||||
|
|
||||||
|
-- DragonX <dan-s-dev@proton.me> Mon, 03 Mar 2026 00:00:00 +0000
|
||||||
|
|
||||||
hush (3.10.5) stable; urgency=medium
|
hush (3.10.5) stable; urgency=medium
|
||||||
|
|
||||||
* DragonX is no longer supported by this codebase
|
* DragonX is no longer supported by this codebase
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
Source: hush
|
Source: dragonx
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Hush <myhushteam@gmail.com>
|
Maintainer: DragonX <dan-s-dev@proton.me>
|
||||||
Homepage: https://hush.is
|
Homepage: https://dragonx.is
|
||||||
Build-Depends: autoconf, automake, bsdmainutils, build-essential,
|
Build-Depends: autoconf, automake, bsdmainutils, build-essential,
|
||||||
cmake, curl, git, g++-multilib, libc6-dev, libsodium-dev,
|
cmake, curl, git, g++-multilib, libc6-dev, libsodium-dev,
|
||||||
libtool, m4, ncurses-dev, pkg-config, python,
|
libtool, m4, ncurses-dev, pkg-config, python,
|
||||||
unzip, wget, zlib1g-dev
|
unzip, wget, zlib1g-dev
|
||||||
Vcs-Git: https://git.hush.is/hush/hush3.git
|
Vcs-Git: https://git.dragonx.is/DragonX/dragonx.git
|
||||||
Vcs-Browser: https://git.hush.is/hush/hush3
|
Vcs-Browser: https://git.dragonx.is/DragonX/dragonx
|
||||||
|
|
||||||
Package: hush
|
Package: dragonx
|
||||||
Architecture: amd64 arm64
|
Architecture: amd64 arm64
|
||||||
Depends: ${shlibs:Depends}
|
Depends: ${shlibs:Depends}
|
||||||
Description: Cryptocoin full node for Hush
|
Description: Privacy-focused cryptocurrency full node for DragonX
|
||||||
Speak And Transact Freely with Hush, which inherits from Bitcoin Protocol and
|
DragonX is a privacy-focused cryptocurrency using RandomX proof-of-work.
|
||||||
Zcash Protocol and is focused on private communications.
|
All transactions are shielded by default. Fork of the Hush Full Node.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
Files: *
|
Files: *
|
||||||
Copyright: 2016-2026, The Hush developers
|
Copyright: 2024-2026, The DragonX developers
|
||||||
|
2016-2026, The Hush developers
|
||||||
2009-2016, Bitcoin Core developers
|
2009-2016, Bitcoin Core developers
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
Comment: https://hush.is
|
Comment: https://dragonx.is
|
||||||
|
|
||||||
Files: depends/sources/libsodium-*.tar.gz
|
Files: depends/sources/libsodium-*.tar.gz
|
||||||
Copyright: 2013-2016 Frank Denis
|
Copyright: 2013-2016 Frank Denis
|
||||||
|
|||||||
1
contrib/debian/dragonx.example
Normal file
1
contrib/debian/dragonx.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DEBIAN/examples/DRAGONX.conf
|
||||||
3
contrib/debian/dragonx.install
Normal file
3
contrib/debian/dragonx.install
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
usr/bin/dragonxd
|
||||||
|
usr/bin/dragonx-cli
|
||||||
|
usr/bin/dragonx-tx
|
||||||
3
contrib/debian/dragonx.manpages
Normal file
3
contrib/debian/dragonx.manpages
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DEBIAN/manpages/dragonx-cli.1
|
||||||
|
DEBIAN/manpages/dragonx-tx.1
|
||||||
|
DEBIAN/manpages/dragonxd.1
|
||||||
209
contrib/debian/examples/DRAGONX.conf
Normal file
209
contrib/debian/examples/DRAGONX.conf
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
## DRAGONX.conf configuration file. Lines beginning with # are comments.
|
||||||
|
|
||||||
|
# Network-related settings:
|
||||||
|
|
||||||
|
# Run a regression test network
|
||||||
|
#regtest=0
|
||||||
|
# Run a test node (which means you can mine with no peers)
|
||||||
|
#testnode=1
|
||||||
|
|
||||||
|
#set a custom client name/user agent
|
||||||
|
#clientName=GoldenSandtrout
|
||||||
|
|
||||||
|
# Rescan from block height
|
||||||
|
#rescan=123
|
||||||
|
|
||||||
|
# Connect via a SOCKS5 proxy
|
||||||
|
#proxy=127.0.0.1:9050
|
||||||
|
|
||||||
|
# Automatically create Tor hidden service
|
||||||
|
#listenonion=1
|
||||||
|
|
||||||
|
#Use separate SOCKS5 proxy to reach peers via Tor hidden services
|
||||||
|
#onion=1.2.3.4:9050
|
||||||
|
|
||||||
|
# Only connect to nodes in network <net> (ipv4, ipv6, onion or i2p)"));
|
||||||
|
#onlynet=<net>
|
||||||
|
|
||||||
|
#Tor control port to use if onion listening enabled
|
||||||
|
#torcontrol=127.0.0.1:9051
|
||||||
|
|
||||||
|
# Bind to given address and always listen on it. Use [host]:port notation for IPv6
|
||||||
|
#bind=<addr>
|
||||||
|
|
||||||
|
# Bind to given address and allowlist peers connecting to it. Use [host]:port notation for IPv6
|
||||||
|
#allowbind=<addr>
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
## Quick Primer on addnode vs connect ##
|
||||||
|
## Let's say for instance you use addnode=4.2.2.4 ##
|
||||||
|
## addnode will connect you to and tell you about the ##
|
||||||
|
## nodes connected to 4.2.2.4. In addition it will tell ##
|
||||||
|
## the other nodes connected to it that you exist so ##
|
||||||
|
## they can connect to you. ##
|
||||||
|
## connect will not do the above when you 'connect' to it. ##
|
||||||
|
## It will *only* connect you to 4.2.2.4 and no one else.##
|
||||||
|
## ##
|
||||||
|
## So if you're behind a firewall, or have other problems ##
|
||||||
|
## finding nodes, add some using 'addnode'. ##
|
||||||
|
## ##
|
||||||
|
## If you want to stay private, use 'connect' to only ##
|
||||||
|
## connect to "trusted" nodes. ##
|
||||||
|
## ##
|
||||||
|
## If you run multiple nodes on a LAN, there's no need for ##
|
||||||
|
## all of them to open lots of connections. Instead ##
|
||||||
|
## 'connect' them all to one node that is port forwarded ##
|
||||||
|
## and has lots of connections. ##
|
||||||
|
## Thanks goes to [Noodle] on Freenode. ##
|
||||||
|
##############################################################
|
||||||
|
|
||||||
|
# Use as many addnode= settings as you like to connect to specific peers
|
||||||
|
#addnode=69.164.218.197
|
||||||
|
#addnode=10.0.0.2:8233
|
||||||
|
|
||||||
|
# Alternatively use as many connect= settings as you like to connect ONLY to specific peers
|
||||||
|
#connect=69.164.218.197
|
||||||
|
#connect=10.0.0.1:8233
|
||||||
|
|
||||||
|
# Listening mode, enabled by default except when 'connect' is being used
|
||||||
|
#listen=1
|
||||||
|
|
||||||
|
# Maximum number of inbound+outbound connections.
|
||||||
|
#maxconnections=
|
||||||
|
|
||||||
|
#
|
||||||
|
# JSON-RPC options (for controlling a running dragonxd process)
|
||||||
|
#
|
||||||
|
|
||||||
|
# server=1 tells node to accept JSON-RPC commands (set as default if not specified)
|
||||||
|
#server=1
|
||||||
|
|
||||||
|
# Bind to given address to listen for JSON-RPC connections. Use [host]:port notation for IPv6.
|
||||||
|
# This option can be specified multiple times (default: bind to all interfaces)
|
||||||
|
#rpcbind=<addr>
|
||||||
|
|
||||||
|
# You must set rpcuser and rpcpassword to secure the JSON-RPC api
|
||||||
|
# These will automatically be created for you
|
||||||
|
#rpcuser=user
|
||||||
|
#rpcpassword=supersecretpassword
|
||||||
|
|
||||||
|
# How many seconds node will wait for a complete RPC HTTP request.
|
||||||
|
# after the HTTP connection is established.
|
||||||
|
#rpcclienttimeout=30
|
||||||
|
|
||||||
|
# By default, only RPC connections from localhost are allowed.
|
||||||
|
# Specify as many rpcallowip= settings as you like to allow connections from other hosts,
|
||||||
|
# either as a single IPv4/IPv6 or with a subnet specification.
|
||||||
|
|
||||||
|
# NOTE: opening up the RPC port to hosts outside your local trusted network is NOT RECOMMENDED,
|
||||||
|
# because the rpcpassword is transmitted over the network unencrypted and also because anyone
|
||||||
|
# that can authenticate on the RPC port can steal your keys + take over the account running dragonxd
|
||||||
|
|
||||||
|
#rpcallowip=10.1.1.34/255.255.255.0
|
||||||
|
#rpcallowip=1.2.3.4/24
|
||||||
|
#rpcallowip=2001:db8:85a3:0:0:8a2e:370:7334/96
|
||||||
|
|
||||||
|
# Listen for RPC connections on this TCP port:
|
||||||
|
#rpcport=1234
|
||||||
|
|
||||||
|
# You can use dragonxd to send commands to dragonxd
|
||||||
|
# running on another host using this option:
|
||||||
|
#rpcconnect=127.0.0.1
|
||||||
|
|
||||||
|
# Transaction Fee
|
||||||
|
|
||||||
|
# Send transactions as zero-fee transactions if possible (default: 0)
|
||||||
|
#sendfreetransactions=0
|
||||||
|
|
||||||
|
# Create transactions that have enough fees (or priority) so they are likely to # begin confirmation within n blocks (default: 1).
|
||||||
|
# This setting is overridden by the -paytxfee option.
|
||||||
|
#txconfirmtarget=n
|
||||||
|
|
||||||
|
# Miscellaneous options
|
||||||
|
|
||||||
|
# Enable mining at startup
|
||||||
|
#gen=1
|
||||||
|
|
||||||
|
# Set the number of threads to be used for mining (-1 = all cores).
|
||||||
|
#genproclimit=1
|
||||||
|
|
||||||
|
# Specify a different Equihash solver (e.g. "tromp") to try to mine
|
||||||
|
# faster when gen=1.
|
||||||
|
#equihashsolver=default
|
||||||
|
|
||||||
|
# Pre-generate this many public/private key pairs, so wallet backups will be valid for
|
||||||
|
# both prior transactions and several dozen future transactions.
|
||||||
|
#keypool=100
|
||||||
|
|
||||||
|
# Pay an optional transaction fee every time you send a tx. Transactions with fees
|
||||||
|
# are more likely than free transactions to be included in generated blocks, so may
|
||||||
|
# be validated sooner. This setting does not affect private transactions created with
|
||||||
|
# 'z_sendmany'.
|
||||||
|
#paytxfee=0.00
|
||||||
|
|
||||||
|
#Rewind the chain to specific block height. This is useful for creating snapshots at a given block height.
|
||||||
|
#rewind=555
|
||||||
|
|
||||||
|
#Stop the chain a specific block height. This is useful for creating snapshots at a given block height.
|
||||||
|
#stopat=1000000
|
||||||
|
|
||||||
|
#Set an address to use as change address for all transactions. This value must be set to a 33 byte pubkey. All mined coins will also be sent to this address.
|
||||||
|
#pubkey=027dc7b5cfb5efca96674b45e9fda18df069d040b9fd9ff32c35df56005e330392
|
||||||
|
|
||||||
|
# Disable clearnet (ipv4 and ipv6) connections to this node
|
||||||
|
#clearnet=0
|
||||||
|
|
||||||
|
# Disable ipv4
|
||||||
|
#disableipv4=1
|
||||||
|
# Disable ipv6
|
||||||
|
#disableipv6=1
|
||||||
|
|
||||||
|
# Enable transaction index
|
||||||
|
#txindex=1
|
||||||
|
# Enable address index
|
||||||
|
#addressindex=1
|
||||||
|
# Enable timestamp index
|
||||||
|
#timestampindex=1
|
||||||
|
# Enable spent index
|
||||||
|
#spentindex=1
|
||||||
|
|
||||||
|
# Enable shielded stats index
|
||||||
|
#zindex=1
|
||||||
|
|
||||||
|
# Attempt to salvage a corrupt wallet
|
||||||
|
# salvagewallet=1
|
||||||
|
|
||||||
|
# Mine all blocks to this address (not good for your privacy and not recommended!)
|
||||||
|
# Disallowed if clearnet=0
|
||||||
|
# mineraddress=XXX
|
||||||
|
|
||||||
|
# Disable wallet
|
||||||
|
#disablewallet=1
|
||||||
|
|
||||||
|
# Allow mining to an address that is not in the current wallet
|
||||||
|
#minetolocalwallet=0
|
||||||
|
|
||||||
|
# Delete all wallet transactions
|
||||||
|
#zapwallettxes=1
|
||||||
|
|
||||||
|
# Enable sapling consolidation
|
||||||
|
# consolidation=1
|
||||||
|
|
||||||
|
# Enable stratum server
|
||||||
|
# stratum=1
|
||||||
|
|
||||||
|
# Run a command each time a new block is seen
|
||||||
|
# %s in command is replaced by block hash
|
||||||
|
#blocknotify=/my/awesome/script.sh %s
|
||||||
|
|
||||||
|
# Run a command when wallet gets a new tx
|
||||||
|
# %s in command is replaced with txid
|
||||||
|
#walletnotify=/my/cool/script.sh %s
|
||||||
|
|
||||||
|
# Run a command when tx expires
|
||||||
|
# %s in command is replaced with txid
|
||||||
|
#txexpirynotify=/my/elite/script.sh %s
|
||||||
|
|
||||||
|
# Execute this commend to send a tx
|
||||||
|
# %s is replaced with tx hex
|
||||||
|
#txsend=/send/it.sh %s
|
||||||
59
contrib/init/dragonxd.conf
Normal file
59
contrib/init/dragonxd.conf
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
description "Hush Daemon"
|
||||||
|
|
||||||
|
start on runlevel [2345]
|
||||||
|
stop on starting rc RUNLEVEL=[016]
|
||||||
|
|
||||||
|
env HUSHD_BIN="/usr/bin/dragonxd"
|
||||||
|
env HUSHD_USER="hush"
|
||||||
|
env HUSHD_GROUP="hush"
|
||||||
|
env HUSHD_PIDDIR="/var/run/dragonxd"
|
||||||
|
# upstart can't handle variables constructed with other variables
|
||||||
|
env HUSHD_PIDFILE="/var/run/dragonxd/dragonxd.pid"
|
||||||
|
env HUSHD_CONFIGFILE="/etc/hush/hush.conf"
|
||||||
|
env HUSHD_DATADIR="/var/lib/dragonxd"
|
||||||
|
|
||||||
|
expect fork
|
||||||
|
|
||||||
|
respawn
|
||||||
|
respawn limit 5 120
|
||||||
|
kill timeout 60
|
||||||
|
|
||||||
|
pre-start script
|
||||||
|
# this will catch non-existent config files
|
||||||
|
# dragonxd will check and exit with this very warning, but it can do so
|
||||||
|
# long after forking, leaving upstart to think everything started fine.
|
||||||
|
# since this is a commonly encountered case on install, just check and
|
||||||
|
# warn here.
|
||||||
|
if ! grep -qs '^rpcpassword=' "$HUSHD_CONFIGFILE" ; then
|
||||||
|
echo "ERROR: You must set a secure rpcpassword to run dragonxd."
|
||||||
|
echo "The setting must appear in $HUSHD_CONFIGFILE"
|
||||||
|
echo
|
||||||
|
echo "This password is security critical to securing wallets "
|
||||||
|
echo "and must not be the same as the rpcuser setting."
|
||||||
|
echo "You can generate a suitable random password using the following"
|
||||||
|
echo "command from the shell:"
|
||||||
|
echo
|
||||||
|
echo "bash -c 'tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo'"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HUSHD_PIDDIR"
|
||||||
|
chmod 0755 "$HUSHD_PIDDIR"
|
||||||
|
chown $HUSHD_USER:$HUSHD_GROUP "$HUSHD_PIDDIR"
|
||||||
|
chown $HUSHD_USER:$HUSHD_GROUP "$HUSHD_CONFIGFILE"
|
||||||
|
chmod 0660 "$HUSHD_CONFIGFILE"
|
||||||
|
end script
|
||||||
|
|
||||||
|
exec start-stop-daemon \
|
||||||
|
--start \
|
||||||
|
--pidfile "$HUSHD_PIDFILE" \
|
||||||
|
--chuid $HUSHD_USER:$HUSHD_GROUP \
|
||||||
|
--exec "$HUSHD_BIN" \
|
||||||
|
-- \
|
||||||
|
-pid="$HUSHD_PIDFILE" \
|
||||||
|
-conf="$HUSHD_CONFIGFILE" \
|
||||||
|
-datadir="$HUSHD_DATADIR" \
|
||||||
|
-disablewallet \
|
||||||
|
-daemon
|
||||||
|
|
||||||
67
contrib/init/dragonxd.init
Normal file
67
contrib/init/dragonxd.init
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# dragonxd The hush core server.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# chkconfig: 345 80 20
|
||||||
|
# description: dragonxd
|
||||||
|
# processname: dragonxd
|
||||||
|
#
|
||||||
|
|
||||||
|
# Source function library.
|
||||||
|
. /etc/init.d/functions
|
||||||
|
|
||||||
|
# you can override defaults in /etc/sysconfig/dragonxd, see below
|
||||||
|
if [ -f /etc/sysconfig/dragonxd ]; then
|
||||||
|
. /etc/sysconfig/dragonxd
|
||||||
|
fi
|
||||||
|
|
||||||
|
RETVAL=0
|
||||||
|
|
||||||
|
prog=dragonxd
|
||||||
|
# you can override the lockfile via HUSHD_LOCKFILE in /etc/sysconfig/dragonxd
|
||||||
|
lockfile=${HUSHD_LOCKFILE-/var/lock/subsys/dragonxd}
|
||||||
|
|
||||||
|
# dragonxd defaults to /usr/bin/dragonxd, override with HUSHD_BIN
|
||||||
|
dragonxd=${HUSHD_BIN-/usr/bin/dragonxd}
|
||||||
|
|
||||||
|
# dragonxd opts default to -disablewallet, override with HUSHD_OPTS
|
||||||
|
dragonxd_opts=${HUSHD_OPTS--disablewallet}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
echo -n $"Starting $prog: "
|
||||||
|
daemon $DAEMONOPTS $dragonxd $dragonxd_opts
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && touch $lockfile
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
echo -n $"Stopping $prog: "
|
||||||
|
killproc $prog
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
[ $RETVAL -eq 0 ] && rm -f $lockfile
|
||||||
|
return $RETVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status $prog
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: service $prog {start|stop|status|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
87
contrib/init/dragonxd.openrc
Normal file
87
contrib/init/dragonxd.openrc
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/sbin/runscript
|
||||||
|
|
||||||
|
# backward compatibility for existing gentoo layout
|
||||||
|
#
|
||||||
|
if [ -d "/var/lib/hush/.hush" ]; then
|
||||||
|
HUSHD_DEFAULT_DATADIR="/var/lib/hush/.hush"
|
||||||
|
else
|
||||||
|
HUSHD_DEFAULT_DATADIR="/var/lib/dragonxd"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HUSHD_CONFIGFILE=${HUSHD_CONFIGFILE:-/etc/hush/hush.conf}
|
||||||
|
HUSHD_PIDDIR=${HUSHD_PIDDIR:-/var/run/dragonxd}
|
||||||
|
HUSHD_PIDFILE=${HUSHD_PIDFILE:-${HUSHD_PIDDIR}/dragonxd.pid}
|
||||||
|
HUSHD_DATADIR=${HUSHD_DATADIR:-${HUSHD_DEFAULT_DATADIR}}
|
||||||
|
HUSHD_USER=${HUSHD_USER:-${HUSH_USER:-hush}}
|
||||||
|
HUSHD_GROUP=${HUSHD_GROUP:-hush}
|
||||||
|
HUSHD_BIN=${HUSHD_BIN:-/usr/bin/dragonxd}
|
||||||
|
HUSHD_NICE=${HUSHD_NICE:-${NICELEVEL:-0}}
|
||||||
|
HUSHD_OPTS="${HUSHD_OPTS:-${HUSH_OPTS}}"
|
||||||
|
|
||||||
|
name="Hush Full Node Daemon"
|
||||||
|
description="Hush cryptocurrency P2P network daemon"
|
||||||
|
|
||||||
|
command="/usr/bin/dragonxd"
|
||||||
|
command_args="-pid=\"${HUSHD_PIDFILE}\" \
|
||||||
|
-conf=\"${HUSHD_CONFIGFILE}\" \
|
||||||
|
-datadir=\"${HUSHD_DATADIR}\" \
|
||||||
|
-daemon \
|
||||||
|
${HUSHD_OPTS}"
|
||||||
|
|
||||||
|
required_files="${HUSHD_CONFIGFILE}"
|
||||||
|
start_stop_daemon_args="-u ${HUSHD_USER} \
|
||||||
|
-N ${HUSHD_NICE} -w 2000"
|
||||||
|
pidfile="${HUSHD_PIDFILE}"
|
||||||
|
|
||||||
|
# The retry schedule to use when stopping the daemon. Could be either
|
||||||
|
# a timeout in seconds or multiple signal/timeout pairs (like
|
||||||
|
# "SIGKILL/180 SIGTERM/300")
|
||||||
|
retry="${HUSHD_SIGTERM_TIMEOUT}"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need localmount net
|
||||||
|
}
|
||||||
|
|
||||||
|
# verify
|
||||||
|
# 1) that the datadir exists and is writable (or create it)
|
||||||
|
# 2) that a directory for the pid exists and is writable
|
||||||
|
# 3) ownership and permissions on the config file
|
||||||
|
start_pre() {
|
||||||
|
checkpath \
|
||||||
|
-d \
|
||||||
|
--mode 0750 \
|
||||||
|
--owner "${HUSHD_USER}:${HUSHD_GROUP}" \
|
||||||
|
"${HUSHD_DATADIR}"
|
||||||
|
|
||||||
|
checkpath \
|
||||||
|
-d \
|
||||||
|
--mode 0755 \
|
||||||
|
--owner "${HUSHD_USER}:${HUSHD_GROUP}" \
|
||||||
|
"${HUSHD_PIDDIR}"
|
||||||
|
|
||||||
|
checkpath -f \
|
||||||
|
-o ${HUSHD_USER}:${HUSHD_GROUP} \
|
||||||
|
-m 0660 \
|
||||||
|
${HUSHD_CONFIGFILE}
|
||||||
|
|
||||||
|
checkconfig || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
checkconfig()
|
||||||
|
{
|
||||||
|
if ! grep -qs '^rpcpassword=' "${HUSHD_CONFIGFILE}" ; then
|
||||||
|
eerror ""
|
||||||
|
eerror "ERROR: You must set a secure rpcpassword to run dragonxd."
|
||||||
|
eerror "The setting must appear in ${HUSHD_CONFIGFILE}"
|
||||||
|
eerror ""
|
||||||
|
eerror "This password is security critical to securing wallets "
|
||||||
|
eerror "and must not be the same as the rpcuser setting."
|
||||||
|
eerror "You can generate a suitable random password using the following"
|
||||||
|
eerror "command from the shell:"
|
||||||
|
eerror ""
|
||||||
|
eerror "bash -c 'tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo'"
|
||||||
|
eerror ""
|
||||||
|
eerror ""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
33
contrib/init/dragonxd.openrcconf
Normal file
33
contrib/init/dragonxd.openrcconf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# /etc/conf.d/dragonxd: config file for /etc/init.d/dragonxd
|
||||||
|
|
||||||
|
# Config file location
|
||||||
|
#HUSHD_CONFIGFILE="/etc/hush/hush.conf"
|
||||||
|
|
||||||
|
# What directory to write pidfile to? (created and owned by $HUSHD_USER)
|
||||||
|
#HUSHD_PIDDIR="/var/run/dragonxd"
|
||||||
|
|
||||||
|
# What filename to give the pidfile
|
||||||
|
#HUSHD_PIDFILE="${HUSHD_PIDDIR}/dragonxd.pid"
|
||||||
|
|
||||||
|
# Where to write dragonxd data (be mindful that the blockchain is large)
|
||||||
|
#HUSHD_DATADIR="/var/lib/dragonxd"
|
||||||
|
|
||||||
|
# User and group to own dragonxd process
|
||||||
|
#HUSHD_USER="hush"
|
||||||
|
#HUSHD_GROUP="hush"
|
||||||
|
|
||||||
|
# Path to dragonxd executable
|
||||||
|
#HUSHD_BIN="/usr/bin/dragonxd"
|
||||||
|
|
||||||
|
# Nice value to run dragonxd under
|
||||||
|
#HUSHD_NICE=0
|
||||||
|
|
||||||
|
# Additional options (avoid -conf and -datadir, use flags above)
|
||||||
|
HUSHD_OPTS="-disablewallet"
|
||||||
|
|
||||||
|
# The timeout in seconds OpenRC will wait for dragonxd to terminate
|
||||||
|
# after a SIGTERM has been raised.
|
||||||
|
# Note that this will be mapped as argument to start-stop-daemon's
|
||||||
|
# '--retry' option, which means you can specify a retry schedule
|
||||||
|
# here. For more information see man 8 start-stop-daemon.
|
||||||
|
HUSHD_SIGTERM_TIMEOUT=60
|
||||||
22
contrib/init/dragonxd.service
Normal file
22
contrib/init/dragonxd.service
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Hush: Speak And Transact Freely
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=hush
|
||||||
|
Group=hush
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/var/lib/dragonxd/dragonxd.pid
|
||||||
|
ExecStart=/usr/bin/dragonxd -daemon -pid=/var/lib/dragonxd/dragonxd.pid \
|
||||||
|
-conf=/etc/hush/hush.conf -datadir=/var/lib/dragonxd -disablewallet
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
PrivateTmp=true
|
||||||
|
TimeoutStopSec=60s
|
||||||
|
TimeoutStartSec=2s
|
||||||
|
StartLimitInterval=120s
|
||||||
|
StartLimitBurst=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
build_darwin_CC = gcc-8
|
build_darwin_CC = gcc-15
|
||||||
build_darwin_CXX = g++-8
|
build_darwin_CXX = g++-15
|
||||||
build_darwin_AR: = $(shell xcrun -f ar)
|
build_darwin_AR: = $(shell xcrun -f ar)
|
||||||
build_darwin_RANLIB: = $(shell xcrun -f ranlib)
|
build_darwin_RANLIB: = $(shell xcrun -f ranlib)
|
||||||
build_darwin_STRIP: = $(shell xcrun -f strip)
|
build_darwin_STRIP: = $(shell xcrun -f strip)
|
||||||
@@ -10,8 +10,8 @@ build_darwin_SHA256SUM = shasum -a 256
|
|||||||
build_darwin_DOWNLOAD = curl --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -L -f -o
|
build_darwin_DOWNLOAD = curl --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -L -f -o
|
||||||
|
|
||||||
#darwin host on darwin builder. overrides darwin host preferences.
|
#darwin host on darwin builder. overrides darwin host preferences.
|
||||||
darwin_CC= gcc-8
|
darwin_CC= gcc-15
|
||||||
darwin_CXX= g++-8
|
darwin_CXX= g++-15
|
||||||
darwin_AR:=$(shell xcrun -f ar)
|
darwin_AR:=$(shell xcrun -f ar)
|
||||||
darwin_RANLIB:=$(shell xcrun -f ranlib)
|
darwin_RANLIB:=$(shell xcrun -f ranlib)
|
||||||
darwin_STRIP:=$(shell xcrun -f strip)
|
darwin_STRIP:=$(shell xcrun -f strip)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ OSX_MIN_VERSION=10.12
|
|||||||
OSX_SDK_VERSION=10.12
|
OSX_SDK_VERSION=10.12
|
||||||
OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk
|
OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk
|
||||||
LD64_VERSION=253.9
|
LD64_VERSION=253.9
|
||||||
darwin_CC=gcc-8 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
|
darwin_CC=gcc-15 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
|
||||||
darwin_CXX=g++-8 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
|
darwin_CXX=g++-15 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
|
||||||
|
|
||||||
darwin_CFLAGS=-pipe
|
darwin_CFLAGS=-pipe
|
||||||
darwin_CXXFLAGS=$(darwin_CFLAGS)
|
darwin_CXXFLAGS=$(darwin_CFLAGS)
|
||||||
|
|||||||
@@ -40,9 +40,15 @@ define $(package)_preprocess_cmds
|
|||||||
cat $($(package)_patch_dir)/cargo.config | sed 's|CRATE_REGISTRY|$(host_prefix)/$(CRATE_REGISTRY)|' > .cargo/config
|
cat $($(package)_patch_dir)/cargo.config | sed 's|CRATE_REGISTRY|$(host_prefix)/$(CRATE_REGISTRY)|' > .cargo/config
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
ifeq ($(build_os),darwin)
|
||||||
|
define $(package)_build_cmds
|
||||||
|
CARGO=$(HOME)/.cargo/bin/cargo RUSTC=$(HOME)/.cargo/bin/rustc $(HOME)/.cargo/bin/cargo build --package librustzcash $($(package)_build_opts)
|
||||||
|
endef
|
||||||
|
else
|
||||||
define $(package)_build_cmds
|
define $(package)_build_cmds
|
||||||
$(host_prefix)/native/bin/cargo build --package librustzcash $($(package)_build_opts)
|
$(host_prefix)/native/bin/cargo build --package librustzcash $($(package)_build_opts)
|
||||||
endef
|
endef
|
||||||
|
endif
|
||||||
|
|
||||||
define $(package)_stage_cmds
|
define $(package)_stage_cmds
|
||||||
mkdir $($(package)_staging_dir)$(host_prefix)/lib/ && \
|
mkdir $($(package)_staging_dir)$(host_prefix)/lib/ && \
|
||||||
|
|||||||
8
depends/strip-rlib-metadata.sh
Executable file
8
depends/strip-rlib-metadata.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Strip rust.metadata.bin from .rlib archives for macOS linker compatibility
|
||||||
|
RLIB_DIR="$1"
|
||||||
|
if [ -d "$RLIB_DIR" ]; then
|
||||||
|
for rlib in "$RLIB_DIR"/*.rlib; do
|
||||||
|
[ -f "$rlib" ] && ar d "$rlib" rust.metadata.bin 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
7
doc/beefy-DRAGONX.conf
Normal file
7
doc/beefy-DRAGONX.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
rpcuser=dontuseweakusernameoryougetrobbed
|
||||||
|
rpcpassword=dontuseweakpasswordoryougetrobbed
|
||||||
|
txindex=1
|
||||||
|
server=1
|
||||||
|
rpcworkqueue=64
|
||||||
|
addnode=1.2.3.4
|
||||||
|
addnode=5.6.7.8
|
||||||
29
doc/dragonxd-systemd.md
Normal file
29
doc/dragonxd-systemd.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Systemd script for the DragonX daemon
|
||||||
|
|
||||||
|
## Set it up
|
||||||
|
|
||||||
|
First set it up as follows:
|
||||||
|
* Copy dragonxd.service to the systemd user directory, which is /usr/lib/systemd/user directory
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
How to start the script:
|
||||||
|
`systemctl start --user dragonxd.service`
|
||||||
|
|
||||||
|
How to stop the script:
|
||||||
|
`systemctl stop --user dragonxd.service`
|
||||||
|
|
||||||
|
How to restart the script:
|
||||||
|
`systemctl restart --user dragonxd.service`
|
||||||
|
|
||||||
|
## How to watch it as it starts
|
||||||
|
|
||||||
|
Use the following on most Linux distros:
|
||||||
|
`watch systemctl status --user dragonxd.service`
|
||||||
|
|
||||||
|
Or watch the log directly:
|
||||||
|
`tail -f ~/.hush/DRAGONX/debug.log`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* Don't run it with sudo or root, or it won't work with the wallet.
|
||||||
9
doc/dragonxd.service
Normal file
9
doc/dragonxd.service
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=DragonX daemon
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/dragonxd
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
87
doc/hd-transparent-keys.md
Normal file
87
doc/hd-transparent-keys.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# HD transparent keys
|
||||||
|
|
||||||
|
DragonX derives **transparent** (t-address) keys deterministically from the
|
||||||
|
wallet's HD seed, so they can be recovered from the seed alone — the same way
|
||||||
|
Sapling (shielded) keys already are.
|
||||||
|
|
||||||
|
## Derivation
|
||||||
|
|
||||||
|
Transparent keys are derived over secp256k1 using BIP32/BIP44:
|
||||||
|
|
||||||
|
```
|
||||||
|
m / 44' / coin_type' / 0' / 0 / i
|
||||||
|
```
|
||||||
|
|
||||||
|
* `coin_type` is `Params().BIP44CoinType()` — **141** on mainnet, **1** on
|
||||||
|
test/regtest.
|
||||||
|
* Account is fixed at `0'` and the chain at `0` (external). The internal/change
|
||||||
|
chain (`1`) is **not** used: on this `ac_private=1` chain a non-coinbase
|
||||||
|
transparent output is consensus-invalid, so transparent change can never carry
|
||||||
|
value.
|
||||||
|
* `i` is `CHDChain.transparentChildCounter`, a monotonic index persisted in the
|
||||||
|
wallet so the same addresses regenerate after a seed-only restore.
|
||||||
|
|
||||||
|
Each derived key records its `hdKeypath` and the seed fingerprint (`seedFp`) in
|
||||||
|
its `CKeyMetadata`, matching the Sapling scheme.
|
||||||
|
|
||||||
|
## Why this matters on a private chain
|
||||||
|
|
||||||
|
On DragonX (`ac_private=1` from genesis) a normal user can never *receive* to a
|
||||||
|
transparent address — inbound t-payments are rejected by consensus. The only
|
||||||
|
thing that legitimately lands spendable value on a t-address is a **mining
|
||||||
|
coinbase** (plus notary/burn special cases). There is no "coinbase must be
|
||||||
|
shielded" rule, so mature coinbase is directly spendable.
|
||||||
|
|
||||||
|
So HD transparent keys exist to let a **miner recover coinbase rewards** that
|
||||||
|
were paid to wallet-derived t-addresses, using only the seed.
|
||||||
|
|
||||||
|
## Enabling / disabling
|
||||||
|
|
||||||
|
Controlled by `-hdtransparent` (default **on**). When on and the wallet has an
|
||||||
|
HD seed, every newly generated transparent key (receive address, change,
|
||||||
|
coinbase payout drawn from the keypool) is HD-derived.
|
||||||
|
|
||||||
|
```
|
||||||
|
-hdtransparent=0 # keep the legacy behaviour (random transparent keys)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backing up and restoring
|
||||||
|
|
||||||
|
* **Back up the seed.** `z_exportwallet <file>` writes the 32-byte HD seed as a
|
||||||
|
`# HDSeed=<hex>` line. Guard this value like a private key.
|
||||||
|
* **Restore into a fresh/empty wallet** by starting the node with:
|
||||||
|
|
||||||
|
```
|
||||||
|
-hdseed=<64-hex-character seed>
|
||||||
|
-hdtransparentgaplimit=<n> # HD transparent keys to pre-derive (default 1000)
|
||||||
|
```
|
||||||
|
|
||||||
|
On restore the node injects the seed, pre-derives `n` transparent keys with a
|
||||||
|
genesis birthday, and the normal startup rescan finds any coinbase paid to
|
||||||
|
them. Raise `-hdtransparentgaplimit` if the wallet minted more than `n`
|
||||||
|
distinct coinbase addresses.
|
||||||
|
|
||||||
|
> **Warning:** passing `-hdseed` on the command line exposes the seed to your
|
||||||
|
> shell history and the process list. Prefer putting it in `DRAGONX.conf` with
|
||||||
|
> tight file permissions, and remove it after the restore completes.
|
||||||
|
|
||||||
|
## Limitations (read before relying on recovery)
|
||||||
|
|
||||||
|
* **Legacy random keys are not recoverable.** Any transparent key created before
|
||||||
|
this feature (or with `-hdtransparent=0`) came from the CSPRNG, not the seed,
|
||||||
|
and the phrase/seed will **not** regenerate it. Keep `wallet.dat` /
|
||||||
|
`dumpwallet` backups for those. A wallet that predates the feature and then
|
||||||
|
enables it becomes a *mix* of random (old) and HD (new) keys.
|
||||||
|
* **Gap limit.** A rescan only discovers keys already present in the wallet.
|
||||||
|
Restore pre-derives `-hdtransparentgaplimit` keys; coinbase paid to an index
|
||||||
|
beyond that window is not found until you derive further and rescan again.
|
||||||
|
* **Scope.** Recovers transparent **coinbase** value only, per the consensus
|
||||||
|
rules above. Shielded funds are recovered separately via the Sapling HD keys.
|
||||||
|
|
||||||
|
## On-disk compatibility
|
||||||
|
|
||||||
|
The transparent counter is stored in `CHDChain` under a new serialization
|
||||||
|
version (`VERSION_HD_TRANSPARENT = 2`). Existing v1 `wallet.dat` records load
|
||||||
|
unchanged (the counter defaults to 0); the record is rewritten as v2 the first
|
||||||
|
time an HD transparent key is derived. Downgrading a v2 wallet to an older
|
||||||
|
binary is not supported.
|
||||||
@@ -1 +1 @@
|
|||||||
dist_man1_MANS=hushd.1 hush-cli.1 hush-tx.1
|
dist_man1_MANS=dragonxd.1 dragonx-cli.1 dragonx-tx.1
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||||
.TH HUSH-CLI "1" "March 2026" "hush-cli v3.10.5" "User Commands"
|
.TH DRAGONX "1" "July 2026" "DragonX RPC client version v1.0.3-95aeaed0c-dirty" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
hush-cli \- manual page for hush-cli v3.10.5
|
DragonX \- manual page for DragonX RPC client version v1.0.3-95aeaed0c-dirty
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Hush RPC client version v3.10.5\-04916cdf5
|
DragonX RPC client version v1.0.3\-95aeaed0c\-dirty
|
||||||
.PP
|
.PP
|
||||||
In order to ensure you are adequately protecting your privacy when using Hush,
|
In order to ensure you are adequately protecting your privacy when using
|
||||||
please see <https://hush.is/security/>.
|
DragonX, please see <https://dragonx.is/security/>.
|
||||||
.SS "Usage:"
|
.SS "Usage:"
|
||||||
.TP
|
.TP
|
||||||
hush\-cli [options] <command> [params]
|
dragonx\-cli [options] <command> [params]
|
||||||
Send command to Hush
|
Send command to DragonX
|
||||||
.TP
|
.TP
|
||||||
hush\-cli [options] help
|
dragonx\-cli [options] help
|
||||||
List commands
|
List commands
|
||||||
.TP
|
.TP
|
||||||
hush\-cli [options] help <command>
|
dragonx\-cli [options] help <command>
|
||||||
Get help for a command
|
Get help for a command
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
@@ -25,7 +25,7 @@ This help message
|
|||||||
.HP
|
.HP
|
||||||
\fB\-conf=\fR<file>
|
\fB\-conf=\fR<file>
|
||||||
.IP
|
.IP
|
||||||
Specify configuration file (default: HUSH3.conf)
|
Specify configuration file (default: DRAGONX.conf)
|
||||||
.HP
|
.HP
|
||||||
\fB\-datadir=\fR<dir>
|
\fB\-datadir=\fR<dir>
|
||||||
.IP
|
.IP
|
||||||
@@ -47,7 +47,7 @@ Send commands to node running on <ip> (default: 127.0.0.1)
|
|||||||
.HP
|
.HP
|
||||||
\fB\-rpcport=\fR<port>
|
\fB\-rpcport=\fR<port>
|
||||||
.IP
|
.IP
|
||||||
Connect to JSON\-RPC on <port> (default: 18030 )
|
Connect to JSON\-RPC on <port> (default: 21769 )
|
||||||
.HP
|
.HP
|
||||||
\fB\-rpcwait\fR
|
\fB\-rpcwait\fR
|
||||||
.IP
|
.IP
|
||||||
@@ -70,20 +70,25 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default:
|
|||||||
.IP
|
.IP
|
||||||
Read extra arguments from standard input, one per line until EOF/Ctrl\-D
|
Read extra arguments from standard input, one per line until EOF/Ctrl\-D
|
||||||
(recommended for sensitive information such as passphrases)
|
(recommended for sensitive information such as passphrases)
|
||||||
|
.PP
|
||||||
|
In order to ensure you are adequately protecting your privacy when using
|
||||||
|
DragonX, please see <https://dragonx.is/security/>.
|
||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
|
Copyright \(co 2024\-2026 The DragonX Developers
|
||||||
In order to ensure you are adequately protecting your privacy when using Hush,
|
.PP
|
||||||
please see <https://hush.is/security/>.
|
.br
|
||||||
|
Copyright \(co 2016\-2024 Duke Leto and The Hush Developers
|
||||||
Copyright (C) 2016-2026 Duke Leto and The Hush Developers
|
.PP
|
||||||
|
.br
|
||||||
Copyright (C) 2016-2020 jl777 and SuperNET developers
|
Copyright \(co 2016\-2020 jl777 and SuperNET developers
|
||||||
|
.PP
|
||||||
Copyright (C) 2016-2018 The Zcash developers
|
.br
|
||||||
|
Copyright \(co 2016\-2018 The Zcash developers
|
||||||
Copyright (C) 2009-2014 The Bitcoin Core developers
|
.PP
|
||||||
|
.br
|
||||||
|
Copyright \(co 2009\-2014 The Bitcoin Core developers
|
||||||
|
.PP
|
||||||
This is experimental Free Software! Fuck Yeah!!!!!
|
This is experimental Free Software! Fuck Yeah!!!!!
|
||||||
|
.PP
|
||||||
Distributed under the GPLv3 software license, see the accompanying file COPYING
|
Distributed under the GPLv3 software license, see the accompanying file COPYING
|
||||||
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
or <https://www.gnu.org/licenses/gpl\-3.0.en.html>.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||||
.TH HUSH-TX "1" "March 2026" "hush-tx v3.10.5" "User Commands"
|
.TH DRAGONX-TX "1" "July 2026" "dragonx-tx v1.0.3-4caf2fc68" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
hush-tx \- manual page for hush-tx v3.10.5
|
dragonx-tx \- DragonX transaction utility
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
hush\-tx utility version v3.10.5\-04916cdf5
|
hush\-tx utility version v1.0.3\-4caf2fc68
|
||||||
.SS "Usage:"
|
.SS "Usage:"
|
||||||
.TP
|
.TP
|
||||||
hush\-tx [options] <hex\-tx> [commands]
|
hush\-tx [options] <hex\-tx> [commands]
|
||||||
@@ -84,20 +84,3 @@ Load JSON file FILENAME into register NAME
|
|||||||
set=NAME:JSON\-STRING
|
set=NAME:JSON\-STRING
|
||||||
.IP
|
.IP
|
||||||
Set register NAME to given JSON\-STRING
|
Set register NAME to given JSON\-STRING
|
||||||
.SH COPYRIGHT
|
|
||||||
|
|
||||||
In order to ensure you are adequately protecting your privacy when using Hush,
|
|
||||||
please see <https://hush.is/security/>.
|
|
||||||
|
|
||||||
Copyright (C) 2016-2026 Duke Leto and The Hush Developers
|
|
||||||
|
|
||||||
Copyright (C) 2016-2020 jl777 and SuperNET developers
|
|
||||||
|
|
||||||
Copyright (C) 2016-2018 The Zcash developers
|
|
||||||
|
|
||||||
Copyright (C) 2009-2014 The Bitcoin Core developers
|
|
||||||
|
|
||||||
This is experimental Free Software! Fuck Yeah!!!!!
|
|
||||||
|
|
||||||
Distributed under the GPLv3 software license, see the accompanying file COPYING
|
|
||||||
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||||
.TH HUSHD "1" "March 2026" "hushd v3.10.5" "User Commands"
|
.TH DRAGONX "1" "July 2026" "DragonX Daemon version v1.0.3-4caf2fc68" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
hushd \- manual page for hushd v3.10.5
|
DragonX \- manual page for DragonX Daemon version v1.0.3-4caf2fc68
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Hush Daemon version v3.10.5\-04916cdf5
|
DragonX Daemon version v1.0.3\-4caf2fc68
|
||||||
.PP
|
.PP
|
||||||
In order to ensure you are adequately protecting your privacy when using Hush,
|
In order to ensure you are adequately protecting your privacy when using
|
||||||
please see <https://hush.is/security/>.
|
DragonX, please see <https://dragonx.is/security/>.
|
||||||
.SS "Usage:"
|
.SS "Usage:"
|
||||||
.TP
|
.TP
|
||||||
hushd [options]
|
dragonxd [options]
|
||||||
Start a Hush Daemon
|
Start DragonX Daemon
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
\-?
|
\-?
|
||||||
@@ -32,11 +32,11 @@ How thorough the block verification of \fB\-checkblocks\fR is (0\-4, default: 3)
|
|||||||
.HP
|
.HP
|
||||||
\fB\-clientname=\fR<SomeName>
|
\fB\-clientname=\fR<SomeName>
|
||||||
.IP
|
.IP
|
||||||
Full node client name, default 'GoldenSandtrout'
|
Full node client name, default 'DragonX'
|
||||||
.HP
|
.HP
|
||||||
\fB\-conf=\fR<file>
|
\fB\-conf=\fR<file>
|
||||||
.IP
|
.IP
|
||||||
Specify configuration file (default: HUSH3.conf)
|
Specify configuration file (default: DRAGONX.conf)
|
||||||
.HP
|
.HP
|
||||||
\fB\-daemon\fR
|
\fB\-daemon\fR
|
||||||
.IP
|
.IP
|
||||||
@@ -52,7 +52,11 @@ Specify directory to be used when exporting data
|
|||||||
.HP
|
.HP
|
||||||
\fB\-dbcache=\fR<n>
|
\fB\-dbcache=\fR<n>
|
||||||
.IP
|
.IP
|
||||||
Set database cache size in megabytes (4 to 16384, default: 512)
|
Set database cache size in megabytes (4 to 16384). Default: adaptive \-
|
||||||
|
uses most free RAM to speed up initial block download (far fewer
|
||||||
|
UTXO flushes to disk) and automatically shrinks if other
|
||||||
|
applications need memory, always leaving a reserve free. Setting
|
||||||
|
a fixed value disables adaptive sizing.
|
||||||
.HP
|
.HP
|
||||||
\fB\-loadblock=\fR<file>
|
\fB\-loadblock=\fR<file>
|
||||||
.IP
|
.IP
|
||||||
@@ -78,9 +82,15 @@ applied)
|
|||||||
.HP
|
.HP
|
||||||
\fB\-par=\fR<n>
|
\fB\-par=\fR<n>
|
||||||
.IP
|
.IP
|
||||||
Set the number of script verification threads (\fB\-32\fR to 16, 0 = auto, <0 =
|
Set the number of script verification threads (\fB\-4\fR to 16, 0 = auto, <0 =
|
||||||
leave that many cores free, default: 0)
|
leave that many cores free, default: 0)
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-randomxverifythreads=\fR<n>
|
||||||
|
.IP
|
||||||
|
Number of threads for parallel RandomX PoW pre\-verification of
|
||||||
|
post\-checkpoint blocks during sync (0 = inline only, max 16,
|
||||||
|
default: same as \fB\-par\fR)
|
||||||
|
.HP
|
||||||
\fB\-pid=\fR<file>
|
\fB\-pid=\fR<file>
|
||||||
.IP
|
.IP
|
||||||
Specify pid file (default: hushd.pid)
|
Specify pid file (default: hushd.pid)
|
||||||
@@ -337,6 +347,40 @@ Do not load the wallet and disable wallet RPC calls
|
|||||||
.IP
|
.IP
|
||||||
Set key pool size to <n> (default: 100)
|
Set key pool size to <n> (default: 100)
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-hdtransparent\fR
|
||||||
|
.IP
|
||||||
|
Derive transparent addresses from the HD seed so they can be recovered
|
||||||
|
from it (default: 1)
|
||||||
|
.HP
|
||||||
|
\fB\-hdseed=\fR<hex>
|
||||||
|
.IP
|
||||||
|
Restore a fresh/empty wallet from a 32\- or 64\-byte HD seed hex (the
|
||||||
|
value shown in z_exportwallet's '# HDSeed=' line). WARNING:
|
||||||
|
exposes the seed to your shell history and process list.
|
||||||
|
.HP
|
||||||
|
\fB\-mnemonic=\fR<words>
|
||||||
|
.IP
|
||||||
|
Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible
|
||||||
|
with SilentDragonXLite (English, no passphrase). WARNING: exposes
|
||||||
|
the phrase to your shell history and process list; prefer
|
||||||
|
DRAGONX.conf with tight permissions.
|
||||||
|
.HP
|
||||||
|
\fB\-usemnemonic\fR
|
||||||
|
.IP
|
||||||
|
Create new wallets from a fresh BIP39 seed phrase so the 24 words can be
|
||||||
|
exported (z_exportmnemonic) and used in SilentDragonXLite
|
||||||
|
(default: 0)
|
||||||
|
.HP
|
||||||
|
\fB\-hdtransparentgaplimit=\fR<n>
|
||||||
|
.IP
|
||||||
|
On \fB\-mnemonic\fR/\-hdseed restore, pre\-derive this many HD transparent keys
|
||||||
|
so a rescan can find coinbase paid to them (default: 1000)
|
||||||
|
.HP
|
||||||
|
\fB\-mnemonicsaplinggap=\fR<n>
|
||||||
|
.IP
|
||||||
|
On \fB\-mnemonic\fR/\-hdseed restore, pre\-derive this many shielded (Sapling)
|
||||||
|
addresses so a rescan can find notes sent to them (default: 100)
|
||||||
|
.HP
|
||||||
\fB\-consolidation\fR
|
\fB\-consolidation\fR
|
||||||
.IP
|
.IP
|
||||||
Enable auto Sapling note consolidation (default: false)
|
Enable auto Sapling note consolidation (default: false)
|
||||||
@@ -649,8 +693,8 @@ multiple times (default: bind to all interfaces)
|
|||||||
.HP
|
.HP
|
||||||
\fB\-stratumport=\fR<port>
|
\fB\-stratumport=\fR<port>
|
||||||
.IP
|
.IP
|
||||||
Listen for Stratum work requests on <port> (default: 19031 or testnet:
|
Listen for Stratum work requests on <port> (default: 22769 or testnet:
|
||||||
19031)
|
22769)
|
||||||
.HP
|
.HP
|
||||||
\fB\-stratumallowip=\fR<ip>
|
\fB\-stratumallowip=\fR<ip>
|
||||||
.IP
|
.IP
|
||||||
@@ -659,7 +703,7 @@ single IP (e.g. 1.2.3.4), a network/netmask (e.g.
|
|||||||
1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This
|
1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This
|
||||||
option can be specified multiple times
|
option can be specified multiple times
|
||||||
.PP
|
.PP
|
||||||
Hush Arrakis Chain options:
|
DragonX Chain options:
|
||||||
.HP
|
.HP
|
||||||
\fB\-ac_algo\fR
|
\fB\-ac_algo\fR
|
||||||
.IP
|
.IP
|
||||||
@@ -760,20 +804,25 @@ Starting supply, default is 10
|
|||||||
\fB\-ac_txpow\fR
|
\fB\-ac_txpow\fR
|
||||||
.IP
|
.IP
|
||||||
Enforce transaction\-rate limit, default 0
|
Enforce transaction\-rate limit, default 0
|
||||||
|
.PP
|
||||||
|
In order to ensure you are adequately protecting your privacy when using
|
||||||
|
DragonX, please see <https://dragonx.is/security/>.
|
||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
|
Copyright \(co 2024\-2026 The DragonX Developers
|
||||||
In order to ensure you are adequately protecting your privacy when using Hush,
|
.PP
|
||||||
please see <https://hush.is/security/>.
|
.br
|
||||||
|
Copyright \(co 2016\-2024 Duke Leto and The Hush Developers
|
||||||
Copyright (C) 2016-2026 Duke Leto and The Hush Developers
|
.PP
|
||||||
|
.br
|
||||||
Copyright (C) 2016-2020 jl777 and SuperNET developers
|
Copyright \(co 2016\-2020 jl777 and SuperNET developers
|
||||||
|
.PP
|
||||||
Copyright (C) 2016-2018 The Zcash developers
|
.br
|
||||||
|
Copyright \(co 2016\-2018 The Zcash developers
|
||||||
Copyright (C) 2009-2014 The Bitcoin Core developers
|
.PP
|
||||||
|
.br
|
||||||
|
Copyright \(co 2009\-2014 The Bitcoin Core developers
|
||||||
|
.PP
|
||||||
This is experimental Free Software! Fuck Yeah!!!!!
|
This is experimental Free Software! Fuck Yeah!!!!!
|
||||||
|
.PP
|
||||||
Distributed under the GPLv3 software license, see the accompanying file COPYING
|
Distributed under the GPLv3 software license, see the accompanying file COPYING
|
||||||
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
or <https://www.gnu.org/licenses/gpl\-3.0.en.html>.
|
||||||
90
doc/seed-phrase.md
Normal file
90
doc/seed-phrase.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# BIP39 seed phrases (SilentDragonXLite-compatible)
|
||||||
|
|
||||||
|
DragonX full-node wallets can be created from and restored to a **BIP39 24-word
|
||||||
|
seed phrase** that is **byte-for-byte compatible with SilentDragonXLite**: the
|
||||||
|
same words produce the same transparent (t-) and shielded (z-) addresses in
|
||||||
|
either wallet, so funds move between the light wallet and the full node with one
|
||||||
|
backup.
|
||||||
|
|
||||||
|
## What makes them compatible
|
||||||
|
|
||||||
|
Compatibility requires the mnemonic, the seed derivation, and every HD path to
|
||||||
|
match exactly. They do:
|
||||||
|
|
||||||
|
| Detail | Value (both wallets) |
|
||||||
|
|---|---|
|
||||||
|
| Word list | BIP39 English, 2048 words |
|
||||||
|
| Passphrase | empty (no "25th word") |
|
||||||
|
| Mnemonic → seed | PBKDF2-HMAC-SHA512, 2048 rounds, salt `"mnemonic"`, 64-byte output |
|
||||||
|
| Coin type | 141 (KMD SLIP-0044) |
|
||||||
|
| Shielded path | `m/32'/141'/i'` (ZIP-32) |
|
||||||
|
| Transparent path | `m/44'/141'/0'/0/i` (BIP44) |
|
||||||
|
|
||||||
|
The node stores the 32-byte BIP39 **entropy** (SilentDragonXLite's on-disk
|
||||||
|
convention) and expands it to the 64-byte seed on demand for derivation. The
|
||||||
|
node's vendored BIP39 library (`src/crypto/bip39`) is byte-identical to
|
||||||
|
SilentDragonXLite's `tiny-bip39` 0.6.2, and the derivation is anchored by a
|
||||||
|
known-answer test (`src/gtest/test_mnemonic_compat.cpp`).
|
||||||
|
|
||||||
|
## Restore from a phrase
|
||||||
|
|
||||||
|
Start the node once, on a **fresh/empty datadir**, with the phrase:
|
||||||
|
|
||||||
|
```
|
||||||
|
dragonxd -mnemonic="word1 word2 ... word24"
|
||||||
|
```
|
||||||
|
|
||||||
|
or, preferably (keeps the phrase out of your shell history and process list),
|
||||||
|
put it in `DRAGONX.conf` with tight permissions:
|
||||||
|
|
||||||
|
```
|
||||||
|
mnemonic=word1 word2 ... word24
|
||||||
|
```
|
||||||
|
|
||||||
|
On restore the node pre-derives keys and rescans from genesis to recover funds:
|
||||||
|
|
||||||
|
* `-hdtransparentgaplimit=<n>` — HD transparent keys to pre-derive (default 1000)
|
||||||
|
* `-mnemonicsaplinggap=<n>` — shielded addresses to pre-derive (default 100)
|
||||||
|
|
||||||
|
Raise these if the wallet used many addresses. Restore only works on a wallet
|
||||||
|
with no seed yet (a brand-new datadir); it refuses to overwrite an existing seed.
|
||||||
|
|
||||||
|
## Create a new phrase on the node
|
||||||
|
|
||||||
|
By default new node wallets use a random (non-mnemonic) seed. To create a new
|
||||||
|
wallet from a fresh 24-word phrase instead — so you can export it and use it in
|
||||||
|
SilentDragonXLite — start with:
|
||||||
|
|
||||||
|
```
|
||||||
|
dragonxd -usemnemonic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Show / back up the phrase
|
||||||
|
|
||||||
|
For a mnemonic wallet (created with `-usemnemonic` or restored with `-mnemonic`):
|
||||||
|
|
||||||
|
```
|
||||||
|
dragonx-cli z_exportmnemonic
|
||||||
|
```
|
||||||
|
|
||||||
|
returns the 24 words and the seed fingerprint. The wallet must be unlocked.
|
||||||
|
Guard the phrase like a private key.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
* **English + empty passphrase only.** Any other word list or a BIP39 passphrase
|
||||||
|
would break compatibility, so they are not accepted.
|
||||||
|
* **Legacy / random-seed wallets have no phrase.** A wallet created before this
|
||||||
|
feature (or without `-usemnemonic`) has a random seed; `z_exportmnemonic`
|
||||||
|
returns an error for it — use `z_exportwallet` to back up the raw seed. Such
|
||||||
|
wallets are not SilentDragonXLite-compatible.
|
||||||
|
* **Scope.** Recovers HD-derived shielded funds and transparent coinbase (see
|
||||||
|
[hd-transparent-keys.md](hd-transparent-keys.md) for why only coinbase lands on
|
||||||
|
t-addresses on this `ac_private=1` chain). Keys imported with `z_importkey` are
|
||||||
|
not seed-derived and are not recovered by the phrase.
|
||||||
|
|
||||||
|
## On-disk compatibility
|
||||||
|
|
||||||
|
Mnemonic wallets set `CHDChain` version 3 (`VERSION_HD_MNEMONIC`). Older wallet
|
||||||
|
records load unchanged. Downgrading a mnemonic wallet to an older binary is not
|
||||||
|
supported.
|
||||||
@@ -39,15 +39,22 @@ BITCOIN_INCLUDES += -I$(srcdir)/univalue/include
|
|||||||
BITCOIN_INCLUDES += -I$(srcdir)/leveldb/include
|
BITCOIN_INCLUDES += -I$(srcdir)/leveldb/include
|
||||||
|
|
||||||
if TARGET_WINDOWS
|
if TARGET_WINDOWS
|
||||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||||
endif
|
endif
|
||||||
if TARGET_DARWIN
|
if TARGET_DARWIN
|
||||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||||
endif
|
endif
|
||||||
if TARGET_LINUX
|
if TARGET_LINUX
|
||||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# libcurl is a linker flag, not a buildable library file. It must NOT live inside
|
||||||
|
# LIBBITCOIN_SERVER, which is also fed into EXTRA_LIBRARIES (a list of files automake
|
||||||
|
# builds) where a -l flag is illegal and triggers a portability error. Keep it as its
|
||||||
|
# own variable, added to each binary's _LDADD after libbitcoin_server.a (whose objects
|
||||||
|
# reference curl symbols) so static link order stays correct.
|
||||||
|
LIBCURL = -lcurl
|
||||||
|
|
||||||
LIBBITCOIN_WALLET=libbitcoin_wallet.a
|
LIBBITCOIN_WALLET=libbitcoin_wallet.a
|
||||||
LIBBITCOIN_COMMON=libbitcoin_common.a
|
LIBBITCOIN_COMMON=libbitcoin_common.a
|
||||||
LIBBITCOIN_CLI=libbitcoin_cli.a
|
LIBBITCOIN_CLI=libbitcoin_cli.a
|
||||||
@@ -318,6 +325,7 @@ libbitcoin_wallet_a_SOURCES = \
|
|||||||
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
||||||
wallet/crypter.cpp \
|
wallet/crypter.cpp \
|
||||||
wallet/db.cpp \
|
wallet/db.cpp \
|
||||||
|
wallet/mnemonic.cpp \
|
||||||
zcash/Note.cpp \
|
zcash/Note.cpp \
|
||||||
transaction_builder.cpp \
|
transaction_builder.cpp \
|
||||||
wallet/rpcdump.cpp \
|
wallet/rpcdump.cpp \
|
||||||
@@ -354,6 +362,23 @@ crypto_libbitcoin_crypto_a_SOURCES = \
|
|||||||
crypto/sha512.cpp \
|
crypto/sha512.cpp \
|
||||||
crypto/sha512.h
|
crypto/sha512.h
|
||||||
|
|
||||||
|
# Vendored trezor-crypto BIP39 (mnemonic seed phrases). Kept self-contained so
|
||||||
|
# the same 24 words are compatible with SilentDragonXLite (tiny-bip39 0.6.2).
|
||||||
|
crypto_libbitcoin_crypto_a_SOURCES += \
|
||||||
|
crypto/bip39/bip39.c \
|
||||||
|
crypto/bip39/bip39.h \
|
||||||
|
crypto/bip39/bip39_english.h \
|
||||||
|
crypto/bip39/pbkdf2.c \
|
||||||
|
crypto/bip39/pbkdf2.h \
|
||||||
|
crypto/bip39/hmac.c \
|
||||||
|
crypto/bip39/hmac.h \
|
||||||
|
crypto/bip39/sha2.c \
|
||||||
|
crypto/bip39/sha2.h \
|
||||||
|
crypto/bip39/memzero.c \
|
||||||
|
crypto/bip39/memzero.h \
|
||||||
|
crypto/bip39/options.h \
|
||||||
|
crypto/bip39/rand.h
|
||||||
|
|
||||||
if EXPERIMENTAL_ASM
|
if EXPERIMENTAL_ASM
|
||||||
crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp
|
crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp
|
||||||
endif
|
endif
|
||||||
@@ -464,6 +489,7 @@ endif
|
|||||||
|
|
||||||
dragonxd_LDADD = \
|
dragonxd_LDADD = \
|
||||||
$(LIBBITCOIN_SERVER) \
|
$(LIBBITCOIN_SERVER) \
|
||||||
|
$(LIBCURL) \
|
||||||
$(LIBBITCOIN_COMMON) \
|
$(LIBBITCOIN_COMMON) \
|
||||||
$(LIBUNIVALUE) \
|
$(LIBUNIVALUE) \
|
||||||
$(LIBBITCOIN_UTIL) \
|
$(LIBBITCOIN_UTIL) \
|
||||||
@@ -594,7 +620,7 @@ libzcash_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBOOST_SPIRIT_THREADSAFE -DHAV
|
|||||||
#libzcash_a_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
#libzcash_a_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||||
#libzcash_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMONTGOMERY_OUTPUT
|
#libzcash_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMONTGOMERY_OUTPUT
|
||||||
|
|
||||||
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
|
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
|
||||||
libzcash_a_LDFLAGS = $(SAN_LDFLAGS) $(HARDENED_LDFLAGS)
|
libzcash_a_LDFLAGS = $(SAN_LDFLAGS) $(HARDENED_LDFLAGS)
|
||||||
libzcash_a_CPPFLAGS += -DMONTGOMERY_OUTPUT
|
libzcash_a_CPPFLAGS += -DMONTGOMERY_OUTPUT
|
||||||
|
|
||||||
@@ -636,7 +662,7 @@ libhush_a_SOURCES = \
|
|||||||
|
|
||||||
libhush_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) -pipe -O1 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES)
|
libhush_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) -pipe -O1 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES)
|
||||||
|
|
||||||
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
|
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
|
||||||
|
|
||||||
libhush_a_LDFLAGS = $(HARDENED_LDFLAGS)
|
libhush_a_LDFLAGS = $(HARDENED_LDFLAGS)
|
||||||
|
|
||||||
@@ -685,5 +711,5 @@ endif
|
|||||||
if ENABLE_TESTS
|
if ENABLE_TESTS
|
||||||
#include Makefile.test-hush.include
|
#include Makefile.test-hush.include
|
||||||
#include Makefile.test.include
|
#include Makefile.test.include
|
||||||
#include Makefile.gtest.include
|
include Makefile.gtest.include
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -4,65 +4,61 @@ TESTS += hush-gtest
|
|||||||
bin_PROGRAMS += hush-gtest
|
bin_PROGRAMS += hush-gtest
|
||||||
|
|
||||||
# tool for generating our public parameters
|
# tool for generating our public parameters
|
||||||
|
# NOTE: the original test list used an invalid automake form (comment after a trailing
|
||||||
|
# backslash, and `zcash_gtest_SOURCES +=` with no prior `=`), which is why the whole
|
||||||
|
# gtest harness was disabled via a `#include`. Minimal valid set: the harness + the
|
||||||
|
# Re-add other gtest sources here as they are revived.
|
||||||
hush_gtest_SOURCES = \
|
hush_gtest_SOURCES = \
|
||||||
gtest/main.cpp \
|
gtest/main.cpp \
|
||||||
gtest/utils.cpp \
|
gtest/utils.cpp \
|
||||||
gtest/test_checktransaction.cpp \
|
gtest/test_randomx_preverify.cpp \
|
||||||
gtest/json_test_vectors.cpp \
|
gtest/test_hdtransparent.cpp \
|
||||||
gtest/json_test_vectors.h \
|
gtest/test_mnemonic_compat.cpp
|
||||||
gtest/test_wallet_zkeys.cpp \
|
|
||||||
# These tests are order-dependent, because they
|
|
||||||
# depend on global state (see #1539)
|
|
||||||
if ENABLE_WALLET
|
|
||||||
zcash_gtest_SOURCES += \
|
|
||||||
wallet/gtest/test_wallet_zkeys.cpp
|
|
||||||
endif
|
|
||||||
zcash_gtest_SOURCES += \
|
|
||||||
gtest/test_tautology.cpp \
|
|
||||||
gtest/test_deprecation.cpp \
|
|
||||||
gtest/test_equihash.cpp \
|
|
||||||
gtest/test_httprpc.cpp \
|
|
||||||
gtest/test_keys.cpp \
|
|
||||||
gtest/test_keystore.cpp \
|
|
||||||
gtest/test_noteencryption.cpp \
|
|
||||||
gtest/test_mempool.cpp \
|
|
||||||
gtest/test_merkletree.cpp \
|
|
||||||
gtest/test_metrics.cpp \
|
|
||||||
gtest/test_miner.cpp \
|
|
||||||
gtest/test_pow.cpp \
|
|
||||||
gtest/test_random.cpp \
|
|
||||||
gtest/test_rpc.cpp \
|
|
||||||
gtest/test_sapling_note.cpp \
|
|
||||||
gtest/test_transaction.cpp \
|
|
||||||
gtest/test_transaction_builder.cpp \
|
|
||||||
gtest/test_upgrades.cpp \
|
|
||||||
gtest/test_validation.cpp \
|
|
||||||
gtest/test_circuit.cpp \
|
|
||||||
gtest/test_txid.cpp \
|
|
||||||
gtest/test_libzcash_utils.cpp \
|
|
||||||
gtest/test_proofs.cpp \
|
|
||||||
gtest/test_pedersen_hash.cpp \
|
|
||||||
gtest/test_checkblock.cpp \
|
|
||||||
gtest/test_zip32.cpp
|
|
||||||
if ENABLE_WALLET
|
|
||||||
zcash_gtest_SOURCES += \
|
|
||||||
wallet/gtest/test_wallet.cpp
|
|
||||||
endif
|
|
||||||
|
|
||||||
hush_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES)
|
hush_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES)
|
||||||
hush_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
hush_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||||
|
|
||||||
hush_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
# Mirror dragonxd_LDADD's working library set/order (the old list used a non-existent
|
||||||
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1)
|
# $(LIBBITCOIN_UNIVALUE) so univalue was never linked, and omitted LIBHUSH/LIBRANDOMX/libcc).
|
||||||
|
hush_gtest_LDADD = -lgtest -lgmock \
|
||||||
|
$(LIBBITCOIN_SERVER) \
|
||||||
|
$(LIBCURL) \
|
||||||
|
$(LIBBITCOIN_COMMON) \
|
||||||
|
$(LIBUNIVALUE) \
|
||||||
|
$(LIBBITCOIN_UTIL) \
|
||||||
|
$(LIBBITCOIN_CRYPTO) \
|
||||||
|
$(LIBZCASH) \
|
||||||
|
$(LIBHUSH) \
|
||||||
|
$(LIBLEVELDB) \
|
||||||
|
$(LIBMEMENV) \
|
||||||
|
$(LIBSECP256K1) \
|
||||||
|
$(LIBRANDOMX)
|
||||||
if ENABLE_WALLET
|
if ENABLE_WALLET
|
||||||
hush_gtest_LDADD += $(LIBBITCOIN_WALLET)
|
hush_gtest_LDADD += $(LIBBITCOIN_WALLET)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
hush_gtest_LDADD += $(LIBZCASH_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(LIBZCASH) $(LIBZCASH_LIBS)
|
hush_gtest_LDADD += \
|
||||||
|
$(BOOST_LIBS) \
|
||||||
|
$(BOOST_UNIT_TEST_FRAMEWORK_LIB) \
|
||||||
|
$(BDB_LIBS) \
|
||||||
|
$(SSL_LIBS) \
|
||||||
|
$(CRYPTO_LIBS) \
|
||||||
|
$(EVENT_PTHREADS_LIBS) \
|
||||||
|
$(EVENT_LIBS) \
|
||||||
|
$(LIBBITCOIN_CRYPTO) \
|
||||||
|
$(LIBZCASH_LIBS)
|
||||||
|
|
||||||
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
if TARGET_DARWIN
|
||||||
|
hush_gtest_LDADD += libcc.dylib $(LIBSECP256K1)
|
||||||
|
endif
|
||||||
|
if TARGET_WINDOWS
|
||||||
|
hush_gtest_LDADD += libcc.dll $(LIBSECP256K1)
|
||||||
|
endif
|
||||||
|
if TARGET_LINUX
|
||||||
|
hush_gtest_LDADD += libcc.so $(LIBSECP256K1)
|
||||||
|
endif
|
||||||
|
|
||||||
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||||
|
|
||||||
hush-gtest-expected-failures: hush-gtest FORCE
|
hush-gtest-expected-failures: hush-gtest FORCE
|
||||||
./hush-gtest --gtest_filter=*DISABLED_* --gtest_also_run_disabled_tests
|
./hush-gtest --gtest_filter=*DISABLED_* --gtest_also_run_disabled_tests
|
||||||
|
|||||||
@@ -112,13 +112,13 @@ endif
|
|||||||
|
|
||||||
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
|
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
|
||||||
test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) -fopenmp $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS)
|
test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) -fopenmp $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS)
|
||||||
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
||||||
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
|
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
|
||||||
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||||
if ENABLE_WALLET
|
if ENABLE_WALLET
|
||||||
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
|
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
|
||||||
endif
|
endif
|
||||||
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
|
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
|
||||||
$(LIBLEVELDB) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
|
$(LIBLEVELDB) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
|
||||||
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ std::string HelpMessageCli()
|
|||||||
strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be "
|
strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be "
|
||||||
"solved instantly. This is intended for regression testing tools and app development."));
|
"solved instantly. This is intended for regression testing tools and app development."));
|
||||||
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), "127.0.0.1"));
|
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), "127.0.0.1"));
|
||||||
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u )"), 18030));
|
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u )"), 21769));
|
||||||
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
|
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
|
||||||
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
|
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
|
||||||
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
|
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
SHELL = /bin/sh
|
SHELL = /bin/sh
|
||||||
CC_DARWIN = g++-8
|
CC_DARWIN = g++-15
|
||||||
CC_WIN = x86_64-w64-mingw32-gcc-posix
|
CC_WIN = x86_64-w64-mingw32-gcc-posix
|
||||||
CC_AARCH64 = aarch64-linux-gnu-g++
|
CC_AARCH64 = aarch64-linux-gnu-g++
|
||||||
CFLAGS_DARWIN = -DBUILD_CUSTOMCC -std=c++11 -arch x86_64 -I../secp256k1/include -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../leveldb/include -I.. -I. -fPIC -Wl,-undefined -Wl,dynamic_lookup -Wno-write-strings -shared -dynamiclib
|
CFLAGS_DARWIN = -DBUILD_CUSTOMCC -std=c++11 -arch x86_64 -I../secp256k1/include -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../leveldb/include -I.. -I. -fPIC -Wl,-undefined -Wl,dynamic_lookup -Wno-write-strings -shared -dynamiclib
|
||||||
|
|||||||
BIN
src/cc/customcc.dylib
Normal file
BIN
src/cc/customcc.dylib
Normal file
Binary file not shown.
48
src/chain.h
48
src/chain.h
@@ -35,9 +35,15 @@ extern bool fZindex;
|
|||||||
// These version thresholds control whether nSproutValue/nSaplingValue are
|
// These version thresholds control whether nSproutValue/nSaplingValue are
|
||||||
// serialized in the block index. They must be <= CLIENT_VERSION or the
|
// serialized in the block index. They must be <= CLIENT_VERSION or the
|
||||||
// values will never be persisted, causing nChainSaplingValue to reset
|
// values will never be persisted, causing nChainSaplingValue to reset
|
||||||
// to 0 after node restart. DragonX CLIENT_VERSION is 1000250 (v1.0.2.50).
|
// to 0 after node restart. DragonX CLIENT_VERSION is 1000350 (v1.0.3.50).
|
||||||
static const int SPROUT_VALUE_VERSION = 1000000;
|
static const int SPROUT_VALUE_VERSION = 1000000;
|
||||||
static const int SAPLING_VALUE_VERSION = 1000000;
|
static const int SAPLING_VALUE_VERSION = 1000000;
|
||||||
|
// Block-index records written at >= this version store nSaplingValue as a boost::optional
|
||||||
|
// (1-byte discriminant + value). Earlier records stored it as a raw 8-byte CAmount; the
|
||||||
|
// deserializer consumes those bytes but reads the value as boost::none (untrusted) so the
|
||||||
|
// turnstile guard stays dormant until such a node reindexes. Keep == the CLIENT_VERSION that
|
||||||
|
// introduced the optional format (v1.0.3, CLIENT_VERSION 1000350).
|
||||||
|
static const int SAPLING_VALUE_OPTIONAL_VERSION = 1000350;
|
||||||
extern int32_t ASSETCHAINS_LWMAPOS;
|
extern int32_t ASSETCHAINS_LWMAPOS;
|
||||||
extern char SMART_CHAIN_SYMBOL[65];
|
extern char SMART_CHAIN_SYMBOL[65];
|
||||||
extern uint64_t ASSETCHAINS_NOTARY_PAY[];
|
extern uint64_t ASSETCHAINS_NOTARY_PAY[];
|
||||||
@@ -373,10 +379,10 @@ public:
|
|||||||
//! Will be boost::none if nChainTx is zero.
|
//! Will be boost::none if nChainTx is zero.
|
||||||
boost::optional<CAmount> nChainSproutValue;
|
boost::optional<CAmount> nChainSproutValue;
|
||||||
|
|
||||||
//! Change in value held by the Sapling circuit over this block.
|
//! Change in value held by the Sapling circuit over this block. boost::none for blocks
|
||||||
//! Not a boost::optional because this was added before Sapling activated, so we can
|
//! before nSaplingValue was tracked, or on nodes that loaded an older-format block index
|
||||||
//! rely on the invariant that every block before this was added had nSaplingValue = 0.
|
//! (see SAPLING_VALUE_OPTIONAL_VERSION) -- propagates to nChainSaplingValue == none.
|
||||||
CAmount nSaplingValue;
|
boost::optional<CAmount> nSaplingValue;
|
||||||
|
|
||||||
//! (memory only) Total value held by the Sapling circuit up to and including this block.
|
//! (memory only) Total value held by the Sapling circuit up to and including this block.
|
||||||
//! Will be boost::none if nChainTx is zero.
|
//! Will be boost::none if nChainTx is zero.
|
||||||
@@ -400,6 +406,15 @@ public:
|
|||||||
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
|
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
|
||||||
uint32_t nSequenceId;
|
uint32_t nSequenceId;
|
||||||
|
|
||||||
|
//! (memory only) Set true once this block's RandomX PoW has been verified by the parallel
|
||||||
|
//! pre-verification pool, letting the inline check in CheckBlockHeader skip the recompute.
|
||||||
|
//! Written by exactly one pre-verify worker (1:1 with the block) and read by the connect
|
||||||
|
//! thread only AFTER the pool barrier (CCheckQueue::Wait provides the happens-before), so a
|
||||||
|
//! plain bool is race-free here. NOT serialized — a pure optimization hint; the inline
|
||||||
|
//! CheckRandomXSolution remains the consensus authority. (Plain bool, not std::atomic, so
|
||||||
|
//! CBlockIndex stays copyable for CDiskBlockIndex's `CBlockIndex(*pindex)` construction.)
|
||||||
|
bool fRandomXVerified;
|
||||||
|
|
||||||
void SetNull()
|
void SetNull()
|
||||||
{
|
{
|
||||||
phashBlock = NULL;
|
phashBlock = NULL;
|
||||||
@@ -414,6 +429,7 @@ public:
|
|||||||
chainPower = CChainPower();
|
chainPower = CChainPower();
|
||||||
nTx = 0;
|
nTx = 0;
|
||||||
nChainTx = 0;
|
nChainTx = 0;
|
||||||
|
fRandomXVerified = false;
|
||||||
|
|
||||||
// Shieldex Index chain stats
|
// Shieldex Index chain stats
|
||||||
nChainPayments = 0;
|
nChainPayments = 0;
|
||||||
@@ -450,7 +466,7 @@ public:
|
|||||||
nSequenceId = 0;
|
nSequenceId = 0;
|
||||||
nSproutValue = boost::none;
|
nSproutValue = boost::none;
|
||||||
nChainSproutValue = boost::none;
|
nChainSproutValue = boost::none;
|
||||||
nSaplingValue = 0;
|
nSaplingValue = boost::none;
|
||||||
nChainSaplingValue = boost::none;
|
nChainSaplingValue = boost::none;
|
||||||
|
|
||||||
nVersion = 0;
|
nVersion = 0;
|
||||||
@@ -657,10 +673,22 @@ public:
|
|||||||
READWRITE(nSproutValue);
|
READWRITE(nSproutValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only read/write nSaplingValue if the client version used to create
|
// nSaplingValue is a boost::optional so "not reliably tracked" reads back as none,
|
||||||
// this index was storing them.
|
// keeping the turnstile guard dormant on old/snapshot-bootstrapped DBs. Records written
|
||||||
if ((s.GetType() & SER_DISK) && (nVersion >= SAPLING_VALUE_VERSION)) {
|
// before SAPLING_VALUE_OPTIONAL_VERSION stored it as a raw 8-byte CAmount: consume those
|
||||||
READWRITE(nSaplingValue);
|
// bytes for alignment but DISCARD the value (read as none), since it may be understated on
|
||||||
|
// a node that never tracked the pool from genesis. That node stays dormant until it
|
||||||
|
// reindexes, which rewrites records at the current version -> the optional path below.
|
||||||
|
if (s.GetType() & SER_DISK) {
|
||||||
|
if (nVersion >= SAPLING_VALUE_OPTIONAL_VERSION) {
|
||||||
|
READWRITE(nSaplingValue); // new format: boost::optional<CAmount>
|
||||||
|
} else if (nVersion >= SAPLING_VALUE_VERSION) {
|
||||||
|
CAmount nLegacySaplingValue = 0; // old format: raw 8 bytes present in the stream
|
||||||
|
READWRITE(nLegacySaplingValue); // consume for alignment; value is discarded
|
||||||
|
if (ser_action.ForRead())
|
||||||
|
nSaplingValue = boost::none;
|
||||||
|
}
|
||||||
|
// else (< SAPLING_VALUE_VERSION): field was never stored -> nSaplingValue stays none
|
||||||
}
|
}
|
||||||
|
|
||||||
// These values only serialized when -zindex enabled
|
// These values only serialized when -zindex enabled
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
// Must be kept in sync with configure.ac , ugh!
|
// Must be kept in sync with configure.ac , ugh!
|
||||||
#define CLIENT_VERSION_MAJOR 1
|
#define CLIENT_VERSION_MAJOR 1
|
||||||
#define CLIENT_VERSION_MINOR 0
|
#define CLIENT_VERSION_MINOR 0
|
||||||
#define CLIENT_VERSION_REVISION 2
|
#define CLIENT_VERSION_REVISION 3
|
||||||
#define CLIENT_VERSION_BUILD 50
|
#define CLIENT_VERSION_BUILD 50
|
||||||
|
|
||||||
//! Set to true for release, false for prerelease or test build
|
//! Set to true for release, false for prerelease or test build
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ bool sanity_test_range_fmt()
|
|||||||
{
|
{
|
||||||
std::string test;
|
std::string test;
|
||||||
try {
|
try {
|
||||||
test.at(1);
|
(void)test.at(1);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return true;
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
|||||||
@@ -33,10 +33,13 @@
|
|||||||
#include "rand.h"
|
#include "rand.h"
|
||||||
#include "sha2.h"
|
#include "sha2.h"
|
||||||
|
|
||||||
#if USE_BIP39_CACHE
|
// BIP39_WORDS is used unconditionally by the wordlist helpers below, so it must
|
||||||
|
// be defined even when the BIP39 cache is disabled (upstream places it inside
|
||||||
|
// the cache block by mistake).
|
||||||
int BIP39_WORDS = 2048;
|
int BIP39_WORDS = 2048;
|
||||||
|
|
||||||
|
#if USE_BIP39_CACHE
|
||||||
|
|
||||||
static int bip39_cache_index = 0;
|
static int bip39_cache_index = 0;
|
||||||
|
|
||||||
static CONFIDENTIAL struct {
|
static CONFIDENTIAL struct {
|
||||||
|
|||||||
@@ -56,8 +56,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// implement BIP39 caching
|
// implement BIP39 caching
|
||||||
|
// Disabled: caching keeps the plaintext mnemonic/passphrase/seed in a static
|
||||||
|
// process-lifetime buffer, which we do not want in a wallet daemon.
|
||||||
#ifndef USE_BIP39_CACHE
|
#ifndef USE_BIP39_CACHE
|
||||||
#define USE_BIP39_CACHE 1
|
#define USE_BIP39_CACHE 0
|
||||||
#define BIP39_CACHE_SIZE 4
|
#define BIP39_CACHE_SIZE 4
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
171
src/gtest/test_hdtransparent.cpp
Normal file
171
src/gtest/test_hdtransparent.cpp
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) 2016-2024 The Hush developers
|
||||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Tests for HD-derived transparent keys (m/44'/coin'/0'/0/i) and the
|
||||||
|
// version-gated CHDChain serialization used to persist the transparent counter.
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "key.h"
|
||||||
|
#include "chainparams.h"
|
||||||
|
#include "streams.h"
|
||||||
|
#include "uint256.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "wallet/wallet.h"
|
||||||
|
#include "wallet/walletdb.h"
|
||||||
|
#include "zcash/zip32.h"
|
||||||
|
|
||||||
|
// Build an in-memory wallet with a known seed + hdChain so that the
|
||||||
|
// HD-transparent path (IsHDTransparentEnabled) is active.
|
||||||
|
static void LoadSeedForTest(CWallet& wallet, const HDSeed& seed)
|
||||||
|
{
|
||||||
|
wallet.LoadHDSeed(seed);
|
||||||
|
CHDChain chain;
|
||||||
|
chain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
|
||||||
|
chain.seedFp = seed.Fingerprint();
|
||||||
|
chain.nCreateTime = 1;
|
||||||
|
wallet.SetHDChain(chain, true /* memonly */);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same seed must reproduce the same transparent addresses in the same order:
|
||||||
|
// this is the recovery guarantee that lets a seed-only restore find coinbase.
|
||||||
|
TEST(hdtransparent_tests, DeterministicFromSeed)
|
||||||
|
{
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
|
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x42);
|
||||||
|
HDSeed seed(rawSeed);
|
||||||
|
|
||||||
|
std::vector<CKeyID> keysA;
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
LoadSeedForTest(wallet, seed);
|
||||||
|
ASSERT_TRUE(wallet.IsHDTransparentEnabled());
|
||||||
|
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
CPubKey pk = wallet.GenerateNewKey();
|
||||||
|
keysA.push_back(pk.GetID());
|
||||||
|
|
||||||
|
const CKeyMetadata& md = wallet.mapKeyMetadata[pk.GetID()];
|
||||||
|
EXPECT_EQ(md.seedFp, seed.Fingerprint());
|
||||||
|
EXPECT_EQ(md.hdKeypath, std::string("m/44'/141'/0'/0/") + std::to_string(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh wallet, same seed -> identical keys.
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
LoadSeedForTest(wallet, seed);
|
||||||
|
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
CPubKey pk = wallet.GenerateNewKey();
|
||||||
|
EXPECT_EQ(pk.GetID(), keysA[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pin the exact derivation path so it can never silently change.
|
||||||
|
TEST(hdtransparent_tests, KnownDerivationPath)
|
||||||
|
{
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
|
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x42);
|
||||||
|
HDSeed seed(rawSeed);
|
||||||
|
|
||||||
|
// Independently derive m/44'/141'/0'/0/0.
|
||||||
|
RawHDSeed raw = seed.RawSeed();
|
||||||
|
CExtKey m, purpose, coinType, account, external, child;
|
||||||
|
m.SetMaster(raw.data(), raw.size());
|
||||||
|
m.Derive(purpose, 44 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
purpose.Derive(coinType, 141 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
coinType.Derive(account, 0 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
account.Derive(external, 0);
|
||||||
|
external.Derive(child, 0);
|
||||||
|
CKeyID expected = child.key.GetPubKey().GetID();
|
||||||
|
|
||||||
|
CWallet wallet;
|
||||||
|
LoadSeedForTest(wallet, seed);
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
CPubKey pk = wallet.GenerateNewKey();
|
||||||
|
EXPECT_EQ(pk.GetID(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pre-existing v1 CHDChain record (no transparent counter) must still
|
||||||
|
// deserialize under v2 code, leaving transparentChildCounter at 0; and a v2
|
||||||
|
// record must round-trip the counter.
|
||||||
|
TEST(hdtransparent_tests, HDChainVersionCompat)
|
||||||
|
{
|
||||||
|
CHDChain v1;
|
||||||
|
v1.nVersion = CHDChain::VERSION_HD_BASE; // 1: transparentChildCounter not serialized
|
||||||
|
v1.seedFp = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
|
||||||
|
v1.nCreateTime = 12345;
|
||||||
|
v1.saplingAccountCounter = 7;
|
||||||
|
|
||||||
|
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
|
||||||
|
ss << v1;
|
||||||
|
|
||||||
|
CHDChain out; // default-constructed: SetNull() zeroes transparentChildCounter
|
||||||
|
ss >> out;
|
||||||
|
EXPECT_EQ(out.nVersion, +CHDChain::VERSION_HD_BASE); // unary + -> rvalue, avoid ODR-use of static const
|
||||||
|
EXPECT_EQ(out.seedFp, v1.seedFp);
|
||||||
|
EXPECT_EQ(out.nCreateTime, (int64_t)12345);
|
||||||
|
EXPECT_EQ(out.saplingAccountCounter, (uint32_t)7);
|
||||||
|
EXPECT_EQ(out.transparentChildCounter, (uint32_t)0);
|
||||||
|
|
||||||
|
CHDChain v2;
|
||||||
|
v2.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
|
||||||
|
v2.saplingAccountCounter = 3;
|
||||||
|
v2.transparentChildCounter = 42;
|
||||||
|
CDataStream ss2(SER_DISK, PROTOCOL_VERSION);
|
||||||
|
ss2 << v2;
|
||||||
|
|
||||||
|
CHDChain out2;
|
||||||
|
ss2 >> out2;
|
||||||
|
EXPECT_EQ(out2.nVersion, +CHDChain::VERSION_HD_TRANSPARENT);
|
||||||
|
EXPECT_EQ(out2.saplingAccountCounter, (uint32_t)3);
|
||||||
|
EXPECT_EQ(out2.transparentChildCounter, (uint32_t)42);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restoring from a 32-byte seed hex reproduces the same keys as the source
|
||||||
|
// wallet, and refuses to run when a seed already exists.
|
||||||
|
TEST(hdtransparent_tests, RestoreFromSeedHex)
|
||||||
|
{
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
|
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x7a);
|
||||||
|
HDSeed seed(rawSeed);
|
||||||
|
std::string seedHex = HexStr(seed.RawSeed());
|
||||||
|
|
||||||
|
// Source wallet: derive some keys.
|
||||||
|
std::vector<CKeyID> expected;
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
LoadSeedForTest(wallet, seed);
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
expected.push_back(wallet.GenerateNewKey().GetID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restored wallet: inject the seed hex, pre-derive, and compare.
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
ASSERT_TRUE(wallet.SetHDSeedFromHex(seedHex));
|
||||||
|
// Second attempt must fail: a seed already exists.
|
||||||
|
EXPECT_FALSE(wallet.SetHDSeedFromHex(seedHex));
|
||||||
|
|
||||||
|
wallet.TopUpHDTransparentKeys(3, 1);
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
EXPECT_TRUE(wallet.HaveKey(expected[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad input is rejected.
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
EXPECT_FALSE(wallet.SetHDSeedFromHex("nothex"));
|
||||||
|
EXPECT_FALSE(wallet.SetHDSeedFromHex("abcd")); // too short
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/gtest/test_mnemonic_compat.cpp
Normal file
141
src/gtest/test_mnemonic_compat.cpp
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright (c) 2016-2024 The Hush developers
|
||||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Proves that a BIP39 seed phrase produces the SAME transparent and shielded
|
||||||
|
// addresses on the DragonX full node as in SilentDragonXLite. The proof chain:
|
||||||
|
// phrase -> entropy (round-trip) -> 64-byte BIP39 seed (known-answer)
|
||||||
|
// -> z/t addresses (wallet path == direct ZIP-32/BIP44 derivation).
|
||||||
|
// The 64-byte seed is anchored to the well-known BIP39 value for the all-zero
|
||||||
|
// "abandon...art" entropy with an EMPTY passphrase, which is exactly what
|
||||||
|
// SilentDragonXLite's tiny-bip39 0.6.2 feeds into the same coin_type=141 paths.
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "chainparams.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "key_io.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "wallet/mnemonic.h"
|
||||||
|
#include "wallet/wallet.h"
|
||||||
|
#include "zcash/Address.hpp"
|
||||||
|
#include "zcash/zip32.h"
|
||||||
|
|
||||||
|
// The canonical 24-word phrase for 32 bytes of all-zero entropy.
|
||||||
|
static const char* ABANDON_ART =
|
||||||
|
"abandon abandon abandon abandon abandon abandon abandon abandon "
|
||||||
|
"abandon abandon abandon abandon abandon abandon abandon abandon "
|
||||||
|
"abandon abandon abandon abandon abandon abandon abandon art";
|
||||||
|
|
||||||
|
// The standard BIP39 seed for that phrase with an EMPTY passphrase
|
||||||
|
// (PBKDF2-HMAC-SHA512, 2048 rounds, salt "mnemonic"). Matches tiny-bip39.
|
||||||
|
static const char* SEED64_HEX =
|
||||||
|
"408b285c123836004f4b8842c89324c1f01382450c0d439af345ba7fc49acf70"
|
||||||
|
"5489c6fc77dbd4e3dc1dd8cc6bc9f043db8ada1e243c4a0eafb290d399480840";
|
||||||
|
|
||||||
|
// First shielded address for a 64-byte seed: m/32'/141'/0' default address.
|
||||||
|
static std::string DeriveZAddrFromSeed64(RawHDSeed seed64)
|
||||||
|
{
|
||||||
|
HDSeed s(seed64);
|
||||||
|
auto m = libzcash::SaplingExtendedSpendingKey::Master(s);
|
||||||
|
auto xsk = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT)
|
||||||
|
.Derive(141 | ZIP32_HARDENED_KEY_LIMIT)
|
||||||
|
.Derive(0 | ZIP32_HARDENED_KEY_LIMIT);
|
||||||
|
return EncodePaymentAddress(xsk.DefaultAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
// First transparent address for a BIP32 master over `seedBytes`:
|
||||||
|
// m/44'/141'/0'/0/0.
|
||||||
|
static std::string DeriveTAddrFromSeedBytes(RawHDSeed seedBytes)
|
||||||
|
{
|
||||||
|
CExtKey master, purpose, coinType, account, external, child;
|
||||||
|
master.SetMaster(seedBytes.data(), seedBytes.size());
|
||||||
|
master.Derive(purpose, 44 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
purpose.Derive(coinType, 141 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
coinType.Derive(account, 0 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
account.Derive(external, 0);
|
||||||
|
external.Derive(child, 0);
|
||||||
|
return EncodeDestination(child.key.GetPubKey().GetID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 64-byte seed derived from the mnemonic must equal the known BIP39 value.
|
||||||
|
// This is the cross-wallet anchor: SilentDragonXLite feeds the identical seed.
|
||||||
|
TEST(mnemonic_compat, Bip39SeedKnownAnswer)
|
||||||
|
{
|
||||||
|
RawHDSeed entropy(32, 0);
|
||||||
|
RawHDSeed seed64;
|
||||||
|
ASSERT_TRUE(Bip39SeedFromEntropy(entropy, seed64));
|
||||||
|
ASSERT_EQ(seed64.size(), (size_t)64);
|
||||||
|
EXPECT_EQ(HexStr(seed64.begin(), seed64.end()), std::string(SEED64_HEX));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(mnemonic_compat, EntropyPhraseRoundTrip)
|
||||||
|
{
|
||||||
|
RawHDSeed zeros(32, 0);
|
||||||
|
std::string phrase;
|
||||||
|
ASSERT_TRUE(EntropyToMnemonic(zeros, phrase));
|
||||||
|
EXPECT_EQ(phrase, std::string(ABANDON_ART));
|
||||||
|
|
||||||
|
EXPECT_TRUE(MnemonicIsValid(ABANDON_ART));
|
||||||
|
|
||||||
|
RawHDSeed entropy;
|
||||||
|
ASSERT_TRUE(MnemonicToEntropy(ABANDON_ART, entropy));
|
||||||
|
EXPECT_EQ(entropy.size(), (size_t)32);
|
||||||
|
EXPECT_EQ(HexStr(entropy.begin(), entropy.end()), std::string(64, '0'));
|
||||||
|
|
||||||
|
// Bad checksum / unknown words are rejected.
|
||||||
|
EXPECT_FALSE(MnemonicIsValid("abandon abandon abandon"));
|
||||||
|
EXPECT_FALSE(MnemonicIsValid("clearly not valid bip39 words at all here"));
|
||||||
|
RawHDSeed junk;
|
||||||
|
EXPECT_FALSE(MnemonicToEntropy("clearly not valid bip39 words at all here", junk));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The wallet's mnemonic derivation must reproduce the exact addresses obtained
|
||||||
|
// by driving ZIP-32 / BIP44 directly from the known 64-byte seed, and must be
|
||||||
|
// deterministic across wallets.
|
||||||
|
TEST(mnemonic_compat, WalletDerivesSdxliteAddresses)
|
||||||
|
{
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
|
RawHDSeed zeros(32, 0), seed64;
|
||||||
|
ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64));
|
||||||
|
const std::string expZ = DeriveZAddrFromSeed64(seed64);
|
||||||
|
const std::string expT = DeriveTAddrFromSeedBytes(seed64);
|
||||||
|
EXPECT_EQ(expZ.substr(0, 2), "zs"); // sapling HRP for mainnet
|
||||||
|
|
||||||
|
CWallet wallet;
|
||||||
|
ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART));
|
||||||
|
ASSERT_TRUE(wallet.IsMnemonicSeed());
|
||||||
|
{
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
EXPECT_EQ(EncodePaymentAddress(wallet.GenerateNewSaplingZKey()), expZ);
|
||||||
|
EXPECT_EQ(EncodeDestination(wallet.GenerateNewKey().GetID()), expT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same phrase, fresh wallet -> identical first addresses.
|
||||||
|
CWallet wallet2;
|
||||||
|
ASSERT_TRUE(wallet2.SetHDSeedFromMnemonic(ABANDON_ART));
|
||||||
|
{
|
||||||
|
LOCK(wallet2.cs_wallet);
|
||||||
|
EXPECT_EQ(EncodePaymentAddress(wallet2.GenerateNewSaplingZKey()), expZ);
|
||||||
|
EXPECT_EQ(EncodeDestination(wallet2.GenerateNewKey().GetID()), expT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The phrase round-trips out of the wallet.
|
||||||
|
std::string exported;
|
||||||
|
ASSERT_TRUE(wallet.GetMnemonicPhrase(exported));
|
||||||
|
EXPECT_EQ(exported, std::string(ABANDON_ART));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative: feeding the 32-byte entropy DIRECTLY as the seed (the classic
|
||||||
|
// interop bug) must produce a different address than the 64-byte BIP39 seed.
|
||||||
|
TEST(mnemonic_compat, RawEntropyDiffersFromMnemonicSeed)
|
||||||
|
{
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
|
RawHDSeed zeros(32, 0), seed64;
|
||||||
|
ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64));
|
||||||
|
|
||||||
|
const std::string seedT = DeriveTAddrFromSeedBytes(seed64); // correct (64-byte)
|
||||||
|
const std::string entropyT = DeriveTAddrFromSeedBytes(zeros); // wrong (32-byte)
|
||||||
|
EXPECT_NE(seedT, entropyT);
|
||||||
|
}
|
||||||
172
src/gtest/test_randomx_preverify.cpp
Normal file
172
src/gtest/test_randomx_preverify.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (c) 2024-2026 The DragonX developers
|
||||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Consensus-equivalence test for the parallel RandomX pre-verification pool. The pool is purely an
|
||||||
|
// optimization: a block's transient fRandomXVerified flag (set by CRandomXCheck on a real hash
|
||||||
|
// match) only lets CheckBlockHeader SKIP the inline recompute. So for every block the pool's
|
||||||
|
// outcome must equal the inline CheckRandomXSolution outcome — `(preVerified || inline) == inline`.
|
||||||
|
// We exercise a valid solution, a corrupted solution, and confirm the pool never "succeeds" on a
|
||||||
|
// block the inline check would reject.
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "arith_uint256.h"
|
||||||
|
#include "chain.h"
|
||||||
|
#include "chainparams.h"
|
||||||
|
#include "pow.h"
|
||||||
|
#include "primitives/block.h"
|
||||||
|
#include "RandomX/src/randomx.h"
|
||||||
|
#include "hush_defs.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <boost/thread.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
extern int32_t HUSH_LOADINGBLOCKS;
|
||||||
|
extern bool fCheckpointsEnabled;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Compute the correct RandomX solution for a header using a standalone reference light VM, via the
|
||||||
|
// SAME key + input helpers the validator uses (so the bytes/key match exactly).
|
||||||
|
void ReferenceRandomXHash(const CBlockHeader& hdr, const std::string& key, unsigned char out[RANDOMX_HASH_SIZE])
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> in = GetRandomXInput(hdr);
|
||||||
|
randomx_flags flags = randomx_get_flags();
|
||||||
|
randomx_cache* c = randomx_alloc_cache(flags);
|
||||||
|
ASSERT_NE(c, nullptr);
|
||||||
|
randomx_init_cache(c, key.data(), key.size());
|
||||||
|
randomx_vm* vm = randomx_create_vm(flags, c, nullptr);
|
||||||
|
ASSERT_NE(vm, nullptr);
|
||||||
|
randomx_calculate_hash(vm, in.data(), in.size(), out);
|
||||||
|
randomx_destroy_vm(vm);
|
||||||
|
randomx_release_cache(c);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(RandomXPreVerify, ConsensusEquivalence)
|
||||||
|
{
|
||||||
|
// Force RandomX validation to actually run at low heights in the test harness.
|
||||||
|
uint32_t savedAlgo = ASSETCHAINS_ALGO, savedRx = ASSETCHAINS_RANDOMX;
|
||||||
|
int32_t savedVal = ASSETCHAINS_RANDOMX_VALIDATION, savedLoad = HUSH_LOADINGBLOCKS;
|
||||||
|
bool savedCkpt = fCheckpointsEnabled;
|
||||||
|
ASSETCHAINS_RANDOMX = 2; // a distinct nonzero algo id
|
||||||
|
ASSETCHAINS_ALGO = ASSETCHAINS_RANDOMX;
|
||||||
|
ASSETCHAINS_RANDOMX_VALIDATION = 1; // enforce from height 1
|
||||||
|
HUSH_LOADINGBLOCKS = 0; // not in initial-load (else RandomX skipped)
|
||||||
|
fCheckpointsEnabled = false; // avoid the below-checkpoint skip
|
||||||
|
|
||||||
|
const int32_t height = 10; // < interval+lag -> the chain-params initial key (no chainActive needed)
|
||||||
|
|
||||||
|
CBlockHeader hdr;
|
||||||
|
hdr.nVersion = 4;
|
||||||
|
hdr.hashPrevBlock = uint256S("0x0000000000000000000000000000000000000000000000000000000000000001");
|
||||||
|
hdr.hashMerkleRoot = uint256S("0x0000000000000000000000000000000000000000000000000000000000000002");
|
||||||
|
hdr.hashFinalSaplingRoot = uint256S("0x0000000000000000000000000000000000000000000000000000000000000003");
|
||||||
|
hdr.nTime = 1700000000;
|
||||||
|
hdr.nBits = 0x200f0f0f;
|
||||||
|
hdr.nNonce = uint256S("0x0000000000000000000000000000000000000000000000000000000000000004");
|
||||||
|
|
||||||
|
std::string key = GetRandomXKey(height);
|
||||||
|
ASSERT_FALSE(key.empty());
|
||||||
|
|
||||||
|
unsigned char good[RANDOMX_HASH_SIZE];
|
||||||
|
ReferenceRandomXHash(hdr, key, good);
|
||||||
|
|
||||||
|
// Run the pool path synchronously on this thread (CRandomXCheck creates its own thread_local VM).
|
||||||
|
auto poolVerifies = [&](const CBlockHeader& h) -> bool {
|
||||||
|
RandomXValidatorPrepareKey(key); // load the shared cache with this key
|
||||||
|
bool slot = false;
|
||||||
|
CRandomXCheck chk(key, GetRandomXInput(h), h.nSolution.data(), &slot);
|
||||||
|
chk();
|
||||||
|
return slot;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Case 1 — valid solution: both inline and pool accept; equivalence holds.
|
||||||
|
hdr.nSolution.assign(good, good + RANDOMX_HASH_SIZE);
|
||||||
|
EXPECT_TRUE(CheckRandomXSolution(&hdr, height));
|
||||||
|
EXPECT_TRUE(poolVerifies(hdr));
|
||||||
|
EXPECT_EQ(poolVerifies(hdr) || CheckRandomXSolution(&hdr, height), CheckRandomXSolution(&hdr, height));
|
||||||
|
|
||||||
|
// Case 2 — corrupted solution: both reject; the pool must NOT set verified.
|
||||||
|
{
|
||||||
|
CBlockHeader bad = hdr;
|
||||||
|
bad.nSolution[0] ^= 0xff;
|
||||||
|
EXPECT_FALSE(CheckRandomXSolution(&bad, height));
|
||||||
|
EXPECT_FALSE(poolVerifies(bad));
|
||||||
|
EXPECT_EQ(poolVerifies(bad) || CheckRandomXSolution(&bad, height), CheckRandomXSolution(&bad, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3 — a verified flag on the block lets CheckBlockHeader skip, but verified is only ever set
|
||||||
|
// by a real hash match, so it can never mask an invalid block. (Pool returns false for the bad
|
||||||
|
// block above, so its fRandomXVerified stays false and the inline path rejects it at connect.)
|
||||||
|
|
||||||
|
ASSETCHAINS_ALGO = savedAlgo; ASSETCHAINS_RANDOMX = savedRx;
|
||||||
|
ASSETCHAINS_RANDOMX_VALIDATION = savedVal; HUSH_LOADINGBLOCKS = savedLoad;
|
||||||
|
fCheckpointsEnabled = savedCkpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A/B: serial inline verification (single VM) vs the parallel pool (worker threads). Directly
|
||||||
|
// measures the speedup the pool delivers. We don't care about validity here (mismatched solutions
|
||||||
|
// still cost a full hash), only wall-clock. parallel must beat serial whenever >1 core is used.
|
||||||
|
TEST(RandomXPreVerify, ParallelSpeedup)
|
||||||
|
{
|
||||||
|
uint32_t savedAlgo = ASSETCHAINS_ALGO, savedRx = ASSETCHAINS_RANDOMX;
|
||||||
|
int32_t savedVal = ASSETCHAINS_RANDOMX_VALIDATION, savedLoad = HUSH_LOADINGBLOCKS;
|
||||||
|
bool savedCkpt = fCheckpointsEnabled;
|
||||||
|
ASSETCHAINS_RANDOMX = 2; ASSETCHAINS_ALGO = ASSETCHAINS_RANDOMX;
|
||||||
|
ASSETCHAINS_RANDOMX_VALIDATION = 1; HUSH_LOADINGBLOCKS = 0; fCheckpointsEnabled = false;
|
||||||
|
|
||||||
|
const int32_t height = 10;
|
||||||
|
std::string key = GetRandomXKey(height);
|
||||||
|
ASSERT_FALSE(key.empty());
|
||||||
|
ASSERT_TRUE(RandomXValidatorPrepareKey(key));
|
||||||
|
|
||||||
|
const int M = 16; // blocks to verify in the window
|
||||||
|
std::vector<CBlockHeader> hdrs(M);
|
||||||
|
for (int i = 0; i < M; i++) {
|
||||||
|
hdrs[i].nVersion = 4;
|
||||||
|
hdrs[i].nTime = 1700000000 + i;
|
||||||
|
hdrs[i].nBits = 0x200f0f0f;
|
||||||
|
hdrs[i].nNonce = ArithToUint256(arith_uint256(i + 1)); // distinct inputs
|
||||||
|
hdrs[i].nSolution.assign(RANDOMX_HASH_SIZE, 0); // arbitrary; we time the hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial baseline: inline single-VM verification (each call hashes, then mismatches -> false).
|
||||||
|
int64_t t0 = GetTimeMicros();
|
||||||
|
for (int i = 0; i < M; i++) CheckRandomXSolution(&hdrs[i], height);
|
||||||
|
int64_t serialUs = GetTimeMicros() - t0;
|
||||||
|
|
||||||
|
// Parallel: spawn K-1 workers + the master (this thread) joining via Wait().
|
||||||
|
int K = std::min(8, std::max(2, (int)boost::thread::hardware_concurrency()));
|
||||||
|
boost::thread_group workers;
|
||||||
|
for (int i = 0; i < K - 1; i++) workers.create_thread(&ThreadRandomXVerify);
|
||||||
|
|
||||||
|
std::unique_ptr<bool[]> slots(new bool[M]());
|
||||||
|
std::vector<CRandomXCheck> checks;
|
||||||
|
checks.reserve(M);
|
||||||
|
for (int i = 0; i < M; i++)
|
||||||
|
checks.push_back(CRandomXCheck(key, GetRandomXInput(hdrs[i]), hdrs[i].nSolution.data(), &slots[i]));
|
||||||
|
|
||||||
|
int64_t t1 = GetTimeMicros();
|
||||||
|
{
|
||||||
|
CCheckQueueControl<CRandomXCheck> control(&rxCheckQueue);
|
||||||
|
control.Add(checks);
|
||||||
|
control.Wait();
|
||||||
|
}
|
||||||
|
int64_t parallelUs = GetTimeMicros() - t1;
|
||||||
|
|
||||||
|
workers.interrupt_all();
|
||||||
|
workers.join_all();
|
||||||
|
|
||||||
|
printf("[ RandomX A/B ] %d blocks: serial(1 VM)=%ldms, parallel(%d threads)=%ldms, speedup=%.1fx\n",
|
||||||
|
M, (long)(serialUs / 1000), K, (long)(parallelUs / 1000),
|
||||||
|
(double)serialUs / (double)std::max<int64_t>(1, parallelUs));
|
||||||
|
|
||||||
|
EXPECT_LT(parallelUs, serialUs); // parallel must be faster than serial on a multi-core box
|
||||||
|
|
||||||
|
ASSETCHAINS_ALGO = savedAlgo; ASSETCHAINS_RANDOMX = savedRx;
|
||||||
|
ASSETCHAINS_RANDOMX_VALIDATION = savedVal; HUSH_LOADINGBLOCKS = savedLoad;
|
||||||
|
fCheckpointsEnabled = savedCkpt;
|
||||||
|
}
|
||||||
@@ -580,7 +580,22 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
|||||||
char pchBuf[0x10000];
|
char pchBuf[0x10000];
|
||||||
bool bIsSSL = false;
|
bool bIsSSL = false;
|
||||||
int nBytes = 0, nRet = 0;
|
int nBytes = 0, nRet = 0;
|
||||||
|
// Drain the socket in a bounded loop rather than one read per select pass: a single
|
||||||
|
// 64K read per pass underfills high-bandwidth/high-latency links. Cap the reads per
|
||||||
|
// pass and honor the receive-flood back-pressure so one peer can neither exhaust
|
||||||
|
// memory nor starve other peers within this pass.
|
||||||
|
int nDrainReads = 0;
|
||||||
|
const int MAX_DRAIN_READS = 16; // up to ~1 MiB per peer per pass (fairness across peers)
|
||||||
|
// Pre-read back-pressure: gate on the flood ceiling BEFORE each read so the per-peer
|
||||||
|
// recv buffer high-water stays at ReceiveFloodSize()+one read (matching the select()
|
||||||
|
// FD_SET gate), and track bytes locally to avoid the O(n) GetTotalRecvSize() per pass.
|
||||||
|
const int64_t nRecvBase = (int64_t)pnode->GetTotalRecvSize();
|
||||||
|
int64_t nPassBytes = 0;
|
||||||
|
bool fKeepReading = true;
|
||||||
|
while (fKeepReading) {
|
||||||
|
int nSSLPending = 0;
|
||||||
|
if (nRecvBase + nPassBytes > (int64_t)ReceiveFloodSize())
|
||||||
|
break;
|
||||||
{
|
{
|
||||||
LOCK(pnode->cs_hSocket);
|
LOCK(pnode->cs_hSocket);
|
||||||
|
|
||||||
@@ -595,6 +610,10 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
|||||||
wolfSSL_ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
|
wolfSSL_ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
|
||||||
nBytes = wolfSSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
|
nBytes = wolfSSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
|
||||||
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
|
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
|
||||||
|
// Capture TLS buffered-byte count while still under cs_hSocket; the drain-continuation
|
||||||
|
// check below runs unlocked, so touching pnode->ssl there would race with
|
||||||
|
// SocketSendData / CloseSocketDisconnect (which free ssl) -> data race / use-after-free.
|
||||||
|
nSSLPending = wolfSSL_pending(pnode->ssl);
|
||||||
} else {
|
} else {
|
||||||
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
|
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
|
||||||
nRet = WSAGetLastError();
|
nRet = WSAGetLastError();
|
||||||
@@ -602,11 +621,22 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nBytes > 0) {
|
if (nBytes > 0) {
|
||||||
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
|
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes)) {
|
||||||
pnode->CloseSocketDisconnect();
|
pnode->CloseSocketDisconnect();
|
||||||
|
fKeepReading = false;
|
||||||
|
}
|
||||||
pnode->nLastRecv = GetTime();
|
pnode->nLastRecv = GetTime();
|
||||||
pnode->nRecvBytes += nBytes;
|
pnode->nRecvBytes += nBytes;
|
||||||
pnode->RecordBytesRecv(nBytes);
|
pnode->RecordBytesRecv(nBytes);
|
||||||
|
nPassBytes += nBytes;
|
||||||
|
// Keep draining only while the socket likely has more data (we filled the
|
||||||
|
// buffer, or TLS has buffered decrypted bytes) and within the per-pass cap.
|
||||||
|
// The flood ceiling is enforced pre-read at the top of the loop.
|
||||||
|
if (fKeepReading) {
|
||||||
|
bool fMore = (nBytes == (int)sizeof(pchBuf)) || (bIsSSL && nSSLPending > 0);
|
||||||
|
if (!fMore || ++nDrainReads >= MAX_DRAIN_READS)
|
||||||
|
fKeepReading = false;
|
||||||
|
}
|
||||||
} else if (nBytes == 0) {
|
} else if (nBytes == 0) {
|
||||||
|
|
||||||
if (bIsSSL) {
|
if (bIsSSL) {
|
||||||
@@ -619,6 +649,7 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
|||||||
if (!pnode->fDisconnect)
|
if (!pnode->fDisconnect)
|
||||||
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
|
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
|
||||||
pnode->CloseSocketDisconnect();
|
pnode->CloseSocketDisconnect();
|
||||||
|
fKeepReading = false;
|
||||||
|
|
||||||
} else if (nBytes < 0) {
|
} else if (nBytes < 0) {
|
||||||
// error
|
// error
|
||||||
@@ -645,6 +676,8 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
|||||||
pnode->CloseSocketDisconnect();
|
pnode->CloseSocketDisconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fKeepReading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
251
src/init.cpp
251
src/init.cpp
@@ -43,6 +43,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "metrics.h"
|
#include "metrics.h"
|
||||||
|
#include "pow.h"
|
||||||
#include "miner.h"
|
#include "miner.h"
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "rpc/server.h"
|
#include "rpc/server.h"
|
||||||
@@ -176,7 +177,7 @@ public:
|
|||||||
// Writes do not need similar protection, as failure to write is handled by the caller.
|
// Writes do not need similar protection, as failure to write is handled by the caller.
|
||||||
};
|
};
|
||||||
|
|
||||||
static CCoinsViewDB *pcoinsdbview = NULL;
|
CCoinsViewDB *pcoinsdbview = NULL; // global (declared extern in main.h) for UTXO-snapshot dump/load
|
||||||
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
|
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
|
||||||
static boost::scoped_ptr<ECCVerifyHandle> globalVerifyHandle;
|
static boost::scoped_ptr<ECCVerifyHandle> globalVerifyHandle;
|
||||||
|
|
||||||
@@ -282,6 +283,7 @@ void Shutdown()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
UnregisterAllValidationInterfaces();
|
UnregisterAllValidationInterfaces();
|
||||||
|
RandomXValidatorShutdown(); // release the ~256MB shared RandomX pre-verify cache (was leaked at exit)
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
delete pwalletMain;
|
delete pwalletMain;
|
||||||
pwalletMain = NULL;
|
pwalletMain = NULL;
|
||||||
@@ -387,7 +389,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||||||
}
|
}
|
||||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||||
strUsage += HelpMessageOpt("-exportdir=<dir>", _("Specify directory to be used when exporting data"));
|
strUsage += HelpMessageOpt("-exportdir=<dir>", _("Specify directory to be used when exporting data"));
|
||||||
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
|
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d). Default: adaptive - uses most free RAM to speed up initial block download (far fewer UTXO flushes to disk) and automatically shrinks if other applications need memory, always leaving a reserve free. Setting a fixed value disables adaptive sizing."), nMinDbCache, nMaxDbCache));
|
||||||
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));
|
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));
|
||||||
strUsage += HelpMessageOpt("-maxdebugfilesize=<n>", strprintf(_("Set the max size of the debug.log file (default: %u)"), 15));
|
strUsage += HelpMessageOpt("-maxdebugfilesize=<n>", strprintf(_("Set the max size of the debug.log file (default: %u)"), 15));
|
||||||
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
|
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
|
||||||
@@ -395,6 +397,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||||||
strUsage += HelpMessageOpt("-mempooltxinputlimit=<n>", _("[DEPRECATED/IGNORED] Set the maximum number of transparent inputs in a transaction that the mempool will accept (default: 0 = no limit applied)"));
|
strUsage += HelpMessageOpt("-mempooltxinputlimit=<n>", _("[DEPRECATED/IGNORED] Set the maximum number of transparent inputs in a transaction that the mempool will accept (default: 0 = no limit applied)"));
|
||||||
strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"),
|
strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"),
|
||||||
-(int)boost::thread::hardware_concurrency(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS));
|
-(int)boost::thread::hardware_concurrency(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS));
|
||||||
|
strUsage += HelpMessageOpt("-randomxverifythreads=<n>", strprintf(_("Number of threads for parallel RandomX PoW pre-verification of post-checkpoint blocks during network sync; no effect on reindex (0 = inline only, max %d, default: same as -par)"), MAX_SCRIPTCHECK_THREADS));
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "hushd.pid"));
|
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "hushd.pid"));
|
||||||
#endif
|
#endif
|
||||||
@@ -465,6 +468,12 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||||||
strUsage += HelpMessageGroup(_("Wallet options:"));
|
strUsage += HelpMessageGroup(_("Wallet options:"));
|
||||||
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
|
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
|
||||||
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), 100));
|
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), 100));
|
||||||
|
strUsage += HelpMessageOpt("-hdtransparent", strprintf(_("Derive transparent addresses from the HD seed so they can be recovered from it (default: %u)"), 1));
|
||||||
|
strUsage += HelpMessageOpt("-hdseed=<hex>", _("Restore a fresh/empty wallet from a 32- or 64-byte HD seed hex (the value shown in z_exportwallet's '# HDSeed=' line). WARNING: exposes the seed to your shell history and process list."));
|
||||||
|
strUsage += HelpMessageOpt("-mnemonic=<words>", _("Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible with SilentDragonXLite (English, no passphrase; cross-wallet restore parity is mainnet-only -- testnet/regtest derive a different HD coin_type). WARNING: exposes the phrase to your shell history and process list; prefer DRAGONX.conf with tight permissions."));
|
||||||
|
strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite (default: %u)"), 0));
|
||||||
|
strUsage += HelpMessageOpt("-hdtransparentgaplimit=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many HD transparent keys so a rescan can find coinbase paid to them (default: %u)"), 1000));
|
||||||
|
strUsage += HelpMessageOpt("-mnemonicsaplinggap=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many shielded (Sapling) addresses so a rescan can find notes sent to them (default: %u)"), 100));
|
||||||
strUsage += HelpMessageOpt("-consolidation", _("Enable auto Sapling note consolidation (default: false)"));
|
strUsage += HelpMessageOpt("-consolidation", _("Enable auto Sapling note consolidation (default: false)"));
|
||||||
strUsage += HelpMessageOpt("-consolidationinterval", _("Block interval between consolidations (default: 25)"));
|
strUsage += HelpMessageOpt("-consolidationinterval", _("Block interval between consolidations (default: 25)"));
|
||||||
strUsage += HelpMessageOpt("-consolidatesaplingaddress=<zaddr>", _("Specify Sapling Address to Consolidate. (default: all)"));
|
strUsage += HelpMessageOpt("-consolidatesaplingaddress=<zaddr>", _("Specify Sapling Address to Consolidate. (default: all)"));
|
||||||
@@ -987,6 +996,123 @@ bool AppInitServers(boost::thread_group& threadGroup)
|
|||||||
*/
|
*/
|
||||||
extern int32_t HUSH_REWIND;
|
extern int32_t HUSH_REWIND;
|
||||||
|
|
||||||
|
// --- Adaptive coins-cache sizing -------------------------------------------------------------
|
||||||
|
// The in-memory UTXO/coins cache (nCoinCacheUsage) is the biggest lever on IBD speed: a bigger
|
||||||
|
// cache means far fewer chainstate flushes to disk. We size it to use most of RAM, but a scheduled
|
||||||
|
// background task (AdjustCoinCacheForMemoryPressure, registered in AppInit2) shrinks the target when
|
||||||
|
// free system memory runs low — e.g. the user opens other apps — and grows it back when memory frees
|
||||||
|
// up, always leaving a reserve free for the rest of the system. The existing per-block flush
|
||||||
|
// (FlushStateToDisk, FLUSH_STATE_IF_NEEDED, which fires when cacheSize > nCoinCacheUsage) enforces
|
||||||
|
// whatever target is current, so the task only moves the threshold: it never touches cs_main or the
|
||||||
|
// flush path. NOTE: the coins cache is application heap, not OS file cache — "freeing" it means an
|
||||||
|
// early flush that clears the map; on Linux the allocator returns the pages, on Windows the heap
|
||||||
|
// returns them best-effort (RSS may lag), but either way the node stops growing past the target.
|
||||||
|
// windows.h / <unistd.h> arrive via compat.h (net.h). Memory helpers return 0 if undeterminable.
|
||||||
|
static int64_t GetPhysicalMemoryMB()
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
MEMORYSTATUSEX status;
|
||||||
|
status.dwLength = sizeof(status);
|
||||||
|
if (GlobalMemoryStatusEx(&status))
|
||||||
|
return (int64_t)(status.ullTotalPhys / (1024 * 1024));
|
||||||
|
return 0;
|
||||||
|
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
|
||||||
|
long pages = sysconf(_SC_PHYS_PAGES);
|
||||||
|
long pageSize = sysconf(_SC_PAGESIZE);
|
||||||
|
if (pages > 0 && pageSize > 0)
|
||||||
|
return (int64_t)((int64_t)pages * (int64_t)pageSize / (1024 * 1024));
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently-available (allocatable) physical RAM in MiB. On Linux uses MemAvailable (counts
|
||||||
|
// reclaimable page cache), falling back to truly-free pages.
|
||||||
|
static int64_t GetAvailableMemoryMB()
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
MEMORYSTATUSEX status;
|
||||||
|
status.dwLength = sizeof(status);
|
||||||
|
if (GlobalMemoryStatusEx(&status))
|
||||||
|
return (int64_t)(status.ullAvailPhys / (1024 * 1024));
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
FILE* f = fopen("/proc/meminfo", "r");
|
||||||
|
if (f) {
|
||||||
|
char line[256];
|
||||||
|
long long availKB = -1;
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
if (sscanf(line, "MemAvailable: %lld kB", &availKB) == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
if (availKB >= 0)
|
||||||
|
return (int64_t)(availKB / 1024);
|
||||||
|
}
|
||||||
|
#if defined(_SC_AVPHYS_PAGES) && defined(_SC_PAGESIZE)
|
||||||
|
long pages = sysconf(_SC_AVPHYS_PAGES);
|
||||||
|
long pageSize = sysconf(_SC_PAGESIZE);
|
||||||
|
if (pages > 0 && pageSize > 0)
|
||||||
|
return (int64_t)((int64_t)pages * (int64_t)pageSize / (1024 * 1024));
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAM (MiB) to always keep free for the OS and other applications: 20% of total, at least 2 GiB.
|
||||||
|
static int64_t GetMemoryReserveMB()
|
||||||
|
{
|
||||||
|
int64_t ramMB = GetPhysicalMemoryMB();
|
||||||
|
int64_t reserve = (ramMB > 0) ? ramMB / 5 : 2048; // 20%
|
||||||
|
if (reserve < 2048) reserve = 2048;
|
||||||
|
return reserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Startup -dbcache default: use most of RAM (total minus the reserve), clamped to
|
||||||
|
// [nDefaultDbCache, nMaxDbCache] MiB. Falls back to the fixed default if RAM can't be detected.
|
||||||
|
static int64_t GetDefaultDbCacheMB()
|
||||||
|
{
|
||||||
|
int64_t ramMB = GetPhysicalMemoryMB();
|
||||||
|
if (ramMB <= 0)
|
||||||
|
return nDefaultDbCache;
|
||||||
|
int64_t cacheMB = ramMB - GetMemoryReserveMB();
|
||||||
|
if (cacheMB < nDefaultDbCache) cacheMB = nDefaultDbCache;
|
||||||
|
if (cacheMB > nMaxDbCache) cacheMB = nMaxDbCache;
|
||||||
|
return cacheMB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ceiling (bytes) the adaptive task may grow the coins cache back up to (the startup nCoinCacheUsage).
|
||||||
|
static size_t g_nMaxCoinCacheUsage = 0;
|
||||||
|
static const int64_t g_nMinCoinCacheMB = 256; // never thrash below this working set
|
||||||
|
|
||||||
|
// Scheduled task: nudge nCoinCacheUsage toward "use all RAM except the reserve". If free RAM is below
|
||||||
|
// the reserve we shrink the target (the next per-block flush releases the excess); if there is spare
|
||||||
|
// RAM we grow it back toward the startup ceiling. nCoinCacheUsage is std::atomic<size_t>, so this
|
||||||
|
// cross-thread write (vs the cs_main-held reads in FlushStateToDisk/VerifyDB) is well-defined, no lock needed.
|
||||||
|
static void AdjustCoinCacheForMemoryPressure()
|
||||||
|
{
|
||||||
|
if (g_nMaxCoinCacheUsage == 0)
|
||||||
|
return; // adaptive sizing disabled (user pinned -dbcache) or RAM undetectable
|
||||||
|
int64_t availMB = GetAvailableMemoryMB();
|
||||||
|
if (availMB <= 0)
|
||||||
|
return; // can't measure pressure; leave the target untouched
|
||||||
|
int64_t reserveMB = GetMemoryReserveMB();
|
||||||
|
// Error term: free RAM beyond the reserve. >0 => spare, grow; <0 => pressure, shrink.
|
||||||
|
int64_t errMB = availMB - reserveMB;
|
||||||
|
// Deadband: ignore small fluctuations so the target settles instead of oscillating.
|
||||||
|
if (errMB > -256 && errMB < 256)
|
||||||
|
return;
|
||||||
|
int64_t curTargetMB = (int64_t)(nCoinCacheUsage >> 20);
|
||||||
|
// Damped proportional step (gain 1/4) toward "free RAM == reserve"; the clamps bound it and the
|
||||||
|
// per-block flush (FLUSH_STATE_IF_NEEDED) enforces a lowered target within ~one block during IBD.
|
||||||
|
int64_t newTargetMB = curTargetMB + errMB / 4;
|
||||||
|
int64_t ceilMB = (int64_t)(g_nMaxCoinCacheUsage >> 20);
|
||||||
|
if (newTargetMB > ceilMB) newTargetMB = ceilMB;
|
||||||
|
if (newTargetMB < g_nMinCoinCacheMB) newTargetMB = g_nMinCoinCacheMB;
|
||||||
|
nCoinCacheUsage = (size_t)(newTargetMB << 20);
|
||||||
|
}
|
||||||
|
|
||||||
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
{
|
{
|
||||||
//fprintf(stderr,"%s start\n", __FUNCTION__);
|
//fprintf(stderr,"%s start\n", __FUNCTION__);
|
||||||
@@ -1309,6 +1435,33 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
|
else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
|
||||||
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
|
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
|
||||||
|
|
||||||
|
// Parallel RandomX pre-verification threads (speeds up post-checkpoint sync). Defaults to the
|
||||||
|
// script-check thread count — RandomX pre-verify and script checks do not run simultaneously
|
||||||
|
// within a single connect, so they can share the same budget. 0 disables (inline-only).
|
||||||
|
nRandomXVerifyThreads = GetArg("-randomxverifythreads", nScriptCheckThreads);
|
||||||
|
if (nRandomXVerifyThreads < 0)
|
||||||
|
nRandomXVerifyThreads = 0;
|
||||||
|
else if (nRandomXVerifyThreads > MAX_SCRIPTCHECK_THREADS)
|
||||||
|
nRandomXVerifyThreads = MAX_SCRIPTCHECK_THREADS;
|
||||||
|
|
||||||
|
// Per-peer block-download window (see MAX_BLOCKS_IN_TRANSIT_PER_PEER). Raising this lifts
|
||||||
|
// the bandwidth-delay-product ceiling on high-latency peers during IBD. Clamp to a sane range.
|
||||||
|
MAX_BLOCKS_IN_TRANSIT_PER_PEER = GetArg("-maxblocksintransit", DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER);
|
||||||
|
if (MAX_BLOCKS_IN_TRANSIT_PER_PEER < 1)
|
||||||
|
MAX_BLOCKS_IN_TRANSIT_PER_PEER = 1;
|
||||||
|
else if (MAX_BLOCKS_IN_TRANSIT_PER_PEER > (int)BLOCK_DOWNLOAD_WINDOW) {
|
||||||
|
// Values above BLOCK_DOWNLOAD_WINDOW are a silent no-op: FindNextBlocksToDownload never fetches
|
||||||
|
// beyond pindexLastCommonBlock + BLOCK_DOWNLOAD_WINDOW, so clamp to the real effective ceiling.
|
||||||
|
LogPrintf("-maxblocksintransit=%d exceeds the effective ceiling BLOCK_DOWNLOAD_WINDOW=%u; clamping\n", MAX_BLOCKS_IN_TRANSIT_PER_PEER, BLOCK_DOWNLOAD_WINDOW);
|
||||||
|
MAX_BLOCKS_IN_TRANSIT_PER_PEER = (int)BLOCK_DOWNLOAD_WINDOW;
|
||||||
|
}
|
||||||
|
LogPrintf("Per-peer max blocks in transit: %d\n", MAX_BLOCKS_IN_TRANSIT_PER_PEER);
|
||||||
|
|
||||||
|
// Opt-in bulk block streaming (DragonX). Drives the requester branch in SendMessages and, when
|
||||||
|
// set, also advertises NODE_BULKBLOCKS below so we serve bulk ranges to peers. OFF by default.
|
||||||
|
fBulkBlockSync = GetBoolArg("-bulkblocksync", DEFAULT_BULKBLOCKSYNC);
|
||||||
|
LogPrintf("Bulk block streaming: %s\n", fBulkBlockSync ? "enabled" : "disabled");
|
||||||
|
|
||||||
fServer = GetBoolArg("-server", false);
|
fServer = GetBoolArg("-server", false);
|
||||||
//fprintf(stderr,"%s tik6\n", __FUNCTION__);
|
//fprintf(stderr,"%s tik6\n", __FUNCTION__);
|
||||||
|
|
||||||
@@ -1545,6 +1698,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
threadGroup.create_thread(&ThreadScriptCheck);
|
threadGroup.create_thread(&ThreadScriptCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spawn the parallel RandomX pre-verification worker pool (the connect thread joins as the Nth
|
||||||
|
// worker via CCheckQueueControl::Wait, so spawn N-1 here, mirroring ThreadScriptCheck).
|
||||||
|
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && nRandomXVerifyThreads > 0) {
|
||||||
|
LogPrintf("Using %u threads for parallel RandomX pre-verification\n", nRandomXVerifyThreads);
|
||||||
|
for (int i = 0; i < nRandomXVerifyThreads - 1; i++)
|
||||||
|
threadGroup.create_thread(&ThreadRandomXVerify);
|
||||||
|
}
|
||||||
|
|
||||||
//fprintf(stderr,"%s tik13\n", __FUNCTION__);
|
//fprintf(stderr,"%s tik13\n", __FUNCTION__);
|
||||||
|
|
||||||
// Start the lightweight task scheduler thread
|
// Start the lightweight task scheduler thread
|
||||||
@@ -1840,14 +2001,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
LogPrintf("* Compression is %s\n", dbCompression ? "enabled" : "disabled");
|
LogPrintf("* Compression is %s\n", dbCompression ? "enabled" : "disabled");
|
||||||
|
|
||||||
// cache size calculations
|
// cache size calculations
|
||||||
int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
|
int64_t nTotalCache = (GetArg("-dbcache", GetDefaultDbCacheMB()) << 20);
|
||||||
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
|
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
|
||||||
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
|
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
|
||||||
int64_t nBlockTreeDBCache = nTotalCache / 8;
|
int64_t nBlockTreeDBCache = nTotalCache / 8;
|
||||||
|
|
||||||
if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) {
|
if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) {
|
||||||
// enable 3/4 of the cache if addressindex and/or spentindex is enabled
|
// Give indexed (address/spent-index) nodes a larger index LevelDB read cache, but CAP it.
|
||||||
|
// With adaptive dbcache, nTotalCache is now multi-GB, so 3/4 of it is several GB -- far more
|
||||||
|
// than the index cache can use, while starving the in-memory UTXO set that actually speeds
|
||||||
|
// IBD (and this chunk is not shrinkable by the memory-pressure controller, which only adjusts
|
||||||
|
// the coins cache). Cap at 1 GiB so the adaptive budget flows to the coins cache. Tunable.
|
||||||
nBlockTreeDBCache = nTotalCache * 3 / 4;
|
nBlockTreeDBCache = nTotalCache * 3 / 4;
|
||||||
|
if (nBlockTreeDBCache > ((int64_t)1024 << 20))
|
||||||
|
nBlockTreeDBCache = ((int64_t)1024 << 20);
|
||||||
} else {
|
} else {
|
||||||
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) {
|
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) {
|
||||||
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
|
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
|
||||||
@@ -1857,6 +2024,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
|
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
|
||||||
nTotalCache -= nCoinDBCache;
|
nTotalCache -= nCoinDBCache;
|
||||||
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
|
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
|
||||||
|
// Adaptive sizing: unless the user pinned -dbcache, grow/shrink the coins cache with free system
|
||||||
|
// memory (AdjustCoinCacheForMemoryPressure), using the startup size as the ceiling.
|
||||||
|
if (!mapArgs.count("-dbcache")) {
|
||||||
|
g_nMaxCoinCacheUsage = nCoinCacheUsage;
|
||||||
|
scheduler.scheduleEvery(&AdjustCoinCacheForMemoryPressure, 5);
|
||||||
|
LogPrintf("* Adaptive dbcache enabled: ceiling %.0fMiB, keeping >= %lldMiB RAM free for the system\n",
|
||||||
|
nCoinCacheUsage * (1.0 / 1024 / 1024), (long long)GetMemoryReserveMB());
|
||||||
|
}
|
||||||
LogPrintf("Cache configuration:\n");
|
LogPrintf("Cache configuration:\n");
|
||||||
LogPrintf("* Max cache setting possible %.1fMiB\n", nMaxDbCache);
|
LogPrintf("* Max cache setting possible %.1fMiB\n", nMaxDbCache);
|
||||||
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
|
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
|
||||||
@@ -1908,7 +2083,25 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex);
|
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex);
|
||||||
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
|
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
|
||||||
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
|
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
|
||||||
|
try {
|
||||||
pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex);
|
pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// The notarizations (dPoW) DB is non-essential and node-regenerable. It has been seen to
|
||||||
|
// snapshot/flush torn (0-byte log -> leveldb "Database I/O error" on reopen), which
|
||||||
|
// previously aborted startup with a spurious "Error opening block database" and forced a
|
||||||
|
// full resync. Wipe and recreate it instead of failing hard.
|
||||||
|
LogPrintf("%s: notarizations DB failed to open (%s); moving aside and regenerating (non-fatal)\n", __FUNCTION__, e.what());
|
||||||
|
// Move (do NOT delete) the old DB aside, so a transient open failure (fd
|
||||||
|
// exhaustion, disk full, permissions) cannot permanently destroy notarization
|
||||||
|
// history. If the recreate below also fails it propagates as fatal and the old
|
||||||
|
// data survives in notarizations.corrupt for recovery.
|
||||||
|
{
|
||||||
|
boost::filesystem::path ndir = GetDataDir() / "notarizations";
|
||||||
|
boost::filesystem::remove_all(ndir.string() + ".corrupt");
|
||||||
|
boost::filesystem::rename(ndir, ndir.string() + ".corrupt");
|
||||||
|
}
|
||||||
|
pnotarizations = new NotarizationDB(100*1024*1024, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (fReindex) {
|
if (fReindex) {
|
||||||
@@ -1938,6 +2131,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
strLoadError = _("Error initializing block database");
|
strLoadError = _("Error initializing block database");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
HUSH_LOADINGBLOCKS = 0;
|
HUSH_LOADINGBLOCKS = 0;
|
||||||
// Check for changed -txindex state
|
// Check for changed -txindex state
|
||||||
if (fTxIndex != GetBoolArg("-txindex", true)) {
|
if (fTxIndex != GetBoolArg("-txindex", true)) {
|
||||||
@@ -2106,11 +2300,57 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!pwalletMain->HaveHDSeed())
|
if (!pwalletMain->HaveHDSeed())
|
||||||
|
{
|
||||||
|
std::string mnemonic = GetArg("-mnemonic", "");
|
||||||
|
std::string hdSeedHex = GetArg("-hdseed", "");
|
||||||
|
bool restoring = false;
|
||||||
|
|
||||||
|
if (!mnemonic.empty() && !hdSeedHex.empty())
|
||||||
|
return InitError(_("Specify only one of -mnemonic or -hdseed, not both"));
|
||||||
|
|
||||||
|
if (!mnemonic.empty())
|
||||||
|
{
|
||||||
|
// Restore/create a wallet from a BIP39 seed phrase, byte-compatible
|
||||||
|
// with SilentDragonXLite. Must be a fresh/empty wallet.
|
||||||
|
if (!pwalletMain->SetHDSeedFromMnemonic(mnemonic))
|
||||||
|
return InitError(_("Invalid -mnemonic: expected a valid BIP39 English phrase on a fresh/empty wallet"));
|
||||||
|
LogPrintf("%s: restoring wallet from -mnemonic seed phrase\n", __func__);
|
||||||
|
restoring = true;
|
||||||
|
}
|
||||||
|
else if (!hdSeedHex.empty())
|
||||||
|
{
|
||||||
|
// Restore from a previously exported HD seed hex (z_exportwallet's
|
||||||
|
// "# HDSeed=" line): 32 bytes (raw) or 64 bytes (BIP39-derived).
|
||||||
|
if (!pwalletMain->SetHDSeedFromHex(hdSeedHex))
|
||||||
|
return InitError(_("Invalid -hdseed: expected a 32- or 64-hex-character seed on a fresh/empty wallet"));
|
||||||
|
LogPrintf("%s: restoring wallet from -hdseed\n", __func__);
|
||||||
|
restoring = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// generate a new HD seed
|
// generate a new HD seed
|
||||||
pwalletMain->GenerateNewSeed();
|
pwalletMain->GenerateNewSeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (restoring)
|
||||||
|
{
|
||||||
|
// Pre-derive keys (birthday = genesis) so the startup rescan finds
|
||||||
|
// funds paid to them: transparent coinbase + shielded notes.
|
||||||
|
int64_t tGap = GetArg("-hdtransparentgaplimit", 1000);
|
||||||
|
if (tGap < 0) tGap = 0;
|
||||||
|
pwalletMain->TopUpHDTransparentKeys((unsigned int)tGap, 1);
|
||||||
|
|
||||||
|
int64_t zGap = GetArg("-mnemonicsaplinggap", 100);
|
||||||
|
if (zGap < 0) zGap = 0;
|
||||||
|
{
|
||||||
|
LOCK(pwalletMain->cs_wallet);
|
||||||
|
for (int i = 0; i < (int)zGap; i++)
|
||||||
|
pwalletMain->GenerateNewSaplingZKey();
|
||||||
|
}
|
||||||
|
LogPrintf("%s: pre-derived %d transparent and %d sapling keys for restore rescan\n", __func__, (int)tGap, (int)zGap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Set Sapling Consolidation
|
//Set Sapling Consolidation
|
||||||
pwalletMain->fSaplingConsolidationEnabled = GetBoolArg("-consolidation", false);
|
pwalletMain->fSaplingConsolidationEnabled = GetBoolArg("-consolidation", false);
|
||||||
if(pwalletMain->fSaplingConsolidationEnabled) {
|
if(pwalletMain->fSaplingConsolidationEnabled) {
|
||||||
@@ -2380,6 +2620,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||||||
nLocalServices |= NODE_ADDRINDEX;
|
nLocalServices |= NODE_ADDRINDEX;
|
||||||
if ( GetBoolArg("-spentindex", DEFAULT_SPENTINDEX) != 0 )
|
if ( GetBoolArg("-spentindex", DEFAULT_SPENTINDEX) != 0 )
|
||||||
nLocalServices |= NODE_SPENTINDEX;
|
nLocalServices |= NODE_SPENTINDEX;
|
||||||
|
// Advertise willingness to SERVE bulk block streams (full nodes only) when opted in.
|
||||||
|
if ( fBulkBlockSync )
|
||||||
|
nLocalServices |= NODE_BULKBLOCKS;
|
||||||
fprintf(stderr,"nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX));
|
fprintf(stderr,"nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX));
|
||||||
}
|
}
|
||||||
// ********************************************************* Step 10: import blocks
|
// ********************************************************* Step 10: import blocks
|
||||||
|
|||||||
@@ -39,6 +39,9 @@
|
|||||||
*/
|
*/
|
||||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
|
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
|
||||||
|
|
||||||
|
/** BIP32: child indices at or above this are hardened. */
|
||||||
|
const unsigned int BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
||||||
|
|
||||||
/** An encapsulated private key. */
|
/** An encapsulated private key. */
|
||||||
class CKey
|
class CKey
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ namespace port {
|
|||||||
|
|
||||||
// Mac OS
|
// Mac OS
|
||||||
#elif defined(OS_MACOSX)
|
#elif defined(OS_MACOSX)
|
||||||
|
#include <atomic>
|
||||||
inline void MemoryBarrier() {
|
inline void MemoryBarrier() {
|
||||||
OSMemoryBarrier();
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
}
|
}
|
||||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||||
|
|
||||||
|
|||||||
BIN
src/libcc.dylib
Normal file
BIN
src/libcc.dylib
Normal file
Binary file not shown.
409
src/main.cpp
409
src/main.cpp
@@ -89,6 +89,12 @@ static int64_t nTimeBestReceived = 0;
|
|||||||
CWaitableCriticalSection csBestBlock;
|
CWaitableCriticalSection csBestBlock;
|
||||||
CConditionVariable cvBlockChange;
|
CConditionVariable cvBlockChange;
|
||||||
int nScriptCheckThreads = 0;
|
int nScriptCheckThreads = 0;
|
||||||
|
int MAX_BLOCKS_IN_TRANSIT_PER_PEER = DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER;
|
||||||
|
bool fBulkBlockSync = DEFAULT_BULKBLOCKSYNC;
|
||||||
|
// Server-side flood throttle: minimum interval between bulk serves to the same peer (main.cpp-local
|
||||||
|
// since only the serve handler uses it; kept out of main.h to avoid a full-tree recompile).
|
||||||
|
static const int64_t BULK_MIN_SERVE_INTERVAL_US = 50000; // 50 ms => <= 20 bulk serves/s/peer
|
||||||
|
int nRandomXVerifyThreads = 0; // parallel RandomX pre-verification worker count (0 = inline only)
|
||||||
bool fExperimentalMode = true;
|
bool fExperimentalMode = true;
|
||||||
bool fImporting = false;
|
bool fImporting = false;
|
||||||
bool fReindex = false;
|
bool fReindex = false;
|
||||||
@@ -103,7 +109,7 @@ bool fIsBareMultisigStd = true;
|
|||||||
bool fCheckBlockIndex = false;
|
bool fCheckBlockIndex = false;
|
||||||
bool fCheckpointsEnabled = true;
|
bool fCheckpointsEnabled = true;
|
||||||
bool fCoinbaseEnforcedProtectionEnabled = true;
|
bool fCoinbaseEnforcedProtectionEnabled = true;
|
||||||
size_t nCoinCacheUsage = 5000 * 300;
|
std::atomic<size_t> nCoinCacheUsage(5000 * 300);
|
||||||
uint64_t nPruneTarget = 0;
|
uint64_t nPruneTarget = 0;
|
||||||
// If the tip is older than this (in seconds), the node is considered to be in initial block download.
|
// If the tip is older than this (in seconds), the node is considered to be in initial block download.
|
||||||
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
|
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
|
||||||
@@ -247,6 +253,7 @@ namespace {
|
|||||||
int64_t nTime; //! Time of "getdata" request in microseconds.
|
int64_t nTime; //! Time of "getdata" request in microseconds.
|
||||||
bool fValidatedHeaders; //! Whether this block has validated headers at the time of request.
|
bool fValidatedHeaders; //! Whether this block has validated headers at the time of request.
|
||||||
int64_t nTimeDisconnect; //! The timeout for this block request (for disconnecting a slow peer)
|
int64_t nTimeDisconnect; //! The timeout for this block request (for disconnecting a slow peer)
|
||||||
|
bool fBulk; //! Requested as part of a bulk stream range (exempt from the front() stall-disconnect).
|
||||||
};
|
};
|
||||||
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
|
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
|
||||||
|
|
||||||
@@ -306,6 +313,21 @@ namespace {
|
|||||||
int nBlocksInFlightValidHeaders;
|
int nBlocksInFlightValidHeaders;
|
||||||
//! Whether we consider this a preferred download peer.
|
//! Whether we consider this a preferred download peer.
|
||||||
bool fPreferredDownload;
|
bool fPreferredDownload;
|
||||||
|
//! Opt-in bulk block streaming (DragonX): whether a bulk range request is outstanding to this peer.
|
||||||
|
bool fBulkInFlight;
|
||||||
|
//! Time (us) the outstanding bulk request was issued, for the response timeout/fallback.
|
||||||
|
int64_t nBulkSince;
|
||||||
|
//! Height of the first block in the outstanding bulk range.
|
||||||
|
int nBulkRangeStart;
|
||||||
|
//! Number of blocks requested in the outstanding bulk range.
|
||||||
|
int nBulkRangeCount;
|
||||||
|
//! Hash of the first block of the outstanding bulk range (request identity; the server echoes it
|
||||||
|
//! in the BLOCKSTREAM header so a stale/duplicate header for an old request can be ignored).
|
||||||
|
uint256 nBulkHashStart;
|
||||||
|
//! Whether the (one-shot) trailing BLOCKSTREAM header for the outstanding request was processed.
|
||||||
|
bool fBulkHeaderSeen;
|
||||||
|
//! (server side) time (us) we last served a bulk stream to this peer, for flood throttling.
|
||||||
|
int64_t nLastBulkServeTime;
|
||||||
|
|
||||||
CNodeState() {
|
CNodeState() {
|
||||||
fCurrentlyConnected = false;
|
fCurrentlyConnected = false;
|
||||||
@@ -319,6 +341,13 @@ namespace {
|
|||||||
nBlocksInFlight = 0;
|
nBlocksInFlight = 0;
|
||||||
nBlocksInFlightValidHeaders = 0;
|
nBlocksInFlightValidHeaders = 0;
|
||||||
fPreferredDownload = false;
|
fPreferredDownload = false;
|
||||||
|
fBulkInFlight = false;
|
||||||
|
nBulkSince = 0;
|
||||||
|
nBulkRangeStart = 0;
|
||||||
|
nBulkRangeCount = 0;
|
||||||
|
nBulkHashStart.SetNull();
|
||||||
|
fBulkHeaderSeen = false;
|
||||||
|
nLastBulkServeTime = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -385,14 +414,14 @@ namespace {
|
|||||||
|
|
||||||
void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age)
|
void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age)
|
||||||
{
|
{
|
||||||
/* int expired = pool.Expire(GetTime() - age);
|
int expired = pool.Expire(GetTime() - age);
|
||||||
if (expired != 0)
|
if (expired != 0)
|
||||||
LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
|
LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
|
||||||
|
|
||||||
std::vector<uint256> vNoSpendsRemaining;
|
// Fee-order trim to the size limit. (Upstream also pcoinsTip->Uncache()s the coins freed
|
||||||
pool.TrimToSize(limit, &vNoSpendsRemaining);
|
// by eviction, but CCoinsViewCache has no Uncache() in this fork -- it is only a UTXO-cache
|
||||||
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining)
|
// perf hint, not eviction correctness, so it is skipped.)
|
||||||
pcoinsTip->Uncache(removed);*/
|
pool.TrimToSize(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Requires cs_main.
|
// Requires cs_main.
|
||||||
@@ -413,7 +442,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Requires cs_main.
|
// Requires cs_main.
|
||||||
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Params& consensusParams, CBlockIndex *pindex = NULL) {
|
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Params& consensusParams, CBlockIndex *pindex = NULL, bool fBulk = false) {
|
||||||
CNodeState *state = State(nodeid);
|
CNodeState *state = State(nodeid);
|
||||||
assert(state != NULL);
|
assert(state != NULL);
|
||||||
|
|
||||||
@@ -421,7 +450,7 @@ namespace {
|
|||||||
MarkBlockAsReceived(hash);
|
MarkBlockAsReceived(hash);
|
||||||
|
|
||||||
int64_t nNow = GetTimeMicros();
|
int64_t nNow = GetTimeMicros();
|
||||||
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams)};
|
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams), fBulk};
|
||||||
nQueuedValidatedHeaders += newentry.fValidatedHeaders;
|
nQueuedValidatedHeaders += newentry.fValidatedHeaders;
|
||||||
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
||||||
state->nBlocksInFlight++;
|
state->nBlocksInFlight++;
|
||||||
@@ -429,6 +458,36 @@ namespace {
|
|||||||
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
|
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Opt-in bulk block streaming (DragonX): free this peer's still-in-flight bulk blocks whose height
|
||||||
|
// falls in [hStart, hEnd), so the normal per-block path re-fetches them. We scan the peer's OWN
|
||||||
|
// vBlocksInFlight by the LITERAL hash marked at request time (via the stored pindex) rather than
|
||||||
|
// re-deriving hashes from the mutable pindexBestKnownBlock - the latter would miss the real entries
|
||||||
|
// after a reorg (leaking in-flight slots) and can never touch another peer's blocks. Requires cs_main.
|
||||||
|
void FreeBulkRangeInFlight(CNodeState* state, int hStart, int hEnd) {
|
||||||
|
if (state == NULL) return;
|
||||||
|
std::vector<uint256> toFree; // collect first: MarkBlockAsReceived erases from vBlocksInFlight
|
||||||
|
BOOST_FOREACH(const QueuedBlock& q, state->vBlocksInFlight) {
|
||||||
|
if (q.fBulk && q.pindex != NULL) {
|
||||||
|
int h = q.pindex->GetHeight();
|
||||||
|
if (h >= hStart && h < hEnd) toFree.push_back(q.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BOOST_FOREACH(const uint256& hh, toFree)
|
||||||
|
MarkBlockAsReceived(hh);
|
||||||
|
}
|
||||||
|
// True if any of this peer's bulk blocks with height in [hStart, hEnd) is still in flight (range not
|
||||||
|
// fully drained). Completion is decided by the RANGE draining, not the global per-peer window count.
|
||||||
|
bool BulkRangeInFlight(CNodeState* state, int hStart, int hEnd) {
|
||||||
|
if (state == NULL) return false;
|
||||||
|
BOOST_FOREACH(const QueuedBlock& q, state->vBlocksInFlight) {
|
||||||
|
if (q.fBulk && q.pindex != NULL) {
|
||||||
|
int h = q.pindex->GetHeight();
|
||||||
|
if (h >= hStart && h < hEnd) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Check whether the last unknown block a peer advertized is not yet known. */
|
/** Check whether the last unknown block a peer advertized is not yet known. */
|
||||||
void ProcessBlockAvailability(NodeId nodeid) {
|
void ProcessBlockAvailability(NodeId nodeid) {
|
||||||
CNodeState *state = State(nodeid);
|
CNodeState *state = State(nodeid);
|
||||||
@@ -485,7 +544,7 @@ namespace {
|
|||||||
|
|
||||||
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
|
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
|
||||||
* at most count entries. */
|
* at most count entries. */
|
||||||
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller) {
|
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller, CBlockIndex** pFrontierStuck = NULL) {
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -562,8 +621,9 @@ namespace {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (waitingfor == -1) {
|
} else if (waitingfor == -1) {
|
||||||
// This is the first already-in-flight block.
|
// This is the first already-in-flight block (the download frontier).
|
||||||
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
|
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
|
||||||
|
if (pFrontierStuck) *pFrontierStuck = pindex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1223,6 +1283,16 @@ bool ContextualCheckTransaction(int32_t slowflag,const CBlock *block, CBlockInde
|
|||||||
const bool overwinterActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
|
const bool overwinterActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
|
||||||
const bool saplingActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
|
const bool saplingActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
|
||||||
|
|
||||||
|
// SECURITY FIX (JoinSplit inflation): reject Sprout JoinSplits at consensus. DragonX is
|
||||||
|
// Sapling-only from genesis (zero JoinSplits in its entire history, verified by the mainnet
|
||||||
|
// supply audit); a tx carrying one is illegitimate. Their zk-proof/sig/nullifier/anchor are
|
||||||
|
// never verified while vpub_new is counted as transparent value-in -> unlimited inflation.
|
||||||
|
// Coinbase/notary (IsMint) exempt. Unconditional is safe: no historical block has a JoinSplit.
|
||||||
|
if (!tx.IsMint() && !tx.vjoinsplit.empty()) {
|
||||||
|
return state.DoS(100, error("ContextualCheckTransaction(): Sprout JoinSplits are disabled (inflation vector)"),
|
||||||
|
REJECT_INVALID, "bad-txns-joinsplit-disabled");
|
||||||
|
}
|
||||||
|
|
||||||
if (saplingActive) {
|
if (saplingActive) {
|
||||||
// Reject transactions with valid version but missing overwintered flag
|
// Reject transactions with valid version but missing overwintered flag
|
||||||
if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) {
|
if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) {
|
||||||
@@ -2024,6 +2094,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
|
|||||||
if (fSpentIndex) {
|
if (fSpentIndex) {
|
||||||
pool.addSpentIndex(entry, view);
|
pool.addSpentIndex(entry, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bound mempool memory with fee-ordered eviction. Now that the tx is in, if the pool
|
||||||
|
// exceeds -maxmempool, TrimToSize drops the lowest-feerate txs -- so a higher-fee tx
|
||||||
|
// DISPLACES lower-fee ones instead of being bluntly rejected. If this very tx was the one
|
||||||
|
// evicted (its feerate was the lowest in the pool), it does not belong here -- reject it.
|
||||||
|
size_t maxmempool = (size_t)GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
|
||||||
|
if ( pool.DynamicMemoryUsage() > maxmempool ) {
|
||||||
|
pool.TrimToSize(maxmempool);
|
||||||
|
if ( !pool.exists(hash) )
|
||||||
|
return state.DoS(0, error("AcceptToMemoryPool: mempool full, tx %s evicted (feerate too low)", hash.ToString()), REJECT_INSUFFICIENTFEE, "mempool-full");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -3472,6 +3553,19 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||||||
error("ConnectBlock(): block's hashFinalSaplingRoot is incorrect"),
|
error("ConnectBlock(): block's hashFinalSaplingRoot is incorrect"),
|
||||||
REJECT_INVALID, "bad-sapling-root-in-block");
|
REJECT_INVALID, "bad-sapling-root-in-block");
|
||||||
}
|
}
|
||||||
|
// Turnstile / inflation guard (belt-and-suspenders to the per-tx binding-signature check):
|
||||||
|
// the cumulative Sapling value pool must never go negative -- a block cannot deshield more
|
||||||
|
// value than was ever shielded. Enforced ONLY when the pool is reliably tracked: pprev's
|
||||||
|
// nChainSaplingValue is engaged only if every ancestor since genesis had a known per-block
|
||||||
|
// value (nSaplingValue). On old/snapshot-bootstrapped nodes it is none -> guard dormant
|
||||||
|
// (until reindex), so this can never false-reject a valid block or split the chain.
|
||||||
|
if (pindex->pprev && pindex->pprev->nChainSaplingValue) {
|
||||||
|
CAmount blockSaplingValue = 0;
|
||||||
|
for (const CTransaction& btx : block.vtx)
|
||||||
|
blockSaplingValue += -btx.valueBalance;
|
||||||
|
if (*pindex->pprev->nChainSaplingValue + blockSaplingValue < 0)
|
||||||
|
return state.DoS(100, error("ConnectBlock(): Sapling value pool would go negative (turnstile/inflation violation)"), REJECT_INVALID, "bad-sapling-value-pool-negative");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
|
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
|
||||||
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
|
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
|
||||||
@@ -4004,6 +4098,10 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
|
|||||||
uiInterface.NotifyTxExpiration(id);
|
uiInterface.NotifyTxExpiration(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bound mempool memory on each block: age-expire (-mempoolexpiry) then fee-order trim to
|
||||||
|
// -maxmempool, evicting the lowest-feerate txs (+ descendants) and uncaching their coins.
|
||||||
|
LimitMempoolSize(mempool, GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
|
||||||
|
|
||||||
// Update chainActive & related variables.
|
// Update chainActive & related variables.
|
||||||
UpdateTip(pindexNew);
|
UpdateTip(pindexNew);
|
||||||
|
|
||||||
@@ -4188,6 +4286,7 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
|
|||||||
return state.DoS(100, error("ActivateBestChainStep(): pindexOldTip->GetHeight().%d > notarizedht %d && pindexFork->GetHeight().%d is < notarizedht %d, so ignore it",(int32_t)pindexOldTip->GetHeight(),notarizedht,(int32_t)pindexFork->GetHeight(),notarizedht),
|
return state.DoS(100, error("ActivateBestChainStep(): pindexOldTip->GetHeight().%d > notarizedht %d && pindexFork->GetHeight().%d is < notarizedht %d, so ignore it",(int32_t)pindexOldTip->GetHeight(),notarizedht,(int32_t)pindexFork->GetHeight(),notarizedht),
|
||||||
REJECT_INVALID, "past-notarized-height");
|
REJECT_INVALID, "past-notarized-height");
|
||||||
}
|
}
|
||||||
|
|
||||||
// - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
|
// - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
|
||||||
// - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
|
// - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
|
||||||
// then pindexFork will be null, and we would need to remove the entire chain including
|
// then pindexFork will be null, and we would need to remove the entire chain including
|
||||||
@@ -4258,6 +4357,36 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
|
|||||||
}
|
}
|
||||||
nHeight = nTargetHeight;
|
nHeight = nTargetHeight;
|
||||||
|
|
||||||
|
// Parallel RandomX pre-verification (Stage 4): verify this about-to-be-connected window's
|
||||||
|
// PoW on the worker pool BEFORE the serial connect, so ConnectBlock rarely pays the
|
||||||
|
// ~tens-of-ms light-mode hash. Pure optimization — CheckBlockHeader's inline
|
||||||
|
// CheckRandomXSolution still verifies anything not pre-verified, so consensus is unchanged.
|
||||||
|
// We hold cs_main; key derivation + the disk reads happen here on the main thread, and the
|
||||||
|
// pool workers receive only value-type work items (no cs_main, no chainstate pointers).
|
||||||
|
if (nRandomXVerifyThreads > 0 && rxCheckQueue.IsIdle()) {
|
||||||
|
std::map<std::string, std::vector<CRandomXCheck> > rxGroups; // grouped by RandomX key
|
||||||
|
BOOST_FOREACH(CBlockIndex *pidx, vpindexToConnect) {
|
||||||
|
if (pidx->fRandomXVerified || !RandomXValidationRequired(pidx->GetHeight()))
|
||||||
|
continue;
|
||||||
|
std::string rxKey = GetRandomXKey(pidx->GetHeight());
|
||||||
|
if (rxKey.empty())
|
||||||
|
continue; // can't derive key -> inline fallback
|
||||||
|
CBlock blk;
|
||||||
|
if (!ReadBlockFromDisk(blk, pidx, false))
|
||||||
|
continue; // -> inline fallback
|
||||||
|
if (blk.nSolution.size() != 32) // RANDOMX_HASH_SIZE; wrong size -> inline (will error)
|
||||||
|
continue;
|
||||||
|
rxGroups[rxKey].push_back(CRandomXCheck(rxKey, GetRandomXInput(blk), blk.nSolution.data(), &pidx->fRandomXVerified));
|
||||||
|
}
|
||||||
|
for (std::map<std::string, std::vector<CRandomXCheck> >::iterator it = rxGroups.begin(); it != rxGroups.end(); ++it) {
|
||||||
|
if (!RandomXValidatorPrepareKey(it->first))
|
||||||
|
break; // cache alloc failed -> leave the rest for the inline fallback
|
||||||
|
CCheckQueueControl<CRandomXCheck> control(&rxCheckQueue);
|
||||||
|
control.Add(it->second);
|
||||||
|
control.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Connect new blocks.
|
// Connect new blocks.
|
||||||
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
|
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
|
||||||
if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
|
if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
|
||||||
@@ -4719,8 +4848,8 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
|
|||||||
} else {
|
} else {
|
||||||
pindex->nChainSproutValue = boost::none;
|
pindex->nChainSproutValue = boost::none;
|
||||||
}
|
}
|
||||||
if (pindex->pprev->nChainSaplingValue) {
|
if (pindex->pprev->nChainSaplingValue && pindex->nSaplingValue) {
|
||||||
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
|
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + *pindex->nSaplingValue;
|
||||||
} else {
|
} else {
|
||||||
pindex->nChainSaplingValue = boost::none;
|
pindex->nChainSaplingValue = boost::none;
|
||||||
}
|
}
|
||||||
@@ -4993,7 +5122,11 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
|
|||||||
{
|
{
|
||||||
if ( !CheckEquihashSolution(&blockhdr, Params()) )
|
if ( !CheckEquihashSolution(&blockhdr, Params()) )
|
||||||
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution");
|
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution");
|
||||||
if ( !CheckRandomXSolution(&blockhdr, height) )
|
// Skip the inline RandomX recompute only if the parallel pre-verify pool already verified
|
||||||
|
// THIS block (fRandomXVerified set 1:1 on a real hash match). Every other case — pool miss,
|
||||||
|
// straggler, disabled pool, or any pindex==NULL caller (TestBlockValidity/VerifyDB/header
|
||||||
|
// accept) — falls through to the inline check, so consensus is unchanged.
|
||||||
|
if ( !(pindex && pindex->fRandomXVerified) && !CheckRandomXSolution(&blockhdr, height) )
|
||||||
return state.DoS(100, error("CheckBlockHeader(): RandomX solution invalid"),REJECT_INVALID, "invalid-randomx-solution");
|
return state.DoS(100, error("CheckBlockHeader(): RandomX solution invalid"),REJECT_INVALID, "invalid-randomx-solution");
|
||||||
}
|
}
|
||||||
// Check proof of work matches claimed amount
|
// Check proof of work matches claimed amount
|
||||||
@@ -5005,6 +5138,14 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
|
|||||||
|
|
||||||
int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height);
|
int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height);
|
||||||
|
|
||||||
|
// RAII: save+restore the thread-local RandomX-skip flag around the verify-once dedup in CheckBlock,
|
||||||
|
// so it can never clobber the miner's own fSkipRandomXValidation (TestBlockValidity -> ConnectBlock
|
||||||
|
// re-entry) nor leak TRUE on an exception thrown out of hush_checkPOW.
|
||||||
|
struct ScopedRandomXSkip {
|
||||||
|
bool prev;
|
||||||
|
ScopedRandomXSkip() : prev(GetSkipRandomXValidation()) { SetSkipRandomXValidation(true); }
|
||||||
|
~ScopedRandomXSkip() { SetSkipRandomXValidation(prev); }
|
||||||
|
};
|
||||||
bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const CBlock& block, CValidationState& state,
|
bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const CBlock& block, CValidationState& state,
|
||||||
libzcash::ProofVerifier& verifier,
|
libzcash::ProofVerifier& verifier,
|
||||||
bool fCheckPOW, bool fCheckMerkleRoot)
|
bool fCheckPOW, bool fCheckMerkleRoot)
|
||||||
@@ -5035,9 +5176,17 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C
|
|||||||
fprintf(stderr," failed hash ht.%d\n",height);
|
fprintf(stderr," failed hash ht.%d\n",height);
|
||||||
return state.DoS(50, error("CheckBlock: proof of work failed"),REJECT_INVALID, "high-hash");
|
return state.DoS(50, error("CheckBlock: proof of work failed"),REJECT_INVALID, "high-hash");
|
||||||
}
|
}
|
||||||
if ( ASSETCHAINS_STAKED == 0 && hush_checkPOW(1,(CBlock *)&block,height) < 0 ) // checks Equihash
|
if ( ASSETCHAINS_STAKED == 0 ) {
|
||||||
|
// verify-once: CheckBlockHeader above already verified this block RandomX solution; skip the
|
||||||
|
// redundant recompute inside hush_checkPOW (the un-deduped 2nd verify, ~half the RandomX cost
|
||||||
|
// that dominates IBD). The scoped guard saves/restores the skip flag (never hardcodes false)
|
||||||
|
// so the miner's own skip is preserved and nothing leaks on throw. Equihash + PoW-target in
|
||||||
|
// hush_checkPOW still run.
|
||||||
|
ScopedRandomXSkip _rxskip;
|
||||||
|
if ( hush_checkPOW(1,(CBlock *)&block,height) < 0 )
|
||||||
return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW");
|
return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check the merkle root.
|
// Check the merkle root.
|
||||||
if (fCheckMerkleRoot) {
|
if (fCheckMerkleRoot) {
|
||||||
@@ -5844,8 +5993,8 @@ bool static LoadBlockIndexDB()
|
|||||||
} else {
|
} else {
|
||||||
pindex->nChainSproutValue = boost::none;
|
pindex->nChainSproutValue = boost::none;
|
||||||
}
|
}
|
||||||
if (pindex->pprev->nChainSaplingValue) {
|
if (pindex->pprev->nChainSaplingValue && pindex->nSaplingValue) {
|
||||||
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
|
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + *pindex->nSaplingValue;
|
||||||
} else {
|
} else {
|
||||||
pindex->nChainSaplingValue = boost::none;
|
pindex->nChainSaplingValue = boost::none;
|
||||||
}
|
}
|
||||||
@@ -6221,7 +6370,7 @@ bool RewindBlockIndex(const CChainParams& params, bool& clearWitnessCaches)
|
|||||||
pindexIter->nChainTx = 0;
|
pindexIter->nChainTx = 0;
|
||||||
pindexIter->nSproutValue = boost::none;
|
pindexIter->nSproutValue = boost::none;
|
||||||
pindexIter->nChainSproutValue = boost::none;
|
pindexIter->nChainSproutValue = boost::none;
|
||||||
pindexIter->nSaplingValue = 0;
|
pindexIter->nSaplingValue = boost::none;
|
||||||
pindexIter->nChainSaplingValue = boost::none;
|
pindexIter->nChainSaplingValue = boost::none;
|
||||||
pindexIter->nSequenceId = 0;
|
pindexIter->nSequenceId = 0;
|
||||||
|
|
||||||
@@ -6746,6 +6895,13 @@ void static ProcessGetData(CNode* pfrom)
|
|||||||
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
|
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
|
||||||
|
|
||||||
vector<CInv> vNotFound;
|
vector<CInv> vNotFound;
|
||||||
|
// Serve up to this many blocks per ProcessGetData pass. The old code broke after a SINGLE block,
|
||||||
|
// so a 16-block getdata was dribbled out one block per message-handler tick (~100ms), throttling
|
||||||
|
// block download for every peer fetching from us. Bound the per-pass work (cs_main is held while
|
||||||
|
// reading blocks from disk); any remainder is served on the next pass (the message handler keeps
|
||||||
|
// fSleep=false while vRecvGetData is non-empty, so there is no 100ms park between passes).
|
||||||
|
const unsigned int nMaxBlocksServedPerPass = 16;
|
||||||
|
unsigned int nBlocksServed = 0;
|
||||||
|
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
|
||||||
@@ -6788,7 +6944,15 @@ void static ProcessGetData(CNode* pfrom)
|
|||||||
CBlock block;
|
CBlock block;
|
||||||
if (!ReadBlockFromDisk(block, (*mi).second,1))
|
if (!ReadBlockFromDisk(block, (*mi).second,1))
|
||||||
{
|
{
|
||||||
assert(!"cannot load block from disk");
|
// A block we advertised (BLOCK_HAVE_DATA) failed to load from disk: a transient
|
||||||
|
// I/O error or on-disk corruption. This previously asserted and crashed the whole
|
||||||
|
// node -- any peer getdata for such a block could take us down. Log and drop this
|
||||||
|
// peer instead; it can re-fetch from another node. (The bulk GETBLOCKSTREAM serve
|
||||||
|
// path already fails gracefully rather than asserting.)
|
||||||
|
LogPrintf("%s: ReadBlockFromDisk failed for block %s (peer=%i);"
|
||||||
|
" disconnecting peer instead of asserting\n",
|
||||||
|
__func__, inv.hash.ToString(), pfrom->GetId());
|
||||||
|
pfrom->fDisconnect = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -6863,7 +7027,10 @@ void static ProcessGetData(CNode* pfrom)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
|
// Serve a bounded batch of blocks per pass rather than one (see nMaxBlocksServedPerPass
|
||||||
|
// above). The send-buffer gate at the top of the loop still pauses us if the buffer fills;
|
||||||
|
// this counter bounds the cs_main hold for a (possibly malicious) large getdata.
|
||||||
|
if ((inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK) && ++nBlocksServed >= nMaxBlocksServedPerPass)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7666,7 +7833,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
|||||||
if (state.IsInvalid(nDoS) && futureblock == 0)
|
if (state.IsInvalid(nDoS) && futureblock == 0)
|
||||||
{
|
{
|
||||||
if (nDoS > 0 && futureblock == 0)
|
if (nDoS > 0 && futureblock == 0)
|
||||||
Misbehaving(pfrom->GetId(), nDoS/nDoS);
|
Misbehaving(pfrom->GetId(), nDoS);
|
||||||
return error("invalid header received");
|
return error("invalid header received");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7688,6 +7855,118 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CheckBlockIndex();
|
CheckBlockIndex();
|
||||||
|
} else if (strCommand == NetMsgType::GETBLOCKSTREAM) {
|
||||||
|
// Opt-in bulk block streaming (DragonX): a peer asks us to stream a contiguous range of
|
||||||
|
// old blocks as back-to-back BLOCK messages. We only honor it if we advertised the bit
|
||||||
|
// (i.e. were started with -bulkblocksync) and we are not mid-import/reindex.
|
||||||
|
if ((nLocalServices & NODE_BULKBLOCKS) == 0 || fImporting || fReindex)
|
||||||
|
return true;
|
||||||
|
uint256 hashStart; int32_t nStartHeight; uint16_t nCount;
|
||||||
|
vRecv >> hashStart >> nStartHeight >> nCount;
|
||||||
|
|
||||||
|
// Resolve the range under cs_main (cheap, no disk I/O), then read + stream the blocks WITHOUT
|
||||||
|
// holding the lock, so a 128-block / 8 MiB serve never holds cs_main across disk reads (the
|
||||||
|
// analogous ProcessGetData caps per-pass work precisely because it reads under cs_main).
|
||||||
|
std::vector<CBlockIndex*> vSend;
|
||||||
|
int firstH = -1;
|
||||||
|
bool refuse = false;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
if (nCount == 0 || nCount > BULK_MAX_BLOCKS_PER_REQUEST) {
|
||||||
|
Misbehaving(pfrom->GetId(), 20); // mirrors the getdata MAX_INV_SZ penalty
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Light flood throttle: at most one bulk serve per peer per BULK_MIN_SERVE_INTERVAL_US. On
|
||||||
|
// throttle, send a refusal header so the requester falls back immediately (not after 90s).
|
||||||
|
int64_t nNowServe = GetTimeMicros();
|
||||||
|
CNodeState* sst = State(pfrom->GetId());
|
||||||
|
if (sst != NULL && sst->nLastBulkServeTime > nNowServe - BULK_MIN_SERVE_INTERVAL_US) {
|
||||||
|
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)-1, (uint16_t)0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sst != NULL) sst->nLastBulkServeTime = nNowServe;
|
||||||
|
|
||||||
|
BlockMap::iterator mi = mapBlockIndex.find(hashStart);
|
||||||
|
// Don't flood old blocks while WE are still syncing (unless allowlisted); only serve blocks
|
||||||
|
// on our active chain at the height the requester expects (nStartHeight, tamper-checked).
|
||||||
|
if ((IsInitialBlockDownload() && !pfrom->fAllowlisted) ||
|
||||||
|
mi == mapBlockIndex.end() || !chainActive.Contains(mi->second) ||
|
||||||
|
mi->second->GetHeight() != nStartHeight) {
|
||||||
|
refuse = true;
|
||||||
|
} else {
|
||||||
|
CBlockIndex* pindex = mi->second;
|
||||||
|
firstH = pindex->GetHeight();
|
||||||
|
for (uint16_t i = 0; i < nCount && pindex != NULL; i++, pindex = chainActive.Next(pindex)) {
|
||||||
|
if ((pindex->nStatus & BLOCK_HAVE_DATA) == 0) break; // pruned/missing
|
||||||
|
vSend.push_back(pindex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (refuse) {
|
||||||
|
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)-1, (uint16_t)0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Read from disk + stream OUTSIDE cs_main. CBlockIndex pointers are stable and block files are
|
||||||
|
// append-only, so reading by pindex without the lock is safe (a concurrent reorg cannot delete
|
||||||
|
// block data, and the requester validates every block against its own headers regardless).
|
||||||
|
uint16_t nSent = 0;
|
||||||
|
size_t cumBytes = 0;
|
||||||
|
BOOST_FOREACH(CBlockIndex* pb, vSend) {
|
||||||
|
if (pfrom->nSendSize >= SendBufferSize()) break; // send-buffer backpressure
|
||||||
|
boost::this_thread::interruption_point();
|
||||||
|
CBlock block;
|
||||||
|
if (!ReadBlockFromDisk(block, pb, 1)) break; // graceful, never assert
|
||||||
|
size_t sz = GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
if (nSent > 0 && cumBytes + sz > BULK_MAX_RESPONSE_BYTES) break; // total byte cap
|
||||||
|
cumBytes += sz;
|
||||||
|
pfrom->PushMessage(NetMsgType::BLOCK, block);
|
||||||
|
nSent++;
|
||||||
|
}
|
||||||
|
// Trailing control header carries the ACTUAL count sent (authoritative), so the requester can
|
||||||
|
// free any undelivered tail immediately rather than waiting for the bulk response timeout.
|
||||||
|
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)firstH, nSent);
|
||||||
|
LogPrint("net", "Bulk stream serve: %u/%u blocks from height %d (%lu bytes) peer=%d\n",
|
||||||
|
(unsigned)nSent, (unsigned)nCount, firstH, (unsigned long)cumBytes, pfrom->id);
|
||||||
|
return true;
|
||||||
|
} else if (strCommand == NetMsgType::BLOCKSTREAM) {
|
||||||
|
// Opt-in bulk block streaming (DragonX): the trailing control header for a streamed range. The
|
||||||
|
// blocks themselves arrive as ordinary BLOCK messages (handled below); this reconciles what the
|
||||||
|
// peer actually delivered so the undelivered tail (or a refusal) falls back at once instead of
|
||||||
|
// waiting for the bulk timeout. Service bits are unauthenticated, so we ignore anything that
|
||||||
|
// doesn't match our exact outstanding request.
|
||||||
|
uint256 hashStart; int32_t nFirstHeight; uint16_t nBlocks;
|
||||||
|
vRecv >> hashStart >> nFirstHeight >> nBlocks;
|
||||||
|
|
||||||
|
LOCK(cs_main);
|
||||||
|
CNodeState* state = State(pfrom->GetId());
|
||||||
|
if (state == NULL || !state->fBulkInFlight)
|
||||||
|
return true; // nothing outstanding
|
||||||
|
if (hashStart != state->nBulkHashStart)
|
||||||
|
return true; // header for a different/stale request; ignore
|
||||||
|
if (state->fBulkHeaderSeen)
|
||||||
|
return true; // one-shot: already reconciled this request
|
||||||
|
state->fBulkHeaderSeen = true;
|
||||||
|
|
||||||
|
// nBlocks==0 (refusal) or an over-count => free our whole outstanding range and fall back.
|
||||||
|
// 0 < nBlocks <= count => the peer commits to that many; free only the undelivered tail now.
|
||||||
|
// FreeBulkRangeInFlight scans THIS peer's vBlocksInFlight by literal hash, so it only ever frees
|
||||||
|
// heights still genuinely in flight to this peer (no cross-peer effect, reorg-proof).
|
||||||
|
bool refuse = (nBlocks == 0 || nBlocks > state->nBulkRangeCount);
|
||||||
|
int deliver = refuse ? 0 : (int)nBlocks;
|
||||||
|
FreeBulkRangeInFlight(state, state->nBulkRangeStart + deliver,
|
||||||
|
state->nBulkRangeStart + state->nBulkRangeCount);
|
||||||
|
if (refuse) {
|
||||||
|
state->fBulkInFlight = false;
|
||||||
|
pfrom->nServices &= ~(uint64_t)NODE_BULKBLOCKS; // local hint: don't retry bulk on this peer
|
||||||
|
LogPrint("net", "Bulk stream refused by peer=%d (nBlocks=%u), falling back\n", pfrom->id, (unsigned)nBlocks);
|
||||||
|
} else {
|
||||||
|
// Track only what was promised; fBulkInFlight clears once that prefix fully drains
|
||||||
|
// (range-drain check in SendMessages) or via the timeout fallback.
|
||||||
|
state->nBulkRangeCount = deliver;
|
||||||
|
if (deliver == 0)
|
||||||
|
state->fBulkInFlight = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing
|
} else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing
|
||||||
{
|
{
|
||||||
CBlock block;
|
CBlock block;
|
||||||
@@ -8133,24 +8412,108 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
|||||||
LogPrint("net", "Reducing block download timeout for peer=%d block=%s, orig=%d new=%d\n", pto->id, queuedBlock.hash.ToString(), queuedBlock.nTimeDisconnect, nTimeoutIfRequestedNow);
|
LogPrint("net", "Reducing block download timeout for peer=%d block=%s, orig=%d new=%d\n", pto->id, queuedBlock.hash.ToString(), queuedBlock.nTimeDisconnect, nTimeoutIfRequestedNow);
|
||||||
queuedBlock.nTimeDisconnect = nTimeoutIfRequestedNow;
|
queuedBlock.nTimeDisconnect = nTimeoutIfRequestedNow;
|
||||||
}
|
}
|
||||||
if (queuedBlock.nTimeDisconnect < nNow) {
|
if (queuedBlock.nTimeDisconnect < nNow && !queuedBlock.fBulk) {
|
||||||
|
// Bulk-stream blocks are exempt: a 128-block batch shares one request time, so the
|
||||||
|
// front() entry could expire before the tail streams in. The bulk response timeout
|
||||||
|
// below frees the range without disconnecting instead.
|
||||||
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
|
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
|
||||||
pto->fDisconnect = true;
|
pto->fDisconnect = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Opt-in bulk block streaming (DragonX): manage the outstanding bulk range, then (below)
|
||||||
|
// possibly issue a new one. Clearing fBulkInFlight once the batch has drained below the
|
||||||
|
// normal window re-enables the next bulk request; a never-fully-delivered batch is freed
|
||||||
|
// after BULK_RESPONSE_TIMEOUT_US so the normal per-block path re-fetches it (no disconnect).
|
||||||
|
if (state.fBulkInFlight) {
|
||||||
|
int hEnd = state.nBulkRangeStart + state.nBulkRangeCount;
|
||||||
|
if (!BulkRangeInFlight(&state, state.nBulkRangeStart, hEnd)) {
|
||||||
|
// Whole (possibly shrunk) range received -> done. Completion is keyed on the RANGE
|
||||||
|
// draining, NOT on the global in-flight count crossing the window, so a partially
|
||||||
|
// delivered batch can never leave undelivered heights stuck in-flight.
|
||||||
|
state.fBulkInFlight = false;
|
||||||
|
} else if (state.nBulkSince > 0 && state.nBulkSince < nNow - BULK_RESPONSE_TIMEOUT_US) {
|
||||||
|
// Promised blocks never fully arrived: free the still-in-flight remainder (the normal
|
||||||
|
// per-block path re-fetches it), give up bulk on this unresponsive peer. No disconnect.
|
||||||
|
FreeBulkRangeInFlight(&state, state.nBulkRangeStart, hEnd);
|
||||||
|
state.fBulkInFlight = false;
|
||||||
|
pto->nServices &= ~(uint64_t)NODE_BULKBLOCKS;
|
||||||
|
LogPrint("net", "Bulk stream timeout peer=%d, freed range [%d,%d)\n",
|
||||||
|
pto->id, state.nBulkRangeStart, hEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Message: getdata (blocks)
|
// Message: getdata (blocks)
|
||||||
static uint256 zero;
|
static uint256 zero;
|
||||||
vector<CInv> vGetData;
|
vector<CInv> vGetData;
|
||||||
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER && !state.fBulkInFlight) {
|
||||||
vector<CBlockIndex*> vToDownload;
|
vector<CBlockIndex*> vToDownload;
|
||||||
NodeId staller = -1;
|
NodeId staller = -1;
|
||||||
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
|
CBlockIndex *pFrontierStuck = NULL;
|
||||||
|
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, &pFrontierStuck);
|
||||||
|
|
||||||
|
// Opt-in bulk block streaming (DragonX): if the first block we need is in the deep,
|
||||||
|
// stable region (>= BULK_TIP_MARGIN below the NETWORK tip) and the peer advertised the
|
||||||
|
// capability, request a whole contiguous range in one shot instead of per-block getdata.
|
||||||
|
// FindNextBlocksToDownload already advanced the cursor past what we have, so
|
||||||
|
// vToDownload.front() is the correct, cursor-managed starting point.
|
||||||
|
bool didBulk = false;
|
||||||
|
if (fBulkBlockSync && (pto->nServices & NODE_BULKBLOCKS) && IsInitialBlockDownload()
|
||||||
|
&& !vToDownload.empty() && state.pindexBestKnownBlock != NULL) {
|
||||||
|
CBlockIndex* pfirst = vToDownload.front();
|
||||||
|
int cursorH = pfirst->GetHeight();
|
||||||
|
int maxH = state.pindexBestKnownBlock->GetHeight() - BULK_TIP_MARGIN;
|
||||||
|
if (cursorH <= maxH) {
|
||||||
|
int want = std::min(maxH - cursorH + 1, (int)BULK_MAX_BLOCKS_PER_REQUEST);
|
||||||
|
uint16_t n = 0;
|
||||||
|
for (int i = 0; i < want; i++) {
|
||||||
|
CBlockIndex* pb = state.pindexBestKnownBlock->GetAncestor(cursorH + i);
|
||||||
|
if (pb == NULL || mapBlocksInFlight.count(pb->GetBlockHash())) break;
|
||||||
|
MarkBlockAsInFlight(pto->GetId(), pb->GetBlockHash(), consensusParams, pb, true);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
if (n > 0) {
|
||||||
|
pto->PushMessage(NetMsgType::GETBLOCKSTREAM, pfirst->GetBlockHash(), (int32_t)cursorH, n);
|
||||||
|
state.fBulkInFlight = true;
|
||||||
|
state.nBulkSince = nNow;
|
||||||
|
state.nBulkRangeStart = cursorH;
|
||||||
|
state.nBulkRangeCount = n;
|
||||||
|
state.nBulkHashStart = pfirst->GetBlockHash(); // request identity (matched in BLOCKSTREAM)
|
||||||
|
state.fBulkHeaderSeen = false; // arm the one-shot header reconciliation
|
||||||
|
didBulk = true;
|
||||||
|
LogPrint("net", "Requesting bulk stream [%d..%d] (%u blocks) peer=%d\n",
|
||||||
|
cursorH, cursorH + n - 1, (unsigned)n, pto->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!didBulk) {
|
||||||
BOOST_FOREACH(CBlockIndex *pindex, vToDownload) {
|
BOOST_FOREACH(CBlockIndex *pindex, vToDownload) {
|
||||||
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
||||||
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
|
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
|
||||||
LogPrint("net", "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->GetHeight(), pto->id);
|
LogPrint("net", "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->GetHeight(), pto->id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Frontier reassignment: when this peer has nothing new to fetch because the next-needed
|
||||||
|
// (frontier) block is in flight from another, slow peer and has been stuck beyond a short
|
||||||
|
// threshold, re-request it from THIS (responsive) peer instead of waiting out the long
|
||||||
|
// (~72s) timeout or disconnecting the slow peer. This breaks the head-of-line stall that
|
||||||
|
// throttles IBD when downloading from few, distant peers. Trustless: the block is still
|
||||||
|
// fully validated on arrival - we only change which peer serves it. -blockreassigntimeout
|
||||||
|
// = seconds (0 disables; default 5).
|
||||||
|
static const int64_t nReassignUs = GetArg("-blockreassigntimeout", 5) * 1000000LL;
|
||||||
|
if (nReassignUs > 0 && vToDownload.empty() && pFrontierStuck != NULL &&
|
||||||
|
staller != -1 && staller != pto->GetId()) {
|
||||||
|
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itF =
|
||||||
|
mapBlocksInFlight.find(pFrontierStuck->GetBlockHash());
|
||||||
|
if (itF != mapBlocksInFlight.end() && itF->second.first == staller &&
|
||||||
|
itF->second.second->nTime < nNow - nReassignUs) {
|
||||||
|
uint256 hReassign = pFrontierStuck->GetBlockHash();
|
||||||
|
LogPrint("net", "Reassigning stalled frontier block %s (%d) from peer=%d to peer=%d\n",
|
||||||
|
hReassign.ToString(), pFrontierStuck->GetHeight(), staller, pto->id);
|
||||||
|
MarkBlockAsReceived(hReassign); // free from slow peer (no disconnect)
|
||||||
|
vGetData.push_back(CInv(MSG_BLOCK, hReassign));
|
||||||
|
MarkBlockAsInFlight(pto->GetId(), hReassign, consensusParams, pFrontierStuck); // re-request from this peer
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state.nBlocksInFlight == 0 && staller != -1) {
|
if (state.nBlocksInFlight == 0 && staller != -1) {
|
||||||
if (State(staller)->nStallingSince == 0) {
|
if (State(staller)->nStallingSince == 0) {
|
||||||
State(staller)->nStallingSince = nNow;
|
State(staller)->nStallingSince = nNow;
|
||||||
|
|||||||
43
src/main.h
43
src/main.h
@@ -43,6 +43,7 @@
|
|||||||
#include "txmempool.h"
|
#include "txmempool.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -64,7 +65,9 @@ class CValidationState;
|
|||||||
class PrecomputedTransactionData;
|
class PrecomputedTransactionData;
|
||||||
|
|
||||||
struct CNodeStateStats;
|
struct CNodeStateStats;
|
||||||
#define DEFAULT_MEMPOOL_EXPIRY 1
|
#define DEFAULT_MEMPOOL_EXPIRY 72 // hours; age-based Expire is now live via LimitMempoolSize -- was 1, too aggressive
|
||||||
|
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
|
||||||
|
#define DEFAULT_MAX_MEMPOOL_SIZE 300
|
||||||
#define _COINBASE_MATURITY 100
|
#define _COINBASE_MATURITY 100
|
||||||
|
|
||||||
/** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/
|
/** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/
|
||||||
@@ -95,12 +98,37 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
|
|||||||
static const int MAX_SCRIPTCHECK_THREADS = 16;
|
static const int MAX_SCRIPTCHECK_THREADS = 16;
|
||||||
/** -par default (number of script-checking threads, 0 = auto) */
|
/** -par default (number of script-checking threads, 0 = auto) */
|
||||||
static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
|
static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
|
||||||
/** Number of blocks that can be requested at any given time from a single peer. */
|
/** Number of blocks that can be requested at any given time from a single peer.
|
||||||
static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
|
* Runtime-tunable via -maxblocksintransit. The default of 16 caps single-peer IBD
|
||||||
|
* throughput at (window / RTT): on a high-latency peer with tiny (sub-checkpoint)
|
||||||
|
* blocks the transfer is bandwidth-delay-product bound, so a larger window lifts the
|
||||||
|
* ceiling at negligible bandwidth cost. */
|
||||||
|
static const int DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
|
||||||
|
extern int MAX_BLOCKS_IN_TRANSIT_PER_PEER;
|
||||||
|
/** Opt-in bulk block streaming (DragonX, -bulkblocksync). A single GETBLOCKSTREAM request makes a
|
||||||
|
* peer stream a contiguous range of old blocks as back-to-back BLOCK messages, amortizing the
|
||||||
|
* per-block round-trip over the whole range instead of the MAX_BLOCKS_IN_TRANSIT_PER_PEER window.
|
||||||
|
* OFF by default; negotiated via NODE_BULKBLOCKS; only used during IBD for blocks more than
|
||||||
|
* BULK_TIP_MARGIN below the active tip; never alters the default getdata path. */
|
||||||
|
static const bool DEFAULT_BULKBLOCKSYNC = false;
|
||||||
|
extern bool fBulkBlockSync;
|
||||||
|
/** Only bulk-stream blocks at least this far below the active tip (near-tip uses the normal path). */
|
||||||
|
static const int BULK_TIP_MARGIN = 5000;
|
||||||
|
/** Hard DoS cap: max blocks a single GETBLOCKSTREAM may request/serve. */
|
||||||
|
static const uint16_t BULK_MAX_BLOCKS_PER_REQUEST = 128;
|
||||||
|
/** Hard DoS cap: max total bytes streamed in response to one GETBLOCKSTREAM. */
|
||||||
|
static const size_t BULK_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
||||||
|
/** Requester fallback: if a promised bulk range doesn't fully arrive within this many microseconds,
|
||||||
|
* free the in-flight range so the normal per-block path re-fetches it. */
|
||||||
|
static const int64_t BULK_RESPONSE_TIMEOUT_US = 90 * 1000000LL;
|
||||||
/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */
|
/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */
|
||||||
static const unsigned int BLOCK_STALLING_TIMEOUT = 2;
|
static const unsigned int BLOCK_STALLING_TIMEOUT = 2;
|
||||||
/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends
|
/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends
|
||||||
* less than this number, we reached its tip. Changing this value is a protocol upgrade. */
|
* less than this number, we reached its tip. Changing this value is a protocol upgrade: the
|
||||||
|
* continuation logic (main.cpp, "nCount == MAX_HEADERS_RESULTS") and the serve-side limit must
|
||||||
|
* match across the network, so a single node raising it unilaterally would mis-detect a stock
|
||||||
|
* peer's 160-header reply as "tip reached" and stall header sync. Raise only as a coordinated
|
||||||
|
* network upgrade (with a protocol-version bump). */
|
||||||
static const unsigned int MAX_HEADERS_RESULTS = 160;
|
static const unsigned int MAX_HEADERS_RESULTS = 160;
|
||||||
/** Size of the "block download window": how far ahead of our current height do we fetch?
|
/** Size of the "block download window": how far ahead of our current height do we fetch?
|
||||||
* Larger windows tolerate larger download speed differences between peer, but increase the potential
|
* Larger windows tolerate larger download speed differences between peer, but increase the potential
|
||||||
@@ -155,6 +183,7 @@ extern bool fExperimentalMode;
|
|||||||
extern bool fImporting;
|
extern bool fImporting;
|
||||||
extern bool fReindex;
|
extern bool fReindex;
|
||||||
extern int nScriptCheckThreads;
|
extern int nScriptCheckThreads;
|
||||||
|
extern int nRandomXVerifyThreads;
|
||||||
extern bool fTxIndex;
|
extern bool fTxIndex;
|
||||||
extern bool fZindex;
|
extern bool fZindex;
|
||||||
extern bool fIsBareMultisigStd;
|
extern bool fIsBareMultisigStd;
|
||||||
@@ -163,7 +192,7 @@ extern bool fCheckpointsEnabled;
|
|||||||
// TODO: remove this flag by structuring our code such that
|
// TODO: remove this flag by structuring our code such that
|
||||||
// it is unneeded for testing
|
// it is unneeded for testing
|
||||||
extern bool fCoinbaseEnforcedProtectionEnabled;
|
extern bool fCoinbaseEnforcedProtectionEnabled;
|
||||||
extern size_t nCoinCacheUsage;
|
extern std::atomic<size_t> nCoinCacheUsage;
|
||||||
extern CFeeRate minRelayTxFee;
|
extern CFeeRate minRelayTxFee;
|
||||||
extern int64_t nMaxTipAge;
|
extern int64_t nMaxTipAge;
|
||||||
|
|
||||||
@@ -930,6 +959,10 @@ extern CChain chainActive;
|
|||||||
/** Global variable that points to the active CCoinsView (protected by cs_main) */
|
/** Global variable that points to the active CCoinsView (protected by cs_main) */
|
||||||
extern CCoinsViewCache *pcoinsTip;
|
extern CCoinsViewCache *pcoinsTip;
|
||||||
|
|
||||||
|
/** Global variable that points to the coins database (chainstate/, protected by cs_main). */
|
||||||
|
class CCoinsViewDB;
|
||||||
|
extern CCoinsViewDB *pcoinsdbview;
|
||||||
|
|
||||||
/** Global variable that points to the active block tree (protected by cs_main) */
|
/** Global variable that points to the active block tree (protected by cs_main) */
|
||||||
extern CBlockTreeDB *pblocktree;
|
extern CBlockTreeDB *pblocktree;
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include "crypto/common.h"
|
#include "crypto/common.h"
|
||||||
#include "hush/utiltls.h"
|
#include "hush/utiltls.h"
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
|
#include <random>
|
||||||
|
#include <limits>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#else
|
#else
|
||||||
@@ -2004,7 +2006,7 @@ void ThreadMessageHandler()
|
|||||||
// Randomize the order in which we process messages from/to our peers.
|
// Randomize the order in which we process messages from/to our peers.
|
||||||
// This prevents attacks in which an attacker exploits having multiple
|
// This prevents attacks in which an attacker exploits having multiple
|
||||||
// consecutive connections in the vNodes list.
|
// consecutive connections in the vNodes list.
|
||||||
random_shuffle(vNodesCopy.begin(), vNodesCopy.end(), GetRandInt);
|
std::shuffle(vNodesCopy.begin(), vNodesCopy.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
|
||||||
|
|
||||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||||
{
|
{
|
||||||
@@ -2516,7 +2518,7 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
|
|||||||
// We always round down, except when we have only 1 connection
|
// We always round down, except when we have only 1 connection
|
||||||
auto newSize = (vNodes.size() / 2) == 0 ? 1 : (vNodes.size() / 2);
|
auto newSize = (vNodes.size() / 2) == 0 ? 1 : (vNodes.size() / 2);
|
||||||
|
|
||||||
random_shuffle( vRelayNodes.begin(), vRelayNodes.end(), GetRandInt );
|
std::shuffle( vRelayNodes.begin(), vRelayNodes.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
|
||||||
|
|
||||||
vRelayNodes.resize(newSize);
|
vRelayNodes.resize(newSize);
|
||||||
if (HUSH_TESTNODE==1 && vNodes.size() == 0) {
|
if (HUSH_TESTNODE==1 && vNodes.size() == 0) {
|
||||||
|
|||||||
226
src/pow.cpp
226
src/pow.cpp
@@ -18,6 +18,7 @@
|
|||||||
* *
|
* *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include "pow.h"
|
#include "pow.h"
|
||||||
|
#include "checkpoints.h"
|
||||||
#include "consensus/upgrades.h"
|
#include "consensus/upgrades.h"
|
||||||
#include "arith_uint256.h"
|
#include "arith_uint256.h"
|
||||||
#include "chain.h"
|
#include "chain.h"
|
||||||
@@ -30,6 +31,8 @@
|
|||||||
#include "sodium.h"
|
#include "sodium.h"
|
||||||
#include "RandomX/src/randomx.h"
|
#include "RandomX/src/randomx.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <boost/thread/shared_mutex.hpp>
|
||||||
|
#include <boost/thread/locks.hpp>
|
||||||
|
|
||||||
#ifdef ENABLE_RUST
|
#ifdef ENABLE_RUST
|
||||||
#include "librustzcash.h"
|
#include "librustzcash.h"
|
||||||
@@ -704,6 +707,7 @@ static std::mutex cs_randomx_validator;
|
|||||||
static randomx_cache *s_rxCache = nullptr;
|
static randomx_cache *s_rxCache = nullptr;
|
||||||
static randomx_vm *s_rxVM = nullptr;
|
static randomx_vm *s_rxVM = nullptr;
|
||||||
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
||||||
|
static int64_t nTimeRandomX = 0; // cumulative RandomX validation time (us), reported under -debug=bench
|
||||||
|
|
||||||
// Thread-local flag: skip CheckRandomXSolution when the miner is validating its own block
|
// Thread-local flag: skip CheckRandomXSolution when the miner is validating its own block
|
||||||
// The miner already computed the correct RandomX hash — re-verifying with a separate
|
// The miner already computed the correct RandomX hash — re-verifying with a separate
|
||||||
@@ -711,29 +715,84 @@ static std::string s_rxCurrentKey; // tracks current key to avoid re-init
|
|||||||
thread_local bool fSkipRandomXValidation = false;
|
thread_local bool fSkipRandomXValidation = false;
|
||||||
|
|
||||||
void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
|
void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
|
||||||
|
bool GetSkipRandomXValidation() { return fSkipRandomXValidation; }
|
||||||
|
|
||||||
CBlockIndex *hush_chainactive(int32_t height);
|
CBlockIndex *hush_chainactive(int32_t height);
|
||||||
|
|
||||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
// Centralized predicate: does a block at this height actually require a RandomX hash check?
|
||||||
|
// Shared by CheckRandomXSolution (inline path) and the parallel pre-verify pool so the two can
|
||||||
|
// never drift. Returns false when the recompute is unnecessary:
|
||||||
|
// - non-RandomX chain, or RandomX validation disabled (activation height < 0)
|
||||||
|
// - below the RandomX activation height (those blocks used Equihash, validated elsewhere)
|
||||||
|
// - during initial on-disk block loading / reindex (HUSH_LOADINGBLOCKS)
|
||||||
|
// - below the last hardcoded checkpoint (chain pinned by checkpoint hash + linkage + work)
|
||||||
|
// Deliberately does NOT consider the thread-local fSkipRandomXValidation (miner self-check) — that
|
||||||
|
// is a property of the calling thread, handled only in the inline CheckRandomXSolution below.
|
||||||
|
bool RandomXValidationRequired(int32_t height)
|
||||||
{
|
{
|
||||||
// Only applies to RandomX chains
|
|
||||||
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
|
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
|
||||||
return true;
|
return false;
|
||||||
|
|
||||||
// Disabled if activation height is negative
|
|
||||||
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
|
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
|
||||||
return true;
|
return false;
|
||||||
|
|
||||||
// Not yet at activation height
|
|
||||||
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
|
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
|
||||||
return true;
|
return false;
|
||||||
|
|
||||||
// Do not affect initial block loading
|
|
||||||
extern int32_t HUSH_LOADINGBLOCKS;
|
extern int32_t HUSH_LOADINGBLOCKS;
|
||||||
if (HUSH_LOADINGBLOCKS != 0)
|
if (HUSH_LOADINGBLOCKS != 0)
|
||||||
|
return false;
|
||||||
|
extern bool fCheckpointsEnabled;
|
||||||
|
// Gate the RandomX skip on the last checkpoint actually LOCKED INTO this node's block index
|
||||||
|
// (GetLastCheckpoint), NOT the static top checkpoint (GetTotalBlocksEstimate). The fork-rejection
|
||||||
|
// guard uses this same in-index boundary, so a block below it is provably on the checkpoint-pinned
|
||||||
|
// chain and cannot be a forged fork. Using the static boundary would, once checkpoints extend above
|
||||||
|
// the RandomX activation height, leave a gap (in-index checkpoint .. static top) during IBD/eclipse
|
||||||
|
// where a no-hashpower peer could get SHA256-grinded, RandomX-forged blocks accepted. Safe to walk
|
||||||
|
// mapBlockIndex here: called only under cs_main (ActivateBestChainStep + inline CheckRandomXSolution).
|
||||||
|
if (fCheckpointsEnabled) {
|
||||||
|
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(Params().Checkpoints());
|
||||||
|
if (pcheckpoint != NULL && height < pcheckpoint->GetHeight())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the RandomX hash input: the block header without nSolution (but with nNonce). Used by
|
||||||
|
// both the inline CheckRandomXSolution and the parallel pre-verify pool, so the bytes are identical.
|
||||||
|
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block)
|
||||||
|
{
|
||||||
|
CRandomXInput rxInput(block);
|
||||||
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ss << rxInput;
|
||||||
|
return std::vector<unsigned char>(ss.begin(), ss.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the RandomX key string for a block at `height`. Below interval+lag it is the chain-params
|
||||||
|
// initial key; otherwise the block hash at the key-rotation height. MUST be called under cs_main
|
||||||
|
// (reads chainActive via hush_chainactive). Returns empty if the key-height block is unavailable.
|
||||||
|
std::string GetRandomXKey(int32_t height)
|
||||||
|
{
|
||||||
|
static int randomxInterval = GetRandomXInterval();
|
||||||
|
static int randomxBlockLag = GetRandomXBlockLag();
|
||||||
|
if (height < randomxInterval + randomxBlockLag) {
|
||||||
|
char initialKey[82];
|
||||||
|
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
|
||||||
|
return std::string(initialKey, strlen(initialKey));
|
||||||
|
}
|
||||||
|
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||||
|
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
|
||||||
|
if (pKeyIndex == nullptr)
|
||||||
|
return std::string();
|
||||||
|
uint256 blockKey = pKeyIndex->GetBlockHash();
|
||||||
|
return std::string((const char*)&blockKey, sizeof(blockKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||||
|
{
|
||||||
|
// Centralized height gate (shared with the parallel pre-verify pool, Stage 0).
|
||||||
|
if (!RandomXValidationRequired(height))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Skip when miner is validating its own block via TestBlockValidity
|
// Skip when the miner is validating its own freshly-mined block via TestBlockValidity
|
||||||
|
// (thread-local; never set on the connect thread or the pre-verify worker threads).
|
||||||
if (fSkipRandomXValidation)
|
if (fSkipRandomXValidation)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -743,47 +802,44 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
|
pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int randomxInterval = GetRandomXInterval();
|
// Derive the key (shared helper) and serialize the input (identical bytes to the pool path).
|
||||||
static int randomxBlockLag = GetRandomXBlockLag();
|
std::string rxKey = GetRandomXKey(height);
|
||||||
|
if (rxKey.empty())
|
||||||
// Determine the correct RandomX key for this height
|
return error("CheckRandomXSolution(): cannot derive RandomX key for height %d", height);
|
||||||
char initialKey[82];
|
std::vector<unsigned char> ssInput = GetRandomXInput(*pblock);
|
||||||
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
|
|
||||||
|
|
||||||
std::string rxKey;
|
|
||||||
if (height < randomxInterval + randomxBlockLag) {
|
|
||||||
// Use initial key derived from chain params
|
|
||||||
rxKey = std::string(initialKey, strlen(initialKey));
|
|
||||||
} else {
|
|
||||||
// Use block hash at the key height
|
|
||||||
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
|
||||||
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
|
|
||||||
if (pKeyIndex == nullptr) {
|
|
||||||
return error("CheckRandomXSolution(): cannot get block index at key height %d for block %d", keyHeight, height);
|
|
||||||
}
|
|
||||||
uint256 blockKey = pKeyIndex->GetBlockHash();
|
|
||||||
rxKey = std::string((const char*)&blockKey, sizeof(blockKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize the block header without nSolution (but with nNonce) as RandomX input
|
|
||||||
CRandomXInput rxInput(*pblock);
|
|
||||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ss << rxInput;
|
|
||||||
|
|
||||||
char computedHash[RANDOMX_HASH_SIZE];
|
char computedHash[RANDOMX_HASH_SIZE];
|
||||||
|
|
||||||
|
// Measurement (Track 1): isolate RandomX verification cost during IBD. The
|
||||||
|
// expensive parts are the per-key cache (re)init (~every GetRandomXInterval()
|
||||||
|
// blocks) and the hash computation itself; both happen under the lock below.
|
||||||
|
int64_t nTimeRxStart = GetTimeMicros();
|
||||||
|
bool fKeyInit = false;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(cs_randomx_validator);
|
std::lock_guard<std::mutex> lock(cs_randomx_validator);
|
||||||
|
|
||||||
// Initialize cache + VM if needed, or re-init if key changed
|
// Initialize cache + VM if needed, or re-init if key changed
|
||||||
if (s_rxCache == nullptr) {
|
if (s_rxCache == nullptr) {
|
||||||
randomx_flags flags = randomx_get_flags();
|
randomx_flags flags = randomx_get_flags();
|
||||||
|
// Try large pages for the 256MB validator cache: fewer TLB misses → ~15-30% faster
|
||||||
|
// light-mode validation where the OS has hugepages configured. Falls back transparently
|
||||||
|
// when unavailable, exactly as the miner does (miner.cpp:1097). Page size does not affect
|
||||||
|
// the computed hash, so this is consensus-neutral.
|
||||||
|
bool fLargePages = true;
|
||||||
|
s_rxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
|
||||||
|
if (s_rxCache == nullptr) {
|
||||||
|
fLargePages = false;
|
||||||
s_rxCache = randomx_alloc_cache(flags);
|
s_rxCache = randomx_alloc_cache(flags);
|
||||||
|
}
|
||||||
if (s_rxCache == nullptr) {
|
if (s_rxCache == nullptr) {
|
||||||
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
|
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
|
||||||
}
|
}
|
||||||
|
// Confirm the fast paths are active (JIT off would be ~9x slower; see randomx-benchmark).
|
||||||
|
LogPrint("bench", "CheckRandomXSolution: RandomX flags=0x%x JIT=%d HARD_AES=%d largePages=%d\n",
|
||||||
|
(unsigned int)flags, !!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES), (int)fLargePages);
|
||||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||||
s_rxCurrentKey = rxKey;
|
s_rxCurrentKey = rxKey;
|
||||||
|
fKeyInit = true;
|
||||||
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
|
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
|
||||||
if (s_rxVM == nullptr) {
|
if (s_rxVM == nullptr) {
|
||||||
randomx_release_cache(s_rxCache);
|
randomx_release_cache(s_rxCache);
|
||||||
@@ -793,11 +849,17 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
} else if (s_rxCurrentKey != rxKey) {
|
} else if (s_rxCurrentKey != rxKey) {
|
||||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||||
s_rxCurrentKey = rxKey;
|
s_rxCurrentKey = rxKey;
|
||||||
|
fKeyInit = true;
|
||||||
randomx_vm_set_cache(s_rxVM, s_rxCache);
|
randomx_vm_set_cache(s_rxVM, s_rxCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
randomx_calculate_hash(s_rxVM, &ss[0], ss.size(), computedHash);
|
randomx_calculate_hash(s_rxVM, ssInput.data(), ssInput.size(), computedHash);
|
||||||
}
|
}
|
||||||
|
int64_t nTimeRxEnd = GetTimeMicros();
|
||||||
|
nTimeRandomX += nTimeRxEnd - nTimeRxStart;
|
||||||
|
LogPrint("bench", " - RandomX verify ht=%d: %.2fms%s [%.2fs]\n",
|
||||||
|
height, (nTimeRxEnd - nTimeRxStart) * 0.001,
|
||||||
|
fKeyInit ? " (key-init)" : "", nTimeRandomX * 0.000001);
|
||||||
|
|
||||||
// Compare computed hash against nSolution
|
// Compare computed hash against nSolution
|
||||||
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
||||||
@@ -814,7 +876,7 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
fprintf(stderr, " computed : %s\n", computedHex.c_str());
|
fprintf(stderr, " computed : %s\n", computedHex.c_str());
|
||||||
fprintf(stderr, " nSolution: %s\n", solutionHex.c_str());
|
fprintf(stderr, " nSolution: %s\n", solutionHex.c_str());
|
||||||
fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||||
rxKey.size(), ss.size(), pblock->nNonce.ToString().c_str());
|
rxKey.size(), ssInput.size(), pblock->nNonce.ToString().c_str());
|
||||||
fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n",
|
fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n",
|
||||||
pblock->nSolution.size(), RANDOMX_HASH_SIZE);
|
pblock->nSolution.size(), RANDOMX_HASH_SIZE);
|
||||||
// Also log to debug.log
|
// Also log to debug.log
|
||||||
@@ -822,7 +884,7 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
LogPrintf(" computed : %s\n", computedHex);
|
LogPrintf(" computed : %s\n", computedHex);
|
||||||
LogPrintf(" nSolution: %s\n", solutionHex);
|
LogPrintf(" nSolution: %s\n", solutionHex);
|
||||||
LogPrintf(" rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
LogPrintf(" rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||||
rxKey.size(), ss.size(), pblock->nNonce.ToString());
|
rxKey.size(), ssInput.size(), pblock->nNonce.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,6 +892,88 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// Parallel RandomX pre-verification pool (Stage 2).
|
||||||
|
// One shared light-mode cache (holding a single key at a time) + per-thread VMs, mirroring the
|
||||||
|
// miner's RandomXDatasetManager pattern (miner.cpp). The connect thread (ActivateBestChainStep)
|
||||||
|
// loads the cache key for a same-key group of about-to-be-connected blocks, dispatches them to
|
||||||
|
// this pool, and barrier-waits; each worker hashes on its own VM (sharing the read-only cache)
|
||||||
|
// and, on a match, sets the block's transient fRandomXVerified flag so the inline check in
|
||||||
|
// CheckBlockHeader can be skipped. The inline path remains the consensus authority for anything
|
||||||
|
// not pre-verified, so the pool can only ever flip false->true on a real hash match.
|
||||||
|
static boost::shared_mutex g_rxvMutex; // shared = hashing; exclusive = cache (re)init
|
||||||
|
static randomx_cache* g_rxvCache = nullptr; // shared, read-only during hashing
|
||||||
|
static std::string g_rxvKey; // key currently loaded into g_rxvCache
|
||||||
|
static randomx_flags g_rxvFlags;
|
||||||
|
static thread_local randomx_vm* tls_rxvVM = nullptr;
|
||||||
|
static thread_local std::string tls_rxvVMKey;
|
||||||
|
|
||||||
|
CCheckQueue<CRandomXCheck> rxCheckQueue(1); // batch size 1: each item is ~tens of ms
|
||||||
|
|
||||||
|
bool RandomXValidatorPrepareKey(const std::string& rxKey)
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||||
|
if (g_rxvCache == nullptr) {
|
||||||
|
g_rxvFlags = randomx_get_flags();
|
||||||
|
g_rxvCache = randomx_alloc_cache(g_rxvFlags | RANDOMX_FLAG_LARGE_PAGES);
|
||||||
|
if (g_rxvCache == nullptr)
|
||||||
|
g_rxvCache = randomx_alloc_cache(g_rxvFlags);
|
||||||
|
if (g_rxvCache == nullptr) {
|
||||||
|
LogPrintf("RandomXValidatorPrepareKey: cache alloc failed; parallel pre-verify disabled\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
|
||||||
|
g_rxvKey = rxKey;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (g_rxvKey != rxKey) {
|
||||||
|
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
|
||||||
|
g_rxvKey = rxKey;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CRandomXCheck::operator()()
|
||||||
|
{
|
||||||
|
boost::shared_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||||
|
// The connect thread set the shared cache to one key before dispatching this group. If this
|
||||||
|
// item's key doesn't match (e.g. a key-rotation straggler) or the cache is unavailable, skip it
|
||||||
|
// and leave *presult false — the inline CheckRandomXSolution will verify it.
|
||||||
|
if (g_rxvCache == nullptr || g_rxvKey != rxKey)
|
||||||
|
return true;
|
||||||
|
if (tls_rxvVM == nullptr) {
|
||||||
|
tls_rxvVM = randomx_create_vm(g_rxvFlags, g_rxvCache, nullptr);
|
||||||
|
if (tls_rxvVM == nullptr)
|
||||||
|
return true; // cannot verify here -> inline fallback
|
||||||
|
tls_rxvVMKey = g_rxvKey;
|
||||||
|
} else if (tls_rxvVMKey != g_rxvKey) {
|
||||||
|
// Cache was re-initialized to a new key since this VM last ran; rebind.
|
||||||
|
randomx_vm_set_cache(tls_rxvVM, g_rxvCache);
|
||||||
|
tls_rxvVMKey = g_rxvKey;
|
||||||
|
}
|
||||||
|
unsigned char h[RANDOMX_HASH_SIZE];
|
||||||
|
randomx_calculate_hash(tls_rxvVM, input.data(), input.size(), h);
|
||||||
|
if (memcmp(h, expected, RANDOMX_HASH_SIZE) == 0 && presult != nullptr)
|
||||||
|
*presult = true;
|
||||||
|
return true; // ALWAYS true: never short-circuit the queue; per-block result is in *presult
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadRandomXVerify()
|
||||||
|
{
|
||||||
|
RenameThread("hush-rxverify");
|
||||||
|
rxCheckQueue.Thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RandomXValidatorShutdown()
|
||||||
|
{
|
||||||
|
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
|
||||||
|
// Per-thread VMs are intentionally leaked (process exiting); release the shared cache.
|
||||||
|
if (g_rxvCache != nullptr) {
|
||||||
|
randomx_release_cache(g_rxvCache);
|
||||||
|
g_rxvCache = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
|
int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
|
||||||
int32_t hush_currentheight();
|
int32_t hush_currentheight();
|
||||||
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
||||||
|
|||||||
55
src/pow.h
55
src/pow.h
@@ -21,8 +21,13 @@
|
|||||||
#define HUSH_POW_H
|
#define HUSH_POW_H
|
||||||
|
|
||||||
#include "chain.h"
|
#include "chain.h"
|
||||||
|
#include "checkqueue.h"
|
||||||
#include "consensus/params.h"
|
#include "consensus/params.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class CBlockHeader;
|
class CBlockHeader;
|
||||||
class CBlockIndex;
|
class CBlockIndex;
|
||||||
@@ -41,8 +46,58 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&);
|
|||||||
/** Check whether a block header contains a valid RandomX solution */
|
/** Check whether a block header contains a valid RandomX solution */
|
||||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
|
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
|
||||||
|
|
||||||
|
/** Whether a block at this height requires a RandomX hash check (shared gate used by both the
|
||||||
|
* inline CheckRandomXSolution and the parallel pre-verification pool). */
|
||||||
|
bool RandomXValidationRequired(int32_t height);
|
||||||
|
|
||||||
|
/** Serialize the RandomX hash input (block header without nSolution) — identical bytes to the
|
||||||
|
* inline CheckRandomXSolution path, so the parallel pool computes the same hash. */
|
||||||
|
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block);
|
||||||
|
|
||||||
|
/** Derive the RandomX key string for a block at `height`. MUST be called under cs_main (reads
|
||||||
|
* chainActive). Returns empty string if the key-height block is unavailable. */
|
||||||
|
std::string GetRandomXKey(int32_t height);
|
||||||
|
|
||||||
|
/** A single RandomX pre-verification work item for the parallel validator pool. Pure value type
|
||||||
|
* (no chainstate pointers) so workers need no cs_main. On a hash match it sets *presult=true; on
|
||||||
|
* any failure it leaves *presult untouched — the inline CheckRandomXSolution remains the
|
||||||
|
* consensus authority and re-verifies anything not pre-verified. operator() ALWAYS returns true,
|
||||||
|
* so one block's failure never short-circuits the rest of the CCheckQueue batch. */
|
||||||
|
class CRandomXCheck
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string rxKey; // RandomX key for this block's height
|
||||||
|
std::vector<unsigned char> input; // serialized CRandomXInput(header)
|
||||||
|
unsigned char expected[32]; // block.nSolution (claimed RandomX hash)
|
||||||
|
bool* presult; // -> pindex->fRandomXVerified (set true only on a hash match)
|
||||||
|
public:
|
||||||
|
CRandomXCheck() : presult(nullptr) { memset(expected, 0, sizeof(expected)); }
|
||||||
|
CRandomXCheck(const std::string& keyIn, std::vector<unsigned char> inputIn,
|
||||||
|
const unsigned char* expectedIn, bool* presultIn)
|
||||||
|
: rxKey(keyIn), input(std::move(inputIn)), presult(presultIn)
|
||||||
|
{ memcpy(expected, expectedIn, sizeof(expected)); }
|
||||||
|
bool operator()();
|
||||||
|
void swap(CRandomXCheck& c) {
|
||||||
|
rxKey.swap(c.rxKey);
|
||||||
|
input.swap(c.input);
|
||||||
|
std::swap(presult, c.presult);
|
||||||
|
for (int i = 0; i < 32; i++) std::swap(expected[i], c.expected[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The RandomX pre-verification check queue (parallel pool). */
|
||||||
|
extern CCheckQueue<CRandomXCheck> rxCheckQueue;
|
||||||
|
/** Worker entry point (spawn N at startup, mirrors ThreadScriptCheck). */
|
||||||
|
void ThreadRandomXVerify();
|
||||||
|
/** Load `rxKey` into the shared validator cache (alloc on first use); call before dispatching a
|
||||||
|
* same-key group of checks. Returns false on allocation failure. */
|
||||||
|
bool RandomXValidatorPrepareKey(const std::string& rxKey);
|
||||||
|
/** Release the shared validator cache at shutdown. */
|
||||||
|
void RandomXValidatorShutdown();
|
||||||
|
|
||||||
/** Set thread-local flag to skip RandomX validation (used by miner during TestBlockValidity) */
|
/** Set thread-local flag to skip RandomX validation (used by miner during TestBlockValidity) */
|
||||||
void SetSkipRandomXValidation(bool skip);
|
void SetSkipRandomXValidation(bool skip);
|
||||||
|
bool GetSkipRandomXValidation();
|
||||||
|
|
||||||
/** Return the RandomX key rotation interval in blocks */
|
/** Return the RandomX key rotation interval in blocks */
|
||||||
int GetRandomXInterval();
|
int GetRandomXInterval();
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ const char *GETNSPV="getnSPV"; //used
|
|||||||
const char *NSPV="nSPV"; //used
|
const char *NSPV="nSPV"; //used
|
||||||
const char *ALERT="alert"; //used
|
const char *ALERT="alert"; //used
|
||||||
const char *REJECT="reject"; //used
|
const char *REJECT="reject"; //used
|
||||||
|
const char *GETBLOCKSTREAM="getblockstrm"; // 12 chars (COMMAND_SIZE max); "getblockstream" would truncate
|
||||||
|
const char *BLOCKSTREAM="blockstream";
|
||||||
} // namespace NetMsgType
|
} // namespace NetMsgType
|
||||||
|
|
||||||
/** All known message types. Keep this in the same order as the list of
|
/** All known message types. Keep this in the same order as the list of
|
||||||
@@ -119,6 +121,8 @@ const static std::string allNetMessageTypes[] = {
|
|||||||
NetMsgType::NSPV,
|
NetMsgType::NSPV,
|
||||||
NetMsgType::ALERT,
|
NetMsgType::ALERT,
|
||||||
NetMsgType::REJECT,
|
NetMsgType::REJECT,
|
||||||
|
NetMsgType::GETBLOCKSTREAM,
|
||||||
|
NetMsgType::BLOCKSTREAM,
|
||||||
};
|
};
|
||||||
|
|
||||||
CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn)
|
CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn)
|
||||||
|
|||||||
@@ -285,6 +285,10 @@ extern const char* GETNSPV;
|
|||||||
extern const char* NSPV;
|
extern const char* NSPV;
|
||||||
extern const char* ALERT;
|
extern const char* ALERT;
|
||||||
extern const char* REJECT;
|
extern const char* REJECT;
|
||||||
|
/** Opt-in bulk block streaming (DragonX): request a contiguous range of old blocks. */
|
||||||
|
extern const char* GETBLOCKSTREAM;
|
||||||
|
/** Opt-in bulk block streaming (DragonX): control header preceding a streamed block range. */
|
||||||
|
extern const char* BLOCKSTREAM;
|
||||||
}; // namespace NetMsgType
|
}; // namespace NetMsgType
|
||||||
|
|
||||||
/* Get a vector of all valid message types (see above) */
|
/* Get a vector of all valid message types (see above) */
|
||||||
@@ -304,6 +308,9 @@ enum ServiceFlags : uint64_t {
|
|||||||
NODE_NSPV = (1 << 30),
|
NODE_NSPV = (1 << 30),
|
||||||
NODE_ADDRINDEX = (1 << 29),
|
NODE_ADDRINDEX = (1 << 29),
|
||||||
NODE_SPENTINDEX = (1 << 28),
|
NODE_SPENTINDEX = (1 << 28),
|
||||||
|
// Opt-in bulk block streaming (DragonX). Unauthenticated advertisement; serve/request
|
||||||
|
// handlers validate every block regardless, so robustness against false advertisement holds.
|
||||||
|
NODE_BULKBLOCKS = (1 << 27),
|
||||||
|
|
||||||
// Bits 24-31 are reserved for temporary experiments. Just pick a bit that
|
// Bits 24-31 are reserved for temporary experiments. Just pick a bit that
|
||||||
// isn't getting used, or one not being used much, and notify the
|
// isn't getting used, or one not being used much, and notify the
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
#include "rpc/server.h"
|
#include "rpc/server.h"
|
||||||
#include "streams.h"
|
#include "streams.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
|
#include "txdb.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
#include "script/script_error.h"
|
#include "script/script_error.h"
|
||||||
#include "script/sign.h"
|
#include "script/sign.h"
|
||||||
@@ -860,6 +862,7 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp, const CPubKey& mypk
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UniValue getblockmerkletree(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
UniValue getblockmerkletree(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||||
{
|
{
|
||||||
if (fHelp || params.size() != 1 )
|
if (fHelp || params.size() != 1 )
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ UniValue getaddednodeinfo(const UniValue& params, bool fHelp, const CPubKey& myp
|
|||||||
" \"connected\" : true|false, (boolean) If connected\n"
|
" \"connected\" : true|false, (boolean) If connected\n"
|
||||||
" \"addresses\" : [\n"
|
" \"addresses\" : [\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" \"address\" : \"192.168.0.201:18030\", (string) The Hush server host and port\n"
|
" \"address\" : \"192.168.0.201:21768\", (string) The DragonX server host and port\n"
|
||||||
" \"connected\" : \"outbound\" (string) connection, inbound or outbound\n"
|
" \"connected\" : \"outbound\" (string) connection, inbound or outbound\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ static const CRPCCommand vRPCCommands[] =
|
|||||||
{ "wallet", "z_listaddresses", &z_listaddresses, true },
|
{ "wallet", "z_listaddresses", &z_listaddresses, true },
|
||||||
{ "wallet", "z_listnullifiers", &z_listnullifiers, true },
|
{ "wallet", "z_listnullifiers", &z_listnullifiers, true },
|
||||||
{ "wallet", "z_exportkey", &z_exportkey, true },
|
{ "wallet", "z_exportkey", &z_exportkey, true },
|
||||||
|
{ "wallet", "z_exportmnemonic", &z_exportmnemonic, true },
|
||||||
{ "wallet", "z_importkey", &z_importkey, true },
|
{ "wallet", "z_importkey", &z_importkey, true },
|
||||||
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
|
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
|
||||||
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ extern UniValue nspv_listccmoduleunspent(const UniValue& params, bool fHelp, con
|
|||||||
extern UniValue getblocksubsidy(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue getblocksubsidy(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
|
|
||||||
extern UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
extern UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
||||||
|
extern UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
||||||
extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
||||||
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
||||||
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include <boost/optional/optional_io.hpp>
|
#include <boost/optional/optional_io.hpp>
|
||||||
#include <librustzcash.h>
|
#include <librustzcash.h>
|
||||||
#include "zcash/Note.hpp"
|
#include "zcash/Note.hpp"
|
||||||
|
#include <random>
|
||||||
|
#include <limits>
|
||||||
extern bool fZDebug;
|
extern bool fZDebug;
|
||||||
|
|
||||||
SpendDescriptionInfo::SpendDescriptionInfo(
|
SpendDescriptionInfo::SpendDescriptionInfo(
|
||||||
@@ -66,7 +68,7 @@ void TransactionBuilder::AddSaplingOutput(
|
|||||||
void TransactionBuilder::ShuffleOutputs()
|
void TransactionBuilder::ShuffleOutputs()
|
||||||
{
|
{
|
||||||
LogPrintf("%s: Shuffling %d zouts\n", __func__, outputs.size() );
|
LogPrintf("%s: Shuffling %d zouts\n", __func__, outputs.size() );
|
||||||
random_shuffle( outputs.begin(), outputs.end(), GetRandInt );
|
std::shuffle( outputs.begin(), outputs.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value, uint32_t _nSequence)
|
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value, uint32_t _nSequence)
|
||||||
|
|||||||
@@ -21,9 +21,11 @@
|
|||||||
|
|
||||||
#include "txdb.h"
|
#include "txdb.h"
|
||||||
#include "chainparams.h"
|
#include "chainparams.h"
|
||||||
|
#include "clientversion.h"
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "pow.h"
|
#include "pow.h"
|
||||||
|
#include "streams.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
#include "core_io.h"
|
#include "core_io.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class uint256;
|
|||||||
//! -dbcache default (MiB)
|
//! -dbcache default (MiB)
|
||||||
static const int64_t nDefaultDbCache = 512;
|
static const int64_t nDefaultDbCache = 512;
|
||||||
//! max. -dbcache (MiB)
|
//! max. -dbcache (MiB)
|
||||||
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
|
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 65536 : 1024; // 64 GiB ceiling on 64-bit so adaptive dbcache can use most of RAM on large hosts (was 16384)
|
||||||
//! min. -dbcache in (MiB)
|
//! min. -dbcache in (MiB)
|
||||||
static const int64_t nMinDbCache = 4;
|
static const int64_t nMinDbCache = 4;
|
||||||
|
|
||||||
|
|||||||
@@ -469,10 +469,17 @@ extern char SMART_CHAIN_SYMBOL[];
|
|||||||
|
|
||||||
std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
|
std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
|
||||||
{
|
{
|
||||||
CBlockIndex *tipindex;
|
// Remove expired txs from the mempool. (Regression fix: the scan that populates
|
||||||
// Remove expired txs from the mempool
|
// transactionsToRemove had been dropped, making this a no-op, so expired txs -- which
|
||||||
|
// can never be mined -- were never evicted and accumulated without bound.)
|
||||||
LOCK(cs);
|
LOCK(cs);
|
||||||
list<CTransaction> transactionsToRemove;
|
list<CTransaction> transactionsToRemove;
|
||||||
|
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
|
||||||
|
const CTransaction& tx = it->GetTx();
|
||||||
|
if (IsExpiredTx(tx, nBlockHeight)) {
|
||||||
|
transactionsToRemove.push_back(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint256> ids;
|
std::vector<uint256> ids;
|
||||||
for (const CTransaction& tx : transactionsToRemove) {
|
for (const CTransaction& tx : transactionsToRemove) {
|
||||||
@@ -484,6 +491,45 @@ std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
|
|||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Age-based eviction: remove txs whose entry time is older than `time`. Distinct from
|
||||||
|
// removeExpired() (which drops txs past their consensus nExpiryHeight); this is the wall-clock
|
||||||
|
// sweep LimitMempoolSize wants. Collect-then-remove to avoid iterating mapTx while mutating it.
|
||||||
|
int CTxMemPool::Expire(int64_t time)
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
std::list<CTransaction> toRemove;
|
||||||
|
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
|
||||||
|
if (it->GetTime() < time)
|
||||||
|
toRemove.push_back(it->GetTx());
|
||||||
|
}
|
||||||
|
for (const CTransaction& tx : toRemove) {
|
||||||
|
std::list<CTransaction> removed;
|
||||||
|
remove(tx, removed, true);
|
||||||
|
}
|
||||||
|
return (int)toRemove.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fee-ordered eviction: drop the lowest-feerate txs (and their in-mempool descendants, via the
|
||||||
|
// recursive remove) until DynamicMemoryUsage() is at or below sizelimit. Uses the per-tx feerate
|
||||||
|
// index (mapTx index 1, sorted feerate DESCENDING, so the worst tx is the tail). NOTE: this is a
|
||||||
|
// per-tx feerate, not a descendant-aggregate score, so a low-fee parent funded by a high-fee child
|
||||||
|
// (CPFP) can be evicted -- an accepted simplification (no descendant tracking in this fork). The
|
||||||
|
// admission cap in AcceptToMemoryPool bounds growth between block connects (no rollingMinFee here).
|
||||||
|
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining)
|
||||||
|
{
|
||||||
|
LOCK(cs);
|
||||||
|
while (DynamicMemoryUsage() > sizelimit && !mapTx.empty()) {
|
||||||
|
// Re-derive the tail each iteration: remove() invalidates iterators.
|
||||||
|
CTransaction tx = std::prev(mapTx.get<1>().end())->GetTx();
|
||||||
|
std::list<CTransaction> removed;
|
||||||
|
remove(tx, removed, true);
|
||||||
|
if (pvNoSpendsRemaining) {
|
||||||
|
for (const CTransaction& r : removed)
|
||||||
|
pvNoSpendsRemaining->push_back(r.GetHash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Called when a block is connected. Removes from mempool and updates the miner fee estimator.
|
// Called when a block is connected. Removes from mempool and updates the miner fee estimator.
|
||||||
void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
||||||
std::list<CTransaction>& conflicts, bool fCurrentEstimate)
|
std::list<CTransaction>& conflicts, bool fCurrentEstimate)
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ public:
|
|||||||
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
|
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
|
||||||
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
|
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
|
||||||
std::vector<uint256> removeExpired(unsigned int nBlockHeight);
|
std::vector<uint256> removeExpired(unsigned int nBlockHeight);
|
||||||
|
int Expire(int64_t time);
|
||||||
|
void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining = NULL);
|
||||||
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
||||||
std::list<CTransaction>& conflicts, bool fCurrentEstimate = true);
|
std::list<CTransaction>& conflicts, bool fCurrentEstimate = true);
|
||||||
void removeWithoutBranchId(uint32_t nMemPoolBranchId);
|
void removeWithoutBranchId(uint32_t nMemPoolBranchId);
|
||||||
|
|||||||
@@ -156,10 +156,17 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
assert(pcoinsTip->GetSaplingAnchorAt(SaplingMerkleTree::empty_root(), oldSaplingTree));
|
assert(pcoinsTip->GetSaplingAnchorAt(SaplingMerkleTree::empty_root(), oldSaplingTree));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use operator[] (empty-list default), NOT .at(): a block's conflict entry can be
|
||||||
|
// absent if it was drained in an earlier cycle whose connect loop hit the IBD
|
||||||
|
// ReadBlockFromDisk retry below (that break leaves pindexLastTip behind, so the block
|
||||||
|
// gets rebuilt here but its conflicts were already cleared and are never re-inserted).
|
||||||
|
// .at() would throw std::out_of_range which, uncaught under cs_main in this boost
|
||||||
|
// thread, aborts the node (and crash-loops since pindexLastTip cannot advance). A
|
||||||
|
// missing entry simply means no conflict notifications for this block (best-effort).
|
||||||
blockStack.emplace_back(
|
blockStack.emplace_back(
|
||||||
pindex,
|
pindex,
|
||||||
std::make_pair(oldSproutTree, oldSaplingTree),
|
std::make_pair(oldSproutTree, oldSaplingTree),
|
||||||
recentlyConflicted.first.at(pindex));
|
recentlyConflicted.first[pindex]);
|
||||||
|
|
||||||
pindex = pindex->pprev;
|
pindex = pindex->pprev;
|
||||||
}
|
}
|
||||||
@@ -174,11 +181,23 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
// network message processing thread.
|
// network message processing thread.
|
||||||
//
|
//
|
||||||
|
|
||||||
// Notify block disconnects
|
// Notify block disconnects. If an IBD read fails mid-disconnect we must NOT fall through
|
||||||
|
// to the connect loop (which advances pindexLastTip to the new tip and permanently abandons
|
||||||
|
// the remaining disconnects -> wallet witness/anchor desync); skip connects this cycle and
|
||||||
|
// retry the whole disconnect next cycle, exactly as the connect-side break retries.
|
||||||
|
bool fDisconnectIncomplete = false;
|
||||||
while (pindexLastTip && pindexLastTip != pindexFork) {
|
while (pindexLastTip && pindexLastTip != pindexFork) {
|
||||||
// Read block from disk.
|
// Read block from disk.
|
||||||
CBlock block;
|
CBlock block;
|
||||||
if (!ReadBlockFromDisk(block, pindexLastTip,1)) {
|
if (!ReadBlockFromDisk(block, pindexLastTip,1)) {
|
||||||
|
if (IsInitialBlockDownload()) {
|
||||||
|
// During IBD, block data may not be flushed to disk yet.
|
||||||
|
// Sleep briefly and retry on the next cycle instead of crashing.
|
||||||
|
LogPrintf("%s: block at height %d not yet readable, will retry\n",
|
||||||
|
__func__, pindexLastTip->GetHeight());
|
||||||
|
fDisconnectIncomplete = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
|
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
|
||||||
uiInterface.ThreadSafeMessageBox(
|
uiInterface.ThreadSafeMessageBox(
|
||||||
_("Error: A fatal internal error occurred, see debug.log for details"),
|
_("Error: A fatal internal error occurred, see debug.log for details"),
|
||||||
@@ -198,14 +217,23 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
pindexLastTip = pindexLastTip->pprev;
|
pindexLastTip = pindexLastTip->pprev;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify block connections
|
// Notify block connections (skipped this cycle if the disconnect loop broke to retry an
|
||||||
while (!blockStack.empty()) {
|
// unreadable block, so pindexLastTip is not advanced past the un-disconnected blocks).
|
||||||
|
while (!fDisconnectIncomplete && !blockStack.empty()) {
|
||||||
auto blockData = blockStack.back();
|
auto blockData = blockStack.back();
|
||||||
blockStack.pop_back();
|
blockStack.pop_back();
|
||||||
|
|
||||||
// Read block from disk.
|
// Read block from disk.
|
||||||
CBlock block;
|
CBlock block;
|
||||||
if (!ReadBlockFromDisk(block, blockData.pindex, 1)) {
|
if (!ReadBlockFromDisk(block, blockData.pindex, 1)) {
|
||||||
|
if (IsInitialBlockDownload()) {
|
||||||
|
// During IBD, block data may not be flushed to disk yet.
|
||||||
|
// Push unprocessed blocks back and retry on the next cycle.
|
||||||
|
LogPrintf("%s: block at height %d not yet readable, will retry\n",
|
||||||
|
__func__, blockData.pindex->GetHeight());
|
||||||
|
blockStack.push_back(blockData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block connects");
|
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block connects");
|
||||||
uiInterface.ThreadSafeMessageBox(
|
uiInterface.ThreadSafeMessageBox(
|
||||||
_("Error: A fatal internal error occurred, see debug.log for details"),
|
_("Error: A fatal internal error occurred, see debug.log for details"),
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
|||||||
// recoverable, while keeping it logically separate from the ZIP 32
|
// recoverable, while keeping it logically separate from the ZIP 32
|
||||||
// Sapling key hierarchy, which the user might not be using.
|
// Sapling key hierarchy, which the user might not be using.
|
||||||
HDSeed seed;
|
HDSeed seed;
|
||||||
if (!pwalletMain->GetHDSeed(seed)) {
|
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
|
||||||
throw JSONRPCError(
|
throw JSONRPCError(
|
||||||
RPC_WALLET_ERROR,
|
RPC_WALLET_ERROR,
|
||||||
"AsyncRPCOperation_sendmany: HD seed not found");
|
"AsyncRPCOperation_sendmany: HD seed not found");
|
||||||
|
|||||||
@@ -377,7 +377,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
// recoverable, while keeping it logically separate from the ZIP 32
|
// recoverable, while keeping it logically separate from the ZIP 32
|
||||||
// Sapling key hierarchy, which the user might not be using.
|
// Sapling key hierarchy, which the user might not be using.
|
||||||
HDSeed seed;
|
HDSeed seed;
|
||||||
if (!pwalletMain->GetHDSeed(seed)) {
|
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
|
||||||
throw JSONRPCError(
|
throw JSONRPCError(
|
||||||
RPC_WALLET_ERROR,
|
RPC_WALLET_ERROR,
|
||||||
"AsyncRPCOperation_sendmany::main_impl(): HD seed not found");
|
"AsyncRPCOperation_sendmany::main_impl(): HD seed not found");
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
|
|||||||
// recoverable, while keeping it logically separate from the ZIP 32
|
// recoverable, while keeping it logically separate from the ZIP 32
|
||||||
// Sapling key hierarchy, which the user might not be using.
|
// Sapling key hierarchy, which the user might not be using.
|
||||||
HDSeed seed;
|
HDSeed seed;
|
||||||
if (!pwalletMain->GetHDSeed(seed)) {
|
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
|
||||||
throw JSONRPCError(
|
throw JSONRPCError(
|
||||||
RPC_WALLET_ERROR,
|
RPC_WALLET_ERROR,
|
||||||
"CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
"CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
||||||
|
|||||||
99
src/wallet/mnemonic.cpp
Normal file
99
src/wallet/mnemonic.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) 2016-2024 The Hush developers
|
||||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
#include "wallet/mnemonic.h"
|
||||||
|
|
||||||
|
#include "random.h"
|
||||||
|
#include "support/cleanse.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "crypto/bip39/bip39.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vendored BIP39 library references random_buffer() (used by its
|
||||||
|
// mnemonic_generate()). We do not compile trezor's insecure rand.c; instead we
|
||||||
|
// route it to the node CSPRNG so any BIP39 randomness is cryptographically
|
||||||
|
// sound. random_buffer is declared weak in rand.c, so this strong definition
|
||||||
|
// is the one that links.
|
||||||
|
extern "C" void random_buffer(uint8_t* buf, size_t len)
|
||||||
|
{
|
||||||
|
GetRandBytes(buf, (int)len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mnemonic_from_data()/mnemonic_to_seed() use process-static scratch buffers,
|
||||||
|
// so serialize all access behind one lock and copy results out immediately.
|
||||||
|
static std::mutex cs_bip39;
|
||||||
|
|
||||||
|
bool MnemonicIsValid(const std::string& phrase)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
||||||
|
return mnemonic_check(phrase.c_str()) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MnemonicToEntropy(const std::string& phrase, RawHDSeed& entropyOut)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
||||||
|
// Reject bad checksum / unknown words first.
|
||||||
|
if (mnemonic_check(phrase.c_str()) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// mnemonic_to_entropy() writes 33 bytes (entropy || 1 checksum byte) and
|
||||||
|
// returns the total bit count (words * 11).
|
||||||
|
uint8_t buf[33];
|
||||||
|
int totalBits = mnemonic_to_entropy(phrase.c_str(), buf);
|
||||||
|
if (totalBits <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int words = totalBits / 11;
|
||||||
|
if (words != 12 && words != 18 && words != 24) {
|
||||||
|
memory_cleanse(buf, sizeof(buf));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int entropyBytes = words * 4 / 3; // 12->16, 18->24, 24->32
|
||||||
|
entropyOut.assign(buf, buf + entropyBytes);
|
||||||
|
memory_cleanse(buf, sizeof(buf));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntropyToMnemonic(const RawHDSeed& entropy, std::string& phraseOut)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
||||||
|
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
|
||||||
|
if (phrase == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
phraseOut.assign(phrase);
|
||||||
|
mnemonic_clear(); // wipe the static buffer
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bip39SeedFromEntropy(const RawHDSeed& entropy, RawHDSeed& seed64Out)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cs_bip39);
|
||||||
|
// Regenerate the canonical phrase from entropy (matches SDXLite's
|
||||||
|
// Mnemonic::from_entropy(entropy).phrase()), then PBKDF2 with an EMPTY
|
||||||
|
// passphrase to get the standard 64-byte BIP39 seed.
|
||||||
|
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
|
||||||
|
if (phrase == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t seed[64];
|
||||||
|
mnemonic_to_seed(phrase, "", seed, nullptr);
|
||||||
|
mnemonic_clear();
|
||||||
|
seed64Out.assign(seed, seed + 64);
|
||||||
|
memory_cleanse(seed, sizeof(seed));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenerateMnemonicEntropy(int bits, RawHDSeed& entropyOut)
|
||||||
|
{
|
||||||
|
if (bits != 128 && bits != 160 && bits != 192 && bits != 224 && bits != 256) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entropyOut.resize(bits / 8);
|
||||||
|
GetRandBytes(entropyOut.data(), (int)entropyOut.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
39
src/wallet/mnemonic.h
Normal file
39
src/wallet/mnemonic.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2016-2024 The Hush developers
|
||||||
|
// Distributed under the GPLv3 software license, see the accompanying
|
||||||
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
#ifndef HUSH_WALLET_MNEMONIC_H
|
||||||
|
#define HUSH_WALLET_MNEMONIC_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "zcash/zip32.h" // RawHDSeed
|
||||||
|
|
||||||
|
// Thin, thread-safe C++ wrapper over the vendored BIP39 (trezor-crypto) library.
|
||||||
|
// It reproduces SilentDragonXLite's tiny-bip39 0.6.2 conventions EXACTLY so the
|
||||||
|
// same 24 words yield the same addresses in both wallets:
|
||||||
|
// - English wordlist only (byte-identical to tiny-bip39's english.txt)
|
||||||
|
// - empty BIP39 passphrase (no "25th word")
|
||||||
|
// - PBKDF2-HMAC-SHA512, 2048 rounds, 64-byte seed
|
||||||
|
// - the seed is derived from the CANONICAL phrase regenerated from entropy,
|
||||||
|
// matching SDXLite's Mnemonic::from_entropy(entropy).phrase() round-trip.
|
||||||
|
|
||||||
|
//! True if `phrase` is a valid BIP39 mnemonic (word list + checksum).
|
||||||
|
bool MnemonicIsValid(const std::string& phrase);
|
||||||
|
|
||||||
|
//! Parse `phrase` into its BIP39 entropy (16/20/24/28/32 bytes). Validates the
|
||||||
|
//! checksum first. Returns false on any invalid input.
|
||||||
|
bool MnemonicToEntropy(const std::string& phrase, RawHDSeed& entropyOut);
|
||||||
|
|
||||||
|
//! Regenerate the canonical English mnemonic phrase from `entropy`.
|
||||||
|
bool EntropyToMnemonic(const RawHDSeed& entropy, std::string& phraseOut);
|
||||||
|
|
||||||
|
//! Derive the 64-byte BIP39 seed used for HD derivation from `entropy`, exactly
|
||||||
|
//! as SilentDragonXLite does: canonical phrase from entropy, then PBKDF2 with an
|
||||||
|
//! empty passphrase.
|
||||||
|
bool Bip39SeedFromEntropy(const RawHDSeed& entropy, RawHDSeed& seed64Out);
|
||||||
|
|
||||||
|
//! Generate fresh BIP39 entropy of `bits` (128/160/192/224/256) from the node
|
||||||
|
//! CSPRNG, for creating a new mnemonic wallet.
|
||||||
|
bool GenerateMnemonicEntropy(int bits, RawHDSeed& entropyOut);
|
||||||
|
|
||||||
|
#endif // HUSH_WALLET_MNEMONIC_H
|
||||||
@@ -742,7 +742,9 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
|
|||||||
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
|
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
|
||||||
{
|
{
|
||||||
HDSeed hdSeed;
|
HDSeed hdSeed;
|
||||||
pwalletMain->GetHDSeed(hdSeed);
|
// Dump the 64-byte derivation seed (for mnemonic wallets this is the
|
||||||
|
// expanded BIP39 seed), so re-importing the hex reproduces the same keys.
|
||||||
|
pwalletMain->GetHDSeedForDerivation(hdSeed);
|
||||||
auto rawSeed = hdSeed.RawSeed();
|
auto rawSeed = hdSeed.RawSeed();
|
||||||
file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex());
|
file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex());
|
||||||
file << "\n";
|
file << "\n";
|
||||||
@@ -1026,6 +1028,50 @@ UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
|||||||
return EncodeSpendingKey(sk.get());
|
return EncodeSpendingKey(sk.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||||
|
{
|
||||||
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
return NullUniValue;
|
||||||
|
|
||||||
|
if (fHelp || params.size() != 0)
|
||||||
|
throw runtime_error(
|
||||||
|
"z_exportmnemonic\n"
|
||||||
|
"\nReveal the wallet's BIP39 seed phrase (24 words).\n"
|
||||||
|
"The phrase is byte-compatible with SilentDragonXLite: the same words\n"
|
||||||
|
"restore the same transparent and shielded addresses in either wallet.\n"
|
||||||
|
"Only works for wallets created or restored from a mnemonic (see the\n"
|
||||||
|
"-mnemonic and -usemnemonic options). Requires the wallet be unlocked.\n"
|
||||||
|
"\nResult:\n"
|
||||||
|
"{\n"
|
||||||
|
" \"mnemonic\" : \"word1 ... word24\", (string) the BIP39 seed phrase\n"
|
||||||
|
" \"seedfp\" : \"hex\" (string) the HD seed fingerprint\n"
|
||||||
|
"}\n"
|
||||||
|
"\nExamples:\n"
|
||||||
|
+ HelpExampleCli("z_exportmnemonic", "")
|
||||||
|
+ HelpExampleRpc("z_exportmnemonic", "")
|
||||||
|
);
|
||||||
|
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
EnsureWalletIsUnlocked();
|
||||||
|
|
||||||
|
if (!pwalletMain->IsMnemonicSeed()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
"This wallet's seed was not derived from a mnemonic, so no seed phrase is available. "
|
||||||
|
"Use z_exportwallet to back up the raw HD seed instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string phrase;
|
||||||
|
if (!pwalletMain->GetMnemonicPhrase(phrase)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Could not retrieve the seed phrase (is the wallet unlocked?)");
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue ret(UniValue::VOBJ);
|
||||||
|
ret.push_back(Pair("mnemonic", phrase));
|
||||||
|
ret.push_back(Pair("seedfp", pwalletMain->GetHDChain().seedFp.GetHex()));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||||
{
|
{
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends,
|
|||||||
//Decrypt sapling outgoing t to z transaction using HDseed
|
//Decrypt sapling outgoing t to z transaction using HDseed
|
||||||
if (wtx.vShieldedSpend.size()==0) {
|
if (wtx.vShieldedSpend.size()==0) {
|
||||||
HDSeed seed;
|
HDSeed seed;
|
||||||
if (pwalletMain->GetHDSeed(seed)) {
|
if (pwalletMain->GetHDSeedForDerivation(seed)) {
|
||||||
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
|
||||||
outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
|
outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
|
||||||
|
|
||||||
|
|||||||
@@ -6272,6 +6272,7 @@ extern UniValue importaddress(const UniValue& params, bool fHelp, const CPubKey&
|
|||||||
extern UniValue dumpwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue dumpwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
extern UniValue importwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue importwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
extern UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
|
extern UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
|
||||||
@@ -6351,6 +6352,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "wallet", "z_getnewaddress", &z_getnewaddress, true },
|
{ "wallet", "z_getnewaddress", &z_getnewaddress, true },
|
||||||
{ "wallet", "z_listaddresses", &z_listaddresses, true },
|
{ "wallet", "z_listaddresses", &z_listaddresses, true },
|
||||||
{ "wallet", "z_exportkey", &z_exportkey, true },
|
{ "wallet", "z_exportkey", &z_exportkey, true },
|
||||||
|
{ "wallet", "z_exportmnemonic", &z_exportmnemonic, true },
|
||||||
{ "wallet", "z_importkey", &z_importkey, true },
|
{ "wallet", "z_importkey", &z_importkey, true },
|
||||||
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
|
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
|
||||||
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
||||||
|
|||||||
@@ -36,9 +36,15 @@
|
|||||||
#include "utilmoneystr.h"
|
#include "utilmoneystr.h"
|
||||||
#include "zcash/Note.hpp"
|
#include "zcash/Note.hpp"
|
||||||
#include "crypter.h"
|
#include "crypter.h"
|
||||||
|
#include "wallet/mnemonic.h"
|
||||||
#include "coins.h"
|
#include "coins.h"
|
||||||
#include "wallet/asyncrpcoperation_saplingconsolidation.h"
|
#include "wallet/asyncrpcoperation_saplingconsolidation.h"
|
||||||
#include "wallet/asyncrpcoperation_sweep.h"
|
#include "wallet/asyncrpcoperation_sweep.h"
|
||||||
|
#include <random>
|
||||||
|
#include <limits>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
#include "zcash/zip32.h"
|
#include "zcash/zip32.h"
|
||||||
#include "cc/CCinclude.h"
|
#include "cc/CCinclude.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -126,7 +132,7 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey(bool addToWallet)
|
|||||||
|
|
||||||
// Try to get the seed
|
// Try to get the seed
|
||||||
HDSeed seed;
|
HDSeed seed;
|
||||||
if (!GetHDSeed(seed))
|
if (!GetHDSeedForDerivation(seed))
|
||||||
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
||||||
|
|
||||||
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
|
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
|
||||||
@@ -217,7 +223,20 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
|
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
|
||||||
|
|
||||||
CKey secret;
|
CKey secret;
|
||||||
|
|
||||||
|
// Create new metadata
|
||||||
|
int64_t nCreationTime = GetTime();
|
||||||
|
CKeyMetadata metadata(nCreationTime);
|
||||||
|
|
||||||
|
// Derive the transparent key deterministically from the HD seed when the
|
||||||
|
// feature is enabled, so it can be recovered from the seed alone. Otherwise
|
||||||
|
// fall back to a random key (e.g. legacy wallets that have no HD seed).
|
||||||
|
if (IsHDTransparentEnabled()) {
|
||||||
|
DeriveNewChildKey(metadata, secret);
|
||||||
|
fCompressed = true; // BIP32-derived keys are always compressed
|
||||||
|
} else {
|
||||||
secret.MakeNewKey(fCompressed);
|
secret.MakeNewKey(fCompressed);
|
||||||
|
}
|
||||||
|
|
||||||
// Compressed public keys were introduced in version 0.6.0
|
// Compressed public keys were introduced in version 0.6.0
|
||||||
if (fCompressed)
|
if (fCompressed)
|
||||||
@@ -226,9 +245,7 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
CPubKey pubkey = secret.GetPubKey();
|
CPubKey pubkey = secret.GetPubKey();
|
||||||
assert(secret.VerifyPubKey(pubkey));
|
assert(secret.VerifyPubKey(pubkey));
|
||||||
|
|
||||||
// Create new metadata
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
||||||
int64_t nCreationTime = GetTime();
|
|
||||||
mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime);
|
|
||||||
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
|
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
|
||||||
nTimeFirstKey = nCreationTime;
|
nTimeFirstKey = nCreationTime;
|
||||||
|
|
||||||
@@ -237,6 +254,57 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
return pubkey;
|
return pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive a new transparent key from the HD seed along the BIP44 external chain
|
||||||
|
// m/44'/coin_type'/0'/0/i. The child index is taken from (and advances)
|
||||||
|
// hdChain.transparentChildCounter, which is persisted so the same keys can be
|
||||||
|
// regenerated after a seed-only restore. Mirrors GenerateNewSaplingZKey.
|
||||||
|
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secretRet)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_wallet); // mapKeyMetadata / hdChain
|
||||||
|
|
||||||
|
HDSeed seed;
|
||||||
|
if (!GetHDSeedForDerivation(seed))
|
||||||
|
throw std::runtime_error("CWallet::DeriveNewChildKey(): HD seed not found");
|
||||||
|
|
||||||
|
RawHDSeed rawSeed = seed.RawSeed();
|
||||||
|
|
||||||
|
CExtKey masterKey; // m
|
||||||
|
CExtKey purposeKey; // m/44'
|
||||||
|
CExtKey coinTypeKey; // m/44'/coin_type'
|
||||||
|
CExtKey accountKey; // m/44'/coin_type'/0'
|
||||||
|
CExtKey externalChainKey; // m/44'/coin_type'/0'/0
|
||||||
|
CExtKey childKey; // m/44'/coin_type'/0'/0/i
|
||||||
|
|
||||||
|
masterKey.SetMaster(rawSeed.data(), rawSeed.size());
|
||||||
|
|
||||||
|
uint32_t bip44CoinType = Params().BIP44CoinType();
|
||||||
|
|
||||||
|
// BIP44 path, single account (0'), external chain (0). On this ac_private=1
|
||||||
|
// chain the internal/change chain can never hold value, so it is unused.
|
||||||
|
masterKey.Derive(purposeKey, 44 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
purposeKey.Derive(coinTypeKey, bip44CoinType | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
coinTypeKey.Derive(accountKey, 0 | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
accountKey.Derive(externalChainKey, 0);
|
||||||
|
|
||||||
|
// Derive the next child index, skipping any key already in the wallet.
|
||||||
|
do {
|
||||||
|
externalChainKey.Derive(childKey, hdChain.transparentChildCounter);
|
||||||
|
metadata.hdKeypath = "m/44'/" + std::to_string(bip44CoinType) + "'/0'/0/" + std::to_string(hdChain.transparentChildCounter);
|
||||||
|
metadata.seedFp = hdChain.seedFp;
|
||||||
|
hdChain.transparentChildCounter++;
|
||||||
|
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
||||||
|
|
||||||
|
secretRet = childKey.key;
|
||||||
|
|
||||||
|
// Bump a legacy v1 chain to v2 so the transparent counter gets persisted.
|
||||||
|
if (hdChain.nVersion < CHDChain::VERSION_HD_TRANSPARENT)
|
||||||
|
hdChain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
|
||||||
|
|
||||||
|
// Persist the advanced counter so restarts / restores don't reuse indices.
|
||||||
|
if (fFileBacked && !CWalletDB(strWalletFile).WriteHDChain(hdChain))
|
||||||
|
throw std::runtime_error("CWallet::DeriveNewChildKey(): Writing HD chain model failed");
|
||||||
|
}
|
||||||
|
|
||||||
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
|
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||||
@@ -988,11 +1056,22 @@ void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
|
|||||||
if (nd->nullifier && pwalletMain->GetSaplingSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
|
if (nd->nullifier && pwalletMain->GetSaplingSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
|
||||||
// Only decrement witnesses that are not above the current height
|
// Only decrement witnesses that are not above the current height
|
||||||
if (nd->witnessHeight <= pindex->GetHeight()) {
|
if (nd->witnessHeight <= pindex->GetHeight()) {
|
||||||
|
//PART B1: a rolled-back note must re-validate on reconnect (flag is in-memory only).
|
||||||
|
nd->witnessRootValidated = false;
|
||||||
if (nd->witnesses.size() > 1) {
|
if (nd->witnesses.size() > 1) {
|
||||||
// indexHeight is the height of the block being removed, so
|
// indexHeight is the height of the block being removed, so
|
||||||
// the new witness cache height is one below it.
|
// the new witness cache height is one below it.
|
||||||
nd->witnesses.pop_front();
|
nd->witnesses.pop_front();
|
||||||
nd->witnessHeight = pindex->GetHeight() - 1;
|
nd->witnessHeight = pindex->GetHeight() - 1;
|
||||||
|
} else {
|
||||||
|
//PART B1: with only the base witness left we cannot pop_front without emptying
|
||||||
|
//the cache, but we must NOT leave witnessHeight stranded above the disconnected
|
||||||
|
//tip (that high-height/stale-witness state is the desync originator). Force a
|
||||||
|
//clean reseed on reconnect instead of lying about the height. (Inlined because
|
||||||
|
//ClearSingleNoteWitnessCache is defined later in this file.)
|
||||||
|
nd->witnesses.clear();
|
||||||
|
nd->witnessHeight = -1;
|
||||||
|
nd->witnessRootValidated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1058,11 +1137,24 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Skip Validation when witness height is greater that block height
|
//PART A: a witness whose height is at/above the build height must NOT be blindly trusted.
|
||||||
|
//Validate its root against the canonical sapling root at witnessHeight when that block is
|
||||||
|
//on the active chain. Match -> safe to skip. Mismatch (witnessHeight advanced past the real
|
||||||
|
//witness state = the desync signature) -> fall through to ClearSingleNoteWitnessCache + reseed.
|
||||||
if (nd->witnessHeight > pindex->GetHeight() - 1) {
|
if (nd->witnessHeight > pindex->GetHeight() - 1) {
|
||||||
|
CBlockIndex* whIndex = chainActive[nd->witnessHeight];
|
||||||
|
if (whIndex == NULL) {
|
||||||
|
//witnessHeight strictly above the active chain (transient catch-up): cannot validate yet
|
||||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (nd->witnesses.front().root() == whIndex->hashFinalSaplingRoot) {
|
||||||
|
nd->witnessRootValidated = true;
|
||||||
|
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//root mismatch on the active chain -> desynced; fall through to rebuild below
|
||||||
|
}
|
||||||
|
|
||||||
//Validate the witness at the witness height
|
//Validate the witness at the witness height
|
||||||
witnessRoot = nd->witnesses.front().root();
|
witnessRoot = nd->witnesses.front().root();
|
||||||
@@ -1137,80 +1229,226 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
|
|||||||
|
|
||||||
LOCK2(cs_main, cs_wallet);
|
LOCK2(cs_main, cs_wallet);
|
||||||
|
|
||||||
|
// The Phase-1 loop below walks the ACTIVE chain (chainActive.Next) and sizes blockCms from
|
||||||
|
// pindex->GetHeight(), terminating only on pbi==pindex. If pindex was reorged OFF the active
|
||||||
|
// chain (a reorg landed while ThreadNotifyWallets drained its connect backlog with cs_main
|
||||||
|
// released), the loop never reaches pindex and, once the active tip passes pindex's height, it
|
||||||
|
// writes blockCms[h-startHeight] out of bounds -> heap overflow. Rebuilding witnesses for an
|
||||||
|
// abandoned block is meaningless; the ChainTip for the new active tip re-drives this. cs_main is
|
||||||
|
// held for the whole function, so this check cannot race the loop below.
|
||||||
|
if (pindex != chainActive[pindex->GetHeight()]) {
|
||||||
|
if (fZdebug)
|
||||||
|
LogPrintf("%s: pindex height=%d not on active chain (reorg); skipping witness rebuild\n",
|
||||||
|
__func__, pindex->GetHeight());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int startHeight = VerifyAndSetInitialWitness(pindex, witnessOnly) + 1;
|
int startHeight = VerifyAndSetInitialWitness(pindex, witnessOnly) + 1;
|
||||||
|
|
||||||
if (startHeight > pindex->GetHeight() || witnessOnly) {
|
if (startHeight > pindex->GetHeight() || witnessOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 saplingRoot;
|
|
||||||
CBlockIndex* pblockindex = chainActive[startHeight];
|
|
||||||
int height = chainActive.Height();
|
|
||||||
if(fZdebug)
|
if(fZdebug)
|
||||||
LogPrintf("%s: height=%d, startHeight=%d\n", __func__, height, startHeight);
|
LogPrintf("%s: startHeight=%d, tip=%d\n", __func__, startHeight, chainActive.Height());
|
||||||
|
|
||||||
while (pblockindex) {
|
// Tier 1 optimization: build the set of notes that still need extension ONCE instead of
|
||||||
|
// rescanning all of mapWallet for every block. A note's gate conditions (nullifier present,
|
||||||
|
// spend depth <= WITNESS_CACHE_SIZE, tx confirmed) are invariant across this loop (the active
|
||||||
|
// chain is fixed under cs_main), so they are evaluated once here rather than per block. A note
|
||||||
|
// becomes "active" at the block where witnessHeight == GetHeight()-1 (exactly the original
|
||||||
|
// per-block gate) and is then extended every subsequent block, so the produced witnesses are
|
||||||
|
// byte-for-byte identical to the original full-rescan implementation -- only the bookkeeping
|
||||||
|
// cost changes from O(blocks * walletSize) to O(blocks + activeNotes).
|
||||||
|
struct PendingNote { int startWitnessHeight; SaplingNoteData* nd; };
|
||||||
|
std::vector<PendingNote> pending;
|
||||||
|
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
||||||
|
if (wtxItem.second.mapSaplingNoteData.empty())
|
||||||
|
continue;
|
||||||
|
if (wtxItem.second.GetDepthInMainChain() <= 0)
|
||||||
|
continue;
|
||||||
|
for (mapSaplingNoteData_t::value_type& item : wtxItem.second.mapSaplingNoteData) {
|
||||||
|
SaplingNoteData* nd = &(item.second);
|
||||||
|
if (!nd->nullifier)
|
||||||
|
continue;
|
||||||
|
if (nd->witnesses.empty()) // cannot extend (front() would be UB); the original gate also never matched these in practice
|
||||||
|
continue;
|
||||||
|
if (GetSaplingSpendDepth(*nd->nullifier) > WITNESS_CACHE_SIZE)
|
||||||
|
continue;
|
||||||
|
// Only notes that still lag the build target need extension. The lowest such witnessHeight
|
||||||
|
// is exactly startHeight-1 (startHeight = nMinimumHeight+1 from SaplingWitnessMinimumHeight).
|
||||||
|
if (nd->witnessHeight >= startHeight - 1 && nd->witnessHeight <= pindex->GetHeight() - 1)
|
||||||
|
pending.push_back({ nd->witnessHeight, nd });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(pending.begin(), pending.end(),
|
||||||
|
[](const PendingNote& a, const PendingNote& b) { return a.startWitnessHeight < b.startWitnessHeight; });
|
||||||
|
|
||||||
|
// Phase 1 (serial, main thread under cs_main/cs_wallet): extract the per-block Sapling
|
||||||
|
// commitments for [startHeight, tip] into memory. This is the only part touching chain/disk;
|
||||||
|
// profiling showed it is ~1% of rebuild time. ~9MB for a full-chain range.
|
||||||
|
const int tipHeight = pindex->GetHeight();
|
||||||
|
const int rangeLen = tipHeight - startHeight + 1;
|
||||||
|
std::vector<std::vector<uint256>> blockCms(rangeLen > 0 ? rangeLen : 0);
|
||||||
|
int64_t tRead = 0;
|
||||||
|
{
|
||||||
|
int64_t r0 = GetTimeMicros();
|
||||||
|
CBlockIndex* pbi = chainActive[startHeight];
|
||||||
|
while (pbi) {
|
||||||
if (ShutdownRequested()) {
|
if (ShutdownRequested()) {
|
||||||
LogPrintf("%s: shutdown requested, aborting building witnesses\n", __func__);
|
LogPrintf("%s: shutdown requested, aborting witness rebuild\n", __func__);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
if (pwalletMain->fAbortRescan) {
|
if (pwalletMain->fAbortRescan) {
|
||||||
LogPrintf("%s: rescan aborted at block %d, stopping witness building\n", pwalletMain->rescanHeight);
|
LogPrintf("%s: rescan aborted during witness rebuild\n", __func__);
|
||||||
pwalletMain->fRescanning = false;
|
pwalletMain->fRescanning = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int h = pbi->GetHeight();
|
||||||
if (pblockindex->GetHeight() % 100 == 0 && pblockindex->GetHeight() < height - 5) {
|
if (h % 5000 == 0 && h < tipHeight - 5)
|
||||||
LogPrintf("Building Witnesses for block %i %.4f complete, %d remaining\n", pblockindex->GetHeight(), pblockindex->GetHeight() / double(height), height - pblockindex->GetHeight() );
|
LogPrintf("Reading blocks for witness rebuild: %d / %d\n", h - startHeight, rangeLen);
|
||||||
}
|
|
||||||
|
|
||||||
SaplingMerkleTree saplingTree;
|
|
||||||
saplingRoot = pblockindex->pprev->hashFinalSaplingRoot;
|
|
||||||
pcoinsTip->GetSaplingAnchorAt(saplingRoot, saplingTree);
|
|
||||||
|
|
||||||
//Cycle through blocks and transactions building sapling tree until the commitment needed is reached
|
|
||||||
CBlock block;
|
CBlock block;
|
||||||
if (!ReadBlockFromDisk(block, pblockindex, 1)) {
|
if (!ReadBlockFromDisk(block, pbi, 1)) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(strprintf("Cannot read block height %d from disk", h));
|
||||||
strprintf("Cannot read block height %d (%s) from disk", pindex->GetHeight(), pindex->GetBlockHash().GetHex()));
|
}
|
||||||
|
std::vector<uint256>& cms = blockCms[h - startHeight];
|
||||||
|
for (const CTransaction& tx : block.vtx)
|
||||||
|
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); i++)
|
||||||
|
cms.push_back(tx.vShieldedOutput[i].cm);
|
||||||
|
if (pbi == pindex) break;
|
||||||
|
pbi = chainActive.Next(pbi);
|
||||||
|
}
|
||||||
|
tRead = GetTimeMicros() - r0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
// Phase 2 (parallel): each note's witness extension is independent, so partition the lagging
|
||||||
|
// notes across worker threads. Workers touch ONLY their own notes' witness lists plus the
|
||||||
|
// read-only commitment cache -- no locks, no chain access -- while the main thread keeps
|
||||||
|
// cs_main/cs_wallet held throughout. Round-robin assignment over the start-sorted `pending`
|
||||||
|
// spreads the long (low-start) notes across threads. Produces witnesses byte-identical to the
|
||||||
|
// serial path: every note is extended over exactly its [witnessHeight+1, tip] block range, in
|
||||||
|
// order, appending each block's commitments.
|
||||||
|
std::vector<SaplingNoteData*> work;
|
||||||
|
work.reserve(pending.size());
|
||||||
|
for (const PendingNote& p : pending) work.push_back(p.nd);
|
||||||
|
|
||||||
if (wtxItem.second.mapSaplingNoteData.empty())
|
// Only a substantial bulk rebuild (e.g. a one-time post-upgrade repair) is worth parallelizing
|
||||||
|
// and logging; routine 1-block tip extension runs serially to avoid per-block thread-spawn
|
||||||
|
// overhead and log spam.
|
||||||
|
const bool bulkRebuild = (rangeLen > 100);
|
||||||
|
|
||||||
|
int nPar = (int)GetArg("-witnessbuildthreads", 0);
|
||||||
|
if (nPar <= 0) nPar = (int)std::thread::hardware_concurrency();
|
||||||
|
if (nPar <= 0) nPar = 1;
|
||||||
|
if (nPar > (int)work.size()) nPar = (int)work.size();
|
||||||
|
if (nPar < 1) nPar = 1;
|
||||||
|
if (!bulkRebuild) nPar = 1;
|
||||||
|
|
||||||
|
std::atomic<bool> failed(false);
|
||||||
|
std::mutex failMtx;
|
||||||
|
std::string failMsg;
|
||||||
|
|
||||||
|
const int CACHE = (int)WITNESS_CACHE_SIZE;
|
||||||
|
// Tier 2b: advance one witness in place for the deep part (no per-block heap clone), then
|
||||||
|
// materialize only the final CACHE snapshots. -witnessfastrebuild=0 forces the reference
|
||||||
|
// clone-every-block path for A/B verification.
|
||||||
|
bool fastRebuild = GetBoolArg("-witnessfastrebuild", true);
|
||||||
|
|
||||||
|
auto worker = [&](int tid) {
|
||||||
|
try {
|
||||||
|
for (size_t k = (size_t)tid; k < work.size(); k += (size_t)nPar) {
|
||||||
|
SaplingNoteData* nd = work[k];
|
||||||
|
int startH = nd->witnessHeight;
|
||||||
|
int nBlocks = tipHeight - startH;
|
||||||
|
if (nBlocks <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (wtxItem.second.GetDepthInMainChain() > 0) {
|
if (!fastRebuild || nBlocks <= CACHE) {
|
||||||
|
// Reference / shallow path: clone every block (preserves pre-existing older snapshots).
|
||||||
//Sapling
|
for (int h = startH + 1; h <= tipHeight; h++) {
|
||||||
for (mapSaplingNoteData_t::value_type& item : wtxItem.second.mapSaplingNoteData) {
|
|
||||||
auto* nd = &(item.second);
|
|
||||||
if (nd->nullifier && nd->witnessHeight == pblockindex->GetHeight() - 1
|
|
||||||
&& GetSaplingSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
|
|
||||||
|
|
||||||
nd->witnesses.push_front(nd->witnesses.front());
|
nd->witnesses.push_front(nd->witnesses.front());
|
||||||
while (nd->witnesses.size() > WITNESS_CACHE_SIZE) {
|
while ((int)nd->witnesses.size() > CACHE)
|
||||||
nd->witnesses.pop_back();
|
nd->witnesses.pop_back();
|
||||||
|
const std::vector<uint256>& cms = blockCms[h - startHeight];
|
||||||
|
for (size_t c = 0; c < cms.size(); c++)
|
||||||
|
nd->witnesses.front().append(cms[c]);
|
||||||
|
nd->witnessHeight = h;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Deep fast path: advance ONE witness in place through [startH+1, tipHeight-CACHE]
|
||||||
|
// with no per-block clone, then build only the final CACHE snapshots. The reference
|
||||||
|
// loop pops all but the last CACHE snapshots, so the resulting deque is identical
|
||||||
|
// ([W@tip .. W@(tip-CACHE+1)]), but with ~CACHE heap allocations instead of ~nBlocks.
|
||||||
|
int deepEnd = tipHeight - CACHE;
|
||||||
|
{
|
||||||
|
SaplingWitness& w = nd->witnesses.front();
|
||||||
|
for (int h = startH + 1; h <= deepEnd; h++) {
|
||||||
|
const std::vector<uint256>& cms = blockCms[h - startHeight];
|
||||||
|
for (size_t c = 0; c < cms.size(); c++)
|
||||||
|
w.append(cms[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Drop pre-existing older snapshots; keep only the advanced front (W@deepEnd).
|
||||||
|
while (nd->witnesses.size() > 1)
|
||||||
|
nd->witnesses.pop_back();
|
||||||
|
// Materialize the last CACHE snapshots (heights deepEnd+1 .. tip).
|
||||||
|
for (int h = deepEnd + 1; h <= tipHeight; h++) {
|
||||||
|
nd->witnesses.push_front(nd->witnesses.front());
|
||||||
|
const std::vector<uint256>& cms = blockCms[h - startHeight];
|
||||||
|
for (size_t c = 0; c < cms.size(); c++)
|
||||||
|
nd->witnesses.front().append(cms[c]);
|
||||||
|
}
|
||||||
|
while ((int)nd->witnesses.size() > CACHE)
|
||||||
|
nd->witnesses.pop_back();
|
||||||
|
nd->witnessHeight = tipHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::lock_guard<std::mutex> lk(failMtx);
|
||||||
|
if (failMsg.empty()) failMsg = e.what();
|
||||||
|
failed = true;
|
||||||
|
} catch (...) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (const CTransaction& tx : block.vtx) {
|
int64_t e0 = GetTimeMicros();
|
||||||
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); i++) {
|
if (nPar <= 1) {
|
||||||
const uint256& note_commitment = tx.vShieldedOutput[i].cm;
|
worker(0);
|
||||||
nd->witnesses.front().append(note_commitment);
|
} else {
|
||||||
}
|
std::vector<std::thread> threads;
|
||||||
}
|
threads.reserve(nPar);
|
||||||
nd->witnessHeight = pblockindex->GetHeight();
|
for (int t = 0; t < nPar; t++) threads.emplace_back(worker, t);
|
||||||
}
|
for (std::thread& th : threads) th.join();
|
||||||
}
|
}
|
||||||
|
int64_t tExtend = GetTimeMicros() - e0;
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
throw std::runtime_error(std::string("Witness rebuild worker failed: ") + (failMsg.empty() ? "unknown" : failMsg));
|
||||||
|
|
||||||
|
// Latch the rebuilt notes as validated so they are not re-validated and reseeded on every
|
||||||
|
// subsequent block connect. Without this, any note whose witness cannot be reconstructed to
|
||||||
|
// the canonical anchor (e.g. legacy corruption) would be reseeded and fully replayed on every
|
||||||
|
// block forever. A note whose rebuilt root still disagrees with the canonical finalsaplingroot
|
||||||
|
// is unrecoverable here: it is left flagged (the GetSaplingNoteWitnesses majority-anchor guard
|
||||||
|
// skips it for spends) and reported, rather than spun on indefinitely. witnessRootValidated is
|
||||||
|
// in-memory only, so a fresh validation pass still runs on each restart and after any reorg
|
||||||
|
// (DecrementNoteWitnesses clears it), keeping the heal self-correcting.
|
||||||
|
if (!work.empty()) {
|
||||||
|
const uint256& canonicalRoot = pindex->hashFinalSaplingRoot;
|
||||||
|
int nUnrecoverable = 0;
|
||||||
|
for (SaplingNoteData* nd : work) {
|
||||||
|
if (nd->witnesses.empty() || nd->witnesses.front().root() != canonicalRoot)
|
||||||
|
nUnrecoverable++;
|
||||||
|
nd->witnessRootValidated = true;
|
||||||
}
|
}
|
||||||
|
if (bulkRebuild) {
|
||||||
|
LogPrintf("%s: rebuilt %u note witness cache(s) to height %d in %ldms using %d thread(s)%s\n",
|
||||||
|
__func__, (unsigned)work.size(), tipHeight, (long)((tRead + tExtend) / 1000), nPar,
|
||||||
|
nUnrecoverable
|
||||||
|
? strprintf(" [WARNING: %d note(s) could not be rebuilt to the canonical anchor and were skipped]", nUnrecoverable).c_str()
|
||||||
|
: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pblockindex == pindex)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pblockindex = chainActive.Next(pblockindex);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1591,9 +1829,12 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
|
|||||||
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
|
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
|
||||||
tmp.at(nd.first).witnesses.assign(
|
tmp.at(nd.first).witnesses.assign(
|
||||||
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
|
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
|
||||||
}
|
//PART B2: only carry over witnessHeight TOGETHER with the witnesses it describes.
|
||||||
|
//Copying it unconditionally (when witnesses are NOT copied) advances the height past
|
||||||
|
//the actual witness state for a whole tx's notes at once = the batch desync originator.
|
||||||
tmp.at(nd.first).witnessHeight = nd.second.witnessHeight;
|
tmp.at(nd.first).witnessHeight = nd.second.witnessHeight;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now copy over the updated note data
|
// Now copy over the updated note data
|
||||||
wtx.mapSaplingNoteData = tmp;
|
wtx.mapSaplingNoteData = tmp;
|
||||||
@@ -1831,37 +2072,54 @@ void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
|
|||||||
uint256 &final_anchor)
|
uint256 &final_anchor)
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
witnesses.clear();
|
||||||
witnesses.resize(notes.size());
|
witnesses.resize(notes.size());
|
||||||
boost::optional<uint256> rt;
|
|
||||||
int i = 0;
|
// Pass 1: collect each note's most-recent cached witness and tally roots. Use find() so we do
|
||||||
for (SaplingOutPoint note : notes) {
|
// NOT default-construct mapWallet / mapSaplingNoteData entries (the original indexed
|
||||||
//fprintf(stderr,"%s: i=%d\n", __func__,i);
|
// mapWallet[note.hash] BEFORE its own count() guard, silently inserting empty entries).
|
||||||
auto noteData = mapWallet[note.hash].mapSaplingNoteData;
|
std::vector<boost::optional<SaplingWitness>> cand(notes.size());
|
||||||
auto nWitnesses = noteData[note].witnesses.size();
|
std::map<uint256, int> rootVotes;
|
||||||
if (mapWallet.count(note.hash) && noteData.count(note) && nWitnesses > 0) {
|
for (size_t i = 0; i < notes.size(); i++) {
|
||||||
fprintf(stderr,"%s: Found %lu witnesses for note %s...\n", __func__, nWitnesses, note.hash.ToString().substr(0,8).c_str() );
|
const SaplingOutPoint& note = notes[i];
|
||||||
witnesses[i] = noteData[note].witnesses.front();
|
auto wi = mapWallet.find(note.hash);
|
||||||
if (!rt) {
|
if (wi == mapWallet.end())
|
||||||
//fprintf(stderr,"%s: Setting witness root\n",__func__);
|
continue;
|
||||||
rt = witnesses[i]->root();
|
const mapSaplingNoteData_t& noteData = wi->second.mapSaplingNoteData;
|
||||||
} else {
|
auto ni = noteData.find(note);
|
||||||
if(*rt == witnesses[i]->root()) {
|
if (ni == noteData.end() || ni->second.witnesses.empty())
|
||||||
} else {
|
continue;
|
||||||
// Something is fucky
|
cand[i] = ni->second.witnesses.front();
|
||||||
std::string err = string("CWallet::GetSaplingNoteWitnesses: Invalid witness root! rt=") + rt.get().ToString();
|
rootVotes[cand[i]->root()]++;
|
||||||
err += string("\n!= witness[i]->root()=") + witnesses[i]->root().ToString();
|
|
||||||
fprintf(stderr,"%s: IGNORING %s\n", __func__,err.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choose the anchor that the most witnesses agree on (robust even when the FIRST note is the
|
||||||
|
// desynced one - the original code blindly took the first note's root as the anchor).
|
||||||
|
boost::optional<uint256> anchor;
|
||||||
|
int bestVotes = 0;
|
||||||
|
for (const std::pair<uint256, int>& rv : rootVotes) {
|
||||||
|
if (rv.second > bestVotes) { bestVotes = rv.second; anchor = rv.first; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2: only emit witnesses whose root matches the common anchor. A desynced witness is left
|
||||||
|
// as boost::none rather than returned: handing the spend prover a witness whose root disagrees
|
||||||
|
// with the anchor guarantees a "Failed to build transaction". Note selection / callers skip
|
||||||
|
// notes that have no usable witness (see asyncrpcoperation_*: "Missing witness for Sapling note").
|
||||||
|
for (size_t i = 0; i < notes.size(); i++) {
|
||||||
|
if (cand[i] && anchor && cand[i]->root() == *anchor) {
|
||||||
|
witnesses[i] = cand[i];
|
||||||
|
} else {
|
||||||
|
if (cand[i])
|
||||||
|
LogPrintf("%s: note %s has a desynced witness (root=%s != anchor=%s); skipping it\n",
|
||||||
|
__func__, notes[i].hash.ToString().substr(0, 16).c_str(),
|
||||||
|
cand[i]->root().ToString().c_str(),
|
||||||
|
anchor ? anchor->ToString().c_str() : "none");
|
||||||
|
witnesses[i] = boost::none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
if (anchor)
|
||||||
// All returned witnesses have the same anchor
|
final_anchor = *anchor;
|
||||||
if (rt) {
|
|
||||||
final_anchor = *rt;
|
|
||||||
//fprintf(stderr,"%s: final_anchor=%s\n", __func__, rt.get().ToString().c_str() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isminetype CWallet::IsMine(const CTxIn &txin) const
|
isminetype CWallet::IsMine(const CTxIn &txin) const
|
||||||
@@ -2109,18 +2367,39 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
|
|||||||
|
|
||||||
bool CWallet::IsHDFullyEnabled() const
|
bool CWallet::IsHDFullyEnabled() const
|
||||||
{
|
{
|
||||||
// Only Sapling addresses are HD for now
|
// Both Sapling and transparent addresses are HD when transparent HD is on.
|
||||||
return false;
|
return IsHDTransparentEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::IsHDTransparentEnabled() const
|
||||||
|
{
|
||||||
|
// Transparent keys are HD-derived when the wallet has an HD seed and the
|
||||||
|
// feature is enabled (default on). Legacy wallets keep any pre-existing
|
||||||
|
// random t-keys; only newly generated keys become HD (and those old random
|
||||||
|
// keys are NOT seed-recoverable, so wallet.dat backups remain necessary).
|
||||||
|
return !hdChain.seedFp.IsNull() && GetBoolArg("-hdtransparent", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::GenerateNewSeed()
|
void CWallet::GenerateNewSeed()
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
|
|
||||||
|
|
||||||
int64_t nCreationTime = GetTime();
|
int64_t nCreationTime = GetTime();
|
||||||
|
|
||||||
|
// Opt-in: create the wallet from a fresh BIP39 mnemonic so its 24 words can
|
||||||
|
// be exported (z_exportmnemonic) and used in SilentDragonXLite.
|
||||||
|
if (GetBoolArg("-usemnemonic", false)) {
|
||||||
|
RawHDSeed entropy;
|
||||||
|
if (GenerateMnemonicEntropy(256, entropy)) {
|
||||||
|
HDSeed seed(entropy);
|
||||||
|
if (InstallHDSeed(seed, true, nCreationTime))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogPrintf("%s: -usemnemonic seed generation failed, falling back to a random seed\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH);
|
||||||
|
|
||||||
// If the wallet is encrypted and locked, this will fail.
|
// If the wallet is encrypted and locked, this will fail.
|
||||||
if (!SetHDSeed(seed))
|
if (!SetHDSeed(seed))
|
||||||
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
|
throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed");
|
||||||
@@ -2129,7 +2408,7 @@ void CWallet::GenerateNewSeed()
|
|||||||
// the child index counter in the database
|
// the child index counter in the database
|
||||||
// as a hdchain object
|
// as a hdchain object
|
||||||
CHDChain newHdChain;
|
CHDChain newHdChain;
|
||||||
newHdChain.nVersion = CHDChain::VERSION_HD_BASE;
|
newHdChain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
|
||||||
newHdChain.seedFp = seed.Fingerprint();
|
newHdChain.seedFp = seed.Fingerprint();
|
||||||
newHdChain.nCreateTime = nCreationTime;
|
newHdChain.nCreateTime = nCreationTime;
|
||||||
SetHDChain(newHdChain, false);
|
SetHDChain(newHdChain, false);
|
||||||
@@ -2193,6 +2472,122 @@ bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigne
|
|||||||
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
|
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CWallet::InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_wallet);
|
||||||
|
|
||||||
|
if (!SetHDSeed(seed))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CHDChain newHdChain;
|
||||||
|
newHdChain.nVersion = fMnemonic ? CHDChain::VERSION_HD_MNEMONIC
|
||||||
|
: CHDChain::VERSION_HD_TRANSPARENT;
|
||||||
|
newHdChain.seedFp = seed.Fingerprint();
|
||||||
|
newHdChain.nCreateTime = nCreateTime;
|
||||||
|
newHdChain.fMnemonicSeed = fMnemonic;
|
||||||
|
SetHDChain(newHdChain, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::SetHDSeedFromHex(const std::string& seedHex)
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
|
// Refuse to clobber an existing seed (the keystore refuses too); restore
|
||||||
|
// must run on a fresh/empty wallet.
|
||||||
|
if (HaveHDSeed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!IsHex(seedHex))
|
||||||
|
return false;
|
||||||
|
std::vector<unsigned char> raw = ParseHex(seedHex);
|
||||||
|
// 32 = legacy raw seed; 64 = BIP39-derived seed (as exported by a mnemonic
|
||||||
|
// wallet). Either is used directly for derivation (fMnemonicSeed = false).
|
||||||
|
if (raw.size() != 32 && raw.size() != 64)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
RawHDSeed rawSeed(raw.begin(), raw.end());
|
||||||
|
HDSeed seed(rawSeed);
|
||||||
|
|
||||||
|
return InstallHDSeed(seed, false, 1); // birthday = genesis for a restore
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::SetHDSeedFromMnemonic(const std::string& phrase)
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
|
if (HaveHDSeed())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
RawHDSeed entropy;
|
||||||
|
if (!MnemonicToEntropy(phrase, entropy))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Store the BIP39 entropy as the HDSeed (SilentDragonXLite's on-disk
|
||||||
|
// convention); the 64-byte seed is expanded from it on demand.
|
||||||
|
HDSeed seed(entropy);
|
||||||
|
return InstallHDSeed(seed, true, 1); // birthday = genesis for a restore
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const
|
||||||
|
{
|
||||||
|
HDSeed stored;
|
||||||
|
if (!GetHDSeed(stored))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hdChain.fMnemonicSeed) {
|
||||||
|
seedOut = stored; // legacy / hex seed: fed to derivation directly
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mnemonic wallet: the stored seed is the 32-byte BIP39 entropy. Expand it
|
||||||
|
// to the 64-byte BIP39 seed exactly as SilentDragonXLite does.
|
||||||
|
RawHDSeed seed64;
|
||||||
|
if (!Bip39SeedFromEntropy(stored.RawSeed(), seed64))
|
||||||
|
return false;
|
||||||
|
seedOut = HDSeed(seed64);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CWallet::GetMnemonicPhrase(std::string& phraseOut) const
|
||||||
|
{
|
||||||
|
if (!hdChain.fMnemonicSeed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HDSeed stored;
|
||||||
|
if (!GetHDSeed(stored)) // fails on an encrypted+locked wallet
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return EntropyToMnemonic(stored.RawSeed(), phraseOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWallet::TopUpHDTransparentKeys(unsigned int count, int64_t nBirthday)
|
||||||
|
{
|
||||||
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
|
if (!IsHDTransparentEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
CKey secret;
|
||||||
|
CKeyMetadata metadata(nBirthday);
|
||||||
|
DeriveNewChildKey(metadata, secret);
|
||||||
|
|
||||||
|
CPubKey pubkey = secret.GetPubKey();
|
||||||
|
assert(secret.VerifyPubKey(pubkey));
|
||||||
|
|
||||||
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
||||||
|
// Keep the birthday floor at nBirthday so the rescan is not clipped
|
||||||
|
// (derived keys are stamped nBirthday, not "now", precisely for this).
|
||||||
|
if (!nTimeFirstKey || nBirthday < nTimeFirstKey)
|
||||||
|
nTimeFirstKey = nBirthday;
|
||||||
|
|
||||||
|
if (!AddKeyPubKey(secret, pubkey))
|
||||||
|
throw std::runtime_error("CWallet::TopUpHDTransparentKeys(): AddKeyPubKey failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t ¬eData)
|
void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t ¬eData)
|
||||||
{
|
{
|
||||||
mapSaplingNoteData.clear();
|
mapSaplingNoteData.clear();
|
||||||
@@ -3416,7 +3811,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
|
|||||||
vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
|
vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
|
||||||
CAmount nTotalLower = 0;
|
CAmount nTotalLower = 0;
|
||||||
|
|
||||||
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
|
std::shuffle(vCoins.begin(), vCoins.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
|
||||||
|
|
||||||
BOOST_FOREACH(const COutput &output, vCoins)
|
BOOST_FOREACH(const COutput &output, vCoins)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -311,7 +311,9 @@ public:
|
|||||||
boost::optional<uint256> nullifier;
|
boost::optional<uint256> nullifier;
|
||||||
|
|
||||||
//In Memory Only
|
//In Memory Only
|
||||||
bool witnessRootValidated;
|
// Never serialized (see SerializationOp): must default false so a garbage value can't
|
||||||
|
// read true and short-circuit the witness self-heal in VerifyAndSetInitialWitness.
|
||||||
|
bool witnessRootValidated = false;
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
ADD_SERIALIZE_METHODS;
|
||||||
|
|
||||||
@@ -1061,6 +1063,9 @@ public:
|
|||||||
* Generate a new key
|
* Generate a new key
|
||||||
*/
|
*/
|
||||||
CPubKey GenerateNewKey();
|
CPubKey GenerateNewKey();
|
||||||
|
//! Derive a new transparent key from the HD seed along the BIP44 external
|
||||||
|
//! chain m/44'/coin_type'/0'/0/i, advancing hdChain.transparentChildCounter.
|
||||||
|
void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secretRet);
|
||||||
//! Adds a key to the store, and saves it to disk.
|
//! Adds a key to the store, and saves it to disk.
|
||||||
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
|
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
|
||||||
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
|
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
|
||||||
@@ -1292,6 +1297,10 @@ public:
|
|||||||
/* Returns true if HD is enabled for all address types, false if only for Sapling */
|
/* Returns true if HD is enabled for all address types, false if only for Sapling */
|
||||||
bool IsHDFullyEnabled() const;
|
bool IsHDFullyEnabled() const;
|
||||||
|
|
||||||
|
/* Returns true if transparent keys should be HD-derived from the seed.
|
||||||
|
Requires an HD seed and the -hdtransparent option (default on). */
|
||||||
|
bool IsHDTransparentEnabled() const;
|
||||||
|
|
||||||
/* Generates a new HD seed (will reset the chain child index counters)
|
/* Generates a new HD seed (will reset the chain child index counters)
|
||||||
Sets the seed's version based on the current wallet version (so the
|
Sets the seed's version based on the current wallet version (so the
|
||||||
caller must ensure the current wallet version is correct before calling
|
caller must ensure the current wallet version is correct before calling
|
||||||
@@ -1301,6 +1310,41 @@ public:
|
|||||||
bool SetHDSeed(const HDSeed& seed);
|
bool SetHDSeed(const HDSeed& seed);
|
||||||
bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
|
bool SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char> &vchCryptedSecret);
|
||||||
|
|
||||||
|
/* Restore a wallet's HD seed from a hex string (as exported in the
|
||||||
|
z_exportwallet "# HDSeed=" comment): 32 bytes for a legacy raw seed, or
|
||||||
|
64 bytes for a BIP39-derived seed. Only succeeds on a wallet that has no
|
||||||
|
seed yet. Sets the chain birthday to genesis so a rescan finds all
|
||||||
|
historical (coinbase) funds. Returns false on bad input or existing seed. */
|
||||||
|
bool SetHDSeedFromHex(const std::string& seedHex);
|
||||||
|
|
||||||
|
/* Restore/create a wallet from a BIP39 mnemonic phrase, byte-compatible with
|
||||||
|
SilentDragonXLite: stores the 32-byte entropy, marks the chain mnemonic,
|
||||||
|
and derives the 64-byte BIP39 seed on demand. Only succeeds on a wallet
|
||||||
|
with no seed yet. Returns false on an invalid phrase or existing seed. */
|
||||||
|
bool SetHDSeedFromMnemonic(const std::string& phrase);
|
||||||
|
|
||||||
|
/* Return the wallet's 24-word BIP39 recovery phrase, if this is a mnemonic
|
||||||
|
wallet and the seed is available (unlocked). Returns false otherwise. */
|
||||||
|
bool GetMnemonicPhrase(std::string& phraseOut) const;
|
||||||
|
|
||||||
|
/* True if the HD seed was derived from a BIP39 mnemonic (stored as entropy). */
|
||||||
|
bool IsMnemonicSeed() const { return hdChain.fMnemonicSeed; }
|
||||||
|
|
||||||
|
/* Return the seed to feed into HD derivation. For mnemonic wallets this
|
||||||
|
expands the stored 32-byte entropy into the 64-byte BIP39 seed; for legacy
|
||||||
|
wallets it is the stored seed unchanged. Use this everywhere keys/OVKs are
|
||||||
|
derived so behaviour matches SilentDragonXLite. */
|
||||||
|
bool GetHDSeedForDerivation(HDSeed& seedOut) const;
|
||||||
|
|
||||||
|
/* Shared tail of the seed-install paths: stores `seed` and a fresh CHDChain
|
||||||
|
(mnemonic vs raw) with the given birthday. Caller must hold cs_wallet. */
|
||||||
|
bool InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime);
|
||||||
|
|
||||||
|
/* Pre-derive `count` HD transparent keys (external chain) into the keystore,
|
||||||
|
stamped with creation time `nBirthday`, so a subsequent rescan can find
|
||||||
|
funds paid to them after a seed-only restore. */
|
||||||
|
void TopUpHDTransparentKeys(unsigned int count, int64_t nBirthday);
|
||||||
|
|
||||||
/* Set the HD chain model (chain child index counters) */
|
/* Set the HD chain model (chain child index counters) */
|
||||||
void SetHDChain(const CHDChain& chain, bool memonly);
|
void SetHDChain(const CHDChain& chain, bool memonly);
|
||||||
const CHDChain& GetHDChain() const { return hdChain; }
|
const CHDChain& GetHDChain() const { return hdChain; }
|
||||||
|
|||||||
@@ -62,11 +62,24 @@ class CHDChain
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const int VERSION_HD_BASE = 1;
|
static const int VERSION_HD_BASE = 1;
|
||||||
static const int CURRENT_VERSION = VERSION_HD_BASE;
|
// Version 2 adds the transparent (secp256k1/BIP44) external-chain counter.
|
||||||
|
static const int VERSION_HD_TRANSPARENT = 2;
|
||||||
|
// Version 3 marks a seed derived from a BIP39 mnemonic: the stored HDSeed is
|
||||||
|
// the 32-byte BIP39 entropy, expanded to the 64-byte seed for derivation
|
||||||
|
// (matches SilentDragonXLite's on-disk convention).
|
||||||
|
static const int VERSION_HD_MNEMONIC = 3;
|
||||||
|
static const int CURRENT_VERSION = VERSION_HD_MNEMONIC;
|
||||||
int nVersion;
|
int nVersion;
|
||||||
uint256 seedFp;
|
uint256 seedFp;
|
||||||
int64_t nCreateTime; // 0 means unknown
|
int64_t nCreateTime; // 0 means unknown
|
||||||
uint32_t saplingAccountCounter;
|
uint32_t saplingAccountCounter;
|
||||||
|
// Next index on the HD transparent external chain m/44'/coin'/0'/0/i.
|
||||||
|
// Only serialized/consulted when nVersion >= VERSION_HD_TRANSPARENT.
|
||||||
|
uint32_t transparentChildCounter;
|
||||||
|
// True when the stored HDSeed is BIP39 entropy that must be expanded to the
|
||||||
|
// 64-byte BIP39 seed before HD derivation. Only serialized when
|
||||||
|
// nVersion >= VERSION_HD_MNEMONIC (false for all pre-existing wallets).
|
||||||
|
bool fMnemonicSeed;
|
||||||
|
|
||||||
CHDChain() { SetNull(); }
|
CHDChain() { SetNull(); }
|
||||||
|
|
||||||
@@ -79,6 +92,14 @@ public:
|
|||||||
READWRITE(seedFp);
|
READWRITE(seedFp);
|
||||||
READWRITE(nCreateTime);
|
READWRITE(nCreateTime);
|
||||||
READWRITE(saplingAccountCounter);
|
READWRITE(saplingAccountCounter);
|
||||||
|
// Version-gated so pre-existing v1 wallet.dat records still deserialize
|
||||||
|
// (they simply leave the newer fields at their SetNull defaults).
|
||||||
|
if (this->nVersion >= VERSION_HD_TRANSPARENT) {
|
||||||
|
READWRITE(transparentChildCounter);
|
||||||
|
}
|
||||||
|
if (this->nVersion >= VERSION_HD_MNEMONIC) {
|
||||||
|
READWRITE(fMnemonicSeed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNull()
|
void SetNull()
|
||||||
@@ -87,6 +108,8 @@ public:
|
|||||||
seedFp.SetNull();
|
seedFp.SetNull();
|
||||||
nCreateTime = 0;
|
nCreateTime = 0;
|
||||||
saplingAccountCounter = 0;
|
saplingAccountCounter = 0;
|
||||||
|
transparentChildCounter = 0;
|
||||||
|
fMnemonicSeed = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
264
util/bootstrap-dragonx.bat
Normal file
264
util/bootstrap-dragonx.bat
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
@echo off
|
||||||
|
REM Copyright 2024 The Hush Developers
|
||||||
|
REM Copyright 2024 The DragonX Developers
|
||||||
|
REM Released under the GPLv3
|
||||||
|
REM
|
||||||
|
REM Download and apply a DRAGONX blockchain bootstrap on Windows.
|
||||||
|
REM Safely preserves wallet.dat and configuration files.
|
||||||
|
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
set "BOOTSTRAP_BASE_URL=https://bootstrap.dragonx.is"
|
||||||
|
set "BOOTSTRAP_FALLBACK_URL=https://bootstrap2.dragonx.is"
|
||||||
|
set "BOOTSTRAP_FILE=DRAGONX.zip"
|
||||||
|
set "CHAIN_NAME=DRAGONX"
|
||||||
|
|
||||||
|
REM Data directory on Windows
|
||||||
|
set "DATADIR=%APPDATA%\Hush\%CHAIN_NAME%"
|
||||||
|
|
||||||
|
REM Find dragonx-cli relative to this script
|
||||||
|
set "CLI="
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
if exist "%SCRIPT_DIR%dragonx-cli.exe" (
|
||||||
|
set "CLI=%SCRIPT_DIR%dragonx-cli.exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo ============================================
|
||||||
|
echo DragonX Bootstrap Installer
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
echo [INFO] Data directory: %DATADIR%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Step 1: Stop daemon if running
|
||||||
|
call :stop_daemon
|
||||||
|
if errorlevel 1 goto :error_exit
|
||||||
|
|
||||||
|
REM Step 2: Clean old chain data
|
||||||
|
call :clean_chain_data
|
||||||
|
if errorlevel 1 goto :error_exit
|
||||||
|
|
||||||
|
REM Step 3: Download bootstrap
|
||||||
|
call :download_bootstrap
|
||||||
|
if errorlevel 1 goto :error_exit
|
||||||
|
|
||||||
|
REM Step 4: Extract bootstrap
|
||||||
|
call :extract_bootstrap
|
||||||
|
if errorlevel 1 goto :error_exit
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [INFO] Bootstrap installation complete!
|
||||||
|
echo [INFO] You can now start DragonX with: dragonxd.exe
|
||||||
|
echo.
|
||||||
|
goto :EOF
|
||||||
|
|
||||||
|
REM ============================================
|
||||||
|
REM Stop daemon if running
|
||||||
|
REM ============================================
|
||||||
|
:stop_daemon
|
||||||
|
if "%CLI%"=="" (
|
||||||
|
echo [WARN] dragonx-cli.exe not found next to this script.
|
||||||
|
echo [WARN] Please make sure the DragonX daemon is stopped before continuing.
|
||||||
|
set /p "ANSWER=Is the DragonX daemon stopped? (y/N): "
|
||||||
|
if /i not "!ANSWER!"=="y" (
|
||||||
|
echo [ERROR] Please stop the daemon first and run this script again.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
"%CLI%" getinfo >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [INFO] Daemon is not running.
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [INFO] Stopping DragonX daemon...
|
||||||
|
"%CLI%" stop >nul 2>&1
|
||||||
|
|
||||||
|
set "TRIES=0"
|
||||||
|
:wait_loop
|
||||||
|
"%CLI%" getinfo >nul 2>&1
|
||||||
|
if errorlevel 1 goto :daemon_stopped
|
||||||
|
timeout /t 2 /nobreak >nul
|
||||||
|
set /a TRIES+=1
|
||||||
|
if %TRIES% geq 60 (
|
||||||
|
echo [ERROR] Daemon did not stop after 120 seconds. Please stop it manually and retry.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
goto :wait_loop
|
||||||
|
|
||||||
|
:daemon_stopped
|
||||||
|
echo [INFO] Daemon stopped.
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
REM ============================================
|
||||||
|
REM Clean blockchain data, preserving wallet and config
|
||||||
|
REM ============================================
|
||||||
|
:clean_chain_data
|
||||||
|
if not exist "%DATADIR%" (
|
||||||
|
echo [INFO] Data directory does not exist yet, creating it.
|
||||||
|
mkdir "%DATADIR%"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [INFO] Cleaning blockchain data from %DATADIR% ...
|
||||||
|
|
||||||
|
REM Preserve wallet.dat and config
|
||||||
|
set "TMPDIR=%TEMP%\dragonx-bootstrap-%RANDOM%"
|
||||||
|
mkdir "%TMPDIR%" 2>nul
|
||||||
|
|
||||||
|
for %%F in (wallet.dat DRAGONX.conf peers.dat) do (
|
||||||
|
if exist "%DATADIR%\%%F" (
|
||||||
|
copy /y "%DATADIR%\%%F" "%TMPDIR%\%%F" >nul
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Remove blockchain directories and files
|
||||||
|
for %%D in (blocks chainstate notarizations komodo) do (
|
||||||
|
if exist "%DATADIR%\%%D" (
|
||||||
|
rmdir /s /q "%DATADIR%\%%D" 2>nul
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for %%F in (db.log debug.log fee_estimates.dat banlist.dat) do (
|
||||||
|
if exist "%DATADIR%\%%F" (
|
||||||
|
del /f /q "%DATADIR%\%%F" 2>nul
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Restore preserved files
|
||||||
|
for %%F in (wallet.dat DRAGONX.conf peers.dat) do (
|
||||||
|
if exist "%TMPDIR%\%%F" (
|
||||||
|
copy /y "%TMPDIR%\%%F" "%DATADIR%\%%F" >nul
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rmdir /s /q "%TMPDIR%" 2>nul
|
||||||
|
|
||||||
|
echo [INFO] Blockchain data cleaned.
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
REM ============================================
|
||||||
|
REM Download bootstrap (with fallback)
|
||||||
|
REM ============================================
|
||||||
|
:download_bootstrap
|
||||||
|
echo [INFO] Downloading bootstrap from %BOOTSTRAP_BASE_URL% ...
|
||||||
|
echo [INFO] This may take a while depending on your connection speed.
|
||||||
|
|
||||||
|
REM Try primary URL
|
||||||
|
call :do_download "%BOOTSTRAP_BASE_URL%"
|
||||||
|
if not errorlevel 1 goto :download_verify
|
||||||
|
|
||||||
|
echo [WARN] Primary download failed, trying fallback %BOOTSTRAP_FALLBACK_URL% ...
|
||||||
|
call :do_download "%BOOTSTRAP_FALLBACK_URL%"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Download failed from both primary and fallback servers.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:download_verify
|
||||||
|
echo [INFO] Bootstrap download complete.
|
||||||
|
|
||||||
|
REM Verify SHA256 checksum
|
||||||
|
echo [INFO] Verifying checksum...
|
||||||
|
pushd "%DATADIR%"
|
||||||
|
|
||||||
|
REM Read expected hash from the .sha256 file (format: "hash filename" or "hash *filename")
|
||||||
|
set "EXPECTED_HASH="
|
||||||
|
for /f "tokens=1" %%A in (%BOOTSTRAP_FILE%.sha256) do (
|
||||||
|
set "EXPECTED_HASH=%%A"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%EXPECTED_HASH%"=="" (
|
||||||
|
echo [WARN] Could not read expected checksum, skipping verification.
|
||||||
|
goto :verify_done
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Use certutil to compute SHA256
|
||||||
|
certutil -hashfile "%BOOTSTRAP_FILE%" SHA256 > "%TEMP%\dragonx_hash.tmp" 2>nul
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [WARN] certutil not available, skipping checksum verification.
|
||||||
|
goto :verify_done
|
||||||
|
)
|
||||||
|
|
||||||
|
REM certutil outputs hash on the second line
|
||||||
|
set "ACTUAL_HASH="
|
||||||
|
set "LINE_NUM=0"
|
||||||
|
for /f "skip=1 tokens=*" %%H in (%TEMP%\dragonx_hash.tmp) do (
|
||||||
|
if not defined ACTUAL_HASH (
|
||||||
|
set "ACTUAL_HASH=%%H"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
REM Remove spaces from certutil output
|
||||||
|
set "ACTUAL_HASH=!ACTUAL_HASH: =!"
|
||||||
|
del /f /q "%TEMP%\dragonx_hash.tmp" 2>nul
|
||||||
|
|
||||||
|
if /i "!ACTUAL_HASH!"=="!EXPECTED_HASH!" (
|
||||||
|
echo [INFO] SHA256 checksum verified.
|
||||||
|
) else (
|
||||||
|
echo [ERROR] SHA256 checksum verification failed! The download may be corrupted.
|
||||||
|
echo [ERROR] Expected: !EXPECTED_HASH!
|
||||||
|
echo [ERROR] Got: !ACTUAL_HASH!
|
||||||
|
popd
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:verify_done
|
||||||
|
REM Clean up checksum files
|
||||||
|
del /f /q "%BOOTSTRAP_FILE%.md5" 2>nul
|
||||||
|
del /f /q "%BOOTSTRAP_FILE%.sha256" 2>nul
|
||||||
|
popd
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
REM ============================================
|
||||||
|
REM Download files from a given base URL
|
||||||
|
REM Usage: call :do_download "base_url"
|
||||||
|
REM ============================================
|
||||||
|
:do_download
|
||||||
|
set "BASE=%~1"
|
||||||
|
|
||||||
|
REM Use PowerShell to download (available on all modern Windows)
|
||||||
|
echo [INFO] Downloading %BASE%/%BOOTSTRAP_FILE% ...
|
||||||
|
powershell -NoProfile -Command ^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; try { (New-Object Net.WebClient).DownloadFile('%BASE%/%BOOTSTRAP_FILE%', '%DATADIR%\%BOOTSTRAP_FILE%') } catch { exit 1 }"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
echo [INFO] Downloading checksums...
|
||||||
|
powershell -NoProfile -Command ^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; try { (New-Object Net.WebClient).DownloadFile('%BASE%/%BOOTSTRAP_FILE%.md5', '%DATADIR%\%BOOTSTRAP_FILE%.md5') } catch { exit 1 }"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
powershell -NoProfile -Command ^
|
||||||
|
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; try { (New-Object Net.WebClient).DownloadFile('%BASE%/%BOOTSTRAP_FILE%.sha256', '%DATADIR%\%BOOTSTRAP_FILE%.sha256') } catch { exit 1 }"
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
REM ============================================
|
||||||
|
REM Extract bootstrap zip
|
||||||
|
REM ============================================
|
||||||
|
:extract_bootstrap
|
||||||
|
echo [INFO] Extracting bootstrap...
|
||||||
|
pushd "%DATADIR%"
|
||||||
|
|
||||||
|
REM Use PowerShell to extract zip, excluding wallet.dat and .conf files
|
||||||
|
powershell -NoProfile -Command ^
|
||||||
|
"Add-Type -AssemblyName System.IO.Compression.FileSystem; $zip = [System.IO.Compression.ZipFile]::OpenRead('%DATADIR%\%BOOTSTRAP_FILE%'); foreach ($entry in $zip.Entries) { if ($entry.Name -eq 'wallet.dat' -or $entry.Name -like '*.conf') { continue } $dest = Join-Path '%DATADIR%' $entry.FullName; if ($entry.FullName.EndsWith('/')) { New-Item -ItemType Directory -Force -Path $dest | Out-Null } else { $parent = Split-Path $dest -Parent; if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Force -Path $parent | Out-Null } [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $dest, $true) } }; $zip.Dispose()"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [ERROR] Extraction failed.
|
||||||
|
popd
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [INFO] Bootstrap extracted successfully.
|
||||||
|
|
||||||
|
REM Clean up archive
|
||||||
|
del /f /q "%BOOTSTRAP_FILE%" 2>nul
|
||||||
|
echo [INFO] Removed downloaded archive to save disk space.
|
||||||
|
|
||||||
|
popd
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:error_exit
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Bootstrap installation failed.
|
||||||
|
exit /b 1
|
||||||
@@ -9,9 +9,24 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BOOTSTRAP_BASE_URL="https://bootstrap.dragonx.is"
|
BOOTSTRAP_BASE_URL="https://bootstrap.dragonx.is"
|
||||||
|
BOOTSTRAP_FALLBACK_URL="https://bootstrap2.dragonx.is"
|
||||||
BOOTSTRAP_FILE="DRAGONX.zip"
|
BOOTSTRAP_FILE="DRAGONX.zip"
|
||||||
CHAIN_NAME="DRAGONX"
|
CHAIN_NAME="DRAGONX"
|
||||||
|
|
||||||
|
# DragonX bootstrap signing public key (PEM, openssl-compatible).
|
||||||
|
# WHY: the .md5/.sha256 files are served from the same host as the archive, so they
|
||||||
|
# only detect transmission corruption — a compromised bootstrap server could publish a
|
||||||
|
# malicious archive with matching checksums. A detached signature verified against THIS
|
||||||
|
# embedded public key (shipped in the repo, not downloaded) closes that gap: a bad server
|
||||||
|
# cannot forge a signature without the maintainer's offline private key.
|
||||||
|
#
|
||||||
|
# ROLLOUT: until the maintainer embeds a real key here and publishes DRAGONX.zip.sig,
|
||||||
|
# this stays as the placeholder and signature enforcement is skipped (with a loud warning),
|
||||||
|
# so existing users are unaffected. Once a real key is pasted in, an unsigned/invalid
|
||||||
|
# bootstrap is refused (fail-closed). See util/sign-bootstrap.md for the signing procedure.
|
||||||
|
BOOTSTRAP_PUBKEY_PLACEHOLDER="REPLACE_WITH_DRAGONX_BOOTSTRAP_PUBLIC_KEY_PEM"
|
||||||
|
BOOTSTRAP_PUBKEY="$BOOTSTRAP_PUBKEY_PLACEHOLDER"
|
||||||
|
|
||||||
# Determine data directory
|
# Determine data directory
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
DATADIR="$HOME/Library/Application Support/Hush/$CHAIN_NAME"
|
DATADIR="$HOME/Library/Application Support/Hush/$CHAIN_NAME"
|
||||||
@@ -118,35 +133,86 @@ clean_chain_data() {
|
|||||||
info "Blockchain data cleaned."
|
info "Blockchain data cleaned."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download a file via wget or curl
|
# Download a file via wget or curl (returns non-zero on failure)
|
||||||
download_file() {
|
download_file() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local outfile="$2"
|
local outfile="$2"
|
||||||
|
|
||||||
if command -v wget &>/dev/null; then
|
if command -v wget &>/dev/null; then
|
||||||
wget --progress=bar:force -O "$outfile" "$url" || error "Download failed: $url"
|
wget --progress=bar:force -O "$outfile" "$url"
|
||||||
elif command -v curl &>/dev/null; then
|
elif command -v curl &>/dev/null; then
|
||||||
curl -L --progress-bar -o "$outfile" "$url" || error "Download failed: $url"
|
curl -L --progress-bar -o "$outfile" "$url"
|
||||||
else
|
else
|
||||||
error "Neither wget nor curl found. Please install one and retry."
|
error "Neither wget nor curl found. Please install one and retry."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Try downloading from a given base URL; returns non-zero on failure
|
||||||
|
download_from() {
|
||||||
|
local base_url="$1"
|
||||||
|
local outfile="$DATADIR/$BOOTSTRAP_FILE"
|
||||||
|
local md5file="$DATADIR/${BOOTSTRAP_FILE}.md5"
|
||||||
|
local sha256file="$DATADIR/${BOOTSTRAP_FILE}.sha256"
|
||||||
|
local sigfile="$DATADIR/${BOOTSTRAP_FILE}.sig"
|
||||||
|
|
||||||
|
info "Downloading bootstrap from $base_url ..."
|
||||||
|
info "This may take a while depending on your connection speed."
|
||||||
|
|
||||||
|
download_file "$base_url/$BOOTSTRAP_FILE" "$outfile" || return 1
|
||||||
|
info "Bootstrap download complete."
|
||||||
|
|
||||||
|
info "Downloading checksums..."
|
||||||
|
download_file "$base_url/${BOOTSTRAP_FILE}.md5" "$md5file" || return 1
|
||||||
|
download_file "$base_url/${BOOTSTRAP_FILE}.sha256" "$sha256file" || return 1
|
||||||
|
# Detached signature is optional during rollout (non-fatal if absent); enforcement
|
||||||
|
# is decided in verify_signature() based on whether a real public key is embedded.
|
||||||
|
rm -f "$sigfile"
|
||||||
|
download_file "$base_url/${BOOTSTRAP_FILE}.sig" "$sigfile" || warn "No signature file at $base_url (${BOOTSTRAP_FILE}.sig)"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify the detached signature of the archive against the embedded release public key.
|
||||||
|
# Fail-closed once a real key is configured; skip (with warning) while the placeholder is in place.
|
||||||
|
verify_signature() {
|
||||||
|
local archive="$1"
|
||||||
|
local sigfile="$2"
|
||||||
|
|
||||||
|
if [[ "$BOOTSTRAP_PUBKEY" == "$BOOTSTRAP_PUBKEY_PLACEHOLDER" ]]; then
|
||||||
|
warn "Bootstrap signature verification is not yet configured (no maintainer key embedded)."
|
||||||
|
warn "Relying on TLS + checksum integrity only. See util/sign-bootstrap.md."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v openssl &>/dev/null; then
|
||||||
|
error "openssl is required to verify the bootstrap signature but was not found. Install openssl and retry."
|
||||||
|
fi
|
||||||
|
if [[ ! -s "$sigfile" ]]; then
|
||||||
|
error "Bootstrap signature (${BOOTSTRAP_FILE}.sig) is missing; refusing to use an unsigned bootstrap."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pubfile
|
||||||
|
pubfile=$(mktemp)
|
||||||
|
printf '%s\n' "$BOOTSTRAP_PUBKEY" > "$pubfile"
|
||||||
|
if openssl dgst -sha256 -verify "$pubfile" -signature "$sigfile" "$archive" >&2; then
|
||||||
|
rm -f "$pubfile"
|
||||||
|
info "Bootstrap signature verified against embedded DragonX release key."
|
||||||
|
else
|
||||||
|
rm -f "$pubfile"
|
||||||
|
error "Bootstrap signature verification FAILED — the archive is NOT signed by the DragonX release key. Aborting; do not use this bootstrap."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Download the bootstrap and verify checksums
|
# Download the bootstrap and verify checksums
|
||||||
download_bootstrap() {
|
download_bootstrap() {
|
||||||
local outfile="$DATADIR/$BOOTSTRAP_FILE"
|
local outfile="$DATADIR/$BOOTSTRAP_FILE"
|
||||||
local md5file="$DATADIR/${BOOTSTRAP_FILE}.md5"
|
local md5file="$DATADIR/${BOOTSTRAP_FILE}.md5"
|
||||||
local sha256file="$DATADIR/${BOOTSTRAP_FILE}.sha256"
|
local sha256file="$DATADIR/${BOOTSTRAP_FILE}.sha256"
|
||||||
|
local sigfile="$DATADIR/${BOOTSTRAP_FILE}.sig"
|
||||||
|
|
||||||
info "Downloading bootstrap from $BOOTSTRAP_BASE_URL ..."
|
if ! download_from "$BOOTSTRAP_BASE_URL"; then
|
||||||
info "This may take a while depending on your connection speed."
|
warn "Primary download failed, trying fallback $BOOTSTRAP_FALLBACK_URL ..."
|
||||||
|
download_from "$BOOTSTRAP_FALLBACK_URL" || error "Download failed from both primary and fallback servers."
|
||||||
download_file "$BOOTSTRAP_BASE_URL/$BOOTSTRAP_FILE" "$outfile"
|
fi
|
||||||
info "Bootstrap download complete."
|
|
||||||
|
|
||||||
info "Downloading checksums..."
|
|
||||||
download_file "$BOOTSTRAP_BASE_URL/${BOOTSTRAP_FILE}.md5" "$md5file"
|
|
||||||
download_file "$BOOTSTRAP_BASE_URL/${BOOTSTRAP_FILE}.sha256" "$sha256file"
|
|
||||||
|
|
||||||
# Verify checksums
|
# Verify checksums
|
||||||
info "Verifying checksums..."
|
info "Verifying checksums..."
|
||||||
@@ -172,8 +238,11 @@ download_bootstrap() {
|
|||||||
warn "sha256sum not found, skipping SHA256 verification."
|
warn "sha256sum not found, skipping SHA256 verification."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up checksum files
|
# Verify the cryptographic signature (fail-closed once a release key is embedded).
|
||||||
rm -f "$md5file" "$sha256file"
|
verify_signature "$outfile" "$sigfile"
|
||||||
|
|
||||||
|
# Clean up checksum + signature files
|
||||||
|
rm -f "$md5file" "$sha256file" "$sigfile"
|
||||||
|
|
||||||
echo "$outfile"
|
echo "$outfile"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
# Distributed under the GPLv3 software license, see the accompanying
|
# Distributed under the GPLv3 software license, see the accompanying
|
||||||
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
export CC=gcc-8
|
export CC=gcc-15
|
||||||
export CXX=g++-8
|
export CXX=g++-15
|
||||||
export LIBTOOL=libtool
|
export LIBTOOL=libtool
|
||||||
export AR=ar
|
export AR=ar
|
||||||
export RANLIB=ranlib
|
export RANLIB=ranlib
|
||||||
@@ -34,16 +34,24 @@ fi
|
|||||||
# If --enable-lcov is the first argument, enable lcov coverage support:
|
# If --enable-lcov is the first argument, enable lcov coverage support:
|
||||||
LCOV_ARG=''
|
LCOV_ARG=''
|
||||||
HARDENING_ARG='--disable-hardening'
|
HARDENING_ARG='--disable-hardening'
|
||||||
|
TEST_ARG=''
|
||||||
if [ "x${1:-}" = 'x--enable-lcov' ]
|
if [ "x${1:-}" = 'x--enable-lcov' ]
|
||||||
then
|
then
|
||||||
LCOV_ARG='--enable-lcov'
|
LCOV_ARG='--enable-lcov'
|
||||||
HARDENING_ARG='--disable-hardening'
|
HARDENING_ARG='--disable-hardening'
|
||||||
shift
|
shift
|
||||||
|
elif [ "x${1:-}" = 'x--disable-tests' ]
|
||||||
|
then
|
||||||
|
TEST_ARG='--enable-tests=no'
|
||||||
|
shift
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TRIPLET=`./depends/config.guess`
|
TRIPLET=`./depends/config.guess`
|
||||||
PREFIX="$(pwd)/depends/$TRIPLET"
|
PREFIX="$(pwd)/depends/$TRIPLET"
|
||||||
|
|
||||||
|
# Ensure system Rust is in PATH for modern macOS compatibility
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
|
||||||
make "$@" -C ./depends/ V=1 NO_QT=1
|
make "$@" -C ./depends/ V=1 NO_QT=1
|
||||||
|
|
||||||
#BUILD CCLIB
|
#BUILD CCLIB
|
||||||
@@ -68,7 +76,7 @@ cd $WD
|
|||||||
|
|
||||||
./autogen.sh
|
./autogen.sh
|
||||||
CPPFLAGS="-I$PREFIX/include -arch x86_64" LDFLAGS="-L$PREFIX/lib -arch x86_64 -Wl,-no_pie" \
|
CPPFLAGS="-I$PREFIX/include -arch x86_64" LDFLAGS="-L$PREFIX/lib -arch x86_64 -Wl,-no_pie" \
|
||||||
CXXFLAGS='-arch x86_64 -I/usr/local/Cellar/gcc\@8/8.3.0/include/c++/8.3.0/ -I$PREFIX/include -fwrapv -fno-strict-aliasing -Wno-builtin-declaration-mismatch -Werror -g -Wl,-undefined -Wl,dynamic_lookup' \
|
CXXFLAGS='-arch x86_64 -I/usr/local/Cellar/gcc/15.2.0_1/include/c++/15/ -I$PREFIX/include -fwrapv -fno-strict-aliasing -Wno-builtin-declaration-mismatch -Werror -Wno-error=deprecated-declarations -g -Wl,-undefined -Wl,dynamic_lookup' \
|
||||||
./configure --prefix="${PREFIX}" --with-gui=no "$HARDENING_ARG" "$LCOV_ARG"
|
./configure --prefix="${PREFIX}" --with-gui=no "$HARDENING_ARG" "$LCOV_ARG" $TEST_ARG
|
||||||
|
|
||||||
make "$@" V=1 NO_GTEST=1 STATIC=1
|
make "$@" V=1 NO_GTEST=1 STATIC=1
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Just show the useful info
|
# Just show the useful info
|
||||||
eval "$MAKE" --version | head -n2
|
eval "$MAKE" --version | head -n2 || true
|
||||||
as --version | head -n1
|
as --version | head -n1 || true
|
||||||
as --version | tail -n1
|
as --version | tail -n1
|
||||||
ld -v
|
ld -v
|
||||||
autoconf --version
|
autoconf --version
|
||||||
|
|||||||
56
util/sign-bootstrap.md
Normal file
56
util/sign-bootstrap.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Signing the DragonX bootstrap archive
|
||||||
|
|
||||||
|
`util/bootstrap-dragonx.sh` verifies a detached signature of `DRAGONX.zip` against a
|
||||||
|
public key **embedded in the script** (`BOOTSTRAP_PUBKEY`). Because the key ships in the
|
||||||
|
repo/binary and is not downloaded from the bootstrap server, a compromised bootstrap host
|
||||||
|
cannot forge a valid signature — unlike the `.md5`/`.sha256` files, which are served from
|
||||||
|
the same host and only detect corruption.
|
||||||
|
|
||||||
|
Until a real key is embedded, `BOOTSTRAP_PUBKEY` is the placeholder and the script skips
|
||||||
|
signature enforcement (with a warning), so existing users are unaffected. Once a real key
|
||||||
|
is pasted in, an unsigned or invalid bootstrap is **refused**.
|
||||||
|
|
||||||
|
## One-time: create the signing keypair (offline)
|
||||||
|
|
||||||
|
Keep the private key OFFLINE (air-gapped if possible). Ed25519 or RSA-4096 both work with
|
||||||
|
the `openssl dgst -sha256 -verify` check the script uses; RSA-4096 maximizes compatibility:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Private key — keep secret, never publish
|
||||||
|
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out dragonx-bootstrap.key
|
||||||
|
# Public key — paste into bootstrap-dragonx.sh
|
||||||
|
openssl pkey -in dragonx-bootstrap.key -pubout -out dragonx-bootstrap.pub
|
||||||
|
cat dragonx-bootstrap.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste the full PEM (including the `-----BEGIN/END PUBLIC KEY-----` lines) into
|
||||||
|
`BOOTSTRAP_PUBKEY` in `util/bootstrap-dragonx.sh`, e.g.:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BOOTSTRAP_PUBKEY="$(cat <<'PEM'
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
... base64 ...
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
PEM
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Each release: sign the archive and publish the signature
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl dgst -sha256 -sign dragonx-bootstrap.key -out DRAGONX.zip.sig DRAGONX.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Upload `DRAGONX.zip.sig` next to `DRAGONX.zip` (and its `.md5`/`.sha256`) on every
|
||||||
|
bootstrap host (`bootstrap.dragonx.is`, `bootstrap2.dragonx.is`). Verify locally first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl dgst -sha256 -verify dragonx-bootstrap.pub -signature DRAGONX.zip.sig DRAGONX.zip
|
||||||
|
# -> "Verified OK"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rotating the key
|
||||||
|
|
||||||
|
Embed the new public key in the script, sign future archives with the new private key, and
|
||||||
|
release a new client version. Old clients keep trusting the old key; coordinate the cutover
|
||||||
|
with a release so users upgrade before the old key is retired.
|
||||||
Reference in New Issue
Block a user