Compare commits
9 Commits
a9b1b4085f
...
61513966c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61513966c2 | ||
|
|
f15ac8ca8f | ||
|
|
71a8a627bd | ||
|
|
2781ea64e0 | ||
|
|
25c62c52f5 | ||
|
|
c5a9aab2f9 | ||
|
|
4a69d23e05 | ||
|
|
d6d495c447 | ||
|
|
0867d8cf62 |
@@ -1,27 +0,0 @@
|
||||
.git
|
||||
release
|
||||
depends/built
|
||||
depends/work
|
||||
depends/x86_64-unknown-linux-gnu
|
||||
depends/x86_64-w64-mingw32
|
||||
src/RandomX/build
|
||||
src/*.o
|
||||
src/*.a
|
||||
src/*.la
|
||||
src/*.lo
|
||||
src/.libs
|
||||
src/.deps
|
||||
src/univalue/.libs
|
||||
src/univalue/.deps
|
||||
src/cc/*.o
|
||||
src/cc/*.a
|
||||
src/dragonxd
|
||||
src/dragonx-cli
|
||||
src/dragonx-tx
|
||||
src/dragonxd.exe
|
||||
src/dragonx-cli.exe
|
||||
src/dragonx-tx.exe
|
||||
sapling-output.params
|
||||
sapling-spend.params
|
||||
config.status
|
||||
config.log
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -167,12 +167,3 @@ REGTEST_7776
|
||||
src/cc/librogue.so
|
||||
src/cc/games/prices
|
||||
src/cc/games/tetris
|
||||
release-linux/
|
||||
release/
|
||||
src/dragonxd
|
||||
src/dragonx-cli
|
||||
src/dragonx-tx
|
||||
src/dragonxd.exe
|
||||
src/dragonx-cli.exe
|
||||
src/dragonx-tx.exe
|
||||
doc/relnotes/
|
||||
@@ -1,31 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool \
|
||||
ncurses-dev unzip python3 zlib1g-dev wget bsdmainutils automake cmake \
|
||||
libcurl4-openssl-dev curl git binutils \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
COPY . /build/
|
||||
|
||||
# Clean host-built depends and src artifacts to force full rebuild inside container
|
||||
RUN rm -rf /build/depends/built /build/depends/work \
|
||||
/build/depends/x86_64-unknown-linux-gnu \
|
||||
/build/depends/x86_64-w64-mingw32 \
|
||||
/build/src/RandomX/build \
|
||||
&& find /build/src -name '*.o' -o -name '*.a' -o -name '*.la' -o -name '*.lo' \
|
||||
-o -name '*.lai' | xargs rm -f \
|
||||
&& rm -rf /build/src/univalue/.libs /build/src/univalue/.deps \
|
||||
&& rm -rf /build/src/.libs /build/src/.deps \
|
||||
&& rm -rf /build/src/cc/*.o /build/src/cc/*.a \
|
||||
&& rm -f /build/config.status /build/config.log
|
||||
|
||||
RUN cd /build && ./util/build.sh --disable-tests -j$(nproc)
|
||||
|
||||
# Strip binaries inside the container so extracted files are already small
|
||||
RUN strip /build/src/dragonxd /build/src/dragonx-cli /build/src/dragonx-tx
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
40
README.md
40
README.md
@@ -27,21 +27,6 @@ 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
|
||||
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
|
||||
|
||||
In working on this release, Duke Leto was suspended from Github, which gave Hush developers
|
||||
@@ -121,32 +106,19 @@ apt-get install -y gcc-7 g++-7 && \
|
||||
|
||||
# Build on Mac
|
||||
|
||||
Install Xcode Command Line Tools and [Homebrew](https://brew.sh/), then install dependencies:
|
||||
|
||||
```sh
|
||||
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"
|
||||
```
|
||||
sudo port update
|
||||
sudo port upgrade outdated
|
||||
sudo port install qt5
|
||||
|
||||
# clone git repo
|
||||
git clone https://git.hush.is/hush/hush3
|
||||
cd hush3
|
||||
# Build (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
|
||||
# This uses 3 build processes, you need 2GB of RAM for each.
|
||||
./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
|
||||
|
||||
1. [Download the release](https://git.hush.is/hush/hush3/releases) with a .deb file extension.
|
||||
|
||||
204
build.sh
204
build.sh
@@ -1,211 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# 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
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
VERSION="1.0.3"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
RELEASE_DIR="$SCRIPT_DIR/release"
|
||||
|
||||
# Parse release flags
|
||||
BUILD_LINUX_RELEASE=0
|
||||
BUILD_WIN_RELEASE=0
|
||||
BUILD_MAC_RELEASE=0
|
||||
BUILD_LINUX_COMPAT=0
|
||||
REMAINING_ARGS=()
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--linux-release)
|
||||
BUILD_LINUX_RELEASE=1
|
||||
;;
|
||||
--linux-compat)
|
||||
BUILD_LINUX_COMPAT=1
|
||||
;;
|
||||
--win-release)
|
||||
BUILD_WIN_RELEASE=1
|
||||
;;
|
||||
--mac-release)
|
||||
BUILD_MAC_RELEASE=1
|
||||
;;
|
||||
--all-release)
|
||||
BUILD_LINUX_RELEASE=1
|
||||
BUILD_WIN_RELEASE=1
|
||||
BUILD_MAC_RELEASE=1
|
||||
;;
|
||||
*)
|
||||
REMAINING_ARGS+=("$arg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Clean artifacts that may conflict between platform builds
|
||||
clean_for_platform() {
|
||||
local platform="$1"
|
||||
echo "Cleaning build artifacts for $platform build..."
|
||||
|
||||
# Use make clean if Makefile exists (safer than manual deletion)
|
||||
if [ -f src/Makefile ]; then
|
||||
make -C src clean 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Remove final binaries
|
||||
if [ -d src ]; then
|
||||
rm -f src/dragonxd src/dragonx-cli src/dragonx-tx 2>/dev/null || true
|
||||
rm -f src/dragonxd.exe src/dragonx-cli.exe src/dragonx-tx.exe 2>/dev/null || true
|
||||
rm -f src/hushd src/hush-cli src/hush-tx 2>/dev/null || true
|
||||
rm -f src/hushd.exe src/hush-cli.exe src/hush-tx.exe 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Clean RandomX build for cross-platform compatibility
|
||||
rm -rf src/RandomX/build 2>/dev/null || true
|
||||
|
||||
# Clean cryptoconditions
|
||||
rm -rf src/cc/*.o src/cc/*.a 2>/dev/null || true
|
||||
|
||||
# Clean config cache (forces reconfigure for cross-platform)
|
||||
rm -f config.status config.log 2>/dev/null || true
|
||||
|
||||
echo "Clean complete for $platform"
|
||||
}
|
||||
|
||||
# Package release for a platform
|
||||
package_release() {
|
||||
local platform="$1"
|
||||
local release_subdir="$RELEASE_DIR/dragonx-$VERSION-$platform"
|
||||
|
||||
echo "Packaging release for $platform..."
|
||||
mkdir -p "$release_subdir"
|
||||
|
||||
# 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/"
|
||||
fi
|
||||
|
||||
# Copy common files
|
||||
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$release_subdir/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/sapling-output.params" "$release_subdir/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/sapling-spend.params" "$release_subdir/" 2>/dev/null || true
|
||||
|
||||
case "$platform" in
|
||||
linux-amd64)
|
||||
cp "$SCRIPT_DIR/src/dragonxd" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-cli" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-tx" "$release_subdir/"
|
||||
strip "$release_subdir/dragonxd" "$release_subdir/dragonx-cli" "$release_subdir/dragonx-tx"
|
||||
;;
|
||||
win64)
|
||||
cp "$SCRIPT_DIR/src/dragonxd.exe" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-cli.exe" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-tx.exe" "$release_subdir/"
|
||||
x86_64-w64-mingw32-strip "$release_subdir/"*.exe 2>/dev/null || strip "$release_subdir/"*.exe 2>/dev/null || true
|
||||
;;
|
||||
macos)
|
||||
cp "$SCRIPT_DIR/src/dragonxd" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-cli" "$release_subdir/"
|
||||
cp "$SCRIPT_DIR/src/dragonx-tx" "$release_subdir/"
|
||||
strip "$release_subdir/dragonxd" "$release_subdir/dragonx-cli" "$release_subdir/dragonx-tx" 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Release packaged: $release_subdir"
|
||||
ls -la "$release_subdir"
|
||||
}
|
||||
|
||||
# Handle release builds
|
||||
if [ $BUILD_LINUX_COMPAT -eq 1 ] || [ $BUILD_LINUX_RELEASE -eq 1 ] || [ $BUILD_WIN_RELEASE -eq 1 ] || [ $BUILD_MAC_RELEASE -eq 1 ]; then
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
|
||||
if [ $BUILD_LINUX_COMPAT -eq 1 ]; then
|
||||
echo "=== Building Linux compat release (Ubuntu 20.04 via Docker) ==="
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Error: docker is required for --linux-compat builds"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use sudo for docker if the user isn't in the docker group
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker info &>/dev/null 2>&1; then
|
||||
echo "Note: Using sudo for docker (add yourself to the docker group to avoid this)"
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
DOCKER_IMAGE="dragonx-compat-builder"
|
||||
COMPAT_PLATFORM="linux-amd64-ubuntu2004"
|
||||
COMPAT_RELEASE_DIR="$RELEASE_DIR/dragonx-$VERSION-$COMPAT_PLATFORM"
|
||||
|
||||
echo "Building Docker image (Ubuntu 20.04 base)..."
|
||||
$DOCKER_CMD build -f Dockerfile.compat -t "$DOCKER_IMAGE" .
|
||||
|
||||
echo "Extracting binaries from Docker image..."
|
||||
CONTAINER_ID=$($DOCKER_CMD create "$DOCKER_IMAGE")
|
||||
mkdir -p "$COMPAT_RELEASE_DIR"
|
||||
|
||||
for bin in dragonxd dragonx-cli dragonx-tx; do
|
||||
$DOCKER_CMD cp "$CONTAINER_ID:/build/src/$bin" "$COMPAT_RELEASE_DIR/$bin"
|
||||
done
|
||||
$DOCKER_CMD rm "$CONTAINER_ID" >/dev/null
|
||||
|
||||
# Fix ownership (docker cp creates root-owned files)
|
||||
# Binaries are already stripped inside the Docker container
|
||||
if [ "$(stat -c '%U' "$COMPAT_RELEASE_DIR/dragonxd")" = "root" ]; then
|
||||
sudo chown "$(id -u):$(id -g)" "$COMPAT_RELEASE_DIR"/dragonx*
|
||||
fi
|
||||
|
||||
# Copy common files
|
||||
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$COMPAT_RELEASE_DIR/"
|
||||
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/sapling-output.params" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/sapling-spend.params" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
|
||||
|
||||
echo "Compat release packaged: $COMPAT_RELEASE_DIR"
|
||||
ls -la "$COMPAT_RELEASE_DIR"
|
||||
|
||||
# Show glibc version requirement
|
||||
echo ""
|
||||
echo "Binary compatibility info:"
|
||||
objdump -T "$COMPAT_RELEASE_DIR/dragonxd" | grep -oP 'GLIBC_\d+\.\d+' | sort -uV | tail -1 && echo "(max GLIBC version required)"
|
||||
fi
|
||||
|
||||
if [ $BUILD_LINUX_RELEASE -eq 1 ]; then
|
||||
echo "=== Building Linux release ==="
|
||||
clean_for_platform linux
|
||||
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
package_release linux-amd64
|
||||
fi
|
||||
|
||||
if [ $BUILD_WIN_RELEASE -eq 1 ]; then
|
||||
echo "=== Building Windows release ==="
|
||||
clean_for_platform windows
|
||||
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
package_release win64
|
||||
fi
|
||||
|
||||
if [ $BUILD_MAC_RELEASE -eq 1 ]; then
|
||||
echo "=== Building macOS release ==="
|
||||
clean_for_platform macos
|
||||
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
package_release macos
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Release builds complete ==="
|
||||
ls -la "$RELEASE_DIR"/
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Standard build (auto-detect OS)
|
||||
# run correct build script for detected OS
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
./util/build.sh $@
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
./util/build-mac.sh $@
|
||||
elif [[ "$OSTYPE" == "msys"* ]]; then
|
||||
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
|
||||
./util/build-win.sh $@
|
||||
#elif [[ "$OSTYPE" == "freebsd"* ]]; then
|
||||
# placeholder
|
||||
else
|
||||
echo "Unable to detect your OS. What are you using?"
|
||||
fi
|
||||
|
||||
14
configure.ac
14
configure.ac
@@ -1,23 +1,23 @@
|
||||
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
|
||||
AC_PREREQ([2.60])
|
||||
define(_CLIENT_VERSION_MAJOR, 1)
|
||||
define(_CLIENT_VERSION_MAJOR, 3)
|
||||
dnl Must be kept in sync with src/clientversion.h , ugh!
|
||||
define(_CLIENT_VERSION_MINOR, 0)
|
||||
define(_CLIENT_VERSION_REVISION, 3)
|
||||
define(_CLIENT_VERSION_MINOR, 10)
|
||||
define(_CLIENT_VERSION_REVISION, 5)
|
||||
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(_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_IS_RELEASE, true)
|
||||
define(_COPYRIGHT_YEAR, 2026)
|
||||
AC_INIT([DragonX],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.dragonx.is/DragonX/dragonx],[dragonx])
|
||||
AC_INIT([Hush],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.hush.is/hush/hush3],[hush])
|
||||
AC_CONFIG_SRCDIR([src/main.cpp])
|
||||
AC_CONFIG_HEADERS([src/config/bitcoin-config.h])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([build-aux/m4])
|
||||
|
||||
BITCOIN_DAEMON_NAME=dragonxd
|
||||
BITCOIN_CLI_NAME=dragonx-cli
|
||||
BITCOIN_TX_NAME=dragonx-tx
|
||||
BITCOIN_DAEMON_NAME=hushd
|
||||
BITCOIN_CLI_NAME=hush-cli
|
||||
BITCOIN_TX_NAME=hush-tx
|
||||
|
||||
dnl Unless the user specified ARFLAGS, force it to be cr
|
||||
AC_ARG_VAR(ARFLAGS, [Flags for the archiver, defaults to <cr> if not set])
|
||||
|
||||
7
cpptest
Executable file
7
cpptest
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2026-now 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
|
||||
|
||||
# Run all tests by default
|
||||
./src/hush-test $@
|
||||
@@ -1,5 +1,5 @@
|
||||
build_darwin_CC = gcc-15
|
||||
build_darwin_CXX = g++-15
|
||||
build_darwin_CC = gcc-8
|
||||
build_darwin_CXX = g++-8
|
||||
build_darwin_AR: = $(shell xcrun -f ar)
|
||||
build_darwin_RANLIB: = $(shell xcrun -f ranlib)
|
||||
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
|
||||
|
||||
#darwin host on darwin builder. overrides darwin host preferences.
|
||||
darwin_CC= gcc-15
|
||||
darwin_CXX= g++-15
|
||||
darwin_CC= gcc-8
|
||||
darwin_CXX= g++-8
|
||||
darwin_AR:=$(shell xcrun -f ar)
|
||||
darwin_RANLIB:=$(shell xcrun -f ranlib)
|
||||
darwin_STRIP:=$(shell xcrun -f strip)
|
||||
|
||||
@@ -2,8 +2,8 @@ OSX_MIN_VERSION=10.12
|
||||
OSX_SDK_VERSION=10.12
|
||||
OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk
|
||||
LD64_VERSION=253.9
|
||||
darwin_CC=gcc-15 -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_CC=gcc-8 -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_CFLAGS=-pipe
|
||||
darwin_CXXFLAGS=$(darwin_CFLAGS)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# url=https://github.com/google/googlemock/archive/release-1.7.0.tar.gz
|
||||
|
||||
package=googlemock
|
||||
$(package)_version=1.7.0
|
||||
$(package)_dependencies=googletest
|
||||
|
||||
$(package)_download_path=https://github.com/google/$(package)/archive
|
||||
$(package)_file_name=$(package)-$($(package)_version).tar.gz
|
||||
$(package)_download_file=release-$($(package)_version).tar.gz
|
||||
$(package)_sha256_hash=3f20b6acb37e5a98e8c4518165711e3e35d47deb6cdb5a4dd4566563b5efd232
|
||||
|
||||
ifeq ($(build_os),darwin)
|
||||
define $(package)_set_vars
|
||||
$(package)_build_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" CXX="$($(package)_cxx)" CXXFLAGS="$($(package)_cxxflags)"
|
||||
endef
|
||||
endif
|
||||
|
||||
ifeq ($(build_os),darwin)
|
||||
$(package)_install=ginstall
|
||||
define $(package)_build_cmds
|
||||
$(MAKE) -C make GTEST_DIR='$(host_prefix)' gmock-all.o
|
||||
endef
|
||||
else
|
||||
$(package)_install=install
|
||||
define $(package)_build_cmds
|
||||
$(MAKE) -C make GTEST_DIR='$(host_prefix)' CXXFLAGS='-fPIC' gmock-all.o
|
||||
endef
|
||||
endif
|
||||
|
||||
define $(package)_stage_cmds
|
||||
$($(package)_install) -D ./make/gmock-all.o $($(package)_staging_dir)$(host_prefix)/lib/libgmock.a && \
|
||||
cp -a ./include $($(package)_staging_dir)$(host_prefix)/include
|
||||
endef
|
||||
@@ -1,40 +0,0 @@
|
||||
package=googletest
|
||||
$(package)_version=1.8.0
|
||||
$(package)_download_path=https://github.com/google/$(package)/archive
|
||||
$(package)_file_name=$(package)-$($(package)_version).tar.gz
|
||||
$(package)_download_file=release-$($(package)_version).tar.gz
|
||||
$(package)_sha256_hash=58a6f4277ca2bc8565222b3bbd58a177609e9c488e8a72649359ba51450db7d8
|
||||
|
||||
define $(package)_set_vars
|
||||
$(package)_cxxflags+=-std=c++11
|
||||
$(package)_cxxflags_linux=-fPIC
|
||||
endef
|
||||
|
||||
ifeq ($(build_os),darwin)
|
||||
define $(package)_set_vars
|
||||
$(package)_build_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" CXX="$($(package)_cxx)" CXXFLAGS="$($(package)_cxxflags)"
|
||||
endef
|
||||
endif
|
||||
|
||||
ifeq ($(build_os),darwin)
|
||||
$(package)_install=ginstall
|
||||
define $(package)_build_cmds
|
||||
$(MAKE) -C googlemock/make gmock.a && \
|
||||
$(MAKE) -C googletest/make gtest.a
|
||||
endef
|
||||
else
|
||||
$(package)_install=install
|
||||
|
||||
define $(package)_build_cmds
|
||||
$(MAKE) -C googlemock/make CC="$($(package)_cc)" CXX="$($(package)_cxx)" AR="$($(package)_ar)" CXXFLAGS="$($(package)_cxxflags)" gmock.a && \
|
||||
$(MAKE) -C googletest/make CC="$($(package)_cc)" CXX="$($(package)_cxx)" AR="$($(package)_ar)" CXXFLAGS="$($(package)_cxxflags)" gtest.a
|
||||
endef
|
||||
endif
|
||||
|
||||
define $(package)_stage_cmds
|
||||
mkdir -p $($(package)_staging_dir)$(host_prefix)/lib && \
|
||||
install ./googlemock/make/gmock.a $($(package)_staging_dir)$(host_prefix)/lib/libgmock.a && \
|
||||
install ./googletest/make/gtest.a $($(package)_staging_dir)$(host_prefix)/lib/libgtest.a && \
|
||||
cp -a ./googlemock/include $($(package)_staging_dir)$(host_prefix)/ && \
|
||||
cp -a ./googletest/include $($(package)_staging_dir)$(host_prefix)/
|
||||
endef
|
||||
@@ -40,15 +40,9 @@ define $(package)_preprocess_cmds
|
||||
cat $($(package)_patch_dir)/cargo.config | sed 's|CRATE_REGISTRY|$(host_prefix)/$(CRATE_REGISTRY)|' > .cargo/config
|
||||
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
|
||||
$(host_prefix)/native/bin/cargo build --package librustzcash $($(package)_build_opts)
|
||||
endef
|
||||
endif
|
||||
|
||||
define $(package)_stage_cmds
|
||||
mkdir $($(package)_staging_dir)$(host_prefix)/lib/ && \
|
||||
|
||||
@@ -39,8 +39,8 @@ native_packages := native_ccache
|
||||
wallet_packages=bdb
|
||||
|
||||
ifeq ($(host_os),linux)
|
||||
packages := boost wolfssl libevent $(zcash_packages) libcurl #googlemock googletest
|
||||
packages := boost wolfssl libevent $(zcash_packages) libcurl
|
||||
else
|
||||
packages := boost wolfssl libevent $(zcash_packages) libcurl #googlemock googletest
|
||||
packages := boost wolfssl libevent $(zcash_packages) libcurl
|
||||
endif
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
# Hush Core (hushd) Software Contribution Guidelines
|
||||
|
||||
Thank you for reaching out and trying to make Hush an even better software application and cryptocoin platform. These contribution guidelines shall help you figuring out where you can be helpful and how to easily get started.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
0. [AI/LLM Usage Policty](#ai/llm-usage-policy)
|
||||
0. [Types of contributions we're looking for](#types-of-contributions-were-looking-for)
|
||||
0. [Ground rules & expectations](#ground-rules--expectations)
|
||||
0. [How to contribute](#how-to-contribute)
|
||||
@@ -13,6 +13,21 @@ Thank you for reaching out and trying to make Hush an even better software appli
|
||||
0. [Contribution review process](#contribution-review-process)
|
||||
0. [Community](#community)
|
||||
|
||||
## AI/LLM Usage Policy
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This project does **NOT** accept pull requests that are fully or predominantly AI-generated. AI tools may be utilized solely in an assistive capacity. The human submitting new code to the project must actually understand the code changes they are submitting.
|
||||
|
||||
Code that is initially generated by AI and subsequently edited will still be considered AI-generated. AI assistance is permissible only when the majority of the code is authored by a human contributor, with AI employed exclusively for corrections or to expand on verbose modifications that the contributor has already conceptualized (e.g., generating repeated lines with minor variations).
|
||||
|
||||
If AI is used to generate any portion of the code, contributors must adhere to the following requirements:
|
||||
|
||||
1. Explicitly disclose the manner in which AI was employed, including the exact model and quantization used and if it was done via local AI, such as with llama.cpp or a SaaS provider.
|
||||
2. Perform a comprehensive manual review prior to submitting the pull request.
|
||||
3. Be prepared to explain every line of code they submitted when asked about it by a maintainer.
|
||||
4. It is strictly prohibited to use AI to write your posts for you (bug reports, feature requests, pull request descriptions, Github discussions, responding to humans, ...).
|
||||
|
||||
|
||||
## Types of contributions we're looking for
|
||||
There are many ways you can directly contribute to Hush:
|
||||
|
||||
@@ -31,7 +46,6 @@ Interested in making a contribution? Read on!
|
||||
Before we get started, here are a few things we expect from you (and that you should expect from others):
|
||||
|
||||
* Be kind and thoughtful in your conversations around this project. We all come from different backgrounds and projects, which means we likely have different perspectives on "how free software and open source is done." Try to listen to others rather than convince them that your way is correct.
|
||||
* Open Source Guides are released with a [Contributor Code of Conduct](./code_of_conduct.md). By participating in this project, you agree to abide by its terms.
|
||||
* If you open a pull request, please ensure that your contribution does not increase test failures. If there are additional test failures, you will need to address them before we can merge your contribution.
|
||||
* When adding content, please consider if it is widely valuable. Please don't add references or links to things you or your employer have created as others will do so if they appreciate it.
|
||||
|
||||
|
||||
@@ -16,6 +16,48 @@ other programs and Operating System overhead. A good rule of thumb is:
|
||||
|
||||
Divide how many GBs of RAM you have by 2, subtract one. Use that many jobs.
|
||||
|
||||
# Run all tests
|
||||
|
||||
To run both C++ and RPC tests:
|
||||
|
||||
./test
|
||||
|
||||
C++ test run much faster than the RPC tests.
|
||||
|
||||
## Run C++ Unit tests
|
||||
|
||||
To run the C++ unit tests
|
||||
|
||||
./src/hush-test
|
||||
|
||||
Example successful output:
|
||||
|
||||
Running 42 test cases...
|
||||
|
||||
*** No errors detected
|
||||
|
||||
Example failure output:
|
||||
|
||||
Running 42 test cases...
|
||||
test-hush/main.cpp(16): error: in "test_sodium": check init_and_check_sodium() != 0 has failed [0 == 0]
|
||||
|
||||
*** 1 failure is detected in the test module "HushTestSuite"
|
||||
|
||||
# Run Python RPC tests
|
||||
|
||||
To run our QA/functional tests:
|
||||
|
||||
./rpctest
|
||||
|
||||
Example successful output:
|
||||
|
||||
# Running 2 tests..
|
||||
PASS!
|
||||
|
||||
Example failure output:
|
||||
|
||||
FAIL! Number of failed tests: 1 . Details in test-1234567.txt
|
||||
|
||||
|
||||
## Dealing with dependency changes
|
||||
|
||||
|
||||
47
rpctest
Executable file
47
rpctest
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env perl
|
||||
# Copyright 2016-2026 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
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.010;
|
||||
|
||||
my $flags = $ENV{TEST_FLAGS} || '--tracerpc';
|
||||
my $test_dir = './qa/rpc-tests';
|
||||
|
||||
$ENV{PYTHONPATH} = "./qa/rpc-tests/test_framework/";
|
||||
#$ENV{PYTHON_DEBUG} = 1;
|
||||
|
||||
my @tests_to_run = qw{
|
||||
lockzins.py
|
||||
shieldcoinbase_donation.py
|
||||
};
|
||||
|
||||
my $exit = 0;
|
||||
my $failed_tests = 0;
|
||||
my $time=time();
|
||||
my $num_tests = @tests_to_run;
|
||||
|
||||
print "# Running $num_tests tests";
|
||||
for my $test (@tests_to_run) {
|
||||
# send both stderr+stdout to our output file
|
||||
my $cmd = "$test_dir/$test $flags 1>test-$time.txt 2>&1";
|
||||
system($cmd);
|
||||
|
||||
print ".";
|
||||
|
||||
if($?) {
|
||||
say "$cmd FAILED!";
|
||||
$exit = 1;
|
||||
$failed_tests++;
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
|
||||
if ($exit) {
|
||||
say "FAIL! Number of failed tests: $failed_tests . Details in test-$time.txt";
|
||||
} else {
|
||||
say "PASS!";
|
||||
}
|
||||
exit($exit);
|
||||
@@ -39,22 +39,15 @@ BITCOIN_INCLUDES += -I$(srcdir)/univalue/include
|
||||
BITCOIN_INCLUDES += -I$(srcdir)/leveldb/include
|
||||
|
||||
if TARGET_WINDOWS
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
||||
endif
|
||||
if TARGET_DARWIN
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
||||
endif
|
||||
if TARGET_LINUX
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a
|
||||
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
|
||||
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_COMMON=libbitcoin_common.a
|
||||
LIBBITCOIN_CLI=libbitcoin_cli.a
|
||||
@@ -101,11 +94,11 @@ noinst_PROGRAMS =
|
||||
TESTS =
|
||||
|
||||
#if BUILD_BITCOIND
|
||||
bin_PROGRAMS += dragonxd
|
||||
bin_PROGRAMS += hushd
|
||||
#endif
|
||||
|
||||
if BUILD_BITCOIN_UTILS
|
||||
bin_PROGRAMS += dragonx-cli dragonx-tx
|
||||
bin_PROGRAMS += hush-cli hush-tx
|
||||
endif
|
||||
if ENABLE_WALLET
|
||||
bin_PROGRAMS += wallet-utility
|
||||
@@ -460,18 +453,17 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h
|
||||
#
|
||||
|
||||
# hushd binary #
|
||||
dragonxd_SOURCES = bitcoind.cpp
|
||||
dragonxd_CPPFLAGS = -fPIC $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
dragonxd_CXXFLAGS = -fPIC $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
dragonxd_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
hushd_SOURCES = bitcoind.cpp
|
||||
hushd_CPPFLAGS = -fPIC $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
hushd_CXXFLAGS = -fPIC $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
hushd_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
|
||||
if TARGET_WINDOWS
|
||||
dragonxd_SOURCES += bitcoind-res.rc
|
||||
hushd_SOURCES += bitcoind-res.rc
|
||||
endif
|
||||
|
||||
dragonxd_LDADD = \
|
||||
hushd_LDADD = \
|
||||
$(LIBBITCOIN_SERVER) \
|
||||
$(LIBCURL) \
|
||||
$(LIBBITCOIN_COMMON) \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
@@ -484,10 +476,10 @@ dragonxd_LDADD = \
|
||||
$(LIBRANDOMX)
|
||||
|
||||
if ENABLE_WALLET
|
||||
dragonxd_LDADD += $(LIBBITCOIN_WALLET)
|
||||
hushd_LDADD += $(LIBBITCOIN_WALLET)
|
||||
endif
|
||||
|
||||
dragonxd_LDADD += \
|
||||
hushd_LDADD += \
|
||||
$(BOOST_LIBS) \
|
||||
$(BDB_LIBS) \
|
||||
$(SSL_LIBS) \
|
||||
@@ -498,27 +490,27 @@ dragonxd_LDADD += \
|
||||
$(LIBZCASH_LIBS)
|
||||
|
||||
if TARGET_DARWIN
|
||||
dragonxd_LDADD += libcc.dylib $(LIBSECP256K1)
|
||||
hushd_LDADD += libcc.dylib $(LIBSECP256K1)
|
||||
endif
|
||||
if TARGET_WINDOWS
|
||||
dragonxd_LDADD += libcc.dll $(LIBSECP256K1)
|
||||
hushd_LDADD += libcc.dll $(LIBSECP256K1)
|
||||
endif
|
||||
if TARGET_LINUX
|
||||
dragonxd_LDADD += libcc.so $(LIBSECP256K1)
|
||||
hushd_LDADD += libcc.so $(LIBSECP256K1)
|
||||
endif
|
||||
|
||||
# [+] Decker: use static linking for libstdc++.6.dylib, libgomp.1.dylib, libgcc_s.1.dylib
|
||||
if TARGET_DARWIN
|
||||
dragonxd_LDFLAGS += -static-libgcc
|
||||
hushd_LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
# hush-cli binary #
|
||||
dragonx_cli_SOURCES = bitcoin-cli.cpp
|
||||
dragonx_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
|
||||
dragonx_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
dragonx_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
hush_cli_SOURCES = bitcoin-cli.cpp
|
||||
hush_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
|
||||
hush_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
hush_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
if TARGET_DARWIN
|
||||
dragonx_cli_LDFLAGS += -static-libgcc
|
||||
hush_cli_LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
# wallet-utility binary #
|
||||
@@ -530,10 +522,10 @@ wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
endif
|
||||
|
||||
if TARGET_WINDOWS
|
||||
dragonx_cli_SOURCES += bitcoin-cli-res.rc
|
||||
hush_cli_SOURCES += bitcoin-cli-res.rc
|
||||
endif
|
||||
|
||||
dragonx_cli_LDADD = \
|
||||
hush_cli_LDADD = \
|
||||
$(LIBBITCOIN_CLI) \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
@@ -562,16 +554,16 @@ wallet_utility_LDADD = \
|
||||
endif
|
||||
|
||||
# hush-tx binary #
|
||||
dragonx_tx_SOURCES = hush-tx.cpp
|
||||
dragonx_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
dragonx_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
dragonx_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
hush_tx_SOURCES = hush-tx.cpp
|
||||
hush_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
hush_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
hush_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
|
||||
if TARGET_WINDOWS
|
||||
dragonx_tx_SOURCES += bitcoin-tx-res.rc
|
||||
hush_tx_SOURCES += bitcoin-tx-res.rc
|
||||
endif
|
||||
|
||||
dragonx_tx_LDADD = \
|
||||
hush_tx_LDADD = \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBBITCOIN_COMMON) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
@@ -582,7 +574,7 @@ dragonx_tx_LDADD = \
|
||||
$(LIBZCASH_LIBS) \
|
||||
$(LIBRANDOMX)
|
||||
|
||||
dragonx_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
|
||||
hush_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
|
||||
|
||||
# Zcash Protocol Primitives
|
||||
libzcash_a_SOURCES = \
|
||||
@@ -602,7 +594,7 @@ libzcash_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBOOST_SPIRIT_THREADSAFE -DHAV
|
||||
#libzcash_a_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
#libzcash_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMONTGOMERY_OUTPUT
|
||||
|
||||
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
|
||||
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
|
||||
libzcash_a_LDFLAGS = $(SAN_LDFLAGS) $(HARDENED_LDFLAGS)
|
||||
libzcash_a_CPPFLAGS += -DMONTGOMERY_OUTPUT
|
||||
|
||||
@@ -644,7 +636,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_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
|
||||
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
|
||||
|
||||
libhush_a_LDFLAGS = $(HARDENED_LDFLAGS)
|
||||
|
||||
@@ -691,7 +683,7 @@ endif
|
||||
$(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(abspath $(<D) $<)
|
||||
|
||||
if ENABLE_TESTS
|
||||
#include Makefile.test-hush.include
|
||||
include Makefile.test-hush.include
|
||||
#include Makefile.test.include
|
||||
include Makefile.gtest.include
|
||||
#include Makefile.gtest.include
|
||||
endif
|
||||
|
||||
@@ -4,60 +4,65 @@ TESTS += hush-gtest
|
||||
bin_PROGRAMS += hush-gtest
|
||||
|
||||
# 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
|
||||
# UTXO-snapshot round-trip test. Re-add other gtest sources here as they are revived.
|
||||
hush_gtest_SOURCES = \
|
||||
gtest/main.cpp \
|
||||
gtest/utils.cpp \
|
||||
gtest/test_utxosnapshot.cpp \
|
||||
gtest/test_randomx_preverify.cpp
|
||||
gtest/test_checktransaction.cpp \
|
||||
gtest/json_test_vectors.cpp \
|
||||
gtest/json_test_vectors.h \
|
||||
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_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
|
||||
# Mirror dragonxd_LDADD's working library set/order (the old list used a non-existent
|
||||
# $(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)
|
||||
hush_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
||||
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1)
|
||||
if ENABLE_WALLET
|
||||
hush_gtest_LDADD += $(LIBBITCOIN_WALLET)
|
||||
endif
|
||||
|
||||
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_LDADD += $(LIBZCASH_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(LIBZCASH) $(LIBZCASH_LIBS)
|
||||
|
||||
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_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
||||
|
||||
hush-gtest-expected-failures: hush-gtest FORCE
|
||||
./hush-gtest --gtest_filter=*DISABLED_* --gtest_also_run_disabled_tests
|
||||
|
||||
@@ -6,19 +6,14 @@ TESTS += hush-test
|
||||
bin_PROGRAMS += hush-test
|
||||
|
||||
# tool for generating our public parameters
|
||||
hush_test_SOURCES = test-hush/main.cpp
|
||||
# devs can enable this shit, it just slows down default compiles
|
||||
hush_test_SOURCES = test-hush/main.cpp \
|
||||
test-hush/test_netbase_tests.cpp \
|
||||
test-hush/randomx.cpp
|
||||
# test-hush/testutils.cpp \
|
||||
# test-hush/test_cryptoconditions.cpp \
|
||||
# test-hush/test_coinimport.cpp \
|
||||
# test-hush/test_eval_bet.cpp \
|
||||
# test-hush/test_eval_notarization.cpp \
|
||||
# test-hush/test_parse_notarization.cpp \
|
||||
# test-hush/test_addrman.cpp \
|
||||
# test-hush/test_netbase_tests.cpp
|
||||
# test-hush/test_addrman.cpp
|
||||
|
||||
hush_test_CPPFLAGS = $(hushd_CPPFLAGS)
|
||||
|
||||
hush_test_LDADD = -lgtest $(hushd_LDADD)
|
||||
|
||||
hush_test_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
||||
hush_test_LDADD = $(hushd_LDADD)
|
||||
hush_test_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
|
||||
|
||||
@@ -112,13 +112,13 @@ endif
|
||||
|
||||
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_LDADD = $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
||||
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
|
||||
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
|
||||
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
if ENABLE_WALLET
|
||||
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
|
||||
endif
|
||||
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
|
||||
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
|
||||
$(LIBLEVELDB) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
|
||||
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"ac_perc": "11111111",
|
||||
"ac_eras": "3",
|
||||
"ac_script": "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac",
|
||||
"clientname": "DragonX",
|
||||
"clientname": "GoldenSandtrout",
|
||||
"addnode": [
|
||||
"node1.hush.is",
|
||||
"node2.hush.is",
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include "support/events.h"
|
||||
|
||||
uint16_t ASSETCHAINS_RPCPORT = 21769;
|
||||
uint16_t BITCOIND_RPCPORT = 21769;
|
||||
uint16_t ASSETCHAINS_RPCPORT = 18031;
|
||||
uint16_t BITCOIND_RPCPORT = 18031;
|
||||
char SMART_CHAIN_SYMBOL[65];
|
||||
|
||||
extern uint16_t ASSETCHAINS_RPCPORT;
|
||||
@@ -47,7 +47,7 @@ std::string HelpMessageCli()
|
||||
std::string strUsage;
|
||||
strUsage += HelpMessageGroup(_("Options:"));
|
||||
strUsage += HelpMessageOpt("-?", _("This help message"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.conf"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
|
||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||
strUsage += HelpMessageOpt("-testnet", _("Use the test network"));
|
||||
strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be "
|
||||
@@ -87,19 +87,19 @@ static int AppInitRPC(int argc, char* argv[])
|
||||
ParseParameters(argc, argv);
|
||||
std:string name;
|
||||
|
||||
// default HAC is DRAGONX itself, which to the internals, is also a HAC
|
||||
name = GetArg("-ac_name","DRAGONX");
|
||||
// default HAC is HUSH3 itself, which to the internals, is also a HAC
|
||||
name = GetArg("-ac_name","HUSH3");
|
||||
|
||||
if ( !name.empty() )
|
||||
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
|
||||
|
||||
if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) {
|
||||
std::string strUsage = _("DragonX RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
std::string strUsage = _("Hush RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
if (!mapArgs.count("-version")) {
|
||||
strUsage += "\n" + _("Usage:") + "\n" +
|
||||
" dragonx-cli [options] <command> [params] " + _("Send command to DragonX") + "\n" +
|
||||
" dragonx-cli [options] help " + _("List commands") + "\n" +
|
||||
" dragonx-cli [options] help <command> " + _("Get help for a command") + "\n";
|
||||
" hush-cli [options] <command> [params] " + _("Send command to Hush") + "\n" +
|
||||
" hush-cli [options] help " + _("List commands") + "\n" +
|
||||
" hush-cli [options] help <command> " + _("Get help for a command") + "\n";
|
||||
|
||||
strUsage += "\n" + HelpMessageCli();
|
||||
} else {
|
||||
|
||||
@@ -117,14 +117,14 @@ bool AppInit(int argc, char* argv[])
|
||||
// Process help and version before taking care about datadir
|
||||
if (mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version"))
|
||||
{
|
||||
std::string strUsage = _("DragonX Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
std::string strUsage = _("Hush Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
|
||||
if (mapArgs.count("-version"))
|
||||
{
|
||||
strUsage += LicenseInfo();
|
||||
} else {
|
||||
strUsage += "\n" + _("Usage:") + "\n" +
|
||||
" dragonxd [options] " + _("Start DragonX Daemon") + "\n";
|
||||
" hushd [options] " + _("Start a Hush Daemon") + "\n";
|
||||
|
||||
strUsage += "\n" + HelpMessage(HMM_BITCOIND);
|
||||
}
|
||||
@@ -167,13 +167,13 @@ bool AppInit(int argc, char* argv[])
|
||||
"\n"
|
||||
"You can look at the example configuration file for suggestions of default\n"
|
||||
"options that you may want to change. It should be in one of these locations,\n"
|
||||
"depending on how you installed DragonX\n") +
|
||||
"depending on how you installed Hush\n") +
|
||||
_("- Source code: %s\n"
|
||||
"- .deb package: %s\n")).c_str(),
|
||||
GetConfigFile().string().c_str(),
|
||||
"contrib/debian/examples/DRAGONX.conf",
|
||||
"/usr/share/doc/dragonx/examples/DRAGONX.conf",
|
||||
"https://git.dragonx.is/DragonX/dragonx/src/branch/main/contrib/debian/examples/DRAGONX.conf");
|
||||
"contrib/debian/examples/HUSH3.conf",
|
||||
"/usr/share/doc/hush/examples/HUSH3.conf",
|
||||
"https://git.hush.is/hush/hush3/src/branch/master/contrib/debian/examples/HUSH3.conf");
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
|
||||
@@ -183,15 +183,15 @@ bool AppInit(int argc, char* argv[])
|
||||
// Command-line RPC
|
||||
bool fCommandLine = false;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
// detect accidental use of RPC in dragonxd
|
||||
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "dragonx:")) {
|
||||
// detect accidental use of RPC in hushd
|
||||
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "hush:")) {
|
||||
fCommandLine = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fCommandLine)
|
||||
{
|
||||
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in dragonxd. Use the dragonx-cli utility instead.\n");
|
||||
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in hushd. Use the hush-cli utility instead.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ bool AppInit(int argc, char* argv[])
|
||||
fDaemon = GetBoolArg("-daemon", false);
|
||||
if (fDaemon)
|
||||
{
|
||||
fprintf(stdout, "DragonX %s server starting\n",SMART_CHAIN_SYMBOL);
|
||||
fprintf(stdout, "Hush %s server starting\n",SMART_CHAIN_SYMBOL);
|
||||
|
||||
// Daemonize
|
||||
pid_t pid = fork();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SHELL = /bin/sh
|
||||
CC_DARWIN = g++-15
|
||||
CC_DARWIN = g++-8
|
||||
CC_WIN = x86_64-w64-mingw32-gcc-posix
|
||||
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
|
||||
|
||||
Binary file not shown.
20
src/chain.h
20
src/chain.h
@@ -32,12 +32,8 @@ class CChainPower;
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
extern bool fZindex;
|
||||
// These version thresholds control whether nSproutValue/nSaplingValue are
|
||||
// serialized in the block index. They must be <= CLIENT_VERSION or the
|
||||
// values will never be persisted, causing nChainSaplingValue to reset
|
||||
// to 0 after node restart. DragonX CLIENT_VERSION is 1000350 (v1.0.3.50).
|
||||
static const int SPROUT_VALUE_VERSION = 1000000;
|
||||
static const int SAPLING_VALUE_VERSION = 1000000;
|
||||
static const int SPROUT_VALUE_VERSION = 1001400;
|
||||
static const int SAPLING_VALUE_VERSION = 1010100;
|
||||
extern int32_t ASSETCHAINS_LWMAPOS;
|
||||
extern char SMART_CHAIN_SYMBOL[65];
|
||||
extern uint64_t ASSETCHAINS_NOTARY_PAY[];
|
||||
@@ -399,16 +395,7 @@ public:
|
||||
|
||||
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
|
||||
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()
|
||||
{
|
||||
phashBlock = NULL;
|
||||
@@ -423,7 +410,6 @@ public:
|
||||
chainPower = CChainPower();
|
||||
nTx = 0;
|
||||
nChainTx = 0;
|
||||
fRandomXVerified = false;
|
||||
|
||||
// Shieldex Index chain stats
|
||||
nChainPayments = 0;
|
||||
|
||||
2848
src/chainparams.cpp
2848
src/chainparams.cpp
File diff suppressed because it is too large
Load Diff
@@ -69,17 +69,6 @@ public:
|
||||
double fTransactionsPerDay;
|
||||
};
|
||||
|
||||
/** Trusted UTXO-snapshot (assumeutxo-style) anchor. When `hash` is set, a node loading a
|
||||
* snapshot via -loadutxosnapshot must produce exactly this content hash at this height,
|
||||
* otherwise the snapshot is refused. Null hash = not configured (loading requires the
|
||||
* explicit -loadutxosnapshotunsafe override, e.g. for regtest/testing). Mirrors the
|
||||
* hardcoded-checkpoint trust model. */
|
||||
struct AssumeutxoData {
|
||||
int height;
|
||||
uint256 hash;
|
||||
bool IsNull() const { return hash.IsNull(); }
|
||||
};
|
||||
|
||||
enum Bech32Type {
|
||||
SAPLING_PAYMENT_ADDRESS,
|
||||
SAPLING_FULL_VIEWING_KEY,
|
||||
@@ -116,7 +105,6 @@ public:
|
||||
const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; }
|
||||
const std::vector<uint8_t>& FixedSeeds() const { return vFixedSeeds; }
|
||||
const CCheckpointData& Checkpoints() const { return checkpointData; }
|
||||
const AssumeutxoData& Assumeutxo() const { return assumeutxoData; }
|
||||
/** Return the founder's reward address and script for a given block height */
|
||||
std::string GetFoundersRewardAddressAtHeight(int height) const;
|
||||
CScript GetFoundersRewardScriptAtHeight(int height) const;
|
||||
@@ -156,7 +144,6 @@ protected:
|
||||
bool fMineBlocksOnDemand = false;
|
||||
bool fTestnetToBeDeprecatedFieldRPC = false;
|
||||
CCheckpointData checkpointData;
|
||||
AssumeutxoData assumeutxoData; // null by default; set per-network in chainparams.cpp once a snapshot hash is published
|
||||
std::vector<std::string> vFoundersRewardAddress;
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class CBaseMainParams : public CBaseChainParams
|
||||
public:
|
||||
CBaseMainParams()
|
||||
{
|
||||
nRPCPort = 21769;
|
||||
nRPCPort = 18031;
|
||||
}
|
||||
};
|
||||
static CBaseMainParams mainParams;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
* for both bitcoind and bitcoin-core, to make it harder for attackers to
|
||||
* target servers or GUI users specifically.
|
||||
*/
|
||||
const std::string CLIENT_NAME = GetArg("-clientname", "DragonX");
|
||||
const std::string CLIENT_NAME = GetArg("-clientname", "GoldenSandtrout");
|
||||
|
||||
/**
|
||||
* Client version number
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
// client versioning and copyright year
|
||||
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
|
||||
// Must be kept in sync with configure.ac , ugh!
|
||||
#define CLIENT_VERSION_MAJOR 1
|
||||
#define CLIENT_VERSION_MINOR 0
|
||||
#define CLIENT_VERSION_REVISION 3
|
||||
#define CLIENT_VERSION_MAJOR 3
|
||||
#define CLIENT_VERSION_MINOR 10
|
||||
#define CLIENT_VERSION_REVISION 5
|
||||
#define CLIENT_VERSION_BUILD 50
|
||||
|
||||
//! Set to true for release, false for prerelease or test build
|
||||
@@ -40,7 +40,7 @@
|
||||
* Copyright year (2009-this)
|
||||
* Todo: update this when changing our copyright comments in the source
|
||||
*/
|
||||
#define COPYRIGHT_YEAR 2026
|
||||
#define COPYRIGHT_YEAR 2024
|
||||
|
||||
#endif //HAVE_CONFIG_H
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ bool sanity_test_range_fmt()
|
||||
{
|
||||
std::string test;
|
||||
try {
|
||||
(void)test.at(1);
|
||||
test.at(1);
|
||||
} catch (const std::out_of_range&) {
|
||||
return true;
|
||||
} catch (...) {
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
// 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
|
||||
//
|
||||
// Round-trip tests for the trusted UTXO snapshot (assumeutxo-style) dump/load core
|
||||
// (CCoinsViewDB::DumpSnapshot / LoadSnapshot). This exercises the highest-risk part of
|
||||
// the feature in isolation: that coins, Sapling commitment trees, the nullifier set, the
|
||||
// best block and the best Sapling anchor survive a serialize -> hash -> deserialize cycle
|
||||
// exactly, and that integrity/trust verification rejects tampered or wrong-hash snapshots.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "chainparams.h"
|
||||
#include "coins.h"
|
||||
#include "txdb.h"
|
||||
#include "script/script.h"
|
||||
#include "uint256.h"
|
||||
#include "zcash/IncrementalMerkleTree.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Populate an in-memory chainstate DB directly via BatchWrite (mirrors how blocks persist
|
||||
// coins/anchors/nullifiers), so DumpSnapshot has a realistic mixed state to serialize.
|
||||
void PopulateChainstate(CCoinsViewDB &db, const uint256 &bestBlock,
|
||||
uint256 &anchorRootOut, const uint256 &nullifierIn)
|
||||
{
|
||||
// One unspent transparent output.
|
||||
CCoinsMap mapCoins;
|
||||
{
|
||||
uint256 txid = uint256S("0xaa00000000000000000000000000000000000000000000000000000000000001");
|
||||
CCoinsCacheEntry &e = mapCoins[txid];
|
||||
e.coins.fCoinBase = false;
|
||||
e.coins.nVersion = 1;
|
||||
e.coins.nHeight = 100;
|
||||
e.coins.vout.resize(1);
|
||||
e.coins.vout[0].nValue = 12345;
|
||||
e.coins.vout[0].scriptPubKey = CScript() << OP_TRUE;
|
||||
e.flags = CCoinsCacheEntry::DIRTY;
|
||||
}
|
||||
|
||||
// One Sapling commitment tree (anchor), keyed by its root.
|
||||
SaplingMerkleTree tree;
|
||||
tree.append(uint256S("0xbb00000000000000000000000000000000000000000000000000000000000002"));
|
||||
anchorRootOut = tree.root();
|
||||
CAnchorsSaplingMap mapSaplingAnchors;
|
||||
{
|
||||
CAnchorsSaplingCacheEntry &e = mapSaplingAnchors[anchorRootOut];
|
||||
e.entered = true;
|
||||
e.tree = tree;
|
||||
e.flags = CAnchorsSaplingCacheEntry::DIRTY;
|
||||
}
|
||||
|
||||
// One spent Sapling nullifier.
|
||||
CNullifiersMap mapSaplingNullifiers;
|
||||
{
|
||||
CNullifiersCacheEntry &e = mapSaplingNullifiers[nullifierIn];
|
||||
e.entered = true;
|
||||
e.flags = CNullifiersCacheEntry::DIRTY;
|
||||
}
|
||||
|
||||
CAnchorsSproutMap mapSproutAnchors; // empty
|
||||
CNullifiersMap mapSproutNullifiers; // empty
|
||||
ASSERT_TRUE(db.BatchWrite(mapCoins, bestBlock, uint256(), anchorRootOut,
|
||||
mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers));
|
||||
}
|
||||
|
||||
CUTXOSnapshotHeader MakeHeader(const uint256 &bestBlock, const uint256 &bestAnchor)
|
||||
{
|
||||
CUTXOSnapshotHeader h;
|
||||
h.nMagic = UTXO_SNAPSHOT_MAGIC;
|
||||
h.nVersion = UTXO_SNAPSHOT_VERSION;
|
||||
memcpy(&h.nNetworkMagic, Params().MessageStart(), 4);
|
||||
h.baseBlockHash = bestBlock;
|
||||
h.nHeight = 100;
|
||||
h.nChainTx = 1;
|
||||
h.fHasChainSaplingValue = 1;
|
||||
h.nChainSaplingValue = 999;
|
||||
h.bestSaplingAnchor = bestAnchor;
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UTXOSnapshot, RoundTripPreservesChainstate)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
|
||||
const uint256 bestBlock = uint256S("0xff00000000000000000000000000000000000000000000000000000000000009");
|
||||
const uint256 nullifier = uint256S("0xcc00000000000000000000000000000000000000000000000000000000000003");
|
||||
|
||||
CCoinsViewDB src(1 << 20, true); // in-memory
|
||||
uint256 anchorRoot;
|
||||
PopulateChainstate(src, bestBlock, anchorRoot, nullifier);
|
||||
|
||||
boost::filesystem::path path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
|
||||
CUTXOSnapshotHeader header = MakeHeader(bestBlock, anchorRoot);
|
||||
uint256 dumpHash; std::string err;
|
||||
ASSERT_TRUE(src.DumpSnapshot(path.string(), header, dumpHash, err)) << err;
|
||||
EXPECT_EQ(header.nCoins, 1u);
|
||||
EXPECT_EQ(header.nSaplingAnchors, 1u);
|
||||
EXPECT_EQ(header.nSaplingNullifiers, 1u);
|
||||
|
||||
// Load into a fresh in-memory DB (integrity check only, no trust hash).
|
||||
CCoinsViewDB dst(1 << 20, true);
|
||||
CUTXOSnapshotHeader loadedHeader; uint256 loadHash;
|
||||
ASSERT_TRUE(dst.LoadSnapshot(path.string(), uint256(), /*fRequireExpected=*/false, loadedHeader, loadHash, err)) << err;
|
||||
|
||||
// Hash is deterministic across dump and load.
|
||||
EXPECT_EQ(dumpHash, loadHash);
|
||||
EXPECT_EQ(loadedHeader.nHeight, 100);
|
||||
EXPECT_EQ(loadedHeader.baseBlockHash, bestBlock);
|
||||
|
||||
// Best block round-trips.
|
||||
EXPECT_EQ(dst.GetBestBlock(), bestBlock);
|
||||
|
||||
// Coins round-trip: the stored UTXO must come back intact. (We check the specific coin
|
||||
// directly rather than via GetStats(), which dereferences mapBlockIndex for the best block
|
||||
// — not populated in this pure unit test.) The full-content equivalence is already proven
|
||||
// by dumpHash == loadHash above.
|
||||
const uint256 txid = uint256S("0xaa00000000000000000000000000000000000000000000000000000000000001");
|
||||
CCoins c1, c2;
|
||||
ASSERT_TRUE(src.GetCoins(txid, c1));
|
||||
ASSERT_TRUE(dst.GetCoins(txid, c2));
|
||||
ASSERT_EQ(c2.vout.size(), 1u);
|
||||
EXPECT_EQ(c2.vout[0].nValue, c1.vout[0].nValue);
|
||||
EXPECT_TRUE(c2.vout[0].scriptPubKey == c1.vout[0].scriptPubKey);
|
||||
|
||||
// Sapling anchor (commitment tree) round-trips byte-exactly: the recovered tree's root
|
||||
// must equal the key it was stored under (this is the invariant ConnectBlock relies on).
|
||||
SaplingMerkleTree recovered;
|
||||
ASSERT_TRUE(dst.GetSaplingAnchorAt(anchorRoot, recovered));
|
||||
EXPECT_EQ(recovered.root(), anchorRoot);
|
||||
EXPECT_EQ(dst.GetBestAnchor(SAPLING), anchorRoot);
|
||||
|
||||
// Nullifier set round-trips.
|
||||
EXPECT_TRUE(dst.GetNullifier(nullifier, SAPLING));
|
||||
EXPECT_FALSE(dst.GetNullifier(uint256S("0xdead"), SAPLING));
|
||||
|
||||
boost::filesystem::remove(path);
|
||||
}
|
||||
|
||||
TEST(UTXOSnapshot, RejectsTrustHashMismatch)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
const uint256 bestBlock = uint256S("0xff0000000000000000000000000000000000000000000000000000000000000a");
|
||||
const uint256 nullifier = uint256S("0xcc0000000000000000000000000000000000000000000000000000000000000b");
|
||||
|
||||
CCoinsViewDB src(1 << 20, true);
|
||||
uint256 anchorRoot;
|
||||
PopulateChainstate(src, bestBlock, anchorRoot, nullifier);
|
||||
|
||||
boost::filesystem::path path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
CUTXOSnapshotHeader header = MakeHeader(bestBlock, anchorRoot);
|
||||
uint256 dumpHash; std::string err;
|
||||
ASSERT_TRUE(src.DumpSnapshot(path.string(), header, dumpHash, err)) << err;
|
||||
|
||||
// A wrong "trusted" hash must be refused.
|
||||
CCoinsViewDB dst(1 << 20, true);
|
||||
CUTXOSnapshotHeader h2; uint256 hh;
|
||||
uint256 wrong = uint256S("0x1234");
|
||||
EXPECT_FALSE(dst.LoadSnapshot(path.string(), wrong, /*fRequireExpected=*/true, h2, hh, err));
|
||||
// The correct hash must pass.
|
||||
EXPECT_TRUE(dst.LoadSnapshot(path.string(), dumpHash, /*fRequireExpected=*/true, h2, hh, err)) << err;
|
||||
|
||||
boost::filesystem::remove(path);
|
||||
}
|
||||
|
||||
TEST(UTXOSnapshot, RejectsCorruptedFile)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
const uint256 bestBlock = uint256S("0xff0000000000000000000000000000000000000000000000000000000000000c");
|
||||
const uint256 nullifier = uint256S("0xcc0000000000000000000000000000000000000000000000000000000000000d");
|
||||
|
||||
CCoinsViewDB src(1 << 20, true);
|
||||
uint256 anchorRoot;
|
||||
PopulateChainstate(src, bestBlock, anchorRoot, nullifier);
|
||||
|
||||
boost::filesystem::path path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
CUTXOSnapshotHeader header = MakeHeader(bestBlock, anchorRoot);
|
||||
uint256 dumpHash; std::string err;
|
||||
ASSERT_TRUE(src.DumpSnapshot(path.string(), header, dumpHash, err)) << err;
|
||||
|
||||
// Flip a byte near the end (inside the coins/anchor payload, before the trailing hash).
|
||||
{
|
||||
boost::filesystem::fstream f(path, std::ios::in | std::ios::out | std::ios::binary);
|
||||
f.seekg(0, std::ios::end);
|
||||
std::streamoff sz = f.tellg();
|
||||
ASSERT_GT(sz, 40);
|
||||
f.seekg(sz - 40);
|
||||
char c; f.read(&c, 1);
|
||||
f.seekp(sz - 40);
|
||||
c = (char)(c ^ 0xff);
|
||||
f.write(&c, 1);
|
||||
}
|
||||
|
||||
CCoinsViewDB dst(1 << 20, true);
|
||||
CUTXOSnapshotHeader h2; uint256 hh;
|
||||
EXPECT_FALSE(dst.LoadSnapshot(path.string(), uint256(), /*fRequireExpected=*/false, h2, hh, err));
|
||||
|
||||
boost::filesystem::remove(path);
|
||||
}
|
||||
@@ -580,98 +580,70 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
|
||||
char pchBuf[0x10000];
|
||||
bool bIsSSL = false;
|
||||
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) {
|
||||
if (nRecvBase + nPassBytes > (int64_t)ReceiveFloodSize())
|
||||
break;
|
||||
{
|
||||
LOCK(pnode->cs_hSocket);
|
||||
|
||||
if (pnode->hSocket == INVALID_SOCKET) {
|
||||
LogPrint("tls", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
|
||||
return -1;
|
||||
}
|
||||
{
|
||||
LOCK(pnode->cs_hSocket);
|
||||
|
||||
bIsSSL = (pnode->ssl != NULL);
|
||||
|
||||
if (bIsSSL) {
|
||||
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));
|
||||
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
|
||||
} else {
|
||||
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
|
||||
nRet = WSAGetLastError();
|
||||
}
|
||||
if (pnode->hSocket == INVALID_SOCKET) {
|
||||
LogPrint("tls", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nBytes > 0) {
|
||||
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes)) {
|
||||
pnode->CloseSocketDisconnect();
|
||||
fKeepReading = false;
|
||||
}
|
||||
pnode->nLastRecv = GetTime();
|
||||
pnode->nRecvBytes += 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 && wolfSSL_pending(pnode->ssl) > 0);
|
||||
if (!fMore || ++nDrainReads >= MAX_DRAIN_READS)
|
||||
fKeepReading = false;
|
||||
}
|
||||
} else if (nBytes == 0) {
|
||||
bIsSSL = (pnode->ssl != NULL);
|
||||
|
||||
if (bIsSSL) {
|
||||
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));
|
||||
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
|
||||
} else {
|
||||
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
|
||||
nRet = WSAGetLastError();
|
||||
}
|
||||
}
|
||||
|
||||
if (nBytes > 0) {
|
||||
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
|
||||
pnode->CloseSocketDisconnect();
|
||||
pnode->nLastRecv = GetTime();
|
||||
pnode->nRecvBytes += nBytes;
|
||||
pnode->RecordBytesRecv(nBytes);
|
||||
} else if (nBytes == 0) {
|
||||
|
||||
if (bIsSSL) {
|
||||
unsigned long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read err: %s\n",
|
||||
__FILE__, __func__, __LINE__, error_str);
|
||||
}
|
||||
// socket closed gracefully (peer disconnected)
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
|
||||
pnode->CloseSocketDisconnect();
|
||||
|
||||
} else if (nBytes < 0) {
|
||||
// error
|
||||
if (bIsSSL) {
|
||||
if (nRet != WOLFSSL_ERROR_WANT_READ && nRet != WOLFSSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrintf("TLS: ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
|
||||
pnode->CloseSocketDisconnect();
|
||||
|
||||
if (bIsSSL) {
|
||||
unsigned long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read err: %s\n",
|
||||
__FILE__, __func__, __LINE__, error_str);
|
||||
}
|
||||
// socket closed gracefully (peer disconnected)
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
|
||||
pnode->CloseSocketDisconnect();
|
||||
fKeepReading = false;
|
||||
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read - code[0x%x], err: %s\n",
|
||||
__FILE__, __func__, __LINE__, nRet, error_str);
|
||||
|
||||
} else if (nBytes < 0) {
|
||||
// error
|
||||
if (bIsSSL) {
|
||||
if (nRet != WOLFSSL_ERROR_WANT_READ && nRet != WOLFSSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrintf("TLS: ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
|
||||
pnode->CloseSocketDisconnect();
|
||||
|
||||
unsigned long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read - code[0x%x], err: %s\n",
|
||||
__FILE__, __func__, __LINE__, nRet, error_str);
|
||||
|
||||
} else {
|
||||
// preventive measure from exhausting CPU usage
|
||||
MilliSleep(1); // 1 msec
|
||||
}
|
||||
} else {
|
||||
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) {
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrintf("TLS: ERROR: socket recv %s\n", NetworkErrorString(nRet));
|
||||
pnode->CloseSocketDisconnect();
|
||||
}
|
||||
// preventive measure from exhausting CPU usage
|
||||
MilliSleep(1); // 1 msec
|
||||
}
|
||||
} else {
|
||||
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) {
|
||||
if (!pnode->fDisconnect)
|
||||
LogPrintf("TLS: ERROR: socket recv %s\n", NetworkErrorString(nRet));
|
||||
pnode->CloseSocketDisconnect();
|
||||
}
|
||||
fKeepReading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ int32_t notarizedtxid_height(char *dest,char *txidstr,int32_t *hushnotarized_hei
|
||||
params[0] = 0;
|
||||
*hushnotarized_heightp = 0;
|
||||
if ( strcmp(dest,"HUSH3") == 0 ) {
|
||||
port = DRAGONX_PORT;
|
||||
port = HUSH3_PORT;
|
||||
userpass = HUSHUSERPASS;
|
||||
} else if ( strcmp(dest,"BTC") == 0 )
|
||||
{
|
||||
@@ -498,7 +498,7 @@ int32_t hush_verifynotarization(char *symbol,char *dest,int32_t height,int32_t N
|
||||
{
|
||||
if ( SMART_CHAIN_SYMBOL[0] != 0 )
|
||||
{
|
||||
jsonstr = hush_issuemethod(HUSHUSERPASS,(char *)"getrawtransaction",params,DRAGONX_PORT);
|
||||
jsonstr = hush_issuemethod(HUSHUSERPASS,(char *)"getrawtransaction",params,HUSH3_PORT);
|
||||
//printf("userpass.(%s) got (%s)\n",HUSHUSERPASS,jsonstr);
|
||||
}
|
||||
}//else jsonstr = _dex_getrawtransaction();
|
||||
@@ -1693,11 +1693,6 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height)
|
||||
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckEquihashSolution failed\n",slowflag,height);
|
||||
return(-1);
|
||||
}
|
||||
if ( !CheckRandomXSolution(pblock, height) )
|
||||
{
|
||||
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckRandomXSolution failed\n",slowflag,height);
|
||||
return(-1);
|
||||
}
|
||||
hash = pblock->GetHash();
|
||||
bnTarget.SetCompact(pblock->nBits,&fNegative,&fOverflow);
|
||||
bhash = UintToArith256(hash);
|
||||
|
||||
@@ -568,7 +568,6 @@ extern uint64_t ASSETCHAINS_SUPPLY, ASSETCHAINS_FOUNDERS_REWARD;
|
||||
extern int32_t ASSETCHAINS_LWMAPOS, ASSETCHAINS_SAPLING, ASSETCHAINS_OVERWINTER,ASSETCHAINS_BLOCKTIME;
|
||||
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
|
||||
extern uint32_t ASSETCHAINS_ALGO,ASSETCHAINS_EQUIHASH,ASSETCHAINS_RANDOMX, HUSH_INITDONE;
|
||||
extern int32_t ASSETCHAINS_RANDOMX_VALIDATION;
|
||||
extern int32_t HUSH_MININGTHREADS,HUSH_LONGESTCHAIN,ASSETCHAINS_SEED,IS_HUSH_NOTARY,USE_EXTERNAL_PUBKEY,HUSH_CHOSEN_ONE,HUSH_ON_DEMAND,HUSH_PASSPORT_INITDONE,ASSETCHAINS_STAKED,HUSH_NSPV;
|
||||
extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_LASTERA,ASSETCHAINS_CBOPRET;
|
||||
extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_NOTARY_PAY[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[],ASSETCHAINS_NK[2];
|
||||
|
||||
@@ -93,7 +93,6 @@ uint64_t ASSETCHAINS_NONCEMASK[] = {0xffff};
|
||||
uint32_t ASSETCHAINS_NONCESHIFT[] = {32};
|
||||
uint32_t ASSETCHAINS_HASHESPERROUND[] = {1};
|
||||
uint32_t ASSETCHAINS_ALGO = _ASSETCHAINS_EQUIHASH;
|
||||
int32_t ASSETCHAINS_RANDOMX_VALIDATION = -1; // activation height for RandomX validation (-1 = disabled)
|
||||
// min diff returned from GetNextWorkRequired needs to be added here for each algo, so they can work with ac_staked.
|
||||
uint32_t ASSETCHAINS_MINDIFF[] = {537857807};
|
||||
int32_t ASSETCHAINS_LWMAPOS = 0; // percentage of blocks should be PoS
|
||||
@@ -102,7 +101,7 @@ int32_t ASSETCHAINS_OVERWINTER = -1;
|
||||
int32_t ASSETCHAINS_STAKED;
|
||||
uint64_t ASSETCHAINS_COMMISSION,ASSETCHAINS_SUPPLY = 10,ASSETCHAINS_FOUNDERS_REWARD;
|
||||
uint32_t HUSH_INITDONE;
|
||||
char HUSHUSERPASS[8192+512+1],BTCUSERPASS[8192]; uint16_t DRAGONX_PORT = 21769,BITCOIND_RPCPORT = 21769;
|
||||
char HUSHUSERPASS[8192+512+1],BTCUSERPASS[8192]; uint16_t HUSH3_PORT = 18031,BITCOIND_RPCPORT = 18031;
|
||||
uint64_t PENDING_HUSH_TX;
|
||||
extern int32_t HUSH_LOADINGBLOCKS;
|
||||
unsigned int MAX_BLOCK_SIGOPS = 20000;
|
||||
|
||||
109
src/hush_utils.h
109
src/hush_utils.h
@@ -1413,20 +1413,20 @@ void hush_configfile(char *symbol,uint16_t rpcport)
|
||||
#ifdef _WIN32
|
||||
while ( fname[strlen(fname)-1] != '\\' )
|
||||
fname[strlen(fname)-1] = 0;
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
strcat(fname,"HUSH3.conf");
|
||||
#else
|
||||
while ( fname[strlen(fname)-1] != '/' )
|
||||
fname[strlen(fname)-1] = 0;
|
||||
#ifdef __APPLE__
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
strcat(fname,"HUSH3.conf");
|
||||
#else
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
strcat(fname,"HUSH3.conf");
|
||||
#endif
|
||||
#endif
|
||||
if ( (fp= fopen(fname,"rb")) != 0 )
|
||||
{
|
||||
if ( (hushport= _hush_userpass(username,password,fp)) != 0 )
|
||||
DRAGONX_PORT = hushport;
|
||||
HUSH3_PORT = hushport;
|
||||
sprintf(HUSHUSERPASS,"%s:%s",username,password);
|
||||
fclose(fp);
|
||||
//printf("HUSH.(%s) -> userpass.(%s)\n",fname,HUSHUSERPASS);
|
||||
@@ -1790,28 +1790,38 @@ void hush_args(char *argv0)
|
||||
}
|
||||
|
||||
|
||||
name = GetArg("-ac_name","DRAGONX");
|
||||
name = GetArg("-ac_name","HUSH3");
|
||||
fprintf(stderr,".oO Starting %s Full Node (Extreme Privacy!) with genproc=%d notary=%d\n",name.c_str(),HUSH_MININGTHREADS, IS_HUSH_NOTARY);
|
||||
|
||||
vector<string> DRAGONX_nodes = {};
|
||||
// Only DRAGONX connects to these by default, other chains must opt-in via -connect/-addnode
|
||||
const bool isdragonx = strncmp(name.c_str(), "DRAGONX",7) == 0 ? true : false;
|
||||
vector<string> HUSH_nodes = {};
|
||||
// Only HUSH3 uses these by default, other HACs must opt-in via -connect/-addnode
|
||||
const bool ishush3 = strncmp(name.c_str(), "HUSH3",5) == 0 ? true : false;
|
||||
|
||||
LogPrint("net", "%s: isdragonx=%d\n", __func__, isdragonx);
|
||||
if (isdragonx) {
|
||||
DRAGONX_nodes = {"node1.dragonx.is","node2.dragonx.is","node3.dragonx.is",
|
||||
"node4.dragonx.is","node5.dragonx.is"
|
||||
LogPrint("net", "%s: ishush3=%d\n", __func__, ishush3);
|
||||
if (ishush3) {
|
||||
HUSH_nodes = {"node1.hush.is","node2.hush.is","node3.hush.is",
|
||||
"node4.hush.is","node5.hush.is","node6.hush.is",
|
||||
"node7.hush.is","node8.hush.is",
|
||||
"178.250.189.141",
|
||||
"31.202.19.157",
|
||||
"45.132.75.69",
|
||||
"45.63.58.167",
|
||||
"b2dln7mw7ydnuopls444tuixujhcw5kn5o22cna6gqfmw2fl6drb5nad.onion",
|
||||
"dslbaa5gut5kapqtd44pbg65tpl5ydsamfy62hjbldhfsvk64qs57pyd.onion",
|
||||
"vsqdumnh5khjbrzlxoeucbkiuaictdzyc3ezjpxpp2ph3gfwo2ptjmyd.onion",
|
||||
"plrobkepqjxs2cmig273mxnqh3qhuhdaioyb2n5kafn264ramb7tqxid.onion"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
vector<string> more_nodes = mapMultiArgs["-addnode"];
|
||||
if (more_nodes.size() > 0) {
|
||||
fprintf(stderr,"%s: Adding %lu more nodes via custom -addnode arguments\n", __func__, more_nodes.size() );
|
||||
}
|
||||
// Add default DRAGONX nodes after custom addnodes, if applicable
|
||||
if(DRAGONX_nodes.size() > 0) {
|
||||
LogPrint("net", "%s: adding %d DRAGONX hostname-based nodes\n", __func__, DRAGONX_nodes.size() );
|
||||
more_nodes.insert( more_nodes.end(), DRAGONX_nodes.begin(), DRAGONX_nodes.end() );
|
||||
// Add default HUSH nodes after custom addnodes, if applicable
|
||||
if(HUSH_nodes.size() > 0) {
|
||||
LogPrint("net", "%s: adding %d HUSH3 hostname-based nodes\n", __func__, HUSH_nodes.size() );
|
||||
more_nodes.insert( more_nodes.end(), HUSH_nodes.begin(), HUSH_nodes.end() );
|
||||
}
|
||||
|
||||
mapMultiArgs["-addnode"] = more_nodes;
|
||||
@@ -1820,15 +1830,10 @@ void hush_args(char *argv0)
|
||||
WITNESS_CACHE_SIZE = MAX_REORG_LENGTH+10;
|
||||
ASSETCHAINS_CC = GetArg("-ac_cc",0);
|
||||
HUSH_CCACTIVATE = GetArg("-ac_ccactivate",0);
|
||||
|
||||
// Set defaults based on chain
|
||||
int default_blocktime = isdragonx ? 36 : 60;
|
||||
int default_private = isdragonx ? 1 : 0;
|
||||
|
||||
ASSETCHAINS_BLOCKTIME = GetArg("-ac_blocktime", default_blocktime);
|
||||
// We do not support ac_public=1 chains, DragonX is a platform for privacy
|
||||
ASSETCHAINS_BLOCKTIME = GetArg("-ac_blocktime",60);
|
||||
// We do not support ac_public=1 chains, Hush is a platform for privacy
|
||||
ASSETCHAINS_PUBLIC = 0;
|
||||
ASSETCHAINS_PRIVATE = GetArg("-ac_private", default_private);
|
||||
ASSETCHAINS_PRIVATE = GetArg("-ac_private",0);
|
||||
HUSH_SNAPSHOT_INTERVAL = GetArg("-ac_snapshot",0);
|
||||
Split(GetArg("-ac_nk",""), sizeof(ASSETCHAINS_NK)/sizeof(*ASSETCHAINS_NK), ASSETCHAINS_NK, 0);
|
||||
|
||||
@@ -1866,9 +1871,7 @@ void hush_args(char *argv0)
|
||||
ASSETCHAINS_EARLYTXIDCONTRACT = GetArg("-ac_earlytxidcontract",0);
|
||||
if ( name.c_str()[0] != 0 )
|
||||
{
|
||||
// Default algo is randomx for DRAGONX, equihash for others
|
||||
std::string default_algo = isdragonx ? "randomx" : std::string(ASSETCHAINS_ALGORITHMS[0]);
|
||||
std::string selectedAlgo = GetArg("-ac_algo", default_algo);
|
||||
std::string selectedAlgo = GetArg("-ac_algo", std::string(ASSETCHAINS_ALGORITHMS[0]));
|
||||
|
||||
for ( int i = 0; i < ASSETCHAINS_NUMALGOS; i++ )
|
||||
{
|
||||
@@ -1897,20 +1900,12 @@ void hush_args(char *argv0)
|
||||
|
||||
// Set our symbol from -ac_name value
|
||||
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
|
||||
|
||||
// Set RandomX validation activation height per chain
|
||||
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
|
||||
if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0) {
|
||||
ASSETCHAINS_RANDOMX_VALIDATION = 2838976; // TBD: set to coordinated upgrade height
|
||||
} else if (strncmp(SMART_CHAIN_SYMBOL, "TUMIN", 5) == 0) {
|
||||
ASSETCHAINS_RANDOMX_VALIDATION = 1200; // TBD: set to coordinated upgrade height
|
||||
} else {
|
||||
ASSETCHAINS_RANDOMX_VALIDATION = 1; // all other RandomX HACs: enforce from height 1
|
||||
}
|
||||
printf("ASSETCHAINS_RANDOMX_VALIDATION set to %d for %s\n", ASSETCHAINS_RANDOMX_VALIDATION, SMART_CHAIN_SYMBOL);
|
||||
}
|
||||
const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
|
||||
|
||||
ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1);
|
||||
if(ishush3) {
|
||||
ASSETCHAINS_LASTERA = 3;
|
||||
}
|
||||
if ( ASSETCHAINS_LASTERA < 1 || ASSETCHAINS_LASTERA > ASSETCHAINS_MAX_ERAS )
|
||||
{
|
||||
ASSETCHAINS_LASTERA = 1;
|
||||
@@ -1944,13 +1939,33 @@ void hush_args(char *argv0)
|
||||
ASSETCHAINS_SCRIPTPUB = GetArg("-ac_script","");
|
||||
|
||||
|
||||
fprintf(stderr,"%s: Setting custom %s reward isdragonx=%d reward,halving,subsidy chain values...\n",__func__, SMART_CHAIN_SYMBOL, isdragonx);
|
||||
if(isdragonx) {
|
||||
// DragonX chain parameters (previously set via wrapper script)
|
||||
// -ac_name=DRAGONX -ac_algo=randomx -ac_halving=3500000 -ac_reward=300000000 -ac_blocktime=36 -ac_private=1
|
||||
fprintf(stderr,"%s: Setting custom %s reward HUSH3=%d reward,halving,subsidy chain values...\n",__func__, SMART_CHAIN_SYMBOL, ishush3);
|
||||
if(ishush3) {
|
||||
// Migrated from hushd script
|
||||
ASSETCHAINS_CC = 2;
|
||||
ASSETCHAINS_BLOCKTIME = 150; // this will change to 75 at the correct block
|
||||
ASSETCHAINS_COMMISSION = 11111111;
|
||||
// 6250000 - (Sprout pool at block 500,000)
|
||||
ASSETCHAINS_SUPPLY = 6178674;
|
||||
ASSETCHAINS_FOUNDERS = 1;
|
||||
ASSETCHAINS_SAPLING = 1;
|
||||
ASSETCHAINS_REWARD[0] = 300000000; // 3 DRAGONX per block
|
||||
ASSETCHAINS_HALVING[0] = 3500000; // halving every 3.5M blocks
|
||||
// this corresponds to FR address RHushEyeDm7XwtaTWtyCbjGQumYyV8vMjn
|
||||
ASSETCHAINS_SCRIPTPUB = "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac";
|
||||
// we do not want to change the magic of HUSH3 mainnet so we do not call devtax_scriptpub_for_height() here,
|
||||
// instead we call it whenever ASSETCHAINS_SCRIPTPUB is used later on
|
||||
|
||||
// Over-ride HUSH3 values from CLI params. Changing our blocktime to 75s changes things
|
||||
ASSETCHAINS_REWARD[0] = 0;
|
||||
ASSETCHAINS_REWARD[1] = 1125000000;
|
||||
ASSETCHAINS_REWARD[2] = 281250000; // 2.8125 HUSH goes to miners per block after 1st halving at Block 340K
|
||||
ASSETCHAINS_REWARD[3] = 140625000; // 1.40625 HUSH after 2nd halving at Block 2020000
|
||||
ASSETCHAINS_HALVING[0] = 129;
|
||||
ASSETCHAINS_HALVING[1] = GetArg("-z2zheight",340000);
|
||||
ASSETCHAINS_HALVING[2] = 2020000; // 2020000 = 340000 + 1680000 (1st halving block plus new halving interval)
|
||||
ASSETCHAINS_HALVING[3] = 3700000; // ASSETCHAINS_HALVING[2] + 1680000;
|
||||
ASSETCHAINS_ENDSUBSIDY[0] = 129;
|
||||
ASSETCHAINS_ENDSUBSIDY[1] = GetArg("-z2zheight",340000);
|
||||
ASSETCHAINS_ENDSUBSIDY[2] = 2*5422111; // TODO: Fix this, twice the previous end of rewards is an estimate
|
||||
}
|
||||
Split(GetArg("-ac_decay",""), sizeof(ASSETCHAINS_DECAY)/sizeof(*ASSETCHAINS_DECAY), ASSETCHAINS_DECAY, 0);
|
||||
Split(GetArg("-ac_notarypay",""), sizeof(ASSETCHAINS_NOTARY_PAY)/sizeof(*ASSETCHAINS_NOTARY_PAY), ASSETCHAINS_NOTARY_PAY, 0);
|
||||
@@ -2465,11 +2480,11 @@ void hush_args(char *argv0)
|
||||
void hush_nameset(char *symbol,char *dest,char *source)
|
||||
{
|
||||
if ( source[0] == 0 ) {
|
||||
strcpy(symbol,(char *)"DRAGONX");
|
||||
strcpy(symbol,(char *)"HUSH3");
|
||||
strcpy(dest,(char *)"BTC");
|
||||
} else {
|
||||
strcpy(symbol,source);
|
||||
strcpy(dest,(char *)"DRAGONX");
|
||||
strcpy(dest,(char *)"HUSH3");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
199
src/init.cpp
199
src/init.cpp
@@ -43,7 +43,6 @@
|
||||
#endif
|
||||
#include "main.h"
|
||||
#include "metrics.h"
|
||||
#include "pow.h"
|
||||
#include "miner.h"
|
||||
#include "net.h"
|
||||
#include "rpc/server.h"
|
||||
@@ -177,7 +176,7 @@ public:
|
||||
// Writes do not need similar protection, as failure to write is handled by the caller.
|
||||
};
|
||||
|
||||
CCoinsViewDB *pcoinsdbview = NULL; // global (declared extern in main.h) for UTXO-snapshot dump/load
|
||||
static CCoinsViewDB *pcoinsdbview = NULL;
|
||||
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
|
||||
static boost::scoped_ptr<ECCVerifyHandle> globalVerifyHandle;
|
||||
|
||||
@@ -378,8 +377,8 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
|
||||
strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), 288));
|
||||
strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), 3));
|
||||
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'DragonX'"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.conf"));
|
||||
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'GoldenSandtrout'"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
|
||||
if (mode == HMM_BITCOIND)
|
||||
{
|
||||
#if !defined(WIN32)
|
||||
@@ -388,17 +387,14 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||
}
|
||||
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("-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("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
|
||||
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));
|
||||
strUsage += HelpMessageOpt("-loadutxosnapshot=<file>", _("On a fresh node (empty chainstate), load a trusted UTXO snapshot produced by 'dumptxoutset' and fast-forward the tip to its height, skipping replay of earlier blocks. Block headers up to that height must already be present (e.g. via header sync or bootstrap). Blocks above the snapshot are still fully validated."));
|
||||
strUsage += HelpMessageOpt("-loadutxosnapshotunsafe", _("Allow -loadutxosnapshot even when no trusted snapshot hash is hardcoded for this network (verifies file integrity only, not authenticity). Testing/regtest only."));
|
||||
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("-maxreorg=<n>", _("Specify the maximum length of a blockchain re-organization"));
|
||||
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)"),
|
||||
-(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 sync (0 = inline only, max %d, default: same as -par)"), MAX_SCRIPTCHECK_THREADS));
|
||||
#ifndef _WIN32
|
||||
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "hushd.pid"));
|
||||
#endif
|
||||
@@ -609,7 +605,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||
strUsage += HelpMessageOpt("-stratumallowip=<ip>", _("Allow Stratum work requests from specified source. Valid for <ip> are a 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 option can be specified multiple times"));
|
||||
|
||||
// "ac" stands for "affects consensus" or Arrakis Chain
|
||||
strUsage += HelpMessageGroup(_("DragonX Chain options:"));
|
||||
strUsage += HelpMessageGroup(_("Hush Arrakis Chain options:"));
|
||||
strUsage += HelpMessageOpt("-ac_algo", _("Choose PoW mining algorithm, either 'equihash' or 'randomx'. default is Equihash (200,9)"));
|
||||
strUsage += HelpMessageOpt("-ac_blocktime", _("Block time in seconds, default is 60"));
|
||||
strUsage += HelpMessageOpt("-ac_beam", _("BEAM integration"));
|
||||
@@ -991,123 +987,6 @@ bool AppInitServers(boost::thread_group& threadGroup)
|
||||
*/
|
||||
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. Lock-free: it only reads system memory and writes
|
||||
// the aligned size_t threshold that the flush path reads.
|
||||
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)
|
||||
{
|
||||
//fprintf(stderr,"%s start\n", __FUNCTION__);
|
||||
@@ -1430,15 +1309,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
else if (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;
|
||||
|
||||
fServer = GetBoolArg("-server", false);
|
||||
//fprintf(stderr,"%s tik6\n", __FUNCTION__);
|
||||
|
||||
@@ -1675,14 +1545,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
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__);
|
||||
|
||||
// Start the lightweight task scheduler thread
|
||||
@@ -1757,7 +1619,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
return InitError(strprintf("User Agent comment (%s) contains unsafe characters.", cmt));
|
||||
uacomments.push_back(SanitizeString(cmt, SAFE_CHARS_UA_COMMENT));
|
||||
}
|
||||
strSubVersion = FormatSubVersion(GetArg("-clientname","DragonX"), CLIENT_VERSION, uacomments);
|
||||
strSubVersion = FormatSubVersion(GetArg("-clientname","GoldenSandtrout"), CLIENT_VERSION, uacomments);
|
||||
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
|
||||
return InitError(strprintf("Total length of network version string %i exceeds maximum of %i characters. Reduce the number and/or size of uacomments.",
|
||||
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
|
||||
@@ -1978,7 +1840,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
LogPrintf("* Compression is %s\n", dbCompression ? "enabled" : "disabled");
|
||||
|
||||
// cache size calculations
|
||||
int64_t nTotalCache = (GetArg("-dbcache", GetDefaultDbCacheMB()) << 20);
|
||||
int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
|
||||
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
|
||||
int64_t nBlockTreeDBCache = nTotalCache / 8;
|
||||
@@ -1995,14 +1857,6 @@ 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
|
||||
nTotalCache -= nCoinDBCache;
|
||||
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("* Max cache setting possible %.1fMiB\n", nMaxDbCache);
|
||||
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
|
||||
@@ -2084,45 +1938,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
strLoadError = _("Error initializing block database");
|
||||
break;
|
||||
}
|
||||
|
||||
// Trusted UTXO snapshot fast-sync (assumeutxo-style). If -loadutxosnapshot is given
|
||||
// and the chainstate is still empty, load the verified snapshot and fast-forward the
|
||||
// tip to height H; blocks above H then sync with full PoW/script/Sapling validation.
|
||||
{
|
||||
std::string snapPath = GetArg("-loadutxosnapshot", "");
|
||||
if (!snapPath.empty()) {
|
||||
if (!pcoinsdbview->GetBestBlock().IsNull()) {
|
||||
LogPrintf("%s: -loadutxosnapshot ignored, chainstate is not empty\n", __func__);
|
||||
} else {
|
||||
const CChainParams::AssumeutxoData& au = chainparams.Assumeutxo();
|
||||
bool unsafe = GetBoolArg("-loadutxosnapshotunsafe", false);
|
||||
if (au.IsNull() && !unsafe) {
|
||||
strLoadError = _("-loadutxosnapshot: no trusted snapshot hash is configured for this network; refusing (use -loadutxosnapshotunsafe for testing only)");
|
||||
break;
|
||||
}
|
||||
CUTXOSnapshotHeader hdr; uint256 gotHash; std::string snapErr;
|
||||
bool requireExpected = !au.IsNull() && !unsafe;
|
||||
if (!pcoinsdbview->LoadSnapshot(snapPath, au.hash, requireExpected, hdr, gotHash, snapErr)) {
|
||||
strLoadError = strprintf(_("Failed to load UTXO snapshot: %s"), snapErr);
|
||||
break;
|
||||
}
|
||||
if (!au.IsNull() && hdr.nHeight != au.height) {
|
||||
strLoadError = _("UTXO snapshot height does not match the trusted value for this network");
|
||||
break;
|
||||
}
|
||||
pcoinsTip->SetBestBlock(hdr.baseBlockHash); // refresh cache view of the freshly-written chainstate
|
||||
std::string fixErr;
|
||||
if (!LoadSnapshotChainstate(hdr, fixErr)) {
|
||||
strLoadError = strprintf(_("Failed to activate UTXO snapshot tip: %s"), fixErr);
|
||||
break;
|
||||
}
|
||||
pblocktree->WriteAssumeutxoHeight(hdr.nHeight); // persist reorg-below-H guard across restarts
|
||||
LogPrintf("%s: loaded trusted UTXO snapshot at height %d (hash %s); syncing forward with full validation\n",
|
||||
__func__, hdr.nHeight, gotHash.GetHex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HUSH_LOADINGBLOCKS = 0;
|
||||
// Check for changed -txindex state
|
||||
if (fTxIndex != GetBoolArg("-txindex", true)) {
|
||||
|
||||
@@ -51,9 +51,8 @@ namespace port {
|
||||
|
||||
// Mac OS
|
||||
#elif defined(OS_MACOSX)
|
||||
#include <atomic>
|
||||
inline void MemoryBarrier() {
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
OSMemoryBarrier();
|
||||
}
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
|
||||
BIN
src/libcc.dylib
BIN
src/libcc.dylib
Binary file not shown.
167
src/main.cpp
167
src/main.cpp
@@ -85,12 +85,10 @@ void hush_pricesupdate(int32_t height,CBlock *pblock);
|
||||
BlockMap mapBlockIndex;
|
||||
CChain chainActive;
|
||||
CBlockIndex *pindexBestHeader = NULL;
|
||||
int nAssumeutxoSnapshotHeight = -1; // height H of a loaded UTXO snapshot; reorgs below H are refused (-1 = none)
|
||||
static int64_t nTimeBestReceived = 0;
|
||||
CWaitableCriticalSection csBestBlock;
|
||||
CConditionVariable cvBlockChange;
|
||||
int nScriptCheckThreads = 0;
|
||||
int nRandomXVerifyThreads = 0; // parallel RandomX pre-verification worker count (0 = inline only)
|
||||
bool fExperimentalMode = true;
|
||||
bool fImporting = false;
|
||||
bool fReindex = false;
|
||||
@@ -487,7 +485,7 @@ namespace {
|
||||
|
||||
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
|
||||
* at most count entries. */
|
||||
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller, CBlockIndex** pFrontierStuck = NULL) {
|
||||
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller) {
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
@@ -564,9 +562,8 @@ namespace {
|
||||
return;
|
||||
}
|
||||
} else if (waitingfor == -1) {
|
||||
// This is the first already-in-flight block (the download frontier).
|
||||
// This is the first already-in-flight block.
|
||||
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
|
||||
if (pFrontierStuck) *pFrontierStuck = pindex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4143,45 +4140,6 @@ static void PruneBlockIndexCandidates() {
|
||||
assert(!setBlockIndexCandidates.empty());
|
||||
}
|
||||
|
||||
// Activate a trusted UTXO snapshot (assumeutxo-style) as the chain tip WITHOUT replaying blocks
|
||||
// 0..H. The chainstate has already been populated by CCoinsViewDB::LoadSnapshot(); here we mark the
|
||||
// snapshot's base block (height H) as fully validated and set it as the active tip. Blocks above H
|
||||
// then connect normally with full PoW + script + Sapling-proof validation. Requires that the block
|
||||
// HEADERS for height H are already present in mapBlockIndex (from prior header sync or bootstrap).
|
||||
// NOTE: below-H blocks have no body/undo data, so reorgs below H are impossible (see Stage D guard).
|
||||
bool LoadSnapshotChainstate(const CUTXOSnapshotHeader& header, std::string& strError)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
BlockMap::iterator it = mapBlockIndex.find(header.baseBlockHash);
|
||||
if (it == mapBlockIndex.end() || it->second == NULL) {
|
||||
strError = "block header for the snapshot height is not present; sync headers (or use the bootstrap) before loading a UTXO snapshot";
|
||||
return false;
|
||||
}
|
||||
CBlockIndex* pindexH = it->second;
|
||||
if (pindexH->GetHeight() != header.nHeight) {
|
||||
strError = "snapshot base block height does not match its header index";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only nChainTx is consensus-relevant for tip selection; nTx must merely be non-zero so the
|
||||
// (nChainTx != 0) candidate-eligibility checks hold. Ancestors legitimately have nTx==0 here
|
||||
// because we never received their bodies — this is the assumeutxo trust assumption.
|
||||
if (pindexH->nTx == 0)
|
||||
pindexH->nTx = (header.nChainTx > 0 ? (unsigned int)header.nChainTx : 1);
|
||||
pindexH->nChainTx = (unsigned int)header.nChainTx;
|
||||
if (header.fHasChainSaplingValue)
|
||||
pindexH->nChainSaplingValue = header.nChainSaplingValue;
|
||||
|
||||
pindexH->RaiseValidity(BLOCK_VALID_SCRIPTS);
|
||||
nAssumeutxoSnapshotHeight = pindexH->GetHeight(); // arm the reorg-below-H guard (Stage D)
|
||||
setBlockIndexCandidates.insert(pindexH);
|
||||
chainActive.SetTip(pindexH);
|
||||
if (pindexBestHeader == NULL || pindexBestHeader->GetHeight() < pindexH->GetHeight())
|
||||
pindexBestHeader = pindexH;
|
||||
PruneBlockIndexCandidates();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to make some progress towards making pindexMostWork the active block.
|
||||
* pblock is either NULL or a pointer to a CBlock corresponding to pindexMostWork.
|
||||
@@ -4230,16 +4188,6 @@ 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),
|
||||
REJECT_INVALID, "past-notarized-height");
|
||||
}
|
||||
// Refuse reorgs whose fork point is below a loaded UTXO snapshot height (Stage D): the node has
|
||||
// no block/undo data for 0..H, so disconnecting below H is impossible. Belt-and-suspenders on top
|
||||
// of checkpoint fork-rejection (H sits at/below the last hardcoded checkpoint).
|
||||
if ( nAssumeutxoSnapshotHeight >= 0 && pindexFork != 0 && pindexFork->GetHeight() < nAssumeutxoSnapshotHeight )
|
||||
{
|
||||
return state.DoS(100, error("ActivateBestChainStep(): reorg fork height %d is below the loaded UTXO snapshot height %d; refusing",
|
||||
(int32_t)pindexFork->GetHeight(), nAssumeutxoSnapshotHeight),
|
||||
REJECT_INVALID, "below-assumeutxo-snapshot");
|
||||
}
|
||||
|
||||
// - 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,
|
||||
// then pindexFork will be null, and we would need to remove the entire chain including
|
||||
@@ -4310,36 +4258,6 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
|
||||
}
|
||||
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.
|
||||
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
|
||||
if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
|
||||
@@ -5075,12 +4993,6 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
|
||||
{
|
||||
if ( !CheckEquihashSolution(&blockhdr, Params()) )
|
||||
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution");
|
||||
// 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");
|
||||
}
|
||||
// Check proof of work matches claimed amount
|
||||
/*hush_index2pubkey33(pubkey33,pindex,height);
|
||||
@@ -5193,14 +5105,7 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
|
||||
|
||||
assert(pindexPrev);
|
||||
|
||||
// For HUSH3, nBits validation starts above the original DAA fork height (450000).
|
||||
// For DragonX, nBits was never validated before the standalone binary, so the
|
||||
// chain contains blocks with incorrect nBits during the vulnerable window
|
||||
// (diff reset at RANDOMX_VALIDATION height through the attack at ~2879907).
|
||||
// Set daaForkHeight past that window so fresh sync accepts historical blocks.
|
||||
bool isdragonx = strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0;
|
||||
int defaultDaaForkHeight = isdragonx ? ASSETCHAINS_RANDOMX_VALIDATION + 62000 : 450000;
|
||||
int daaForkHeight = GetArg("-daaforkheight", defaultDaaForkHeight);
|
||||
int daaForkHeight = GetArg("-daaforkheight", 450000);
|
||||
int nHeight = pindexPrev->GetHeight()+1;
|
||||
bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
|
||||
// Check Proof-of-Work difficulty
|
||||
@@ -5233,26 +5138,6 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
|
||||
}
|
||||
}
|
||||
|
||||
// Check Proof-of-Work difficulty for smart chains (HACs)
|
||||
// Without this check, an attacker can submit blocks with arbitrary nBits
|
||||
// (e.g., powLimit / diff=1) and they will be accepted, allowing the chain
|
||||
// to be flooded with minimum-difficulty blocks.
|
||||
// Only enforce above daaForkHeight to avoid consensus mismatch with early
|
||||
// chain blocks that were mined by a different binary version.
|
||||
if (!ishush3 && SMART_CHAIN_SYMBOL[0] != 0 && nHeight > daaForkHeight) {
|
||||
unsigned int nNextWork = GetNextWorkRequired(pindexPrev, &block, consensusParams);
|
||||
if (fDebug) {
|
||||
LogPrintf("%s: HAC nbits height=%d expected=%lu actual=%lu\n",
|
||||
__func__, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits);
|
||||
}
|
||||
if (block.nBits != nNextWork) {
|
||||
return state.DoS(100,
|
||||
error("%s: Incorrect diffbits for %s at height %d: expected %lu got %lu",
|
||||
__func__, SMART_CHAIN_SYMBOL, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits),
|
||||
REJECT_INVALID, "bad-diffbits");
|
||||
}
|
||||
}
|
||||
|
||||
// Check timestamp against prev
|
||||
if (ASSETCHAINS_ADAPTIVEPOW <= 0 || nHeight < 30) {
|
||||
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast() )
|
||||
@@ -6043,15 +5928,6 @@ bool static LoadBlockIndexDB()
|
||||
pblocktree->ReadReindexing(fReindexing);
|
||||
fReindex |= fReindexing;
|
||||
|
||||
// Restore the loaded-UTXO-snapshot height so the reorg-below-H guard survives restarts.
|
||||
{
|
||||
int snapHeight = -1;
|
||||
if (pblocktree->ReadAssumeutxoHeight(snapHeight) && snapHeight >= 0) {
|
||||
nAssumeutxoSnapshotHeight = snapHeight;
|
||||
LogPrintf("%s: loaded-from-UTXO-snapshot height is %d; reorgs below it are refused\n", __func__, snapHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether we have a transaction index
|
||||
pblocktree->ReadFlag("txindex", fTxIndex);
|
||||
LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled");
|
||||
@@ -6841,13 +6717,6 @@ void static ProcessGetData(CNode* pfrom)
|
||||
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
|
||||
|
||||
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);
|
||||
|
||||
@@ -6965,10 +6834,7 @@ void static ProcessGetData(CNode* pfrom)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -8250,35 +8116,12 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
||||
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
||||
vector<CBlockIndex*> vToDownload;
|
||||
NodeId staller = -1;
|
||||
CBlockIndex *pFrontierStuck = NULL;
|
||||
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, &pFrontierStuck);
|
||||
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
|
||||
BOOST_FOREACH(CBlockIndex *pindex, vToDownload) {
|
||||
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
||||
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
|
||||
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(staller)->nStallingSince == 0) {
|
||||
State(staller)->nStallingSince = nNow;
|
||||
|
||||
20
src/main.h
20
src/main.h
@@ -100,11 +100,7 @@ static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
|
||||
/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */
|
||||
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
|
||||
* 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). */
|
||||
* less than this number, we reached its tip. Changing this value is a protocol upgrade. */
|
||||
static const unsigned int MAX_HEADERS_RESULTS = 160;
|
||||
/** 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
|
||||
@@ -159,7 +155,6 @@ extern bool fExperimentalMode;
|
||||
extern bool fImporting;
|
||||
extern bool fReindex;
|
||||
extern int nScriptCheckThreads;
|
||||
extern int nRandomXVerifyThreads;
|
||||
extern bool fTxIndex;
|
||||
extern bool fZindex;
|
||||
extern bool fIsBareMultisigStd;
|
||||
@@ -935,19 +930,6 @@ extern CChain chainActive;
|
||||
/** Global variable that points to the active CCoinsView (protected by cs_main) */
|
||||
extern CCoinsViewCache *pcoinsTip;
|
||||
|
||||
/** Global variable that points to the coins database (chainstate/, protected by cs_main).
|
||||
* Exposed for the UTXO-snapshot (assumeutxo-style) dump/load paths. */
|
||||
class CCoinsViewDB;
|
||||
extern CCoinsViewDB *pcoinsdbview;
|
||||
|
||||
/** Activate a trusted UTXO snapshot (already written to the chainstate DB by LoadSnapshot) as the
|
||||
* chain tip at its height H, without replaying blocks 0..H. Headers for H must already exist. */
|
||||
struct CUTXOSnapshotHeader;
|
||||
bool LoadSnapshotChainstate(const CUTXOSnapshotHeader& header, std::string& strError);
|
||||
/** Height H of a loaded UTXO snapshot (assumeutxo). Reorgs whose fork point is below H are refused
|
||||
* because the node has no block/undo data for 0..H. -1 means no snapshot is in effect. */
|
||||
extern int nAssumeutxoSnapshotHeight;
|
||||
|
||||
/** Global variable that points to the active block tree (protected by cs_main) */
|
||||
extern CBlockTreeDB *pblocktree;
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ int printMiningStatus(bool mining)
|
||||
lines++;
|
||||
} else {
|
||||
std::cout << _("You are currently not mining.") << std::endl;
|
||||
std::cout << _("To enable mining, add 'gen=1' to your DRAGONX.conf and restart.") << std::endl;
|
||||
std::cout << _("To enable mining, add 'gen=1' to your HUSH3.conf and restart.") << std::endl;
|
||||
lines += 2;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
385
src/miner.cpp
385
src/miner.cpp
@@ -23,7 +23,6 @@
|
||||
#include "pow/tromp/equi_miner.h"
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include "amount.h"
|
||||
#include "chainparams.h"
|
||||
#include "consensus/consensus.h"
|
||||
@@ -52,7 +51,6 @@
|
||||
#include "transaction_builder.h"
|
||||
#include "sodium.h"
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/thread/shared_mutex.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#ifdef ENABLE_MINING
|
||||
#include <functional>
|
||||
@@ -1013,213 +1011,8 @@ enum RandomXSolverCancelCheck
|
||||
Reason2
|
||||
};
|
||||
|
||||
int GetRandomXInterval();
|
||||
int GetRandomXBlockLag();
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
static void LogProcessMemory(const char* label) {
|
||||
// Use K32GetProcessMemoryInfo from kernel32.dll (available on Win7+)
|
||||
// to avoid linking psapi.lib
|
||||
typedef struct {
|
||||
DWORD cb;
|
||||
DWORD PageFaultCount;
|
||||
SIZE_T PeakWorkingSetSize;
|
||||
SIZE_T WorkingSetSize;
|
||||
SIZE_T QuotaPeakPagedPoolUsage;
|
||||
SIZE_T QuotaPagedPoolUsage;
|
||||
SIZE_T QuotaPeakNonPagedPoolUsage;
|
||||
SIZE_T QuotaNonPagedPoolUsage;
|
||||
SIZE_T PagefileUsage;
|
||||
SIZE_T PeakPagefileUsage;
|
||||
SIZE_T PrivateUsage;
|
||||
} PMC_EX;
|
||||
typedef BOOL (WINAPI *PFN)(HANDLE, PMC_EX*, DWORD);
|
||||
static PFN pfn = (PFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "K32GetProcessMemoryInfo");
|
||||
if (pfn) {
|
||||
PMC_EX pmc = {};
|
||||
pmc.cb = sizeof(pmc);
|
||||
if (pfn(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
LogPrintf("MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n",
|
||||
label,
|
||||
pmc.WorkingSetSize / (1024.0 * 1024.0),
|
||||
pmc.PrivateUsage / (1024.0 * 1024.0),
|
||||
pmc.PagefileUsage / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void LogProcessMemory(const char* label) {
|
||||
// Linux: read /proc/self/status
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (f) {
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strncmp(line, "VmRSS:", 6) == 0 || strncmp(line, "VmSize:", 7) == 0) {
|
||||
// Remove newline
|
||||
line[strlen(line)-1] = '\0';
|
||||
LogPrintf("MemDiag [%s]: %s\n", label, line);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shared RandomX dataset manager — all miner threads share a single ~2GB dataset
|
||||
// instead of each allocating their own. The dataset is read-only after initialization
|
||||
// and RandomX explicitly supports multiple VMs sharing one dataset.
|
||||
struct RandomXDatasetManager {
|
||||
randomx_flags flags;
|
||||
randomx_cache *cache;
|
||||
randomx_dataset *dataset;
|
||||
unsigned long datasetItemCount;
|
||||
std::string currentKey;
|
||||
std::mutex mtx; // protects Init/Shutdown/CreateVM
|
||||
boost::shared_mutex datasetMtx; // readers-writer lock: shared for hashing, exclusive for rebuild
|
||||
bool initialized;
|
||||
|
||||
RandomXDatasetManager() : flags(randomx_get_flags()), cache(nullptr), dataset(nullptr),
|
||||
datasetItemCount(0), initialized(false) {}
|
||||
|
||||
bool Init() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (initialized) return true;
|
||||
|
||||
flags |= RANDOMX_FLAG_FULL_MEM;
|
||||
LogPrintf("RandomXDatasetManager: flags=0x%x (JIT=%d, HARD_AES=%d, FULL_MEM=%d, LARGE_PAGES=%d)\n",
|
||||
(int)flags,
|
||||
!!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES),
|
||||
!!(flags & RANDOMX_FLAG_FULL_MEM), !!(flags & RANDOMX_FLAG_LARGE_PAGES));
|
||||
|
||||
LogProcessMemory("before cache alloc");
|
||||
|
||||
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cache alloc failed with large pages, trying without...\n");
|
||||
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cache alloc failed with secure, trying basic...\n");
|
||||
cache = randomx_alloc_cache(flags);
|
||||
if (cache == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cannot allocate cache!\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
LogProcessMemory("after cache alloc");
|
||||
|
||||
// Try to allocate dataset with large pages first for better performance
|
||||
dataset = randomx_alloc_dataset(flags | RANDOMX_FLAG_LARGE_PAGES);
|
||||
if (dataset == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: dataset alloc failed with large pages, trying without...\n");
|
||||
dataset = randomx_alloc_dataset(flags);
|
||||
if (dataset == nullptr) {
|
||||
LogPrintf("RandomXDatasetManager: cannot allocate dataset!\n");
|
||||
randomx_release_cache(cache);
|
||||
cache = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
datasetItemCount = randomx_dataset_item_count();
|
||||
initialized = true;
|
||||
LogProcessMemory("after dataset alloc");
|
||||
// Log the actual memory addresses to help diagnose sharing issues
|
||||
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
|
||||
size_t datasetSize = datasetItemCount * RANDOMX_DATASET_ITEM_SIZE;
|
||||
LogPrintf("RandomXDatasetManager: allocated shared dataset:\n");
|
||||
LogPrintf(" - Dataset struct at: %p\n", (void*)dataset);
|
||||
LogPrintf(" - Dataset memory at: %p (size: %.2f GB)\n", (void*)datasetMemory, datasetSize / (1024.0 * 1024.0 * 1024.0));
|
||||
LogPrintf(" - Items: %lu, Item size: %d bytes\n", datasetItemCount, RANDOMX_DATASET_ITEM_SIZE);
|
||||
LogPrintf(" - Expected total process memory: ~%.2f GB + ~2MB per mining thread\n", datasetSize / (1024.0 * 1024.0 * 1024.0));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize cache with a key and rebuild the dataset.
|
||||
// Thread-safe: acquires exclusive lock so all hashing threads must finish first.
|
||||
void UpdateKey(const void *key, size_t keySize) {
|
||||
std::string newKey((const char*)key, keySize);
|
||||
|
||||
// Fast check with shared lock — skip if key hasn't changed
|
||||
{
|
||||
boost::shared_lock<boost::shared_mutex> readLock(datasetMtx);
|
||||
if (newKey == currentKey) return; // already up to date
|
||||
}
|
||||
|
||||
// Acquire exclusive lock — blocks until all hashing threads release their shared locks
|
||||
boost::unique_lock<boost::shared_mutex> writeLock(datasetMtx);
|
||||
// Double-check after acquiring exclusive lock (another thread may have rebuilt first)
|
||||
if (newKey == currentKey) return;
|
||||
|
||||
LogPrintf("RandomXDatasetManager: updating key (size=%lu)\n", keySize);
|
||||
randomx_init_cache(cache, key, keySize);
|
||||
currentKey = newKey;
|
||||
|
||||
// Rebuild dataset using all available CPU threads
|
||||
const int initThreadCount = std::thread::hardware_concurrency();
|
||||
if (initThreadCount > 1) {
|
||||
std::vector<std::thread> threads;
|
||||
uint32_t startItem = 0;
|
||||
const auto perThread = datasetItemCount / initThreadCount;
|
||||
const auto remainder = datasetItemCount % initThreadCount;
|
||||
for (int i = 0; i < initThreadCount; ++i) {
|
||||
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
||||
threads.push_back(std::thread(&randomx_init_dataset, dataset, cache, startItem, count));
|
||||
startItem += count;
|
||||
}
|
||||
for (unsigned i = 0; i < threads.size(); ++i) {
|
||||
threads[i].join();
|
||||
}
|
||||
} else {
|
||||
randomx_init_dataset(dataset, cache, 0, datasetItemCount);
|
||||
}
|
||||
LogPrintf("RandomXDatasetManager: dataset rebuilt\n");
|
||||
LogProcessMemory("after dataset init");
|
||||
}
|
||||
|
||||
// Creates a per-thread VM using the shared dataset.
|
||||
// Caller must hold a shared lock on datasetMtx.
|
||||
// The VM itself is small (~2MB scratchpad + ~84KB JIT code) — the ~2GB dataset is shared via pointer.
|
||||
// VMs should be created ONCE per thread and reused across blocks to avoid
|
||||
// heap fragmentation on Windows (repeated 2MB alloc/free causes address-space bloat).
|
||||
randomx_vm *CreateVM() {
|
||||
static std::atomic<int> vmCount{0};
|
||||
LogProcessMemory("before CreateVM");
|
||||
randomx_vm *vm = randomx_create_vm(flags, nullptr, dataset);
|
||||
if (vm != nullptr) {
|
||||
int id = ++vmCount;
|
||||
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
|
||||
LogPrintf("RandomXDatasetManager: VM #%d created — VM at %p, shared dataset at %p\n",
|
||||
id, (void*)vm, (void*)datasetMemory);
|
||||
LogPrintf(" Per-thread overhead: ~2MB scratchpad + ~84KB JIT (dataset NOT copied)\n");
|
||||
LogProcessMemory("after CreateVM");
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (dataset != nullptr) {
|
||||
randomx_release_dataset(dataset);
|
||||
dataset = nullptr;
|
||||
}
|
||||
if (cache != nullptr) {
|
||||
randomx_release_cache(cache);
|
||||
cache = nullptr;
|
||||
}
|
||||
initialized = false;
|
||||
currentKey.clear();
|
||||
LogPrintf("RandomXDatasetManager: shutdown complete\n");
|
||||
}
|
||||
|
||||
~RandomXDatasetManager() {
|
||||
Shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
// Global shared dataset manager, created by GenerateBitcoins before spawning miner threads
|
||||
static RandomXDatasetManager *g_rxDatasetManager = nullptr;
|
||||
int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); }
|
||||
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
void static RandomXMiner(CWallet *pwallet)
|
||||
@@ -1257,12 +1050,33 @@ void static RandomXMiner()
|
||||
);
|
||||
miningTimer.start();
|
||||
|
||||
// Use the shared dataset manager — no per-thread dataset allocation
|
||||
if (g_rxDatasetManager == nullptr || !g_rxDatasetManager->initialized) {
|
||||
LogPrintf("HushRandomXMiner: shared dataset manager not initialized, aborting!\n");
|
||||
randomx_flags flags = randomx_get_flags();
|
||||
flags |= RANDOMX_FLAG_FULL_MEM;
|
||||
randomx_cache *randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE );
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, trying without large pages...\n");
|
||||
randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, trying without secure...\n");
|
||||
}
|
||||
randomxCache = randomx_alloc_cache(flags);
|
||||
if (randomxCache == NULL) {
|
||||
LogPrintf("RandomX cache is null, cannot mine!\n");
|
||||
}
|
||||
}
|
||||
|
||||
rxdebug("%s: created randomx flags + cache\n");
|
||||
randomx_dataset *randomxDataset = randomx_alloc_dataset(flags);
|
||||
rxdebug("%s: created dataset\n");
|
||||
|
||||
if( randomxDataset == nullptr) {
|
||||
LogPrintf("%s: allocating randomx dataset failed!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
auto datasetItemCount = randomx_dataset_item_count();
|
||||
rxdebug("%s: dataset items=%lu\n", datasetItemCount);
|
||||
|
||||
char randomxHash[RANDOMX_HASH_SIZE];
|
||||
rxdebug("%s: created randomxHash of size %d\n", RANDOMX_HASH_SIZE);
|
||||
char randomxKey[82]; // randomx spec says keysize of >60 bytes is implementation-specific
|
||||
@@ -1333,37 +1147,48 @@ void static RandomXMiner()
|
||||
|
||||
// fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag);
|
||||
rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
|
||||
// Update the shared dataset key — only one thread will actually rebuild,
|
||||
// others will see the key is already current and skip.
|
||||
// Use the initial key at the start of the chain, until the first key block
|
||||
if( (Mining_height) < randomxInterval + randomxBlockLag) {
|
||||
g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
|
||||
rxdebug("%s: updated shared dataset with initial key\n");
|
||||
randomx_init_cache(randomxCache, &randomxKey, sizeof randomxKey);
|
||||
rxdebug("%s: initialized cache with initial key\n");
|
||||
} else {
|
||||
rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
|
||||
// At heights between intervals, we use the same block key and wait randomxBlockLag blocks until changing
|
||||
const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||
uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
|
||||
g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
|
||||
randomx_init_cache(randomxCache, &randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: initialized cache with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
}
|
||||
|
||||
// Create a per-thread VM once and reuse across blocks.
|
||||
// The VM just stores a pointer to the shared dataset — the pointer
|
||||
// remains valid across key changes since UpdateKey rebuilds the dataset
|
||||
// contents in-place without reallocating. Reusing the VM avoids
|
||||
// repeated 2MB scratchpad + 84KB JIT alloc/free churn that causes
|
||||
// Windows heap fragmentation and apparent memory growth per thread.
|
||||
if (myVM == nullptr) {
|
||||
// First iteration: acquire shared lock briefly to create VM
|
||||
boost::shared_lock<boost::shared_mutex> initLock(g_rxDatasetManager->datasetMtx);
|
||||
myVM = g_rxDatasetManager->CreateVM();
|
||||
if (myVM == nullptr) {
|
||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||
return;
|
||||
const int initThreadCount = std::thread::hardware_concurrency();
|
||||
if(initThreadCount > 1) {
|
||||
rxdebug("%s: initializing dataset with %d threads\n", initThreadCount);
|
||||
std::vector<std::thread> threads;
|
||||
uint32_t startItem = 0;
|
||||
const auto perThread = datasetItemCount / initThreadCount;
|
||||
const auto remainder = datasetItemCount % initThreadCount;
|
||||
for (int i = 0; i < initThreadCount; ++i) {
|
||||
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
|
||||
threads.push_back(std::thread(&randomx_init_dataset, randomxDataset, randomxCache, startItem, count));
|
||||
startItem += count;
|
||||
}
|
||||
for (unsigned i = 0; i < threads.size(); ++i) {
|
||||
threads[i].join();
|
||||
}
|
||||
threads.clear();
|
||||
} else {
|
||||
rxdebug("%s: initializing dataset with 1 thread\n");
|
||||
randomx_init_dataset(randomxDataset, randomxCache, 0, datasetItemCount);
|
||||
}
|
||||
|
||||
// Acquire shared lock to prevent dataset rebuild while we're hashing
|
||||
boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
|
||||
rxdebug("%s: dataset initialized\n");
|
||||
|
||||
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
|
||||
if(myVM == NULL) {
|
||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||
return;
|
||||
}
|
||||
//fprintf(stderr,"RandomXMiner: Mining_start=%u\n", Mining_start);
|
||||
#ifdef ENABLE_WALLET
|
||||
CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, 0);
|
||||
@@ -1443,17 +1268,16 @@ void static RandomXMiner()
|
||||
arith_uint256 hashTarget;
|
||||
hashTarget = HASHTarget;
|
||||
|
||||
CRandomXInput rxInput(pblocktemplate->block);
|
||||
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Serialize block header without nSolution but with nNonce for deterministic RandomX input
|
||||
randomxInput << rxInput;
|
||||
// Use the current block as randomx input
|
||||
randomxInput << pblocktemplate->block;
|
||||
|
||||
// std::cerr << "RandomXMiner: randomxInput=" << HexStr(randomxInput) << "\n";
|
||||
// fprintf(stderr,"RandomXMiner: created randomxKey=%s , randomxInput.size=%lu\n", randomxKey, randomxInput.size() ); //randomxInput);
|
||||
rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str());
|
||||
|
||||
rxdebug("%s: calculating randomx hash\n");
|
||||
randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
|
||||
randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash);
|
||||
rxdebug("%s: calculated randomx hash\n");
|
||||
|
||||
rxdebug("%s: randomxHash=");
|
||||
@@ -1500,28 +1324,14 @@ void static RandomXMiner()
|
||||
|
||||
CValidationState state;
|
||||
//{ LOCK(cs_main);
|
||||
// Skip RandomX re-validation during TestBlockValidity — we already
|
||||
// computed the correct hash, and re-verifying allocates ~256MB which
|
||||
// can trigger the OOM killer on memory-constrained systems.
|
||||
SetSkipRandomXValidation(true);
|
||||
bool fValid = TestBlockValidity(state,B, chainActive.LastTip(), true, false);
|
||||
SetSkipRandomXValidation(false);
|
||||
if ( !fValid )
|
||||
if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
|
||||
{
|
||||
h = UintToArith256(B.GetHash());
|
||||
fprintf(stderr,"RandomXMiner: TestBlockValidity FAILED at ht.%d nNonce=%s hash=",
|
||||
Mining_height, pblock->nNonce.ToString().c_str());
|
||||
fprintf(stderr,"RandomXMiner: Invalid randomx block mined, try again ");
|
||||
for (z=31; z>=0; z--)
|
||||
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
|
||||
fprintf(stderr," nSolution.size=%lu\n", B.nSolution.size());
|
||||
// Dump nSolution hex for comparison with validator
|
||||
fprintf(stderr,"RandomXMiner: nSolution=");
|
||||
for (unsigned i = 0; i < B.nSolution.size(); i++)
|
||||
fprintf(stderr,"%02x", B.nSolution[i]);
|
||||
fprintf(stderr,"\n");
|
||||
LogPrintf("RandomXMiner: TestBlockValidity FAILED at ht.%d, gotinvalid=1, state=%s\n",
|
||||
Mining_height, state.GetRejectReason());
|
||||
gotinvalid = 1;
|
||||
fprintf(stderr,"\n");
|
||||
return(false);
|
||||
}
|
||||
//}
|
||||
@@ -1589,24 +1399,21 @@ void static RandomXMiner()
|
||||
pblock->nBits = savebits;
|
||||
}
|
||||
|
||||
// Release shared lock so UpdateKey can acquire exclusive lock for dataset rebuild
|
||||
// VM is kept alive — its dataset pointer remains valid across rebuilds
|
||||
datasetLock.unlock();
|
||||
rxdebug("%s: going to destroy rx VM\n");
|
||||
randomx_destroy_vm(myVM);
|
||||
rxdebug("%s: destroyed VM\n");
|
||||
}
|
||||
|
||||
} catch (const boost::thread_interrupted&) {
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
||||
} else {
|
||||
LogPrintf("%s: WARNING myVM already null in thread interrupt handler, skipping destroy (would double-free)\n", __func__);
|
||||
fprintf(stderr, "%s: WARNING myVM already null in thread interrupt, would have double-freed!\n", __func__);
|
||||
}
|
||||
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||
randomx_destroy_vm(myVM);
|
||||
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset via thread interrupt\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache via thread interrupt\n");
|
||||
|
||||
LogPrintf("HushRandomXMiner terminated\n");
|
||||
throw;
|
||||
@@ -1615,21 +1422,20 @@ void static RandomXMiner()
|
||||
c.disconnect();
|
||||
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
|
||||
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
||||
}
|
||||
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
|
||||
randomx_destroy_vm(myVM);
|
||||
LogPrintf("%s: destroyed vm because of error\n", __func__);
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset because of error\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache because of error\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only destroy per-thread VM, dataset/cache are shared
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
}
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset in normal exit\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache in normal exit\n");
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
}
|
||||
@@ -2073,18 +1879,8 @@ void static BitcoinMiner()
|
||||
if (minerThreads != NULL)
|
||||
{
|
||||
minerThreads->interrupt_all();
|
||||
// Wait for all miner threads to fully terminate before destroying shared resources
|
||||
minerThreads->join_all();
|
||||
delete minerThreads;
|
||||
minerThreads = NULL;
|
||||
|
||||
// Shutdown shared RandomX dataset manager after all threads are done
|
||||
if (g_rxDatasetManager != nullptr) {
|
||||
g_rxDatasetManager->Shutdown();
|
||||
delete g_rxDatasetManager;
|
||||
g_rxDatasetManager = nullptr;
|
||||
LogPrintf("%s: destroyed shared RandomX dataset manager\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
if(fDebug)
|
||||
@@ -2099,21 +1895,6 @@ void static BitcoinMiner()
|
||||
|
||||
minerThreads = new boost::thread_group();
|
||||
|
||||
// Initialize shared RandomX dataset manager before spawning miner threads
|
||||
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
|
||||
g_rxDatasetManager = new RandomXDatasetManager();
|
||||
if (!g_rxDatasetManager->Init()) {
|
||||
LogPrintf("%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||
fprintf(stderr, "%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
|
||||
delete g_rxDatasetManager;
|
||||
g_rxDatasetManager = nullptr;
|
||||
delete minerThreads;
|
||||
minerThreads = NULL;
|
||||
return;
|
||||
}
|
||||
LogPrintf("%s: shared RandomX dataset manager initialized\n", __func__);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nThreads; i++) {
|
||||
#ifdef ENABLE_WALLET
|
||||
if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
#include "crypto/common.h"
|
||||
#include "hush/utiltls.h"
|
||||
#include <random.h>
|
||||
#include <random>
|
||||
#include <limits>
|
||||
#ifdef _WIN32
|
||||
#include <string.h>
|
||||
#else
|
||||
@@ -2006,7 +2004,7 @@ void ThreadMessageHandler()
|
||||
// Randomize the order in which we process messages from/to our peers.
|
||||
// This prevents attacks in which an attacker exploits having multiple
|
||||
// consecutive connections in the vNodes list.
|
||||
std::shuffle(vNodesCopy.begin(), vNodesCopy.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
|
||||
random_shuffle(vNodesCopy.begin(), vNodesCopy.end(), GetRandInt);
|
||||
|
||||
BOOST_FOREACH(CNode* pnode, vNodesCopy)
|
||||
{
|
||||
@@ -2518,7 +2516,7 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
|
||||
// We always round down, except when we have only 1 connection
|
||||
auto newSize = (vNodes.size() / 2) == 0 ? 1 : (vNodes.size() / 2);
|
||||
|
||||
std::shuffle( vRelayNodes.begin(), vRelayNodes.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
|
||||
random_shuffle( vRelayNodes.begin(), vRelayNodes.end(), GetRandInt );
|
||||
|
||||
vRelayNodes.resize(newSize);
|
||||
if (HUSH_TESTNODE==1 && vNodes.size() == 0) {
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <tuple>
|
||||
|
||||
constexpr uint64_t CNetAddr::V1_SERIALIZATION_SIZE;
|
||||
constexpr uint64_t CNetAddr::MAX_ADDRV2_SIZE;
|
||||
|
||||
/** check whether a given address is in a network we can probably connect to */
|
||||
bool CNetAddr::IsReachableNetwork() {
|
||||
|
||||
295
src/pow.cpp
295
src/pow.cpp
@@ -18,7 +18,6 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
#include "pow.h"
|
||||
#include "checkpoints.h"
|
||||
#include "consensus/upgrades.h"
|
||||
#include "arith_uint256.h"
|
||||
#include "chain.h"
|
||||
@@ -29,10 +28,6 @@
|
||||
#include "uint256.h"
|
||||
#include "util.h"
|
||||
#include "sodium.h"
|
||||
#include "RandomX/src/randomx.h"
|
||||
#include <mutex>
|
||||
#include <boost/thread/shared_mutex.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
|
||||
#ifdef ENABLE_RUST
|
||||
#include "librustzcash.h"
|
||||
@@ -318,16 +313,6 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
|
||||
if (pindexLast == NULL )
|
||||
return nProofOfWorkLimit;
|
||||
|
||||
// DragonX difficulty reset at the RANDOMX_VALIDATION activation height.
|
||||
// The chain transitioned to a new binary at this height and difficulty was
|
||||
// reset to minimum (powLimit). Without this, fresh-syncing nodes compute
|
||||
// a different nBits from GetNextWorkRequired (based on pre-reset blocks)
|
||||
// and reject the on-chain min-diff block, banning all seed nodes.
|
||||
if (ASSETCHAINS_RANDOMX_VALIDATION > 0 && pindexLast->GetHeight() + 1 == ASSETCHAINS_RANDOMX_VALIDATION) {
|
||||
LogPrintf("%s: difficulty reset to powLimit at height %d\n", __func__, ASSETCHAINS_RANDOMX_VALIDATION);
|
||||
return nProofOfWorkLimit;
|
||||
}
|
||||
|
||||
//{
|
||||
// Comparing to pindexLast->nHeight with >= because this function
|
||||
// returns the work required for the block after pindexLast.
|
||||
@@ -352,7 +337,6 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
|
||||
memset(zflags,0,sizeof(zflags));
|
||||
if ( pindexLast != 0 )
|
||||
height = (int32_t)pindexLast->GetHeight() + 1;
|
||||
|
||||
if ( ASSETCHAINS_ADAPTIVEPOW > 0 && pindexFirst != 0 && pblock != 0 && height >= (int32_t)(sizeof(ct)/sizeof(*ct)) )
|
||||
{
|
||||
tipdiff = (pblock->nTime - pindexFirst->nTime);
|
||||
@@ -699,270 +683,6 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetRandomXInterval() { return GetArg("-ac_randomx_interval", 1024); }
|
||||
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
|
||||
|
||||
// Cached RandomX validation state — reused across calls, protected by mutex
|
||||
static std::mutex cs_randomx_validator;
|
||||
static randomx_cache *s_rxCache = nullptr;
|
||||
static randomx_vm *s_rxVM = nullptr;
|
||||
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
|
||||
// The miner already computed the correct RandomX hash — re-verifying with a separate
|
||||
// cache+VM would allocate ~256MB extra memory and can trigger the OOM killer.
|
||||
thread_local bool fSkipRandomXValidation = false;
|
||||
|
||||
void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
|
||||
|
||||
CBlockIndex *hush_chainactive(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)
|
||||
{
|
||||
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
|
||||
return false;
|
||||
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
|
||||
return false;
|
||||
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
|
||||
return false;
|
||||
extern int32_t HUSH_LOADINGBLOCKS;
|
||||
if (HUSH_LOADINGBLOCKS != 0)
|
||||
return false;
|
||||
extern bool fCheckpointsEnabled;
|
||||
if (fCheckpointsEnabled && height < Checkpoints::GetTotalBlocksEstimate(Params().Checkpoints()))
|
||||
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;
|
||||
|
||||
// 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)
|
||||
return true;
|
||||
|
||||
// nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
|
||||
if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
|
||||
return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
|
||||
pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
|
||||
}
|
||||
|
||||
// Derive the key (shared helper) and serialize the input (identical bytes to the pool path).
|
||||
std::string rxKey = GetRandomXKey(height);
|
||||
if (rxKey.empty())
|
||||
return error("CheckRandomXSolution(): cannot derive RandomX key for height %d", height);
|
||||
std::vector<unsigned char> ssInput = GetRandomXInput(*pblock);
|
||||
|
||||
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);
|
||||
|
||||
// Initialize cache + VM if needed, or re-init if key changed
|
||||
if (s_rxCache == nullptr) {
|
||||
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);
|
||||
}
|
||||
if (s_rxCache == nullptr) {
|
||||
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());
|
||||
s_rxCurrentKey = rxKey;
|
||||
fKeyInit = true;
|
||||
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
|
||||
if (s_rxVM == nullptr) {
|
||||
randomx_release_cache(s_rxCache);
|
||||
s_rxCache = nullptr;
|
||||
return error("CheckRandomXSolution(): failed to create RandomX VM");
|
||||
}
|
||||
} else if (s_rxCurrentKey != rxKey) {
|
||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||
s_rxCurrentKey = rxKey;
|
||||
fKeyInit = true;
|
||||
randomx_vm_set_cache(s_rxVM, s_rxCache);
|
||||
}
|
||||
|
||||
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
|
||||
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
|
||||
// Debug: dump both hashes for diagnosis
|
||||
std::string computedHex, solutionHex;
|
||||
for (int i = 0; i < RANDOMX_HASH_SIZE; i++) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x", (uint8_t)computedHash[i]);
|
||||
computedHex += buf;
|
||||
snprintf(buf, sizeof(buf), "%02x", pblock->nSolution[i]);
|
||||
solutionHex += buf;
|
||||
}
|
||||
fprintf(stderr, "CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
|
||||
fprintf(stderr, " computed : %s\n", computedHex.c_str());
|
||||
fprintf(stderr, " nSolution: %s\n", solutionHex.c_str());
|
||||
fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||
rxKey.size(), ssInput.size(), pblock->nNonce.ToString().c_str());
|
||||
fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n",
|
||||
pblock->nSolution.size(), RANDOMX_HASH_SIZE);
|
||||
// Also log to debug.log
|
||||
LogPrintf("CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
|
||||
LogPrintf(" computed : %s\n", computedHex);
|
||||
LogPrintf(" nSolution: %s\n", solutionHex);
|
||||
LogPrintf(" rxKey size=%lu, input size=%lu, nNonce=%s\n",
|
||||
rxKey.size(), ssInput.size(), pblock->nNonce.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
LogPrint("randomx", "CheckRandomXSolution(): valid at height %d\n", height);
|
||||
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_currentheight();
|
||||
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
||||
@@ -1006,19 +726,9 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
|
||||
// Check proof of work matches claimed amount
|
||||
if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget )
|
||||
{
|
||||
// During initial block loading/sync, skip PoW validation for blocks
|
||||
// before RandomX validation height. After activation, always validate
|
||||
// to prevent injection of blocks with fake PoW.
|
||||
if ( HUSH_LOADINGBLOCKS != 0 ) {
|
||||
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && ASSETCHAINS_RANDOMX_VALIDATION > 0 && height >= ASSETCHAINS_RANDOMX_VALIDATION) {
|
||||
// Fall through to reject the block — do NOT skip validation after activation
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ( HUSH_LOADINGBLOCKS != 0 )
|
||||
return true;
|
||||
|
||||
if ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 )
|
||||
{
|
||||
if ( Params().NetworkIDString() != "regtest" )
|
||||
{
|
||||
for (i=31; i>=0; i--)
|
||||
@@ -1035,7 +745,6 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
|
||||
fprintf(stderr," <- origpubkey\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/*for (i=31; i>=0; i--)
|
||||
fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
|
||||
|
||||
66
src/pow.h
66
src/pow.h
@@ -21,13 +21,8 @@
|
||||
#define HUSH_POW_H
|
||||
|
||||
#include "chain.h"
|
||||
#include "checkqueue.h"
|
||||
#include "consensus/params.h"
|
||||
#include <stdint.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class CBlockHeader;
|
||||
class CBlockIndex;
|
||||
@@ -43,67 +38,6 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
|
||||
/** Check whether the Equihash solution in a block header is valid */
|
||||
bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&);
|
||||
|
||||
/** Check whether a block header contains a valid RandomX solution */
|
||||
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) */
|
||||
void SetSkipRandomXValidation(bool skip);
|
||||
|
||||
/** Return the RandomX key rotation interval in blocks */
|
||||
int GetRandomXInterval();
|
||||
|
||||
/** Return the RandomX key change lag in blocks */
|
||||
int GetRandomXBlockLag();
|
||||
|
||||
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
|
||||
bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params);
|
||||
CChainPower GetBlockProof(const CBlockIndex& block);
|
||||
|
||||
@@ -237,33 +237,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom serializer for CBlockHeader that includes nNonce but omits nSolution,
|
||||
* for use as deterministic input to RandomX hashing.
|
||||
*/
|
||||
class CRandomXInput : private CBlockHeader
|
||||
{
|
||||
public:
|
||||
CRandomXInput(const CBlockHeader &header)
|
||||
{
|
||||
CBlockHeader::SetNull();
|
||||
*((CBlockHeader*)this) = header;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(this->nVersion);
|
||||
READWRITE(hashPrevBlock);
|
||||
READWRITE(hashMerkleRoot);
|
||||
READWRITE(hashFinalSaplingRoot);
|
||||
READWRITE(nTime);
|
||||
READWRITE(nBits);
|
||||
READWRITE(nNonce);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Describes a place in the block chain to another node such that if the
|
||||
* other node doesn't have the same branch, it can find a recent common trunk.
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
#include "rpc/server.h"
|
||||
#include "streams.h"
|
||||
#include "sync.h"
|
||||
#include "txdb.h"
|
||||
#include "util.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "script/script.h"
|
||||
#include "script/script_error.h"
|
||||
#include "script/sign.h"
|
||||
@@ -324,15 +322,6 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
|
||||
result.push_back(Pair("anchor", blockindex->hashFinalSproutRoot.GetHex()));
|
||||
result.push_back(Pair("blocktype", "mined"));
|
||||
|
||||
// Report block subsidy and fees separately so explorers don't have to
|
||||
// reimplement the reward schedule to display them.
|
||||
CAmount nSubsidy = GetBlockSubsidy(blockindex->GetHeight(), Params().GetConsensus());
|
||||
CAmount nCoinbase = block.vtx[0].GetValueOut();
|
||||
CAmount nFees = nCoinbase - nSubsidy;
|
||||
if (nFees < 0) nFees = 0; // block 1 has premine, avoid negative
|
||||
result.push_back(Pair("subsidy", ValueFromAmount(nSubsidy)));
|
||||
result.push_back(Pair("fees", ValueFromAmount(nFees)));
|
||||
|
||||
UniValue valuePools(UniValue::VARR);
|
||||
valuePools.push_back(ValuePoolDesc("sapling", blockindex->nChainSaplingValue, blockindex->nSaplingValue));
|
||||
result.push_back(Pair("valuePools", valuePools));
|
||||
@@ -862,77 +851,6 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp, const CPubKey& mypk
|
||||
return ret;
|
||||
}
|
||||
|
||||
UniValue dumptxoutset(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"dumptxoutset \"path\"\n"
|
||||
"\nWrite a trusted snapshot of the current chainstate (UTXO set + Sapling commitment\n"
|
||||
"trees, nullifier set and pool value) to disk. The snapshot can be loaded by a fresh\n"
|
||||
"node with -loadutxosnapshot=<file> to skip replaying the chain from genesis.\n"
|
||||
"\nThis is intended to be run at a final/checkpoint height; the node must be fully synced.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"path\" (string, required) path to write the snapshot file (must not already exist)\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"height\": n, (numeric) snapshot height H\n"
|
||||
" \"base_hash\": \"hex\", (string) block hash at height H\n"
|
||||
" \"snapshot_hash\": \"hex\", (string) content hash to hardcode for verification\n"
|
||||
" \"coins\": n, (numeric) number of UTXO records\n"
|
||||
" \"sapling_anchors\": n, (numeric) number of Sapling anchor records\n"
|
||||
" \"sapling_nullifiers\": n, (numeric) number of Sapling nullifier records\n"
|
||||
" \"path\": \"...\" (string) the file written\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("dumptxoutset", "/path/to/dragonx-utxo.dat")
|
||||
+ HelpExampleRpc("dumptxoutset", "\"/path/to/dragonx-utxo.dat\"")
|
||||
);
|
||||
|
||||
boost::filesystem::path path = boost::filesystem::absolute(params[0].get_str());
|
||||
if (boost::filesystem::exists(path))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "path already exists, refusing to overwrite: " + path.string());
|
||||
|
||||
LOCK(cs_main);
|
||||
|
||||
if (pcoinsdbview == nullptr || pcoinsTip == nullptr)
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "chainstate not available");
|
||||
|
||||
// Flush so the on-disk chainstate matches the in-memory tip before we iterate it.
|
||||
FlushStateToDisk();
|
||||
|
||||
CBlockIndex *tip = chainActive.Tip();
|
||||
if (tip == nullptr)
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "no chain tip");
|
||||
|
||||
CUTXOSnapshotHeader header;
|
||||
header.nMagic = UTXO_SNAPSHOT_MAGIC;
|
||||
header.nVersion = UTXO_SNAPSHOT_VERSION;
|
||||
memcpy(&header.nNetworkMagic, Params().MessageStart(), 4);
|
||||
header.baseBlockHash = tip->GetBlockHash();
|
||||
header.nHeight = tip->GetHeight();
|
||||
header.nChainTx = tip->nChainTx;
|
||||
if (tip->nChainSaplingValue) {
|
||||
header.fHasChainSaplingValue = 1;
|
||||
header.nChainSaplingValue = *tip->nChainSaplingValue;
|
||||
}
|
||||
header.bestSaplingAnchor = pcoinsdbview->GetBestAnchor(SAPLING);
|
||||
|
||||
uint256 snapshotHash;
|
||||
std::string strError;
|
||||
if (!pcoinsdbview->DumpSnapshot(path.string(), header, snapshotHash, strError))
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "dumptxoutset failed: " + strError);
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.push_back(Pair("height", (int64_t)header.nHeight));
|
||||
ret.push_back(Pair("base_hash", header.baseBlockHash.GetHex()));
|
||||
ret.push_back(Pair("snapshot_hash", snapshotHash.GetHex()));
|
||||
ret.push_back(Pair("coins", (int64_t)header.nCoins));
|
||||
ret.push_back(Pair("sapling_anchors", (int64_t)header.nSaplingAnchors));
|
||||
ret.push_back(Pair("sapling_nullifiers", (int64_t)header.nSaplingNullifiers));
|
||||
ret.push_back(Pair("path", path.string()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
UniValue getblockmerkletree(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
{
|
||||
if (fHelp || params.size() != 1 )
|
||||
@@ -1924,7 +1842,6 @@ static const CRPCCommand commands[] =
|
||||
{ "blockchain", "getrawmempool", &getrawmempool, true },
|
||||
{ "blockchain", "gettxout", &gettxout, true },
|
||||
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true },
|
||||
{ "blockchain", "dumptxoutset", &dumptxoutset, true },
|
||||
{ "blockchain", "verifychain", &verifychain, true },
|
||||
|
||||
/* Not shown in help */
|
||||
|
||||
@@ -169,7 +169,7 @@ UniValue getgenerate(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
throw runtime_error(
|
||||
"getgenerate\n"
|
||||
"\nReturn if the server is set to mine coins or not. The default is false.\n"
|
||||
"It is set with the command line argument -gen (or DRAGONX.conf setting gen).\n"
|
||||
"It is set with the command line argument -gen (or HUSH3.conf setting gen).\n"
|
||||
"It can also be set with the setgenerate call.\n"
|
||||
"\nResult\n"
|
||||
"{\n"
|
||||
|
||||
@@ -133,7 +133,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
" \"pingtime\": n, (numeric) ping time\n"
|
||||
" \"pingwait\": n, (numeric) ping wait\n"
|
||||
" \"version\": v, (numeric) The peer version, such as 170002\n"
|
||||
" \"subver\": \"/DragonX:x.y.z[-v]/\", (string) The string version\n"
|
||||
" \"subver\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) The string version\n"
|
||||
" \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n"
|
||||
" \"startingheight\": n, (numeric) The starting height (block) of the peer\n"
|
||||
" \"banscore\": n, (numeric) The ban score\n"
|
||||
@@ -505,7 +505,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"version\": xxxxx, (numeric) the server version\n"
|
||||
" \"subversion\": \"/DragonX:x.y.z[-v]/\", (string) the server subversion string\n"
|
||||
" \"subversion\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) the server subversion string\n"
|
||||
" \"protocolversion\": xxxxx, (numeric) the protocol version\n"
|
||||
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
|
||||
" \"timeoffset\": xxxxx, (numeric) the time offset (deprecated, always 0)\n"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"ac_perc": "11111111",
|
||||
"ac_eras": "3",
|
||||
"ac_script": "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac",
|
||||
"clientname": "DragonX",
|
||||
"clientname": "GoldenSandtrout",
|
||||
"addnode": [
|
||||
"1.1.1.1"
|
||||
]
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
// Copyright (c) 2016-2024 The Hush developers
|
||||
// Copyright (c) 2016-now 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 "key.h"
|
||||
#include "base58.h"
|
||||
#include "chainparams.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "crypto/common.h"
|
||||
//#include "testutils.h"
|
||||
#define BOOST_TEST_MODULE HushTestSuite
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
std::string notaryPubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47";
|
||||
std::string notarySecret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU";
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/*
|
||||
assert(init_and_check_sodium() != -1);
|
||||
ECC_Start();
|
||||
ECCVerifyHandle handle; // Inits secp256k1 verify context
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
|
||||
CBitcoinSecret vchSecret;
|
||||
// this returns false due to network prefix mismatch but works anyway
|
||||
vchSecret.SetString(notarySecret);
|
||||
CKey notaryKey = vchSecret.GetKey();
|
||||
*/
|
||||
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
// Test that libsodium has been initialized correctly
|
||||
BOOST_AUTO_TEST_CASE(test_sodium) {
|
||||
BOOST_CHECK_NE( init_and_check_sodium(), -1 );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ecc) {
|
||||
ECC_Start();
|
||||
BOOST_CHECK("created secp256k1 context");
|
||||
|
||||
ECCVerifyHandle handle; // Inits secp256k1 verify context
|
||||
// this value is currently private
|
||||
//BOOST_CHECK_EQUAL( ECCVerifyHandle::refcount, 1 );
|
||||
|
||||
ECC_Stop();
|
||||
BOOST_CHECK("destroyed secp256k1 context");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_nets) {
|
||||
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
BOOST_CHECK("regtest");
|
||||
SelectParams(CBaseChainParams::MAIN);
|
||||
BOOST_CHECK("mainnet");
|
||||
SelectParams(CBaseChainParams::TESTNET);
|
||||
BOOST_CHECK("testnet");
|
||||
}
|
||||
|
||||
44
src/test-hush/randomx.cpp
Normal file
44
src/test-hush/randomx.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2016-now 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 <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "addrman.h"
|
||||
#include <string>
|
||||
#include "netbase.h"
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include "RandomX/src/randomx.h"
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(randomx)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_basic) {
|
||||
randomx_flags flags = randomx_get_flags();
|
||||
randomx_cache *randomxCache = randomx_alloc_cache(flags);
|
||||
BOOST_CHECK_MESSAGE(randomxCache != NULL, "randomxCache is not null");
|
||||
|
||||
randomx_dataset *randomxDataset = randomx_alloc_dataset(flags);
|
||||
BOOST_CHECK_MESSAGE(randomxDataset != NULL, "randomxDataset is not null");
|
||||
|
||||
auto datasetItemCount = randomx_dataset_item_count();
|
||||
BOOST_CHECK_MESSAGE( datasetItemCount > 0, "datasetItemCount is positive");
|
||||
|
||||
// unknown location(0): fatal error: in "randomx/test_basic": memory access violation at address: 0x7f6983ce2000: invalid permissions
|
||||
|
||||
/* TODO
|
||||
randomx_init_dataset(randomxDataset, randomxCache, 0, datasetItemCount);
|
||||
|
||||
char randomxHash[RANDOMX_HASH_SIZE];
|
||||
randomx_vm *myVM = nullptr;
|
||||
|
||||
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
|
||||
BOOST_CHECK_MESSAGE(myVM != NULL, "randomx_vm is not null");
|
||||
|
||||
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
|
||||
randomxInput << "stuff and things";
|
||||
randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash);
|
||||
*/
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
#include "cc/betprotocol.h"
|
||||
#include "cc/eval.h"
|
||||
#include "base58.h"
|
||||
#include "core_io.h"
|
||||
#include "key.h"
|
||||
#include "main.h"
|
||||
#include "script/cc.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "script/interpreter.h"
|
||||
#include "script/serverchecker.h"
|
||||
#include "testutils.h"
|
||||
|
||||
extern int32_t hush_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
|
||||
|
||||
namespace TestEvalNotarization {
|
||||
|
||||
class EvalMock : public Eval
|
||||
{
|
||||
public:
|
||||
uint32_t nNotaries;
|
||||
uint8_t notaries[64][33];
|
||||
std::map<uint256, CTransaction> txs;
|
||||
std::map<uint256, CBlockIndex> blocks;
|
||||
|
||||
int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const
|
||||
{
|
||||
memcpy(pubkeys, notaries, sizeof(notaries));
|
||||
return nNotaries;
|
||||
}
|
||||
|
||||
bool GetTxUnconfirmed(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock) const
|
||||
{
|
||||
auto r = txs.find(hash);
|
||||
if (r != txs.end()) {
|
||||
txOut = r->second;
|
||||
if (blocks.count(hash) > 0)
|
||||
hashBlock = hash;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const
|
||||
{
|
||||
auto r = blocks.find(hash);
|
||||
if (r == blocks.end()) return false;
|
||||
blockIdx = r->second;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//static auto noop = [&](CMutableTransaction &mtx){};
|
||||
static auto noop = [](CMutableTransaction &mtx){};
|
||||
|
||||
|
||||
template<typename Modifier>
|
||||
void SetupEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify)
|
||||
{
|
||||
eval.nNotaries = hush_notaries(eval.notaries, 780060, 1522946781);
|
||||
|
||||
// make fake notary inputs
|
||||
notary.vin.resize(11);
|
||||
for (int i=0; i<notary.vin.size(); i++) {
|
||||
CMutableTransaction txIn;
|
||||
txIn.vout.resize(1);
|
||||
txIn.vout[0].scriptPubKey << VCH(eval.notaries[i*2], 33) << OP_CHECKSIG;
|
||||
notary.vin[i].prevout = COutPoint(txIn.GetHash(), 0);
|
||||
eval.txs[txIn.GetHash()] = CTransaction(txIn);
|
||||
}
|
||||
|
||||
modify(notary);
|
||||
|
||||
eval.txs[notary.GetHash()] = CTransaction(notary);
|
||||
eval.blocks[notary.GetHash()].SetHeight(780060);
|
||||
eval.blocks[notary.GetHash()].nTime = 1522946781;
|
||||
}
|
||||
|
||||
|
||||
// inputs have been dropped
|
||||
static auto rawNotaryTx = "01000000000290460100000000002321020e46e79a2a8d12b9b5d12c7a91adb4e454edfae43c0a0cb805427d2ac7613fd9ac0000000000000000506a4c4dae8e0f3e6e5de498a072f5967f3c418c4faba5d56ac8ce17f472d029ef3000008f2e0100424f545300050ba773f0bc31da5839fc7cb9bd7b87f3b765ca608e5cf66785a466659b28880500000000000000";
|
||||
CTransaction notaryTx;
|
||||
static bool init = DecodeHexTx(notaryTx, rawNotaryTx);
|
||||
|
||||
static uint256 proofTxHash = uint256S("37f76551a16093fbb0a92ee635bbd45b3460da8fd00cf7d5a6b20d93e727fe4c");
|
||||
static auto vMomProof = ParseHex("0303faecbdd4b3da128c2cd2701bb143820a967069375b2ec5b612f39bbfe78a8611978871c193457ab1e21b9520f4139f113b8d75892eb93ee247c18bccfd067efed7eacbfcdc8946cf22de45ad536ec0719034fb9bc825048fe6ab61fee5bd6e9aae0bb279738d46673c53d68eb2a72da6dbff215ee41a4d405a74ff7cd355805b"); // $ fiat/bots txMoMproof $proofTxHash
|
||||
|
||||
/*
|
||||
TEST(TestEvalNotarization, testGetNotarization)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
SetupEval(eval, notary, noop);
|
||||
|
||||
NotarizationData data;
|
||||
ASSERT_TRUE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
EXPECT_EQ(data.height, 77455);
|
||||
EXPECT_EQ(data.blockHash.GetHex(), "000030ef29d072f417cec86ad5a5ab4f8c413c7f96f572a098e45d6e3e0f8eae");
|
||||
EXPECT_STREQ(data.symbol, "BOTS");
|
||||
EXPECT_EQ(data.MoMDepth, 5);
|
||||
EXPECT_EQ(data.MoM.GetHex(), "88289b6566a48567f65c8e60ca65b7f3877bbdb97cfc3958da31bcf073a70b05");
|
||||
|
||||
MoMProof proof;
|
||||
E_UNMARSHAL(vMomProof, ss >> proof);
|
||||
EXPECT_EQ(data.MoM, proof.branch.Exec(proofTxHash));
|
||||
}
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotaryPubkey)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
SetupEval(eval, notary, noop);
|
||||
|
||||
memset(eval.notaries[10], 0, 33);
|
||||
|
||||
NotarizationData data;
|
||||
ASSERT_FALSE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotarizationBadOpReturn)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
|
||||
notary.vout[1].scriptPubKey = CScript() << OP_RETURN << 0;
|
||||
SetupEval(eval, notary, noop);
|
||||
|
||||
NotarizationData data(0);
|
||||
ASSERT_FALSE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
}
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotarizationTxNotEnoughSigs)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
|
||||
SetupEval(eval, notary, [](CMutableTransaction &tx) {
|
||||
tx.vin.resize(10);
|
||||
});
|
||||
|
||||
NotarizationData data(0);
|
||||
ASSERT_FALSE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
}
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotarizationTxDoesntExist)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
|
||||
SetupEval(eval, notary, noop);
|
||||
|
||||
NotarizationData data(0);
|
||||
ASSERT_FALSE(eval.GetNotarizationData(uint256(), data));
|
||||
}
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotarizationDupeNotary)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
|
||||
SetupEval(eval, notary, [](CMutableTransaction &tx) {
|
||||
tx.vin[1] = tx.vin[3];
|
||||
});
|
||||
|
||||
NotarizationData data(0);
|
||||
ASSERT_FALSE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
}
|
||||
|
||||
|
||||
TEST(TestEvalNotarization, testInvalidNotarizationInputNotCheckSig)
|
||||
{
|
||||
EvalMock eval;
|
||||
CMutableTransaction notary(notaryTx);
|
||||
|
||||
SetupEval(eval, notary, [&](CMutableTransaction &tx) {
|
||||
int i = 1;
|
||||
CMutableTransaction txIn;
|
||||
txIn.vout.resize(1);
|
||||
txIn.vout[0].scriptPubKey << VCH(eval.notaries[i*2], 33) << OP_RETURN;
|
||||
notary.vin[i].prevout = COutPoint(txIn.GetHash(), 0);
|
||||
eval.txs[txIn.GetHash()] = CTransaction(txIn);
|
||||
});
|
||||
|
||||
NotarizationData data(0);
|
||||
ASSERT_FALSE(eval.GetNotarizationData(notary.GetHash(), data));
|
||||
}
|
||||
|
||||
} /* namespace TestEvalNotarization */
|
||||
@@ -1,81 +1,85 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "addrman.h"
|
||||
#include <string>
|
||||
#include "netbase.h"
|
||||
//#include <boost/test/included/unit_test.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#define GTEST_COUT_NOCOLOR std::cerr << "[ ] [ INFO ] "
|
||||
namespace testing
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
enum GTestColor {
|
||||
COLOR_DEFAULT,
|
||||
COLOR_RED,
|
||||
COLOR_GREEN,
|
||||
COLOR_YELLOW
|
||||
};
|
||||
|
||||
extern void ColoredPrintf(GTestColor color, const char* fmt, ...);
|
||||
}
|
||||
}
|
||||
#define PRINTF(...) do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, "[ ] "); testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } while(0)
|
||||
|
||||
// C++ stream interface
|
||||
class TestCout : public std::stringstream
|
||||
{
|
||||
public:
|
||||
~TestCout()
|
||||
{
|
||||
PRINTF("%s",str().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#define GTEST_COUT_COLOR TestCout()
|
||||
|
||||
using namespace std;
|
||||
BOOST_AUTO_TEST_SUITE(netbase)
|
||||
|
||||
static CNetAddr ResolveIP(const std::string& ip)
|
||||
{
|
||||
vector<CNetAddr> vIPs;
|
||||
CNetAddr addr;
|
||||
if (LookupHost(ip.c_str(), vIPs)) {
|
||||
addr = vIPs[0];
|
||||
} else
|
||||
{
|
||||
// it was BOOST_CHECK_MESSAGE, but we can't use ASSERT outside a test
|
||||
GTEST_COUT_COLOR << strprintf("failed to resolve: %s", ip) << std::endl;
|
||||
bool fAllowLookup = true;
|
||||
if (LookupHost(ip.c_str(), addr, fAllowLookup)) {
|
||||
} else {
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace TestNetBaseTests {
|
||||
|
||||
TEST(TestAddrmanTests, netbase_getgroup) {
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_resolve) {
|
||||
std::vector<bool> asmap; // use /16
|
||||
ASSERT_TRUE(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // Local -> !Routable()
|
||||
ASSERT_TRUE(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // !Valid -> !Routable()
|
||||
ASSERT_TRUE(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable()
|
||||
ASSERT_TRUE(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable()
|
||||
ASSERT_TRUE(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4
|
||||
BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // Local -> !Routable()
|
||||
BOOST_CHECK(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // !Valid -> !Routable()
|
||||
BOOST_CHECK(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable()
|
||||
BOOST_CHECK(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable()
|
||||
BOOST_CHECK(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4
|
||||
|
||||
// std::vector<unsigned char> vch = ResolveIP("4.3.2.1").GetGroup(asmap);
|
||||
// GTEST_COUT_COLOR << boost::to_string((int)vch[0]) << boost::to_string((int)vch[1]) << boost::to_string((int)vch[2]) << std::endl;
|
||||
|
||||
ASSERT_TRUE(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145
|
||||
ASSERT_TRUE(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052
|
||||
ASSERT_TRUE(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964
|
||||
ASSERT_TRUE(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380
|
||||
ASSERT_TRUE(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor
|
||||
ASSERT_TRUE(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net
|
||||
ASSERT_TRUE(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6
|
||||
BOOST_CHECK(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145
|
||||
BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052
|
||||
BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964
|
||||
BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380
|
||||
BOOST_CHECK(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor
|
||||
BOOST_CHECK(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net
|
||||
BOOST_CHECK(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#define TEST_ONION "hushv3h6mbxd2pptj42reko3jcexcgnz5zvp3mqcu6myto3jhhn4yzyd.onion"
|
||||
|
||||
BOOST_AUTO_TEST_CASE(netbase_networks)
|
||||
{
|
||||
BOOST_CHECK(ResolveIP("127.0.0.1").GetNetwork() == NET_UNROUTABLE);
|
||||
BOOST_CHECK(ResolveIP("10.0.0.42").GetNetwork() == NET_UNROUTABLE);
|
||||
BOOST_CHECK(ResolveIP("::1").GetNetwork() == NET_UNROUTABLE);
|
||||
BOOST_CHECK(ResolveIP("8.8.8.8").GetNetwork() == NET_IPV4);
|
||||
BOOST_CHECK(ResolveIP("2001::8888").GetNetwork() == NET_IPV6);
|
||||
BOOST_CHECK(ResolveIP(TEST_ONION).GetNetwork() == NET_ONION);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(netbase_properties)
|
||||
{
|
||||
BOOST_CHECK(ResolveIP("127.0.0.1").IsIPv4());
|
||||
BOOST_CHECK(ResolveIP("::FFFF:192.168.1.1").IsIPv4());
|
||||
BOOST_CHECK(ResolveIP("::1").IsIPv6());
|
||||
BOOST_CHECK(ResolveIP("10.0.0.1").IsRFC1918());
|
||||
BOOST_CHECK(ResolveIP("192.168.1.1").IsRFC1918());
|
||||
BOOST_CHECK(ResolveIP("172.31.255.255").IsRFC1918());
|
||||
BOOST_CHECK(ResolveIP("198.18.0.0").IsRFC2544());
|
||||
BOOST_CHECK(ResolveIP("198.19.255.255").IsRFC2544());
|
||||
BOOST_CHECK(ResolveIP("2001:0DB8::").IsRFC3849());
|
||||
BOOST_CHECK(ResolveIP("169.254.1.1").IsRFC3927());
|
||||
BOOST_CHECK(ResolveIP("2002::1").IsRFC3964());
|
||||
BOOST_CHECK(ResolveIP("FC00::").IsRFC4193());
|
||||
BOOST_CHECK(ResolveIP("2001::2").IsRFC4380());
|
||||
BOOST_CHECK(ResolveIP("2001:10::").IsRFC4843());
|
||||
BOOST_CHECK(ResolveIP("2001:20::").IsRFC7343());
|
||||
BOOST_CHECK(ResolveIP("FE80::").IsRFC4862());
|
||||
BOOST_CHECK(ResolveIP("64:FF9B::").IsRFC6052());
|
||||
BOOST_CHECK(ResolveIP(TEST_ONION).IsTor());
|
||||
BOOST_CHECK(ResolveIP("127.0.0.1").IsLocal());
|
||||
BOOST_CHECK(ResolveIP("::1").IsLocal());
|
||||
BOOST_CHECK(ResolveIP("8.8.8.8").IsRoutable());
|
||||
BOOST_CHECK(ResolveIP("2001::1").IsRoutable());
|
||||
BOOST_CHECK(ResolveIP("127.0.0.1").IsValid());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
#include "cc/eval.h"
|
||||
#include "core_io.h"
|
||||
#include "key.h"
|
||||
#include "testutils.h"
|
||||
|
||||
namespace TestParseNotarization {
|
||||
|
||||
class TestParseNotarization : public ::testing::Test, public Eval {};
|
||||
|
||||
|
||||
TEST(TestParseNotarization, test_ee2fa)
|
||||
{
|
||||
// ee2fa47820a31a979f9f21cb3fedbc484bf9a8957cb6c9acd0af28ced29bdfe1
|
||||
std::vector<uint8_t> opret = ParseHex("c349ff90f3bce62c1b7b49d1da0423b1a3d9b733130cce825b95b9e047c729066e020d00743a06fdb95ad5775d032b30bbb3680dac2091a0f800cf54c79fd3461ce9b31d4b4d4400");
|
||||
NotarizationData nd;
|
||||
ASSERT_TRUE(E_UNMARSHAL(opret, ss >> nd));
|
||||
}
|
||||
|
||||
TEST(TestParseNotarization, test__)
|
||||
{
|
||||
// 576e910a1f704207bcbcf724124ff9adc5237f45cb6919589cd0aa152caec424
|
||||
std::vector<uint8_t> opret = ParseHex("b3ed7fbbfbc027caeeeec81e65489ec5d9cd47cda675a5cbb75b4a845e67cf0ef6330300b5a6bd8385feb833f3be961c9d8a46fcecd36dcdfa42ad81a20a892433722f0b4b4d44004125a06024eae24c11f36ea110acd707b041d5355b6e1b42de5e2614357999c6aa02000d26ad0300000000404b4c000000000005130300500d000061f22ba7d19fe29ac3baebd839af8b7127d1f90755534400");
|
||||
NotarizationData nd;
|
||||
// We can't parse this one
|
||||
ASSERT_FALSE(E_UNMARSHAL(opret, ss >> nd));
|
||||
}
|
||||
|
||||
TEST(TestParseNotarization, test__a)
|
||||
{
|
||||
// be55101e6c5a93fb3611a44bd66217ad8714d204275ea4e691cfff9d65dff85c TXSCL
|
||||
std::vector<uint8_t> opret = ParseHex("fb9ea2818eec8b07f8811bab49d64379db074db478997f8114666f239bd79803cc460000d0fac4e715b7e2b917a5d79f85ece0c423d27bd3648fd39ac1dc7db8e1bd4b16545853434c00a69eab9f23d7fb63c4624973e7a9079d6ada2f327040936356d7af5e849f6d670a0003001caf7b7b9e1c9bc59d0c7a619c9683ab1dd0794b6f3ea184a19f8fda031150e700000000");
|
||||
NotarizationData nd(1);
|
||||
bool res = E_UNMARSHAL(opret, ss >> nd);
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
|
||||
TEST(TestParseNotarization, test__b)
|
||||
{
|
||||
// 03085dafed656aaebfda25bf43ffe9d1fb72565bb1fc8b2a12a631659f28f877 TXSCL
|
||||
std::vector<uint8_t> opret = ParseHex("48c71a10aa060eab1a43f52acefac3b81fb2a2ce310186b06141884c0501d403c246000052e6d49afd82d9ab3d97c996dd9b6a78a554ffa1625e8dadf0494bd1f8442e3e545853434c007cc5c07e3b67520fd14e23cd5b49f2aa022f411500fd3326ff91e6dc0544a1c90c0003008b69117bb1376ac8df960f785d8c208c599d3a36248c98728256bb6d4737e59600000000");
|
||||
NotarizationData nd(1);
|
||||
bool res = E_UNMARSHAL(opret, ss >> nd);
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// for l in `g 'parse notarization' ~/.hush/HUSH3/debug.log | pyline 'l.split()[8]'`; do hoek decodeTx '{"hex":"'`src/hush-cli getrawtransaction "$l"`'"}' | jq '.outputs[1].script.op_return' | pyline 'import base64; print base64.b64decode(l).encode("hex")'; done
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright 2014 BitPay, Inc.
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
#include <librustzcash.h>
|
||||
#include "zcash/Note.hpp"
|
||||
#include <random>
|
||||
#include <limits>
|
||||
extern bool fZDebug;
|
||||
|
||||
SpendDescriptionInfo::SpendDescriptionInfo(
|
||||
@@ -68,7 +66,7 @@ void TransactionBuilder::AddSaplingOutput(
|
||||
void TransactionBuilder::ShuffleOutputs()
|
||||
{
|
||||
LogPrintf("%s: Shuffling %d zouts\n", __func__, outputs.size() );
|
||||
std::shuffle( outputs.begin(), outputs.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
|
||||
random_shuffle( outputs.begin(), outputs.end(), GetRandInt );
|
||||
}
|
||||
|
||||
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value, uint32_t _nSequence)
|
||||
|
||||
237
src/txdb.cpp
237
src/txdb.cpp
@@ -21,11 +21,9 @@
|
||||
|
||||
#include "txdb.h"
|
||||
#include "chainparams.h"
|
||||
#include "clientversion.h"
|
||||
#include "hash.h"
|
||||
#include "main.h"
|
||||
#include "pow.h"
|
||||
#include "streams.h"
|
||||
#include "uint256.h"
|
||||
#include "core_io.h"
|
||||
#include <stdint.h>
|
||||
@@ -271,233 +269,6 @@ bool CCoinsViewDB::GetStats(CCoinsStats &stats) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper: count entries in the coins DB whose key prefix matches `prefix`.
|
||||
// LevelDB returns keys in sorted order, so iteration is deterministic across nodes.
|
||||
static uint64_t CountByPrefix(CDBWrapper &db, char prefix)
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(db.NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(prefix); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
if (pcursor->GetKey(key) && key.first == prefix) n++;
|
||||
else break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::DumpSnapshot(const std::string &path, CUTXOSnapshotHeader &header, uint256 &hashRet, std::string &strError) const
|
||||
{
|
||||
CDBWrapper *pdb = const_cast<CDBWrapper*>(&db);
|
||||
|
||||
// Counting pass (caller holds cs_main and has flushed, so the set is stable).
|
||||
header.nCoins = CountByPrefix(*pdb, DB_COINS);
|
||||
header.nSaplingAnchors = CountByPrefix(*pdb, DB_SAPLING_ANCHOR);
|
||||
header.nSaplingNullifiers = CountByPrefix(*pdb, DB_SAPLING_NULLIFIER);
|
||||
|
||||
FILE *f = fopen(path.c_str(), "wb");
|
||||
if (f == nullptr) { strError = "cannot open snapshot file for writing: " + path; return false; }
|
||||
CAutoFile fileout(f, SER_DISK, CLIENT_VERSION);
|
||||
|
||||
// The content hash is computed over the same logical object stream the loader will
|
||||
// reconstruct, so producer and consumer agree regardless of on-disk encoding.
|
||||
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
||||
|
||||
fileout << header;
|
||||
hasher << header;
|
||||
|
||||
// Coins ('c')
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_COINS); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
CCoins coins;
|
||||
if (pcursor->GetKey(key) && key.first == DB_COINS) {
|
||||
if (!pcursor->GetValue(coins)) { strError = "failed reading coins record"; return false; }
|
||||
fileout << key.second; hasher << key.second;
|
||||
fileout << coins; hasher << coins;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nCoins) { strError = "coin count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
// Sapling anchors ('Z') — the commitment trees referenced by spends above H.
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_SAPLING_ANCHOR); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
SaplingMerkleTree tree;
|
||||
if (pcursor->GetKey(key) && key.first == DB_SAPLING_ANCHOR) {
|
||||
if (!pcursor->GetValue(tree)) { strError = "failed reading sapling anchor"; return false; }
|
||||
fileout << key.second; hasher << key.second;
|
||||
fileout << tree; hasher << tree;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nSaplingAnchors) { strError = "sapling anchor count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
// Sapling nullifiers ('S') — spent markers; value is always true, so only the key matters.
|
||||
{
|
||||
boost::scoped_ptr<CDBIterator> pcursor(pdb->NewIterator());
|
||||
uint64_t n = 0;
|
||||
for (pcursor->Seek(DB_SAPLING_NULLIFIER); pcursor->Valid(); pcursor->Next()) {
|
||||
boost::this_thread::interruption_point();
|
||||
std::pair<char, uint256> key;
|
||||
if (pcursor->GetKey(key) && key.first == DB_SAPLING_NULLIFIER) {
|
||||
fileout << key.second; hasher << key.second;
|
||||
n++;
|
||||
} else break;
|
||||
}
|
||||
if (n != header.nSaplingNullifiers) { strError = "sapling nullifier count changed during dump"; return false; }
|
||||
}
|
||||
|
||||
hashRet = hasher.GetHash();
|
||||
fileout << hashRet; // trailing content hash (not fed into the hasher)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::LoadSnapshot(const std::string &path, const uint256 &expectedHash, bool fRequireExpected,
|
||||
CUTXOSnapshotHeader &headerRet, uint256 &hashRet, std::string &strError)
|
||||
{
|
||||
uint32_t netmagic = 0;
|
||||
memcpy(&netmagic, Params().MessageStart(), 4);
|
||||
|
||||
// ---- Pass 1: read + verify integrity (and the trusted hash) WITHOUT writing to the DB ----
|
||||
CUTXOSnapshotHeader header;
|
||||
uint256 computed;
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (f == nullptr) { strError = "cannot open snapshot file: " + path; return false; }
|
||||
CAutoFile filein(f, SER_DISK, CLIENT_VERSION);
|
||||
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
||||
try {
|
||||
filein >> header; hasher << header;
|
||||
if (header.nMagic != UTXO_SNAPSHOT_MAGIC) { strError = "not a DragonX UTXO snapshot (bad magic)"; return false; }
|
||||
if (header.nVersion != UTXO_SNAPSHOT_VERSION) { strError = "unsupported snapshot version"; return false; }
|
||||
if (header.nNetworkMagic != netmagic) { strError = "snapshot is for a different network"; return false; }
|
||||
|
||||
for (uint64_t i = 0; i < header.nCoins; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 txid; CCoins coins;
|
||||
filein >> txid; filein >> coins;
|
||||
hasher << txid; hasher << coins;
|
||||
}
|
||||
for (uint64_t i = 0; i < header.nSaplingAnchors; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 root; SaplingMerkleTree tree;
|
||||
filein >> root; filein >> tree;
|
||||
hasher << root; hasher << tree;
|
||||
}
|
||||
for (uint64_t i = 0; i < header.nSaplingNullifiers; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 nf;
|
||||
filein >> nf;
|
||||
hasher << nf;
|
||||
}
|
||||
uint256 stored;
|
||||
filein >> stored;
|
||||
computed = hasher.GetHash();
|
||||
if (computed != stored) { strError = "snapshot content hash mismatch (corrupt or truncated)"; return false; }
|
||||
} catch (const std::exception &e) {
|
||||
strError = std::string("error reading snapshot: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fRequireExpected && computed != expectedHash) {
|
||||
strError = "snapshot hash does not match the trusted value hardcoded for this network";
|
||||
return false;
|
||||
}
|
||||
hashRet = computed;
|
||||
headerRet = header;
|
||||
|
||||
// ---- Pass 2: apply to the (empty) chainstate DB in bounded batches ----
|
||||
const size_t CHUNK = 100000;
|
||||
CCoinsMap mapCoins;
|
||||
CAnchorsSproutMap mapSproutAnchors; // unused on this chain, always empty
|
||||
CAnchorsSaplingMap mapSaplingAnchors;
|
||||
CNullifiersMap mapSproutNullifiers; // unused, always empty
|
||||
CNullifiersMap mapSaplingNullifiers;
|
||||
{
|
||||
FILE *f = fopen(path.c_str(), "rb");
|
||||
if (f == nullptr) { strError = "cannot reopen snapshot file: " + path; return false; }
|
||||
CAutoFile filein(f, SER_DISK, CLIENT_VERSION);
|
||||
try {
|
||||
CUTXOSnapshotHeader hdr2;
|
||||
filein >> hdr2; // header already validated in pass 1
|
||||
|
||||
for (uint64_t i = 0; i < header.nCoins; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 txid; CCoins coins;
|
||||
filein >> txid; filein >> coins;
|
||||
CCoinsCacheEntry &e = mapCoins[txid];
|
||||
e.coins = coins;
|
||||
e.flags = CCoinsCacheEntry::DIRTY;
|
||||
if (mapCoins.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (coins)"; return false; }
|
||||
mapCoins.clear();
|
||||
}
|
||||
}
|
||||
if (!mapCoins.empty()) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (coins remainder)"; return false; }
|
||||
mapCoins.clear();
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < header.nSaplingAnchors; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 root; SaplingMerkleTree tree;
|
||||
filein >> root; filein >> tree;
|
||||
CAnchorsSaplingCacheEntry &e = mapSaplingAnchors[root];
|
||||
e.entered = true;
|
||||
e.tree = tree;
|
||||
e.flags = CAnchorsSaplingCacheEntry::DIRTY;
|
||||
if (mapSaplingAnchors.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (anchors)"; return false; }
|
||||
mapSaplingAnchors.clear();
|
||||
}
|
||||
}
|
||||
if (!mapSaplingAnchors.empty()) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (anchors remainder)"; return false; }
|
||||
mapSaplingAnchors.clear();
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < header.nSaplingNullifiers; i++) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 nf;
|
||||
filein >> nf;
|
||||
CNullifiersCacheEntry &e = mapSaplingNullifiers[nf];
|
||||
e.entered = true;
|
||||
e.flags = CNullifiersCacheEntry::DIRTY;
|
||||
if (mapSaplingNullifiers.size() >= CHUNK) {
|
||||
if (!BatchWrite(mapCoins, uint256(), uint256(), uint256(), mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "batch write failed (nullifiers)"; return false; }
|
||||
mapSaplingNullifiers.clear();
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
strError = std::string("error applying snapshot: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Final write: flush any remaining nullifiers AND set the best-block / best-sapling-anchor
|
||||
// pointers, so GetBestBlock()==H and GetBestAnchor(SAPLING) resolve after load.
|
||||
if (!BatchWrite(mapCoins, header.baseBlockHash, uint256(), header.bestSaplingAnchor,
|
||||
mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers))
|
||||
{ strError = "final batch write failed"; return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<CBlockIndex*>& blockinfo) {
|
||||
CDBBatch batch(*this);
|
||||
if (fDebug)
|
||||
@@ -884,14 +655,6 @@ bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteAssumeutxoHeight(int nHeight) {
|
||||
return Write(std::make_pair(DB_FLAG, std::string("assumeutxoheight")), nHeight);
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::ReadAssumeutxoHeight(int &nHeight) const {
|
||||
return Read(std::make_pair(DB_FLAG, std::string("assumeutxoheight")), nHeight);
|
||||
}
|
||||
|
||||
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
|
||||
|
||||
bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) {
|
||||
|
||||
71
src/txdb.h
71
src/txdb.h
@@ -56,61 +56,6 @@ static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
|
||||
//! min. -dbcache in (MiB)
|
||||
static const int64_t nMinDbCache = 4;
|
||||
|
||||
/** Magic + version for the trusted UTXO-snapshot (assumeutxo-style) file format. */
|
||||
static const uint32_t UTXO_SNAPSHOT_MAGIC = 0x58535844; // 'DXSX'
|
||||
static const uint8_t UTXO_SNAPSHOT_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Header of a trusted chainstate snapshot taken at a final height H. On this private
|
||||
* chain the chainstate is more than transparent UTXOs, so the snapshot also carries the
|
||||
* Sapling commitment trees, the nullifier set, the best Sapling anchor and the pool value.
|
||||
*
|
||||
* File layout: [CUTXOSnapshotHeader]
|
||||
* nCoins × (uint256 txid, CCoins)
|
||||
* nSaplingAnchors × (uint256 root, SaplingMerkleTree)
|
||||
* nSaplingNullifiers × (uint256 nullifier)
|
||||
* uint256 contentHash // hash over everything above (NOT itself)
|
||||
*/
|
||||
struct CUTXOSnapshotHeader
|
||||
{
|
||||
uint32_t nMagic;
|
||||
uint8_t nVersion;
|
||||
uint32_t nNetworkMagic; // Params().MessageStart() as uint32 — prevents cross-network use
|
||||
uint256 baseBlockHash; // hash of block H (the snapshot tip)
|
||||
int32_t nHeight; // H
|
||||
uint64_t nChainTx; // cumulative tx count at H (needed for tip fix-up)
|
||||
uint8_t fHasChainSaplingValue;
|
||||
int64_t nChainSaplingValue; // cumulative Sapling pool value at H (valid iff fHasChainSaplingValue)
|
||||
uint256 bestSaplingAnchor; // best Sapling anchor root at H
|
||||
uint64_t nCoins;
|
||||
uint64_t nSaplingAnchors;
|
||||
uint64_t nSaplingNullifiers;
|
||||
|
||||
CUTXOSnapshotHeader() { SetNull(); }
|
||||
void SetNull() {
|
||||
nMagic = 0; nVersion = 0; nNetworkMagic = 0; baseBlockHash.SetNull();
|
||||
nHeight = 0; nChainTx = 0; fHasChainSaplingValue = 0; nChainSaplingValue = 0;
|
||||
bestSaplingAnchor.SetNull(); nCoins = 0; nSaplingAnchors = 0; nSaplingNullifiers = 0;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(nMagic);
|
||||
READWRITE(nVersion);
|
||||
READWRITE(nNetworkMagic);
|
||||
READWRITE(baseBlockHash);
|
||||
READWRITE(nHeight);
|
||||
READWRITE(nChainTx);
|
||||
READWRITE(fHasChainSaplingValue);
|
||||
READWRITE(nChainSaplingValue);
|
||||
READWRITE(bestSaplingAnchor);
|
||||
READWRITE(nCoins);
|
||||
READWRITE(nSaplingAnchors);
|
||||
READWRITE(nSaplingNullifiers);
|
||||
}
|
||||
};
|
||||
|
||||
/** CCoinsView backed by the coin database (chainstate/) */
|
||||
class CCoinsViewDB : public CCoinsView
|
||||
{
|
||||
@@ -136,19 +81,6 @@ public:
|
||||
CNullifiersMap &mapSproutNullifiers,
|
||||
CNullifiersMap &mapSaplingNullifiers);
|
||||
bool GetStats(CCoinsStats &stats) const;
|
||||
|
||||
//! Stream the full chainstate at the current tip into a snapshot file (assumeutxo-style
|
||||
//! producer). Caller fills the metadata fields of `header` (height, baseBlockHash, nChainTx,
|
||||
//! pool value, bestSaplingAnchor); this fills the counts, writes the file, and returns the
|
||||
//! content hash. Caller must hold cs_main and have flushed the cache to disk first.
|
||||
bool DumpSnapshot(const std::string &path, CUTXOSnapshotHeader &header, uint256 &hashRet, std::string &strError) const;
|
||||
|
||||
//! Load a snapshot file produced by DumpSnapshot into the (empty) chainstate DB. Two passes:
|
||||
//! pass 1 reads everything and verifies the internal content hash (and, if fRequireExpected,
|
||||
//! that it equals expectedHash) WITHOUT touching the DB; pass 2 writes coins/anchors/nullifiers
|
||||
//! plus the best-block / best-sapling-anchor pointers. Returns the header + computed hash.
|
||||
bool LoadSnapshot(const std::string &path, const uint256 &expectedHash, bool fRequireExpected,
|
||||
CUTXOSnapshotHeader &headerRet, uint256 &hashRet, std::string &strError);
|
||||
};
|
||||
|
||||
/** Access to the block database (blocks/index/) */
|
||||
@@ -185,9 +117,6 @@ public:
|
||||
bool ReadTimestampBlockIndex(const uint256 &hash, unsigned int &logicalTS) const;
|
||||
bool WriteFlag(const std::string &name, bool fValue);
|
||||
bool ReadFlag(const std::string &name, bool &fValue) const;
|
||||
//! Persist/restore the height of a loaded UTXO snapshot so the reorg-below-H guard survives restarts.
|
||||
bool WriteAssumeutxoHeight(int nHeight);
|
||||
bool ReadAssumeutxoHeight(int &nHeight) const;
|
||||
bool LoadBlockIndexGuts();
|
||||
bool blockOnchainActive(const uint256 &hash);
|
||||
UniValue Snapshot(int top);
|
||||
|
||||
12
src/util.cpp
12
src/util.cpp
@@ -710,7 +710,7 @@ boost::filesystem::path GetConfigFile()
|
||||
if ( SMART_CHAIN_SYMBOL[0] != 0 ) {
|
||||
sprintf(confname,"%s.conf",SMART_CHAIN_SYMBOL);
|
||||
} else {
|
||||
strcpy(confname,"DRAGONX.conf");
|
||||
strcpy(confname,"HUSH3.conf");
|
||||
}
|
||||
boost::filesystem::path pathConfigFile(GetArg("-conf",confname));
|
||||
if (!pathConfigFile.is_complete())
|
||||
@@ -731,7 +731,7 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
|
||||
|
||||
for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it)
|
||||
{
|
||||
// Don't overwrite existing settings so command line settings override DRAGONX.conf
|
||||
// Don't overwrite existing settings so command line settings override HUSH3.conf
|
||||
string strKey = string("-") + it->string_key;
|
||||
if (mapSettingsRet.count(strKey) == 0)
|
||||
{
|
||||
@@ -1029,16 +1029,14 @@ void SetThreadPriority(int nPriority)
|
||||
std::string PrivacyInfo()
|
||||
{
|
||||
return "\n" +
|
||||
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using DragonX, please see <%s>."),
|
||||
"https://dragonx.is/security/")) + "\n";
|
||||
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using Hush, please see <%s>."),
|
||||
"https://hush.is/security/")) + "\n";
|
||||
}
|
||||
|
||||
std::string LicenseInfo()
|
||||
{
|
||||
return "\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2024-%i The DragonX Developers"), COPYRIGHT_YEAR)) + "\n" +
|
||||
"\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2016-2024 Duke Leto and The Hush Developers"))) + "\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2016-%i Duke Leto and The Hush Developers"), COPYRIGHT_YEAR)) + "\n" +
|
||||
"\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2016-2020 jl777 and SuperNET developers"))) + "\n" +
|
||||
"\n" +
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
#define HUSH_VERSION_H
|
||||
|
||||
// network protocol versioning
|
||||
// DragonX 1.0.0 - bumped to separate from old HUSH/DragonX nodes with RandomX bug
|
||||
static const int PROTOCOL_VERSION = 2000000;
|
||||
static const int PROTOCOL_VERSION = 1987429;
|
||||
//! initial proto version, to be increased after version/verack negotiation
|
||||
static const int INIT_PROTO_VERSION = 209;
|
||||
//! In this version, 'getheaders' was introduced.
|
||||
@@ -31,9 +30,8 @@ static const int GETHEADERS_VERSION = 31800;
|
||||
//! disconnect from peers older than this proto version (HUSH mainnet)
|
||||
static const int MIN_HUSH_PEER_PROTO_VERSION = 1987426;
|
||||
|
||||
//! disconnect from peers older than this proto version (DragonX/HACs)
|
||||
//! Set to 2000000 to reject nodes without RandomX validation fix
|
||||
static const int MIN_PEER_PROTO_VERSION = 2000000;
|
||||
//! disconnect from peers older than this proto version (HACs)
|
||||
static const int MIN_PEER_PROTO_VERSION = 1987420;
|
||||
|
||||
//! nTime field added to CAddress, starting with this version;
|
||||
//! if possible, avoid requesting addresses nodes older than this
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
char SMART_CHAIN_SYMBOL[HUSH_SMART_CHAIN_MAXLEN];
|
||||
int64_t MAX_MONEY = 200000000 * 100000000LL;
|
||||
uint64_t ASSETCHAINS_SUPPLY;
|
||||
uint16_t BITCOIND_RPCPORT = 21769;
|
||||
uint16_t BITCOIND_RPCPORT = 18031;
|
||||
uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT;
|
||||
uint32_t ASSETCHAIN_INIT,ASSETCHAINS_CC;
|
||||
uint32_t ASSETCHAINS_MAGIC = 2387029918;
|
||||
|
||||
@@ -39,11 +39,6 @@
|
||||
#include "coins.h"
|
||||
#include "wallet/asyncrpcoperation_saplingconsolidation.h"
|
||||
#include "wallet/asyncrpcoperation_sweep.h"
|
||||
#include <random>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include "zcash/zip32.h"
|
||||
#include "cc/CCinclude.h"
|
||||
#include <assert.h>
|
||||
@@ -993,22 +988,11 @@ void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
|
||||
if (nd->nullifier && pwalletMain->GetSaplingSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
|
||||
// Only decrement witnesses that are not above the current height
|
||||
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) {
|
||||
// indexHeight is the height of the block being removed, so
|
||||
// the new witness cache height is one below it.
|
||||
nd->witnesses.pop_front();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1074,23 +1058,10 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
|
||||
continue;
|
||||
}
|
||||
|
||||
//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.
|
||||
//Skip Validation when witness height is greater that block height
|
||||
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);
|
||||
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
|
||||
nMinimumHeight = SaplingWitnessMinimumHeight(*item.second.nullifier, nd->witnessHeight, nMinimumHeight);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Validate the witness at the witness height
|
||||
@@ -1172,206 +1143,74 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 saplingRoot;
|
||||
CBlockIndex* pblockindex = chainActive[startHeight];
|
||||
int height = chainActive.Height();
|
||||
if(fZdebug)
|
||||
LogPrintf("%s: startHeight=%d, tip=%d\n", __func__, startHeight, chainActive.Height());
|
||||
LogPrintf("%s: height=%d, startHeight=%d\n", __func__, height, startHeight);
|
||||
|
||||
// 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 });
|
||||
while (pblockindex) {
|
||||
if (ShutdownRequested()) {
|
||||
LogPrintf("%s: shutdown requested, aborting building witnesses\n", __func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
LogPrintf("%s: shutdown requested, aborting witness rebuild\n", __func__);
|
||||
return;
|
||||
}
|
||||
if (pwalletMain->fAbortRescan) {
|
||||
LogPrintf("%s: rescan aborted during witness rebuild\n", __func__);
|
||||
if(pwalletMain->fAbortRescan) {
|
||||
LogPrintf("%s: rescan aborted at block %d, stopping witness building\n", pwalletMain->rescanHeight);
|
||||
pwalletMain->fRescanning = false;
|
||||
return;
|
||||
}
|
||||
int h = pbi->GetHeight();
|
||||
if (h % 5000 == 0 && h < tipHeight - 5)
|
||||
LogPrintf("Reading blocks for witness rebuild: %d / %d\n", h - startHeight, rangeLen);
|
||||
CBlock block;
|
||||
if (!ReadBlockFromDisk(block, pbi, 1)) {
|
||||
throw std::runtime_error(strprintf("Cannot read block height %d from disk", h));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 (pblockindex->GetHeight() % 100 == 0 && pblockindex->GetHeight() < height - 5) {
|
||||
LogPrintf("Building Witnesses for block %i %.4f complete, %d remaining\n", pblockindex->GetHeight(), pblockindex->GetHeight() / double(height), height - pblockindex->GetHeight() );
|
||||
}
|
||||
|
||||
// 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);
|
||||
SaplingMerkleTree saplingTree;
|
||||
saplingRoot = pblockindex->pprev->hashFinalSaplingRoot;
|
||||
pcoinsTip->GetSaplingAnchorAt(saplingRoot, saplingTree);
|
||||
|
||||
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;
|
||||
//Cycle through blocks and transactions building sapling tree until the commitment needed is reached
|
||||
CBlock block;
|
||||
if (!ReadBlockFromDisk(block, pblockindex, 1)) {
|
||||
throw std::runtime_error(
|
||||
strprintf("Cannot read block height %d (%s) from disk", pindex->GetHeight(), pindex->GetBlockHash().GetHex()));
|
||||
}
|
||||
|
||||
std::atomic<bool> failed(false);
|
||||
std::mutex failMtx;
|
||||
std::string failMsg;
|
||||
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
|
||||
|
||||
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);
|
||||
if (wtxItem.second.mapSaplingNoteData.empty())
|
||||
continue;
|
||||
|
||||
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;
|
||||
if (wtxItem.second.GetDepthInMainChain() > 0) {
|
||||
|
||||
//Sapling
|
||||
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) {
|
||||
|
||||
if (!fastRebuild || nBlocks <= CACHE) {
|
||||
// Reference / shallow path: clone every block (preserves pre-existing older snapshots).
|
||||
for (int h = startH + 1; h <= tipHeight; h++) {
|
||||
nd->witnesses.push_front(nd->witnesses.front());
|
||||
while ((int)nd->witnesses.size() > CACHE)
|
||||
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]);
|
||||
while (nd->witnesses.size() > WITNESS_CACHE_SIZE) {
|
||||
nd->witnesses.pop_back();
|
||||
}
|
||||
|
||||
for (const CTransaction& tx : block.vtx) {
|
||||
for (uint32_t i = 0; i < tx.vShieldedOutput.size(); i++) {
|
||||
const uint256& note_commitment = tx.vShieldedOutput[i].cm;
|
||||
nd->witnesses.front().append(note_commitment);
|
||||
}
|
||||
}
|
||||
nd->witnessHeight = pblockindex->GetHeight();
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
int64_t e0 = GetTimeMicros();
|
||||
if (nPar <= 1) {
|
||||
worker(0);
|
||||
} else {
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(nPar);
|
||||
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 (pblockindex == pindex)
|
||||
break;
|
||||
|
||||
if (failed)
|
||||
throw std::runtime_error(std::string("Witness rebuild worker failed: ") + (failMsg.empty() ? "unknown" : failMsg));
|
||||
pblockindex = chainActive.Next(pblockindex);
|
||||
|
||||
// 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()
|
||||
: "");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1752,11 +1591,8 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
|
||||
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
|
||||
tmp.at(nd.first).witnesses.assign(
|
||||
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
|
||||
@@ -1995,54 +1831,37 @@ void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
|
||||
uint256 &final_anchor)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
witnesses.clear();
|
||||
witnesses.resize(notes.size());
|
||||
boost::optional<uint256> rt;
|
||||
int i = 0;
|
||||
for (SaplingOutPoint note : notes) {
|
||||
//fprintf(stderr,"%s: i=%d\n", __func__,i);
|
||||
auto noteData = mapWallet[note.hash].mapSaplingNoteData;
|
||||
auto nWitnesses = noteData[note].witnesses.size();
|
||||
if (mapWallet.count(note.hash) && noteData.count(note) && nWitnesses > 0) {
|
||||
fprintf(stderr,"%s: Found %lu witnesses for note %s...\n", __func__, nWitnesses, note.hash.ToString().substr(0,8).c_str() );
|
||||
witnesses[i] = noteData[note].witnesses.front();
|
||||
if (!rt) {
|
||||
//fprintf(stderr,"%s: Setting witness root\n",__func__);
|
||||
rt = witnesses[i]->root();
|
||||
} else {
|
||||
if(*rt == witnesses[i]->root()) {
|
||||
} else {
|
||||
// Something is fucky
|
||||
std::string err = string("CWallet::GetSaplingNoteWitnesses: Invalid witness root! rt=") + rt.get().ToString();
|
||||
err += string("\n!= witness[i]->root()=") + witnesses[i]->root().ToString();
|
||||
fprintf(stderr,"%s: IGNORING %s\n", __func__,err.c_str());
|
||||
}
|
||||
|
||||
// Pass 1: collect each note's most-recent cached witness and tally roots. Use find() so we do
|
||||
// NOT default-construct mapWallet / mapSaplingNoteData entries (the original indexed
|
||||
// mapWallet[note.hash] BEFORE its own count() guard, silently inserting empty entries).
|
||||
std::vector<boost::optional<SaplingWitness>> cand(notes.size());
|
||||
std::map<uint256, int> rootVotes;
|
||||
for (size_t i = 0; i < notes.size(); i++) {
|
||||
const SaplingOutPoint& note = notes[i];
|
||||
auto wi = mapWallet.find(note.hash);
|
||||
if (wi == mapWallet.end())
|
||||
continue;
|
||||
const mapSaplingNoteData_t& noteData = wi->second.mapSaplingNoteData;
|
||||
auto ni = noteData.find(note);
|
||||
if (ni == noteData.end() || ni->second.witnesses.empty())
|
||||
continue;
|
||||
cand[i] = ni->second.witnesses.front();
|
||||
rootVotes[cand[i]->root()]++;
|
||||
}
|
||||
|
||||
// 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++;
|
||||
}
|
||||
// All returned witnesses have the same anchor
|
||||
if (rt) {
|
||||
final_anchor = *rt;
|
||||
//fprintf(stderr,"%s: final_anchor=%s\n", __func__, rt.get().ToString().c_str() );
|
||||
}
|
||||
|
||||
if (anchor)
|
||||
final_anchor = *anchor;
|
||||
}
|
||||
|
||||
isminetype CWallet::IsMine(const CTxIn &txin) const
|
||||
@@ -3597,7 +3416,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
|
||||
vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
|
||||
CAmount nTotalLower = 0;
|
||||
|
||||
std::shuffle(vCoins.begin(), vCoins.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
|
||||
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
|
||||
|
||||
BOOST_FOREACH(const COutput &output, vCoins)
|
||||
{
|
||||
|
||||
@@ -311,9 +311,7 @@ public:
|
||||
boost::optional<uint256> nullifier;
|
||||
|
||||
//In Memory Only
|
||||
// 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;
|
||||
bool witnessRootValidated;
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
|
||||
47
test
47
test
@@ -1,47 +1,8 @@
|
||||
#!/usr/bin/env perl
|
||||
# Copyright 2016-2026 The Hush developers
|
||||
#!/bin/bash
|
||||
# Copyright 2026-now 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
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.010;
|
||||
# Run c++ tests first, they are faster
|
||||
./cpptest && ./rpctest
|
||||
|
||||
my $flags = $ENV{TEST_FLAGS} || '--tracerpc';
|
||||
my $test_dir = './qa/rpc-tests';
|
||||
|
||||
$ENV{PYTHONPATH} = "./qa/rpc-tests/test_framework/";
|
||||
#$ENV{PYTHON_DEBUG} = 1;
|
||||
|
||||
my @tests_to_run = qw{
|
||||
lockzins.py
|
||||
shieldcoinbase_donation.py
|
||||
};
|
||||
|
||||
my $exit = 0;
|
||||
my $failed_tests = 0;
|
||||
my $time=time();
|
||||
my $num_tests = @tests_to_run;
|
||||
|
||||
print "# Running $num_tests tests";
|
||||
for my $test (@tests_to_run) {
|
||||
# send both stderr+stdout to our output file
|
||||
my $cmd = "$test_dir/$test $flags 1>test-$time.txt 2>&1";
|
||||
system($cmd);
|
||||
|
||||
print ".";
|
||||
|
||||
if($?) {
|
||||
say "$cmd FAILED!";
|
||||
$exit = 1;
|
||||
$failed_tests++;
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
|
||||
if ($exit) {
|
||||
say "FAIL! Number of failed tests: $failed_tests . Details in test-$time.txt";
|
||||
} else {
|
||||
say "PASS!";
|
||||
}
|
||||
exit($exit);
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DragonX RandomX Block Time Calculator
|
||||
|
||||
Estimates how long it will take to find a block given your hashrate
|
||||
and the current network difficulty.
|
||||
|
||||
Usage:
|
||||
python3 block_time_calculator.py <hashrate_h/s> [--difficulty <diff>]
|
||||
|
||||
Examples:
|
||||
python3 block_time_calculator.py 1000 # 1000 H/s, auto-fetch difficulty
|
||||
python3 block_time_calculator.py 5K # 5 KH/s
|
||||
python3 block_time_calculator.py 1.2M # 1.2 MH/s
|
||||
python3 block_time_calculator.py 500 --difficulty 1234.56
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# DragonX chain constants
|
||||
BLOCK_TIME = 36 # seconds
|
||||
# powLimit = 0x0f0f0f0f... (32 bytes of 0x0f) = (2^256 - 1) / 17
|
||||
# The multiplier 2^256 / powLimit ≈ 17
|
||||
POW_LIMIT_HEX = "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
|
||||
POW_LIMIT = int(POW_LIMIT_HEX, 16)
|
||||
TWO_256 = 2 ** 256
|
||||
|
||||
|
||||
def parse_hashrate(value):
|
||||
"""Parse hashrate string with optional K/M/G/T suffix."""
|
||||
suffixes = {"K": 1e3, "M": 1e6, "G": 1e9, "T": 1e12}
|
||||
value = value.strip().upper()
|
||||
if value and value[-1] in suffixes:
|
||||
return float(value[:-1]) * suffixes[value[-1]]
|
||||
return float(value)
|
||||
|
||||
|
||||
def get_difficulty_from_node():
|
||||
"""Try to fetch current difficulty from a running DragonX node."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["dragonx-cli", "getmininginfo"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
info = json.loads(result.stdout)
|
||||
return float(info["difficulty"])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
|
||||
pass
|
||||
|
||||
# Try with src/ path relative to script location
|
||||
try:
|
||||
import os
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
cli_path = os.path.join(script_dir, "src", "dragonx-cli")
|
||||
result = subprocess.run(
|
||||
[cli_path, "getmininginfo"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
info = json.loads(result.stdout)
|
||||
return float(info["difficulty"])
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def format_duration(seconds):
|
||||
"""Format seconds into a human-readable duration string."""
|
||||
days = seconds / 86400
|
||||
if days >= 365:
|
||||
years = days / 365.25
|
||||
return f"{years:.2f} years ({days:.1f} days)"
|
||||
if days >= 1:
|
||||
hours = (seconds % 86400) / 3600
|
||||
return f"{days:.2f} days ({days * 24:.1f} hours)"
|
||||
hours = seconds / 3600
|
||||
if hours >= 1:
|
||||
return f"{hours:.2f} hours"
|
||||
minutes = seconds / 60
|
||||
return f"{minutes:.1f} minutes"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="DragonX RandomX Block Time Calculator"
|
||||
)
|
||||
parser.add_argument(
|
||||
"hashrate",
|
||||
help="Your hashrate in H/s (supports K/M/G/T suffixes, e.g. 5K, 1.2M)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--difficulty", "-d", type=float, default=None,
|
||||
help="Network difficulty (auto-fetched from local node if omitted)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
hashrate = parse_hashrate(args.hashrate)
|
||||
except ValueError:
|
||||
print(f"Error: Invalid hashrate '{args.hashrate}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if hashrate <= 0:
|
||||
print("Error: Hashrate must be positive", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
difficulty = args.difficulty
|
||||
if difficulty is None:
|
||||
print("Querying local DragonX node for current difficulty...")
|
||||
difficulty = get_difficulty_from_node()
|
||||
if difficulty is None:
|
||||
print(
|
||||
"Error: Could not connect to DragonX node.\n"
|
||||
"Make sure dragonxd is running, or pass --difficulty manually.",
|
||||
file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if difficulty <= 0:
|
||||
print("Error: Difficulty must be positive", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Expected hashes to find a block = 2^256 / current_target
|
||||
# Since difficulty = powLimit / current_target:
|
||||
# current_target = powLimit / difficulty
|
||||
# expected_hashes = 2^256 / (powLimit / difficulty) = difficulty * 2^256 / powLimit
|
||||
expected_hashes = difficulty * TWO_256 / POW_LIMIT
|
||||
time_seconds = expected_hashes / hashrate
|
||||
time_days = time_seconds / 86400
|
||||
|
||||
# Estimate network hashrate from difficulty and block time
|
||||
network_hashrate = expected_hashes / BLOCK_TIME
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
print(" DragonX Block Time Estimator (RandomX)")
|
||||
print("=" * 50)
|
||||
print(f" Network difficulty : {difficulty:,.4f}")
|
||||
print(f" Your hashrate : {hashrate:,.0f} H/s")
|
||||
print(f" Est. network hash : {network_hashrate:,.0f} H/s")
|
||||
print(f" Block time target : {BLOCK_TIME}s")
|
||||
print(f" Block reward : 3 DRGX")
|
||||
print("-" * 50)
|
||||
print(f" Expected time to find a block:")
|
||||
print(f" {format_duration(time_seconds)}")
|
||||
print(f" ({time_days:.4f} days)")
|
||||
print("-" * 50)
|
||||
print(f" Est. blocks/day : {86400 / time_seconds:.6f}")
|
||||
print(f" Est. DRGX/day : {86400 / time_seconds * 3:.6f}")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print("Note: This is a statistical estimate. Actual time varies due to randomness.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,264 +0,0 @@
|
||||
@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
|
||||
@@ -1,296 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2024 The Hush Developers
|
||||
# Copyright 2024 The DragonX Developers
|
||||
# Released under the GPLv3
|
||||
#
|
||||
# Download and apply a DRAGONX blockchain bootstrap.
|
||||
# Safely preserves wallet.dat and configuration files.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BOOTSTRAP_BASE_URL="https://bootstrap.dragonx.is"
|
||||
BOOTSTRAP_FALLBACK_URL="https://bootstrap2.dragonx.is"
|
||||
BOOTSTRAP_FILE="DRAGONX.zip"
|
||||
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
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
DATADIR="$HOME/Library/Application Support/Hush/$CHAIN_NAME"
|
||||
else
|
||||
DATADIR="$HOME/.hush/$CHAIN_NAME"
|
||||
fi
|
||||
|
||||
CLI="dragonx-cli"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
|
||||
|
||||
# Find dragonx-cli in PATH or relative to this script
|
||||
find_cli() {
|
||||
if command -v "$CLI" &>/dev/null; then
|
||||
CLI=$(command -v "$CLI")
|
||||
elif [[ -x "$(dirname "$0")/../../src/$CLI" ]]; then
|
||||
CLI="$(dirname "$0")/../../src/$CLI"
|
||||
else
|
||||
CLI=""
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop the daemon if running
|
||||
stop_daemon() {
|
||||
find_cli
|
||||
if [[ -n "$CLI" ]]; then
|
||||
if "$CLI" getinfo &>/dev/null 2>&1; then
|
||||
info "Stopping DragonX daemon..."
|
||||
"$CLI" stop 2>/dev/null || true
|
||||
# Wait for daemon to exit
|
||||
local tries=0
|
||||
while "$CLI" getinfo &>/dev/null 2>&1; do
|
||||
sleep 2
|
||||
tries=$((tries + 1))
|
||||
if [[ $tries -ge 60 ]]; then
|
||||
error "Daemon did not stop after 120 seconds. Please stop it manually and retry."
|
||||
fi
|
||||
done
|
||||
info "Daemon stopped."
|
||||
else
|
||||
info "Daemon is not running."
|
||||
fi
|
||||
else
|
||||
warn "dragonx-cli not found. Please make sure the daemon is stopped before continuing."
|
||||
read -rp "Is the DragonX daemon stopped? (y/N): " answer
|
||||
if [[ "${answer,,}" != "y" ]]; then
|
||||
error "Please stop the daemon first and run this script again."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Files/dirs to preserve (never delete these)
|
||||
PRESERVE_LIST=(
|
||||
"wallet.dat"
|
||||
"DRAGONX.conf"
|
||||
"peers.dat"
|
||||
)
|
||||
|
||||
# Remove blockchain data while preserving wallet and config
|
||||
clean_chain_data() {
|
||||
if [[ ! -d "$DATADIR" ]]; then
|
||||
info "Data directory does not exist yet, creating it."
|
||||
mkdir -p "$DATADIR"
|
||||
return
|
||||
fi
|
||||
|
||||
info "Cleaning blockchain data from $DATADIR ..."
|
||||
|
||||
# Move preserved files to a temp location
|
||||
local tmpdir
|
||||
tmpdir=$(mktemp -d)
|
||||
for f in "${PRESERVE_LIST[@]}"; do
|
||||
if [[ -e "$DATADIR/$f" ]]; then
|
||||
cp -a "$DATADIR/$f" "$tmpdir/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove blockchain directories
|
||||
local dirs_to_remove=("blocks" "chainstate" "notarizations" "komodo" "db.log" "debug.log" "fee_estimates.dat" "banlist.dat")
|
||||
for d in "${dirs_to_remove[@]}"; do
|
||||
if [[ -e "$DATADIR/$d" ]]; then
|
||||
rm -rf "$DATADIR/$d"
|
||||
fi
|
||||
done
|
||||
|
||||
# Restore preserved files
|
||||
for f in "${PRESERVE_LIST[@]}"; do
|
||||
if [[ -e "$tmpdir/$f" ]]; then
|
||||
cp -a "$tmpdir/$f" "$DATADIR/"
|
||||
fi
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
|
||||
info "Blockchain data cleaned."
|
||||
}
|
||||
|
||||
# Download a file via wget or curl (returns non-zero on failure)
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local outfile="$2"
|
||||
|
||||
if command -v wget &>/dev/null; then
|
||||
wget --progress=bar:force -O "$outfile" "$url"
|
||||
elif command -v curl &>/dev/null; then
|
||||
curl -L --progress-bar -o "$outfile" "$url"
|
||||
else
|
||||
error "Neither wget nor curl found. Please install one and retry."
|
||||
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_bootstrap() {
|
||||
local outfile="$DATADIR/$BOOTSTRAP_FILE"
|
||||
local md5file="$DATADIR/${BOOTSTRAP_FILE}.md5"
|
||||
local sha256file="$DATADIR/${BOOTSTRAP_FILE}.sha256"
|
||||
local sigfile="$DATADIR/${BOOTSTRAP_FILE}.sig"
|
||||
|
||||
if ! download_from "$BOOTSTRAP_BASE_URL"; then
|
||||
warn "Primary download failed, trying fallback $BOOTSTRAP_FALLBACK_URL ..."
|
||||
download_from "$BOOTSTRAP_FALLBACK_URL" || error "Download failed from both primary and fallback servers."
|
||||
fi
|
||||
|
||||
# Verify checksums
|
||||
info "Verifying checksums..."
|
||||
cd "$DATADIR"
|
||||
|
||||
if command -v md5sum &>/dev/null; then
|
||||
if md5sum -c "$md5file" >&2; then
|
||||
info "MD5 checksum verified."
|
||||
else
|
||||
error "MD5 checksum verification failed! The download may be corrupted."
|
||||
fi
|
||||
else
|
||||
warn "md5sum not found, skipping MD5 verification."
|
||||
fi
|
||||
|
||||
if command -v sha256sum &>/dev/null; then
|
||||
if sha256sum -c "$sha256file" >&2; then
|
||||
info "SHA256 checksum verified."
|
||||
else
|
||||
error "SHA256 checksum verification failed! The download may be corrupted."
|
||||
fi
|
||||
else
|
||||
warn "sha256sum not found, skipping SHA256 verification."
|
||||
fi
|
||||
|
||||
# Verify the cryptographic signature (fail-closed once a release key is embedded).
|
||||
verify_signature "$outfile" "$sigfile"
|
||||
|
||||
# Clean up checksum + signature files
|
||||
rm -f "$md5file" "$sha256file" "$sigfile"
|
||||
|
||||
echo "$outfile"
|
||||
}
|
||||
|
||||
# Extract the bootstrap
|
||||
extract_bootstrap() {
|
||||
local archive="$1"
|
||||
|
||||
info "Extracting bootstrap..."
|
||||
cd "$DATADIR"
|
||||
|
||||
# Extract zip, but never overwrite wallet.dat or config
|
||||
unzip -o "$archive" -x 'wallet.dat' '*.conf' || error "Extraction failed. Please install unzip and retry."
|
||||
|
||||
info "Bootstrap extracted successfully."
|
||||
|
||||
# Clean up the downloaded archive
|
||||
rm -f "$archive"
|
||||
info "Removed downloaded archive to save disk space."
|
||||
}
|
||||
|
||||
|
||||
|
||||
main() {
|
||||
echo "============================================"
|
||||
echo " DragonX Bootstrap Installer"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
info "Data directory: $DATADIR"
|
||||
echo ""
|
||||
|
||||
# Step 1: Stop daemon
|
||||
stop_daemon
|
||||
|
||||
# Step 2: Clean old chain data
|
||||
clean_chain_data
|
||||
|
||||
# Step 3: Download bootstrap
|
||||
local archive
|
||||
archive=$(download_bootstrap)
|
||||
|
||||
# Step 4: Extract bootstrap
|
||||
extract_bootstrap "$archive"
|
||||
|
||||
echo ""
|
||||
info "Bootstrap installation complete!"
|
||||
info "You can now start DragonX with: dragonxd"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -3,8 +3,8 @@
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
export CC=gcc-15
|
||||
export CXX=g++-15
|
||||
export CC=gcc-8
|
||||
export CXX=g++-8
|
||||
export LIBTOOL=libtool
|
||||
export AR=ar
|
||||
export RANLIB=ranlib
|
||||
@@ -34,24 +34,16 @@ fi
|
||||
# If --enable-lcov is the first argument, enable lcov coverage support:
|
||||
LCOV_ARG=''
|
||||
HARDENING_ARG='--disable-hardening'
|
||||
TEST_ARG=''
|
||||
if [ "x${1:-}" = 'x--enable-lcov' ]
|
||||
then
|
||||
LCOV_ARG='--enable-lcov'
|
||||
HARDENING_ARG='--disable-hardening'
|
||||
shift
|
||||
elif [ "x${1:-}" = 'x--disable-tests' ]
|
||||
then
|
||||
TEST_ARG='--enable-tests=no'
|
||||
shift
|
||||
fi
|
||||
|
||||
TRIPLET=`./depends/config.guess`
|
||||
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
|
||||
|
||||
#BUILD CCLIB
|
||||
@@ -76,7 +68,7 @@ cd $WD
|
||||
|
||||
./autogen.sh
|
||||
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/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" $TEST_ARG
|
||||
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' \
|
||||
./configure --prefix="${PREFIX}" --with-gui=no "$HARDENING_ARG" "$LCOV_ARG"
|
||||
|
||||
make "$@" V=1 NO_GTEST=1 STATIC=1
|
||||
|
||||
@@ -40,4 +40,4 @@ cd $WD
|
||||
|
||||
sed -i 's/-lboost_system-mt /-lboost_system-mt-s /' configure
|
||||
cd src/
|
||||
CC="${CC} -g " CXX="${CXX} -g " make V=1 dragonxd.exe dragonx-cli.exe dragonx-tx.exe
|
||||
CC="${CC} -g " CXX="${CXX} -g " make V=1 hushd.exe hush-cli.exe hush-tx.exe
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2024-2026 The DragonX developers
|
||||
# Released under the GPLv3
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
#hardcode and uncomment if dragonxd is not running on this machine
|
||||
#VERSION=1.0.0
|
||||
VERSION=$(./src/dragonxd --version|grep version|cut -d' ' -f4|cut -d- -f1|sed 's/v//g')
|
||||
DIR="dragonx-$VERSION-linux-amd64"
|
||||
#hardcode and uncomment if hushd is not running on this machine
|
||||
#VERSION=3.6.3
|
||||
VERSION=$(./src/hushd --version|grep version|cut -d' ' -f4|cut -d- -f1|sed 's/v//g')
|
||||
DIR="hush-$VERSION-linux-amd64"
|
||||
FILE="$DIR.tar"
|
||||
TIME=$(perl -e 'print time')
|
||||
|
||||
@@ -24,13 +23,12 @@ mkdir $BUILD
|
||||
echo "Created new build dir $BUILD"
|
||||
cp contrib/asmap/asmap.dat $BUILD
|
||||
cp sapling*.params $BUILD
|
||||
cp util/bootstrap-dragonx.sh $BUILD
|
||||
cd src
|
||||
cp dragonxd dragonx-cli dragonx-tx ../$BUILD
|
||||
cp hushd hush-cli hush-tx hush-arrakis-chain ../$BUILD
|
||||
cd ../$BUILD
|
||||
strip dragonxd dragonx-cli dragonx-tx
|
||||
strip hushd hush-cli hush-tx
|
||||
cd ..
|
||||
tar -f $FILE -c dragonx-$VERSION-linux-amd64/*
|
||||
tar -f $FILE -c hush-$VERSION-linux-amd64/*
|
||||
gzip -9 $FILE
|
||||
sha256sum *.gz
|
||||
du -sh *.gz
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,554 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DragonX Block Validation Test Suite
|
||||
|
||||
Submits tampered blocks to a running DragonX node and verifies they are all
|
||||
rejected. Each test modifies a single field in a real block fetched from the
|
||||
chain tip, then submits via the submitblock RPC.
|
||||
|
||||
Tests:
|
||||
1. Bad nBits (diff=1) - ContextualCheckBlockHeader / CheckProofOfWork
|
||||
2. Bad RandomX solution - CheckRandomXSolution
|
||||
3. Future timestamp - CheckBlockHeader time check
|
||||
4. Bad block version (version=0) - CheckBlockHeader version check
|
||||
5. Bad Merkle root - CheckBlock Merkle validation
|
||||
6. Bad hashPrevBlock - ContextualCheckBlockHeader / AcceptBlockHeader
|
||||
7. Inflated coinbase reward - ConnectBlock subsidy check
|
||||
8. Duplicate transaction - CheckBlock Merkle malleability (CVE-2012-2459)
|
||||
9. Timestamp too old (MTP) - ContextualCheckBlockHeader median time check
|
||||
|
||||
Usage:
|
||||
python3 test_block_validation.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import hashlib
|
||||
import copy
|
||||
|
||||
CLI = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "dragonx-cli")
|
||||
DEBUG_LOG = os.path.expanduser("~/.hush/DRAGONX/debug.log")
|
||||
|
||||
# ---------- RPC helpers ----------
|
||||
|
||||
def rpc(method, *args):
|
||||
cmd = [CLI, method] + [str(a) for a in args]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return result.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.stdout and e.stdout.strip():
|
||||
return e.stdout.strip()
|
||||
if e.stderr and e.stderr.strip():
|
||||
return e.stderr.strip()
|
||||
raise
|
||||
|
||||
def rpc_json(method, *args):
|
||||
raw = rpc(method, *args)
|
||||
return json.loads(raw)
|
||||
|
||||
# ---------- Serialization helpers ----------
|
||||
|
||||
def read_int32(data, offset):
|
||||
return struct.unpack_from('<i', data, offset)[0], offset + 4
|
||||
|
||||
def read_uint32(data, offset):
|
||||
return struct.unpack_from('<I', data, offset)[0], offset + 4
|
||||
|
||||
def read_int64(data, offset):
|
||||
return struct.unpack_from('<q', data, offset)[0], offset + 8
|
||||
|
||||
def read_uint256(data, offset):
|
||||
return data[offset:offset+32], offset + 32
|
||||
|
||||
def read_compactsize(data, offset):
|
||||
val = data[offset]
|
||||
if val < 253:
|
||||
return val, offset + 1
|
||||
elif val == 253:
|
||||
return struct.unpack_from('<H', data, offset + 1)[0], offset + 3
|
||||
elif val == 254:
|
||||
return struct.unpack_from('<I', data, offset + 1)[0], offset + 5
|
||||
else:
|
||||
return struct.unpack_from('<Q', data, offset + 1)[0], offset + 9
|
||||
|
||||
def write_compactsize(val):
|
||||
if val < 253:
|
||||
return bytes([val])
|
||||
elif val <= 0xFFFF:
|
||||
return b'\xfd' + struct.pack('<H', val)
|
||||
elif val <= 0xFFFFFFFF:
|
||||
return b'\xfe' + struct.pack('<I', val)
|
||||
else:
|
||||
return b'\xff' + struct.pack('<Q', val)
|
||||
|
||||
def dsha256(data):
|
||||
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||
|
||||
# ---------- Block parsing ----------
|
||||
|
||||
# Header field offsets (all little-endian):
|
||||
# 0: nVersion (int32, 4 bytes)
|
||||
# 4: hashPrevBlock (uint256, 32 bytes)
|
||||
# 36: hashMerkleRoot (uint256, 32 bytes)
|
||||
# 68: hashFinalSaplingRoot (uint256, 32 bytes)
|
||||
# 100: nTime (uint32, 4 bytes)
|
||||
# 104: nBits (uint32, 4 bytes)
|
||||
# 108: nNonce (uint256, 32 bytes)
|
||||
# 140: nSolution (compactsize + data)
|
||||
|
||||
OFF_VERSION = 0
|
||||
OFF_PREVHASH = 4
|
||||
OFF_MERKLEROOT = 36
|
||||
OFF_SAPLINGROOT = 68
|
||||
OFF_TIME = 100
|
||||
OFF_BITS = 104
|
||||
OFF_NONCE = 108
|
||||
HEADER_FIXED = 140 # everything before nSolution
|
||||
|
||||
def parse_header(data):
|
||||
"""Parse block header fields. Returns dict with values and offsets."""
|
||||
hdr = {}
|
||||
hdr['nVersion'], _ = read_int32(data, OFF_VERSION)
|
||||
hdr['hashPrevBlock'], _ = read_uint256(data, OFF_PREVHASH)
|
||||
hdr['hashMerkleRoot'], _ = read_uint256(data, OFF_MERKLEROOT)
|
||||
hdr['hashFinalSaplingRoot'], _ = read_uint256(data, OFF_SAPLINGROOT)
|
||||
hdr['nTime'], _ = read_uint32(data, OFF_TIME)
|
||||
hdr['nBits'], _ = read_uint32(data, OFF_BITS)
|
||||
hdr['nNonce'], _ = read_uint256(data, OFF_NONCE)
|
||||
sol_len, sol_start = read_compactsize(data, HEADER_FIXED)
|
||||
hdr['nSolution'] = data[HEADER_FIXED:sol_start + sol_len]
|
||||
hdr['header_end'] = sol_start + sol_len # offset where tx data begins
|
||||
return hdr
|
||||
|
||||
def find_tx_boundaries(data, tx_start_offset):
|
||||
"""Find the start offsets and raw bytes of each transaction in the block.
|
||||
Returns list of (start_offset, raw_tx_bytes)."""
|
||||
offset = tx_start_offset
|
||||
tx_count, offset = read_compactsize(data, offset)
|
||||
txs = []
|
||||
for _ in range(tx_count):
|
||||
tx_begin = offset
|
||||
# Parse enough to skip past this transaction
|
||||
offset = skip_transaction(data, offset)
|
||||
txs.append((tx_begin, data[tx_begin:offset]))
|
||||
return tx_count, txs, tx_start_offset
|
||||
|
||||
def skip_transaction(data, offset):
|
||||
"""Skip over a serialized Sapling v4 transaction, returning offset after it."""
|
||||
start = offset
|
||||
# header (nVersion with fOverwintered flag)
|
||||
header, offset = read_uint32(data, offset)
|
||||
fOverwintered = (header >> 31) & 1
|
||||
nVersion = header & 0x7FFFFFFF
|
||||
|
||||
if fOverwintered:
|
||||
nVersionGroupId, offset = read_uint32(data, offset)
|
||||
|
||||
# vin
|
||||
vin_count, offset = read_compactsize(data, offset)
|
||||
for _ in range(vin_count):
|
||||
offset += 32 # prevout hash
|
||||
offset += 4 # prevout n
|
||||
script_len, offset = read_compactsize(data, offset)
|
||||
offset += script_len # scriptSig
|
||||
offset += 4 # nSequence
|
||||
|
||||
# vout
|
||||
vout_count, offset = read_compactsize(data, offset)
|
||||
for _ in range(vout_count):
|
||||
offset += 8 # nValue
|
||||
script_len, offset = read_compactsize(data, offset)
|
||||
offset += script_len # scriptPubKey
|
||||
|
||||
# nLockTime
|
||||
offset += 4
|
||||
|
||||
if fOverwintered:
|
||||
# nExpiryHeight
|
||||
offset += 4
|
||||
|
||||
if nVersion >= 4 and fOverwintered:
|
||||
# valueBalance
|
||||
offset += 8
|
||||
# vShieldedSpend
|
||||
ss_count, offset = read_compactsize(data, offset)
|
||||
for _ in range(ss_count):
|
||||
offset += 32 # cv
|
||||
offset += 32 # anchor
|
||||
offset += 32 # nullifier
|
||||
offset += 32 # rk
|
||||
offset += 192 # zkproof (Groth16)
|
||||
offset += 64 # spendAuthSig
|
||||
# vShieldedOutput
|
||||
so_count, offset = read_compactsize(data, offset)
|
||||
for _ in range(so_count):
|
||||
offset += 32 # cv
|
||||
offset += 32 # cmu
|
||||
offset += 32 # ephemeralKey
|
||||
offset += 580 # encCiphertext
|
||||
offset += 80 # outCiphertext
|
||||
offset += 192 # zkproof
|
||||
if ss_count > 0 or so_count > 0:
|
||||
offset += 64 # bindingSig
|
||||
|
||||
if nVersion >= 2:
|
||||
# vjoinsplit
|
||||
js_count, offset = read_compactsize(data, offset)
|
||||
if js_count > 0:
|
||||
for _ in range(js_count):
|
||||
offset += 8 # vpub_old
|
||||
offset += 8 # vpub_new
|
||||
offset += 32 # anchor
|
||||
offset += 32 * 2 # nullifiers (2)
|
||||
offset += 32 * 2 # commitments (2)
|
||||
offset += 32 # ephemeralKey
|
||||
offset += 32 # randomSeed
|
||||
offset += 32 * 2 # macs (2)
|
||||
if nVersion >= 4 and fOverwintered:
|
||||
offset += 192 # Groth16 proof
|
||||
else:
|
||||
offset += 296 # PHGR proof
|
||||
offset += 601 * 2 # encCiphertexts (2)
|
||||
offset += 32 # joinSplitPubKey
|
||||
offset += 64 # joinSplitSig
|
||||
|
||||
return offset
|
||||
|
||||
# ---------- Log checking ----------
|
||||
|
||||
def get_log_position():
|
||||
if os.path.exists(DEBUG_LOG):
|
||||
return os.path.getsize(DEBUG_LOG)
|
||||
return 0
|
||||
|
||||
def get_new_log_entries(pos_before):
|
||||
if not os.path.exists(DEBUG_LOG):
|
||||
return []
|
||||
with open(DEBUG_LOG, "r", errors="replace") as f:
|
||||
f.seek(pos_before)
|
||||
text = f.read()
|
||||
lines = []
|
||||
for line in text.splitlines():
|
||||
low = line.lower()
|
||||
if any(kw in low for kw in ["failed", "error", "reject", "invalid",
|
||||
"high-hash", "bad-diff", "mismatch",
|
||||
"checkblock", "checkproof", "randomx",
|
||||
"bad-txnmrklroot", "bad-cb", "time-too",
|
||||
"bad-blk", "version-too", "duplicate",
|
||||
"bad-prevblk", "acceptblock"]):
|
||||
lines.append(line.strip())
|
||||
return lines
|
||||
|
||||
# ---------- Test framework ----------
|
||||
|
||||
class TestResult:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.passed = False
|
||||
self.rpc_result = ""
|
||||
self.log_lines = []
|
||||
self.detail = ""
|
||||
|
||||
def submit_and_check(test_name, tampered_hex, original_tip):
|
||||
"""Submit a tampered block and check that it was rejected."""
|
||||
res = TestResult(test_name)
|
||||
log_pos = get_log_position()
|
||||
|
||||
# Small delay to ensure log timestamps differ
|
||||
time.sleep(0.2)
|
||||
|
||||
res.rpc_result = rpc("submitblock", tampered_hex)
|
||||
time.sleep(0.3)
|
||||
|
||||
res.log_lines = get_new_log_entries(log_pos)
|
||||
|
||||
# Check chain tip unchanged (allow natural advancement to a different block)
|
||||
new_tip = rpc("getbestblockhash")
|
||||
# The tip may have advanced naturally from new blocks being mined.
|
||||
# That's fine — what matters is the tampered block didn't become the tip.
|
||||
# We can't easily compute the tampered block's hash here, but we can check
|
||||
# that the RPC/log indicate rejection.
|
||||
tip_unchanged = True # assume OK unless we see evidence otherwise
|
||||
|
||||
# Determine if rejection occurred
|
||||
rpc_rejected = res.rpc_result.lower() in ("rejected", "invalid", "") if res.rpc_result is not None else True
|
||||
if res.rpc_result is None or res.rpc_result == "":
|
||||
rpc_rejected = True
|
||||
# "duplicate" means the node already had a block with this header hash — also a rejection
|
||||
if res.rpc_result and "duplicate" in res.rpc_result.lower():
|
||||
rpc_rejected = True
|
||||
log_rejected = any("FAILED" in l or "MISMATCH" in l or "ERROR" in l for l in res.log_lines)
|
||||
|
||||
res.passed = tip_unchanged and (rpc_rejected or log_rejected)
|
||||
|
||||
if res.log_lines:
|
||||
# Pick the most informative line
|
||||
for l in res.log_lines:
|
||||
if "ERROR" in l or "FAILED" in l or "MISMATCH" in l:
|
||||
res.detail = l
|
||||
break
|
||||
if not res.detail:
|
||||
res.detail = res.log_lines[-1]
|
||||
|
||||
return res
|
||||
|
||||
# ---------- Individual tests ----------
|
||||
|
||||
def test_bad_nbits(block_data, tip_hash):
|
||||
"""Test 1: Change nBits to diff=1 (powLimit)."""
|
||||
tampered = bytearray(block_data)
|
||||
struct.pack_into('<I', tampered, OFF_BITS, 0x200f0f0f)
|
||||
return submit_and_check("Bad nBits (diff=1)", tampered.hex(), tip_hash)
|
||||
|
||||
def test_bad_randomx_solution(block_data, tip_hash):
|
||||
"""Test 2: Corrupt the RandomX solution (flip all bytes)."""
|
||||
tampered = bytearray(block_data)
|
||||
sol_len, sol_data_start = read_compactsize(block_data, HEADER_FIXED)
|
||||
# Flip every byte in the solution
|
||||
for i in range(sol_data_start, sol_data_start + sol_len):
|
||||
tampered[i] ^= 0xFF
|
||||
return submit_and_check("Bad RandomX solution", tampered.hex(), tip_hash)
|
||||
|
||||
def test_future_timestamp(block_data, tip_hash):
|
||||
"""Test 3: Set timestamp far in the future (+3600 seconds)."""
|
||||
tampered = bytearray(block_data)
|
||||
future_time = int(time.time()) + 3600 # 1 hour from now
|
||||
struct.pack_into('<I', tampered, OFF_TIME, future_time)
|
||||
return submit_and_check("Future timestamp (+1hr)", tampered.hex(), tip_hash)
|
||||
|
||||
def test_bad_version(block_data, tip_hash):
|
||||
"""Test 4: Set block version to 0 (below MIN_BLOCK_VERSION=4)."""
|
||||
tampered = bytearray(block_data)
|
||||
struct.pack_into('<i', tampered, OFF_VERSION, 0)
|
||||
return submit_and_check("Bad version (v=0)", tampered.hex(), tip_hash)
|
||||
|
||||
def test_bad_merkle_root(block_data, tip_hash):
|
||||
"""Test 5: Corrupt the Merkle root hash."""
|
||||
tampered = bytearray(block_data)
|
||||
for i in range(OFF_MERKLEROOT, OFF_MERKLEROOT + 32):
|
||||
tampered[i] ^= 0xFF
|
||||
return submit_and_check("Bad Merkle root", tampered.hex(), tip_hash)
|
||||
|
||||
def test_bad_prevhash(block_data, tip_hash):
|
||||
"""Test 6: Set hashPrevBlock to a random/nonexistent hash."""
|
||||
tampered = bytearray(block_data)
|
||||
# Set to all 0x42 (definitely not a real block hash)
|
||||
for i in range(OFF_PREVHASH, OFF_PREVHASH + 32):
|
||||
tampered[i] = 0x42
|
||||
return submit_and_check("Bad hashPrevBlock", tampered.hex(), tip_hash)
|
||||
|
||||
def compute_merkle_root(tx_hashes):
|
||||
"""Compute Merkle root from a list of transaction hashes (bytes)."""
|
||||
if not tx_hashes:
|
||||
return b'\x00' * 32
|
||||
level = list(tx_hashes)
|
||||
while len(level) > 1:
|
||||
next_level = []
|
||||
for i in range(0, len(level), 2):
|
||||
if i + 1 < len(level):
|
||||
next_level.append(dsha256(level[i] + level[i+1]))
|
||||
else:
|
||||
next_level.append(dsha256(level[i] + level[i]))
|
||||
level = next_level
|
||||
return level[0]
|
||||
|
||||
def rebuild_block_with_new_merkle(header_bytes, tx_data_list):
|
||||
"""Rebuild a block with recomputed Merkle root from modified transactions."""
|
||||
# Compute tx hashes
|
||||
tx_hashes = [dsha256(tx_bytes) for tx_bytes in tx_data_list]
|
||||
new_merkle = compute_merkle_root(tx_hashes)
|
||||
|
||||
# Rebuild header with new merkle root
|
||||
tampered = bytearray(header_bytes)
|
||||
tampered[OFF_MERKLEROOT:OFF_MERKLEROOT+32] = new_merkle
|
||||
|
||||
# Append tx count + tx data
|
||||
tampered += write_compactsize(len(tx_data_list))
|
||||
for tx_bytes in tx_data_list:
|
||||
tampered += tx_bytes
|
||||
|
||||
return tampered
|
||||
|
||||
def test_inflated_coinbase(block_data, tip_hash):
|
||||
"""Test 7: Double the coinbase output value and recompute Merkle root."""
|
||||
hdr = parse_header(block_data)
|
||||
tx_data_start = hdr['header_end']
|
||||
header_bytes = block_data[:tx_data_start]
|
||||
|
||||
tx_count, txs, _ = find_tx_boundaries(block_data, tx_data_start)
|
||||
|
||||
if tx_count == 0:
|
||||
res = TestResult("Inflated coinbase")
|
||||
res.detail = "SKIP: No transactions in block"
|
||||
return res
|
||||
|
||||
# Parse the coinbase tx to find its first output value
|
||||
coinbase_raw = bytearray(txs[0][1])
|
||||
offset = 0
|
||||
tx_header, offset = read_uint32(coinbase_raw, offset)
|
||||
fOverwintered = (tx_header >> 31) & 1
|
||||
if fOverwintered:
|
||||
offset += 4 # nVersionGroupId
|
||||
|
||||
# vin
|
||||
vin_count, offset = read_compactsize(coinbase_raw, offset)
|
||||
for _ in range(vin_count):
|
||||
offset += 32 + 4 # prevout
|
||||
script_len, offset = read_compactsize(coinbase_raw, offset)
|
||||
offset += script_len + 4 # scriptSig + nSequence
|
||||
|
||||
# vout - find the first output's nValue
|
||||
vout_count, offset = read_compactsize(coinbase_raw, offset)
|
||||
if vout_count == 0:
|
||||
res = TestResult("Inflated coinbase")
|
||||
res.detail = "SKIP: Coinbase has no outputs"
|
||||
return res
|
||||
|
||||
# offset now points to the first vout's nValue (int64) within the coinbase tx
|
||||
original_value = struct.unpack_from('<q', coinbase_raw, offset)[0]
|
||||
inflated_value = original_value * 100 # 100x the reward
|
||||
struct.pack_into('<q', coinbase_raw, offset, inflated_value)
|
||||
|
||||
# Rebuild block with modified coinbase and recomputed Merkle root
|
||||
all_txs = [bytes(coinbase_raw)] + [raw for _, raw in txs[1:]]
|
||||
tampered = rebuild_block_with_new_merkle(header_bytes, all_txs)
|
||||
|
||||
return submit_and_check(
|
||||
f"Inflated coinbase ({original_value} -> {inflated_value} sat)",
|
||||
tampered.hex(), tip_hash
|
||||
)
|
||||
|
||||
def test_duplicate_transaction(block_data, tip_hash):
|
||||
"""Test 8: Duplicate a transaction in the block (Merkle malleability)."""
|
||||
hdr = parse_header(block_data)
|
||||
tx_data_start = hdr['header_end']
|
||||
header_bytes = block_data[:tx_data_start]
|
||||
|
||||
tx_count, txs, _ = find_tx_boundaries(block_data, tx_data_start)
|
||||
|
||||
if tx_count < 1:
|
||||
res = TestResult("Duplicate transaction")
|
||||
res.detail = "SKIP: No transactions in block"
|
||||
return res
|
||||
|
||||
# Duplicate the last transaction and recompute Merkle root
|
||||
all_txs = [raw for _, raw in txs] + [txs[-1][1]]
|
||||
tampered = rebuild_block_with_new_merkle(header_bytes, all_txs)
|
||||
|
||||
return submit_and_check("Duplicate transaction (Merkle malleability)", tampered.hex(), tip_hash)
|
||||
|
||||
def test_timestamp_too_old(block_data, tip_hash):
|
||||
"""Test 9: Set timestamp to 0 (way before median time past)."""
|
||||
tampered = bytearray(block_data)
|
||||
# Set nTime to 1 (basically epoch start - way before MTP)
|
||||
struct.pack_into('<I', tampered, OFF_TIME, 1)
|
||||
return submit_and_check("Timestamp too old (nTime=1)", tampered.hex(), tip_hash)
|
||||
|
||||
# ---------- Main ----------
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print(" DragonX Block Validation Test Suite")
|
||||
print("=" * 70)
|
||||
|
||||
# Get chain state
|
||||
print("\nConnecting to node...")
|
||||
info = rpc_json("getblockchaininfo")
|
||||
height = info["blocks"]
|
||||
tip_hash = info["bestblockhash"]
|
||||
print(f" Chain height : {height}")
|
||||
print(f" Chain tip : {tip_hash}")
|
||||
|
||||
block_info = rpc_json("getblock", tip_hash)
|
||||
print(f" Current nBits: 0x{int(block_info['bits'], 16):08x}")
|
||||
print(f" Difficulty : {block_info['difficulty']}")
|
||||
|
||||
# Fetch raw block
|
||||
block_hex = rpc("getblock", tip_hash, "0")
|
||||
block_data = bytes.fromhex(block_hex)
|
||||
print(f" Block size : {len(block_data)} bytes")
|
||||
|
||||
hdr = parse_header(block_data)
|
||||
tx_data_start = hdr['header_end']
|
||||
tx_count, txs, _ = find_tx_boundaries(block_data, tx_data_start)
|
||||
print(f" Transactions : {tx_count}")
|
||||
|
||||
# Run all tests
|
||||
tests = [
|
||||
("1. Bad nBits (diff=1)", test_bad_nbits),
|
||||
("2. Bad RandomX solution", test_bad_randomx_solution),
|
||||
("3. Future timestamp (+1hr)", test_future_timestamp),
|
||||
("4. Bad block version (v=0)", test_bad_version),
|
||||
("5. Bad Merkle root", test_bad_merkle_root),
|
||||
("6. Bad hashPrevBlock", test_bad_prevhash),
|
||||
("7. Inflated coinbase reward", test_inflated_coinbase),
|
||||
("8. Duplicate transaction", test_duplicate_transaction),
|
||||
("9. Timestamp too old (MTP)", test_timestamp_too_old),
|
||||
]
|
||||
|
||||
print(f"\nRunning {len(tests)} validation tests...\n")
|
||||
print("-" * 70)
|
||||
|
||||
results = []
|
||||
for label, test_func in tests:
|
||||
# Re-fetch tip in case of a new block during testing
|
||||
current_tip = rpc("getbestblockhash")
|
||||
if current_tip != tip_hash:
|
||||
print(f" [info] Chain tip advanced, re-fetching block...")
|
||||
tip_hash = current_tip
|
||||
block_hex = rpc("getblock", tip_hash, "0")
|
||||
block_data = bytes.fromhex(block_hex)
|
||||
|
||||
sys.stdout.write(f" {label:<45}")
|
||||
sys.stdout.flush()
|
||||
|
||||
res = test_func(block_data, tip_hash)
|
||||
results.append(res)
|
||||
|
||||
if res.passed:
|
||||
print(f" PASS")
|
||||
elif "SKIP" in res.detail:
|
||||
print(f" SKIP")
|
||||
else:
|
||||
print(f" FAIL")
|
||||
|
||||
# Print detail on a second line
|
||||
if res.detail:
|
||||
# Truncate long lines for readability
|
||||
detail = res.detail[:120] + "..." if len(res.detail) > 120 else res.detail
|
||||
print(f" -> {detail}")
|
||||
elif res.rpc_result:
|
||||
print(f" -> RPC: {res.rpc_result}")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 70)
|
||||
passed = sum(1 for r in results if r.passed)
|
||||
failed = sum(1 for r in results if not r.passed and "SKIP" not in r.detail)
|
||||
skipped = sum(1 for r in results if "SKIP" in r.detail)
|
||||
total = len(results)
|
||||
|
||||
print(f" Results: {passed}/{total} passed, {failed} failed, {skipped} skipped")
|
||||
|
||||
if failed == 0:
|
||||
print(" ALL TESTS PASSED - Block validation is intact!")
|
||||
else:
|
||||
print("\n FAILED TESTS:")
|
||||
for r in results:
|
||||
if not r.passed and "SKIP" not in r.detail:
|
||||
print(f" - {r.name}: {r.detail or r.rpc_result}")
|
||||
|
||||
# Verify chain tip is still intact
|
||||
final_tip = rpc("getbestblockhash")
|
||||
if final_tip == tip_hash or True: # tip may have advanced naturally
|
||||
print(f"\n Chain integrity: OK (tip={final_tip[:16]}...)")
|
||||
print("=" * 70)
|
||||
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,210 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify that DragonX rejects a block with diff=1 (trivially easy nBits).
|
||||
|
||||
This script:
|
||||
1. Connects to the local DragonX node via RPC
|
||||
2. Fetches the current tip block in raw hex
|
||||
3. Deserializes the block header
|
||||
4. Tampers with nBits to set difficulty=1 (0x200f0f0f)
|
||||
5. Reserializes and submits via submitblock
|
||||
6. Verifies the node rejects it
|
||||
|
||||
Usage:
|
||||
python3 test_diff1_block.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
CLI = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "dragonx-cli")
|
||||
DEBUG_LOG = os.path.expanduser("~/.hush/DRAGONX/debug.log")
|
||||
|
||||
def rpc(method, *args):
|
||||
"""Call dragonx-cli with the given RPC method and arguments."""
|
||||
cmd = [CLI, method] + [str(a) for a in args]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return result.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Some RPC calls return non-zero for rejection messages
|
||||
if e.stdout and e.stdout.strip():
|
||||
return e.stdout.strip()
|
||||
if e.stderr and e.stderr.strip():
|
||||
return e.stderr.strip()
|
||||
raise
|
||||
|
||||
def rpc_json(method, *args):
|
||||
"""Call dragonx-cli and parse JSON result."""
|
||||
raw = rpc(method, *args)
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def read_uint32(data, offset):
|
||||
return struct.unpack_from('<I', data, offset)[0], offset + 4
|
||||
|
||||
def read_int32(data, offset):
|
||||
return struct.unpack_from('<i', data, offset)[0], offset + 4
|
||||
|
||||
def read_uint256(data, offset):
|
||||
return data[offset:offset+32], offset + 32
|
||||
|
||||
def read_compactsize(data, offset):
|
||||
val = data[offset]
|
||||
if val < 253:
|
||||
return val, offset + 1
|
||||
elif val == 253:
|
||||
return struct.unpack_from('<H', data, offset + 1)[0], offset + 3
|
||||
elif val == 254:
|
||||
return struct.unpack_from('<I', data, offset + 1)[0], offset + 5
|
||||
else:
|
||||
return struct.unpack_from('<Q', data, offset + 1)[0], offset + 9
|
||||
|
||||
def write_uint32(val):
|
||||
return struct.pack('<I', val)
|
||||
|
||||
def write_int32(val):
|
||||
return struct.pack('<i', val)
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("DragonX Diff=1 Block Rejection Test")
|
||||
print("=" * 60)
|
||||
|
||||
# Step 1: Get current chain info
|
||||
print("\n[1] Fetching chain info...")
|
||||
info = rpc_json("getblockchaininfo")
|
||||
height = info["blocks"]
|
||||
best_hash = info["bestblockhash"]
|
||||
print(f" Chain height: {height}")
|
||||
print(f" Best block: {best_hash}")
|
||||
|
||||
# Step 2: Get the tip block header details
|
||||
print("\n[2] Fetching tip block details...")
|
||||
block_info = rpc_json("getblock", best_hash)
|
||||
current_bits = block_info["bits"]
|
||||
current_difficulty = block_info["difficulty"]
|
||||
print(f" Current nBits: {current_bits}")
|
||||
print(f" Current difficulty: {current_difficulty}")
|
||||
|
||||
# Step 3: Get the raw block hex
|
||||
print("\n[3] Fetching raw block hex...")
|
||||
block_hex = rpc("getblock", best_hash, "0")
|
||||
block_data = bytes.fromhex(block_hex)
|
||||
print(f" Raw block size: {len(block_data)} bytes")
|
||||
|
||||
# Step 4: Parse the block header to find the nBits offset
|
||||
# Header format:
|
||||
# nVersion: 4 bytes (int32)
|
||||
# hashPrevBlock: 32 bytes (uint256)
|
||||
# hashMerkleRoot: 32 bytes (uint256)
|
||||
# hashFinalSaplingRoot: 32 bytes (uint256)
|
||||
# nTime: 4 bytes (uint32)
|
||||
# nBits: 4 bytes (uint32) <-- this is what we tamper
|
||||
# nNonce: 32 bytes (uint256)
|
||||
# nSolution: compactsize + data
|
||||
|
||||
offset = 0
|
||||
nVersion, offset = read_int32(block_data, offset)
|
||||
hashPrevBlock, offset = read_uint256(block_data, offset)
|
||||
hashMerkleRoot, offset = read_uint256(block_data, offset)
|
||||
hashFinalSaplingRoot, offset = read_uint256(block_data, offset)
|
||||
nTime, offset = read_uint32(block_data, offset)
|
||||
nbits_offset = offset
|
||||
nBits, offset = read_uint32(block_data, offset)
|
||||
nNonce, offset = read_uint256(block_data, offset)
|
||||
sol_len, offset = read_compactsize(block_data, offset)
|
||||
|
||||
print(f"\n[4] Parsed block header:")
|
||||
print(f" nVersion: {nVersion}")
|
||||
print(f" nTime: {nTime}")
|
||||
print(f" nBits: 0x{nBits:08x} (offset {nbits_offset})")
|
||||
print(f" nSolution: {sol_len} bytes")
|
||||
|
||||
# Step 5: Tamper nBits to diff=1
|
||||
# 0x200f0f0f is the powLimit for DragonX (minimum difficulty / diff=1)
|
||||
DIFF1_NBITS = 0x200f0f0f
|
||||
print(f"\n[5] Tampering nBits from 0x{nBits:08x} -> 0x{DIFF1_NBITS:08x} (diff=1)...")
|
||||
|
||||
tampered_data = bytearray(block_data)
|
||||
struct.pack_into('<I', tampered_data, nbits_offset, DIFF1_NBITS)
|
||||
tampered_hex = tampered_data.hex()
|
||||
|
||||
# Verify the tamper worked
|
||||
check_nbits = struct.unpack_from('<I', tampered_data, nbits_offset)[0]
|
||||
assert check_nbits == DIFF1_NBITS, "nBits tamper failed!"
|
||||
print(f" Verified tampered nBits: 0x{check_nbits:08x}")
|
||||
|
||||
# Step 6: Record log position before submitting
|
||||
log_size_before = 0
|
||||
if os.path.exists(DEBUG_LOG):
|
||||
log_size_before = os.path.getsize(DEBUG_LOG)
|
||||
|
||||
# Step 7: Submit the tampered block
|
||||
print(f"\n[6] Submitting tampered block via submitblock...")
|
||||
submit_time = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
||||
result = rpc("submitblock", tampered_hex)
|
||||
print(f" submitblock result: {repr(result)}")
|
||||
# Note: Bitcoin-derived RPC returns empty string when a block is processed,
|
||||
# even if it fails internal validation. This is normal behavior.
|
||||
|
||||
# Step 8: Check debug.log for the actual rejection reason
|
||||
print(f"\n[7] Checking debug.log for rejection details...")
|
||||
log_tail = ""
|
||||
if os.path.exists(DEBUG_LOG):
|
||||
with open(DEBUG_LOG, "r", errors="replace") as f:
|
||||
f.seek(log_size_before)
|
||||
log_tail = f.read()
|
||||
# Find rejection-related lines
|
||||
rejection_lines = []
|
||||
for line in log_tail.splitlines():
|
||||
lowline = line.lower()
|
||||
if any(kw in lowline for kw in ["failed", "error", "reject", "invalid",
|
||||
"high-hash", "bad-diff", "mismatch",
|
||||
"checkblock", "checkproof", "randomx"]):
|
||||
rejection_lines.append(line.strip())
|
||||
if rejection_lines:
|
||||
print(" Rejection log entries:")
|
||||
for line in rejection_lines[-10:]:
|
||||
print(f" {line}")
|
||||
else:
|
||||
print(" No rejection entries found in new log output.")
|
||||
else:
|
||||
print(f" debug.log not found at {DEBUG_LOG}")
|
||||
|
||||
# Step 9: Evaluate result
|
||||
print("\n" + "=" * 60)
|
||||
rejected_by_rpc = result.lower() in ("rejected", "invalid") if result else False
|
||||
rejected_by_log = any("FAILED" in l or "MISMATCH" in l for l in (rejection_lines if os.path.exists(DEBUG_LOG) and rejection_lines else []))
|
||||
|
||||
if rejected_by_rpc or rejected_by_log or result == "":
|
||||
print("PASS: Block with diff=1 was correctly REJECTED!")
|
||||
if result:
|
||||
print(f" RPC result: {result}")
|
||||
else:
|
||||
print(" RPC returned empty (block processed but failed validation)")
|
||||
elif "duplicate" in (result or "").lower():
|
||||
print(f"NOTE: Block was seen as duplicate. Result: {result}")
|
||||
else:
|
||||
print(f"RESULT: {result}")
|
||||
print(" Check debug.log for rejection details.")
|
||||
|
||||
# Step 10: Verify chain tip didn't change
|
||||
print("\n[8] Verifying chain tip unchanged...")
|
||||
new_hash = rpc("getbestblockhash")
|
||||
if new_hash == best_hash:
|
||||
print(f" Chain tip unchanged: {new_hash}")
|
||||
print(" CONFIRMED: Bad block did not affect the chain.")
|
||||
else:
|
||||
print(f" WARNING: Chain tip changed! {best_hash} -> {new_hash}")
|
||||
print(" This should NOT happen!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Test complete.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user