Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d56ad8541 | |||
| 449a00434e | |||
| 5cda31b505 | |||
| ec517f86e6 | |||
| 33e5f646a7 | |||
| c1408871cc | |||
| 0a01ad8bba | |||
| 85c8d7f7dd | |||
| d6ba1aed4e |
27
.dockerignore
Normal file
27
.dockerignore
Normal file
@@ -0,0 +1,27 @@
|
||||
.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
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -167,3 +167,11 @@ 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
|
||||
31
Dockerfile.compat
Normal file
31
Dockerfile.compat
Normal file
@@ -0,0 +1,31 @@
|
||||
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"]
|
||||
200
build.sh
200
build.sh
@@ -1,19 +1,207 @@
|
||||
#!/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
|
||||
|
||||
# run correct build script for detected OS
|
||||
VERSION="1.0.0"
|
||||
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
|
||||
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$release_subdir/"
|
||||
|
||||
# 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[@]}"
|
||||
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[@]}"
|
||||
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[@]}"
|
||||
package_release macos
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Release builds complete ==="
|
||||
ls -la "$RELEASE_DIR"/
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Standard build (auto-detect OS)
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
./util/build.sh --disable-tests $@
|
||||
./util/build.sh --disable-tests "${REMAINING_ARGS[@]}"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
./util/build-mac.sh --disable-tests $@
|
||||
./util/build-mac.sh --disable-tests "${REMAINING_ARGS[@]}"
|
||||
elif [[ "$OSTYPE" == "msys"* ]]; then
|
||||
./util/build-win.sh --disable-tests $@
|
||||
#elif [[ "$OSTYPE" == "freebsd"* ]]; then
|
||||
# placeholder
|
||||
./util/build-win.sh --disable-tests "${REMAINING_ARGS[@]}"
|
||||
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, 3)
|
||||
define(_CLIENT_VERSION_MAJOR, 1)
|
||||
dnl Must be kept in sync with src/clientversion.h , ugh!
|
||||
define(_CLIENT_VERSION_MINOR, 10)
|
||||
define(_CLIENT_VERSION_REVISION, 5)
|
||||
define(_CLIENT_VERSION_MINOR, 0)
|
||||
define(_CLIENT_VERSION_REVISION, 0)
|
||||
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([Hush],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.hush.is/hush/hush3],[hush])
|
||||
AC_INIT([DragonX],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.dragonx.is/DragonX/dragonx],[dragonx])
|
||||
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=hushd
|
||||
BITCOIN_CLI_NAME=hush-cli
|
||||
BITCOIN_TX_NAME=hush-tx
|
||||
BITCOIN_DAEMON_NAME=dragonxd
|
||||
BITCOIN_CLI_NAME=dragonx-cli
|
||||
BITCOIN_TX_NAME=dragonx-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])
|
||||
|
||||
@@ -94,11 +94,11 @@ noinst_PROGRAMS =
|
||||
TESTS =
|
||||
|
||||
#if BUILD_BITCOIND
|
||||
bin_PROGRAMS += hushd
|
||||
bin_PROGRAMS += dragonxd
|
||||
#endif
|
||||
|
||||
if BUILD_BITCOIN_UTILS
|
||||
bin_PROGRAMS += hush-cli hush-tx
|
||||
bin_PROGRAMS += dragonx-cli dragonx-tx
|
||||
endif
|
||||
if ENABLE_WALLET
|
||||
bin_PROGRAMS += wallet-utility
|
||||
@@ -453,16 +453,16 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h
|
||||
#
|
||||
|
||||
# hushd binary #
|
||||
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)
|
||||
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)
|
||||
|
||||
if TARGET_WINDOWS
|
||||
hushd_SOURCES += bitcoind-res.rc
|
||||
dragonxd_SOURCES += bitcoind-res.rc
|
||||
endif
|
||||
|
||||
hushd_LDADD = \
|
||||
dragonxd_LDADD = \
|
||||
$(LIBBITCOIN_SERVER) \
|
||||
$(LIBBITCOIN_COMMON) \
|
||||
$(LIBUNIVALUE) \
|
||||
@@ -476,10 +476,10 @@ hushd_LDADD = \
|
||||
$(LIBRANDOMX)
|
||||
|
||||
if ENABLE_WALLET
|
||||
hushd_LDADD += $(LIBBITCOIN_WALLET)
|
||||
dragonxd_LDADD += $(LIBBITCOIN_WALLET)
|
||||
endif
|
||||
|
||||
hushd_LDADD += \
|
||||
dragonxd_LDADD += \
|
||||
$(BOOST_LIBS) \
|
||||
$(BDB_LIBS) \
|
||||
$(SSL_LIBS) \
|
||||
@@ -490,27 +490,27 @@ hushd_LDADD += \
|
||||
$(LIBZCASH_LIBS)
|
||||
|
||||
if TARGET_DARWIN
|
||||
hushd_LDADD += libcc.dylib $(LIBSECP256K1)
|
||||
dragonxd_LDADD += libcc.dylib $(LIBSECP256K1)
|
||||
endif
|
||||
if TARGET_WINDOWS
|
||||
hushd_LDADD += libcc.dll $(LIBSECP256K1)
|
||||
dragonxd_LDADD += libcc.dll $(LIBSECP256K1)
|
||||
endif
|
||||
if TARGET_LINUX
|
||||
hushd_LDADD += libcc.so $(LIBSECP256K1)
|
||||
dragonxd_LDADD += libcc.so $(LIBSECP256K1)
|
||||
endif
|
||||
|
||||
# [+] Decker: use static linking for libstdc++.6.dylib, libgomp.1.dylib, libgcc_s.1.dylib
|
||||
if TARGET_DARWIN
|
||||
hushd_LDFLAGS += -static-libgcc
|
||||
dragonxd_LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
# hush-cli binary #
|
||||
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)
|
||||
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)
|
||||
if TARGET_DARWIN
|
||||
hush_cli_LDFLAGS += -static-libgcc
|
||||
dragonx_cli_LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
# wallet-utility binary #
|
||||
@@ -522,10 +522,10 @@ wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
|
||||
endif
|
||||
|
||||
if TARGET_WINDOWS
|
||||
hush_cli_SOURCES += bitcoin-cli-res.rc
|
||||
dragonx_cli_SOURCES += bitcoin-cli-res.rc
|
||||
endif
|
||||
|
||||
hush_cli_LDADD = \
|
||||
dragonx_cli_LDADD = \
|
||||
$(LIBBITCOIN_CLI) \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
@@ -554,16 +554,16 @@ wallet_utility_LDADD = \
|
||||
endif
|
||||
|
||||
# hush-tx binary #
|
||||
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)
|
||||
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)
|
||||
|
||||
if TARGET_WINDOWS
|
||||
hush_tx_SOURCES += bitcoin-tx-res.rc
|
||||
dragonx_tx_SOURCES += bitcoin-tx-res.rc
|
||||
endif
|
||||
|
||||
hush_tx_LDADD = \
|
||||
dragonx_tx_LDADD = \
|
||||
$(LIBUNIVALUE) \
|
||||
$(LIBBITCOIN_COMMON) \
|
||||
$(LIBBITCOIN_UTIL) \
|
||||
@@ -574,7 +574,7 @@ hush_tx_LDADD = \
|
||||
$(LIBZCASH_LIBS) \
|
||||
$(LIBRANDOMX)
|
||||
|
||||
hush_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
|
||||
dragonx_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
|
||||
|
||||
# Zcash Protocol Primitives
|
||||
libzcash_a_SOURCES = \
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"ac_perc": "11111111",
|
||||
"ac_eras": "3",
|
||||
"ac_script": "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac",
|
||||
"clientname": "GoldenSandtrout",
|
||||
"clientname": "DragonX",
|
||||
"addnode": [
|
||||
"node1.hush.is",
|
||||
"node2.hush.is",
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
#include <event2/keyvalq_struct.h>
|
||||
#include "support/events.h"
|
||||
|
||||
uint16_t ASSETCHAINS_RPCPORT = 18031;
|
||||
uint16_t BITCOIND_RPCPORT = 18031;
|
||||
uint16_t ASSETCHAINS_RPCPORT = 21769;
|
||||
uint16_t BITCOIND_RPCPORT = 21769;
|
||||
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)"), "HUSH3.conf"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.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 HUSH3 itself, which to the internals, is also a HAC
|
||||
name = GetArg("-ac_name","HUSH3");
|
||||
// default HAC is DRAGONX itself, which to the internals, is also a HAC
|
||||
name = GetArg("-ac_name","DRAGONX");
|
||||
|
||||
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 = _("Hush RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
std::string strUsage = _("DragonX RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
if (!mapArgs.count("-version")) {
|
||||
strUsage += "\n" + _("Usage:") + "\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";
|
||||
" 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";
|
||||
|
||||
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 = _("Hush Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
std::string strUsage = _("DragonX Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
|
||||
|
||||
if (mapArgs.count("-version"))
|
||||
{
|
||||
strUsage += LicenseInfo();
|
||||
} else {
|
||||
strUsage += "\n" + _("Usage:") + "\n" +
|
||||
" hushd [options] " + _("Start a Hush Daemon") + "\n";
|
||||
" dragonxd [options] " + _("Start DragonX 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 Hush\n") +
|
||||
"depending on how you installed DragonX\n") +
|
||||
_("- Source code: %s\n"
|
||||
"- .deb package: %s\n")).c_str(),
|
||||
GetConfigFile().string().c_str(),
|
||||
"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");
|
||||
"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");
|
||||
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 hushd
|
||||
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "hush:")) {
|
||||
// detect accidental use of RPC in dragonxd
|
||||
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "dragonx:")) {
|
||||
fCommandLine = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fCommandLine)
|
||||
{
|
||||
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in hushd. Use the hush-cli utility instead.\n");
|
||||
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in dragonxd. Use the dragonx-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, "Hush %s server starting\n",SMART_CHAIN_SYMBOL);
|
||||
fprintf(stdout, "DragonX %s server starting\n",SMART_CHAIN_SYMBOL);
|
||||
|
||||
// Daemonize
|
||||
pid_t pid = fork();
|
||||
|
||||
2848
src/chainparams.cpp
2848
src/chainparams.cpp
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ class CBaseMainParams : public CBaseChainParams
|
||||
public:
|
||||
CBaseMainParams()
|
||||
{
|
||||
nRPCPort = 18031;
|
||||
nRPCPort = 21769;
|
||||
}
|
||||
};
|
||||
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", "GoldenSandtrout");
|
||||
const std::string CLIENT_NAME = GetArg("-clientname", "DragonX");
|
||||
|
||||
/**
|
||||
* 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 3
|
||||
#define CLIENT_VERSION_MINOR 10
|
||||
#define CLIENT_VERSION_REVISION 5
|
||||
#define CLIENT_VERSION_MAJOR 1
|
||||
#define CLIENT_VERSION_MINOR 0
|
||||
#define CLIENT_VERSION_REVISION 0
|
||||
#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 2024
|
||||
#define COPYRIGHT_YEAR 2026
|
||||
|
||||
#endif //HAVE_CONFIG_H
|
||||
|
||||
|
||||
@@ -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 = HUSH3_PORT;
|
||||
port = DRAGONX_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,HUSH3_PORT);
|
||||
jsonstr = hush_issuemethod(HUSHUSERPASS,(char *)"getrawtransaction",params,DRAGONX_PORT);
|
||||
//printf("userpass.(%s) got (%s)\n",HUSHUSERPASS,jsonstr);
|
||||
}
|
||||
}//else jsonstr = _dex_getrawtransaction();
|
||||
@@ -1693,6 +1693,11 @@ 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,6 +568,7 @@ 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,6 +93,7 @@ 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
|
||||
@@ -101,7 +102,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 HUSH3_PORT = 18031,BITCOIND_RPCPORT = 18031;
|
||||
char HUSHUSERPASS[8192+512+1],BTCUSERPASS[8192]; uint16_t DRAGONX_PORT = 21769,BITCOIND_RPCPORT = 21769;
|
||||
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,"HUSH3.conf");
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
#else
|
||||
while ( fname[strlen(fname)-1] != '/' )
|
||||
fname[strlen(fname)-1] = 0;
|
||||
#ifdef __APPLE__
|
||||
strcat(fname,"HUSH3.conf");
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
#else
|
||||
strcat(fname,"HUSH3.conf");
|
||||
strcat(fname,"DRAGONX.conf");
|
||||
#endif
|
||||
#endif
|
||||
if ( (fp= fopen(fname,"rb")) != 0 )
|
||||
{
|
||||
if ( (hushport= _hush_userpass(username,password,fp)) != 0 )
|
||||
HUSH3_PORT = hushport;
|
||||
DRAGONX_PORT = hushport;
|
||||
sprintf(HUSHUSERPASS,"%s:%s",username,password);
|
||||
fclose(fp);
|
||||
//printf("HUSH.(%s) -> userpass.(%s)\n",fname,HUSHUSERPASS);
|
||||
@@ -1790,38 +1790,28 @@ void hush_args(char *argv0)
|
||||
}
|
||||
|
||||
|
||||
name = GetArg("-ac_name","HUSH3");
|
||||
name = GetArg("-ac_name","DRAGONX");
|
||||
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> 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;
|
||||
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;
|
||||
|
||||
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"
|
||||
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"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
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 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() );
|
||||
// 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() );
|
||||
}
|
||||
|
||||
mapMultiArgs["-addnode"] = more_nodes;
|
||||
@@ -1830,10 +1820,15 @@ void hush_args(char *argv0)
|
||||
WITNESS_CACHE_SIZE = MAX_REORG_LENGTH+10;
|
||||
ASSETCHAINS_CC = GetArg("-ac_cc",0);
|
||||
HUSH_CCACTIVATE = GetArg("-ac_ccactivate",0);
|
||||
ASSETCHAINS_BLOCKTIME = GetArg("-ac_blocktime",60);
|
||||
// We do not support ac_public=1 chains, Hush is a platform for privacy
|
||||
|
||||
// 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_PUBLIC = 0;
|
||||
ASSETCHAINS_PRIVATE = GetArg("-ac_private",0);
|
||||
ASSETCHAINS_PRIVATE = GetArg("-ac_private", default_private);
|
||||
HUSH_SNAPSHOT_INTERVAL = GetArg("-ac_snapshot",0);
|
||||
Split(GetArg("-ac_nk",""), sizeof(ASSETCHAINS_NK)/sizeof(*ASSETCHAINS_NK), ASSETCHAINS_NK, 0);
|
||||
|
||||
@@ -1871,7 +1866,9 @@ void hush_args(char *argv0)
|
||||
ASSETCHAINS_EARLYTXIDCONTRACT = GetArg("-ac_earlytxidcontract",0);
|
||||
if ( name.c_str()[0] != 0 )
|
||||
{
|
||||
std::string selectedAlgo = GetArg("-ac_algo", std::string(ASSETCHAINS_ALGORITHMS[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);
|
||||
|
||||
for ( int i = 0; i < ASSETCHAINS_NUMALGOS; i++ )
|
||||
{
|
||||
@@ -1900,12 +1897,20 @@ void hush_args(char *argv0)
|
||||
|
||||
// Set our symbol from -ac_name value
|
||||
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
|
||||
const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1);
|
||||
if(ishush3) {
|
||||
ASSETCHAINS_LASTERA = 3;
|
||||
}
|
||||
if ( ASSETCHAINS_LASTERA < 1 || ASSETCHAINS_LASTERA > ASSETCHAINS_MAX_ERAS )
|
||||
{
|
||||
ASSETCHAINS_LASTERA = 1;
|
||||
@@ -1939,33 +1944,13 @@ void hush_args(char *argv0)
|
||||
ASSETCHAINS_SCRIPTPUB = GetArg("-ac_script","");
|
||||
|
||||
|
||||
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;
|
||||
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
|
||||
ASSETCHAINS_SAPLING = 1;
|
||||
// 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
|
||||
ASSETCHAINS_REWARD[0] = 300000000; // 3 DRAGONX per block
|
||||
ASSETCHAINS_HALVING[0] = 3500000; // halving every 3.5M blocks
|
||||
}
|
||||
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);
|
||||
@@ -2480,11 +2465,11 @@ void hush_args(char *argv0)
|
||||
void hush_nameset(char *symbol,char *dest,char *source)
|
||||
{
|
||||
if ( source[0] == 0 ) {
|
||||
strcpy(symbol,(char *)"HUSH3");
|
||||
strcpy(symbol,(char *)"DRAGONX");
|
||||
strcpy(dest,(char *)"BTC");
|
||||
} else {
|
||||
strcpy(symbol,source);
|
||||
strcpy(dest,(char *)"HUSH3");
|
||||
strcpy(dest,(char *)"DRAGONX");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -377,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 'GoldenSandtrout'"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
|
||||
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'DragonX'"));
|
||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.conf"));
|
||||
if (mode == HMM_BITCOIND)
|
||||
{
|
||||
#if !defined(WIN32)
|
||||
@@ -605,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(_("Hush Arrakis Chain options:"));
|
||||
strUsage += HelpMessageGroup(_("DragonX 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"));
|
||||
@@ -1619,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","GoldenSandtrout"), CLIENT_VERSION, uacomments);
|
||||
strSubVersion = FormatSubVersion(GetArg("-clientname","DragonX"), 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));
|
||||
|
||||
22
src/main.cpp
22
src/main.cpp
@@ -4993,6 +4993,8 @@ 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");
|
||||
if ( !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);
|
||||
@@ -5138,6 +5140,26 @@ 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() )
|
||||
|
||||
@@ -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 HUSH3.conf and restart.") << std::endl;
|
||||
std::cout << _("To enable mining, add 'gen=1' to your DRAGONX.conf and restart.") << std::endl;
|
||||
lines += 2;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
385
src/miner.cpp
385
src/miner.cpp
@@ -23,6 +23,7 @@
|
||||
#include "pow/tromp/equi_miner.h"
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include "amount.h"
|
||||
#include "chainparams.h"
|
||||
#include "consensus/consensus.h"
|
||||
@@ -51,6 +52,7 @@
|
||||
#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>
|
||||
@@ -1011,8 +1013,213 @@ enum RandomXSolverCancelCheck
|
||||
Reason2
|
||||
};
|
||||
|
||||
int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); }
|
||||
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
|
||||
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;
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
void static RandomXMiner(CWallet *pwallet)
|
||||
@@ -1050,33 +1257,12 @@ void static RandomXMiner()
|
||||
);
|
||||
miningTimer.start();
|
||||
|
||||
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__);
|
||||
// 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");
|
||||
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
|
||||
@@ -1147,48 +1333,37 @@ 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);
|
||||
// Use the initial key at the start of the chain, until the first key block
|
||||
// Update the shared dataset key — only one thread will actually rebuild,
|
||||
// others will see the key is already current and skip.
|
||||
if( (Mining_height) < randomxInterval + randomxBlockLag) {
|
||||
randomx_init_cache(randomxCache, &randomxKey, sizeof randomxKey);
|
||||
rxdebug("%s: initialized cache with initial key\n");
|
||||
g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
|
||||
rxdebug("%s: updated shared dataset 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();
|
||||
|
||||
randomx_init_cache(randomxCache, &randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: initialized cache with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
|
||||
rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
|
||||
}
|
||||
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
rxdebug("%s: dataset initialized\n");
|
||||
|
||||
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
|
||||
if(myVM == NULL) {
|
||||
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
|
||||
return;
|
||||
}
|
||||
// Acquire shared lock to prevent dataset rebuild while we're hashing
|
||||
boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
|
||||
//fprintf(stderr,"RandomXMiner: Mining_start=%u\n", Mining_start);
|
||||
#ifdef ENABLE_WALLET
|
||||
CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, 0);
|
||||
@@ -1268,16 +1443,17 @@ void static RandomXMiner()
|
||||
arith_uint256 hashTarget;
|
||||
hashTarget = HASHTarget;
|
||||
|
||||
CRandomXInput rxInput(pblocktemplate->block);
|
||||
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Use the current block as randomx input
|
||||
randomxInput << pblocktemplate->block;
|
||||
// Serialize block header without nSolution but with nNonce for deterministic RandomX input
|
||||
randomxInput << rxInput;
|
||||
|
||||
// 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, sizeof randomxInput, randomxHash);
|
||||
randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
|
||||
rxdebug("%s: calculated randomx hash\n");
|
||||
|
||||
rxdebug("%s: randomxHash=");
|
||||
@@ -1324,14 +1500,28 @@ void static RandomXMiner()
|
||||
|
||||
CValidationState state;
|
||||
//{ LOCK(cs_main);
|
||||
if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
|
||||
// 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 )
|
||||
{
|
||||
h = UintToArith256(B.GetHash());
|
||||
fprintf(stderr,"RandomXMiner: Invalid randomx block mined, try again ");
|
||||
fprintf(stderr,"RandomXMiner: TestBlockValidity FAILED at ht.%d nNonce=%s hash=",
|
||||
Mining_height, pblock->nNonce.ToString().c_str());
|
||||
for (z=31; z>=0; z--)
|
||||
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
|
||||
gotinvalid = 1;
|
||||
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;
|
||||
return(false);
|
||||
}
|
||||
//}
|
||||
@@ -1399,21 +1589,24 @@ void static RandomXMiner()
|
||||
pblock->nBits = savebits;
|
||||
}
|
||||
|
||||
rxdebug("%s: going to destroy rx VM\n");
|
||||
randomx_destroy_vm(myVM);
|
||||
rxdebug("%s: destroyed VM\n");
|
||||
// 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();
|
||||
}
|
||||
|
||||
} catch (const boost::thread_interrupted&) {
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
|
||||
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");
|
||||
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
|
||||
|
||||
LogPrintf("HushRandomXMiner terminated\n");
|
||||
throw;
|
||||
@@ -1422,20 +1615,21 @@ void static RandomXMiner()
|
||||
c.disconnect();
|
||||
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
|
||||
|
||||
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");
|
||||
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
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
randomx_release_dataset(randomxDataset);
|
||||
rxdebug("%s: released dataset in normal exit\n");
|
||||
randomx_release_cache(randomxCache);
|
||||
rxdebug("%s: released cache in normal exit\n");
|
||||
// Only destroy per-thread VM, dataset/cache are shared
|
||||
if (myVM != nullptr) {
|
||||
randomx_destroy_vm(myVM);
|
||||
myVM = nullptr;
|
||||
}
|
||||
miningTimer.stop();
|
||||
c.disconnect();
|
||||
}
|
||||
@@ -1879,8 +2073,18 @@ 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)
|
||||
@@ -1895,6 +2099,21 @@ 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 ) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#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() {
|
||||
|
||||
152
src/pow.cpp
152
src/pow.cpp
@@ -28,6 +28,8 @@
|
||||
#include "uint256.h"
|
||||
#include "util.h"
|
||||
#include "sodium.h"
|
||||
#include "RandomX/src/randomx.h"
|
||||
#include <mutex>
|
||||
|
||||
#ifdef ENABLE_RUST
|
||||
#include "librustzcash.h"
|
||||
@@ -337,6 +339,7 @@ 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);
|
||||
@@ -683,6 +686,140 @@ 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
|
||||
|
||||
// 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);
|
||||
|
||||
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
|
||||
{
|
||||
// Only applies to RandomX chains
|
||||
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
|
||||
return true;
|
||||
|
||||
// Disabled if activation height is negative
|
||||
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
|
||||
return true;
|
||||
|
||||
// Not yet at activation height
|
||||
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
|
||||
return true;
|
||||
|
||||
// Do not affect initial block loading
|
||||
extern int32_t HUSH_LOADINGBLOCKS;
|
||||
if (HUSH_LOADINGBLOCKS != 0)
|
||||
return true;
|
||||
|
||||
// Skip when miner is validating its own block via TestBlockValidity
|
||||
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);
|
||||
}
|
||||
|
||||
static int randomxInterval = GetRandomXInterval();
|
||||
static int randomxBlockLag = GetRandomXBlockLag();
|
||||
|
||||
// Determine the correct RandomX key for this height
|
||||
char initialKey[82];
|
||||
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
|
||||
|
||||
std::string rxKey;
|
||||
if (height < randomxInterval + randomxBlockLag) {
|
||||
// Use initial key derived from chain params
|
||||
rxKey = std::string(initialKey, strlen(initialKey));
|
||||
} else {
|
||||
// Use block hash at the key height
|
||||
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
|
||||
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
|
||||
if (pKeyIndex == nullptr) {
|
||||
return error("CheckRandomXSolution(): cannot get block index at key height %d for block %d", keyHeight, height);
|
||||
}
|
||||
uint256 blockKey = pKeyIndex->GetBlockHash();
|
||||
rxKey = std::string((const char*)&blockKey, sizeof(blockKey));
|
||||
}
|
||||
|
||||
// Serialize the block header without nSolution (but with nNonce) as RandomX input
|
||||
CRandomXInput rxInput(*pblock);
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << rxInput;
|
||||
|
||||
char computedHash[RANDOMX_HASH_SIZE];
|
||||
|
||||
{
|
||||
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();
|
||||
s_rxCache = randomx_alloc_cache(flags);
|
||||
if (s_rxCache == nullptr) {
|
||||
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
|
||||
}
|
||||
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
|
||||
s_rxCurrentKey = rxKey;
|
||||
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;
|
||||
randomx_vm_set_cache(s_rxVM, s_rxCache);
|
||||
}
|
||||
|
||||
randomx_calculate_hash(s_rxVM, &ss[0], ss.size(), computedHash);
|
||||
}
|
||||
|
||||
// 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(), ss.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(), ss.size(), pblock->nNonce.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
LogPrint("randomx", "CheckRandomXSolution(): valid at height %d\n", height);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -726,9 +863,19 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
|
||||
// Check proof of work matches claimed amount
|
||||
if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget )
|
||||
{
|
||||
if ( HUSH_LOADINGBLOCKS != 0 )
|
||||
return true;
|
||||
// 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 ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 )
|
||||
{
|
||||
if ( Params().NetworkIDString() != "regtest" )
|
||||
{
|
||||
for (i=31; i>=0; i--)
|
||||
@@ -745,6 +892,7 @@ 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]);
|
||||
|
||||
12
src/pow.h
12
src/pow.h
@@ -38,6 +38,18 @@ 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);
|
||||
|
||||
/** 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,6 +237,33 @@ 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.
|
||||
|
||||
@@ -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 HUSH3.conf setting gen).\n"
|
||||
"It is set with the command line argument -gen (or DRAGONX.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\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) The string version\n"
|
||||
" \"subver\": \"/DragonX: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\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) the server subversion string\n"
|
||||
" \"subversion\": \"/DragonX: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": "GoldenSandtrout",
|
||||
"clientname": "DragonX",
|
||||
"addnode": [
|
||||
"1.1.1.1"
|
||||
]
|
||||
|
||||
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,"HUSH3.conf");
|
||||
strcpy(confname,"DRAGONX.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 HUSH3.conf
|
||||
// Don't overwrite existing settings so command line settings override DRAGONX.conf
|
||||
string strKey = string("-") + it->string_key;
|
||||
if (mapSettingsRet.count(strKey) == 0)
|
||||
{
|
||||
@@ -1029,14 +1029,16 @@ void SetThreadPriority(int nPriority)
|
||||
std::string PrivacyInfo()
|
||||
{
|
||||
return "\n" +
|
||||
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using Hush, please see <%s>."),
|
||||
"https://hush.is/security/")) + "\n";
|
||||
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using DragonX, please see <%s>."),
|
||||
"https://dragonx.is/security/")) + "\n";
|
||||
}
|
||||
|
||||
std::string LicenseInfo()
|
||||
{
|
||||
return "\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2016-%i Duke Leto and The Hush Developers"), COPYRIGHT_YEAR)) + "\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" +
|
||||
"\n" +
|
||||
FormatParagraph(strprintf(_("Copyright (C) 2016-2020 jl777 and SuperNET developers"))) + "\n" +
|
||||
"\n" +
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
#define HUSH_VERSION_H
|
||||
|
||||
// network protocol versioning
|
||||
static const int PROTOCOL_VERSION = 1987429;
|
||||
// DragonX 1.0.0 - bumped to separate from old HUSH/DragonX nodes with RandomX bug
|
||||
static const int PROTOCOL_VERSION = 2000000;
|
||||
//! initial proto version, to be increased after version/verack negotiation
|
||||
static const int INIT_PROTO_VERSION = 209;
|
||||
//! In this version, 'getheaders' was introduced.
|
||||
@@ -30,8 +31,9 @@ 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 (HACs)
|
||||
static const int MIN_PEER_PROTO_VERSION = 1987420;
|
||||
//! 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;
|
||||
|
||||
//! 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 = 18031;
|
||||
uint16_t BITCOIND_RPCPORT = 21769;
|
||||
uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT;
|
||||
uint32_t ASSETCHAIN_INIT,ASSETCHAINS_CC;
|
||||
uint32_t ASSETCHAINS_MAGIC = 2387029918;
|
||||
|
||||
163
util/block_time_calculator.py
Executable file
163
util/block_time_calculator.py
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/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()
|
||||
227
util/bootstrap-dragonx.sh
Executable file
227
util/bootstrap-dragonx.sh
Executable file
@@ -0,0 +1,227 @@
|
||||
#!/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_FILE="DRAGONX.zip"
|
||||
CHAIN_NAME="DRAGONX"
|
||||
|
||||
# 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
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local outfile="$2"
|
||||
|
||||
if command -v wget &>/dev/null; then
|
||||
wget --progress=bar:force -O "$outfile" "$url" || error "Download failed: $url"
|
||||
elif command -v curl &>/dev/null; then
|
||||
curl -L --progress-bar -o "$outfile" "$url" || error "Download failed: $url"
|
||||
else
|
||||
error "Neither wget nor curl found. Please install one and retry."
|
||||
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"
|
||||
|
||||
info "Downloading bootstrap from $BOOTSTRAP_BASE_URL ..."
|
||||
info "This may take a while depending on your connection speed."
|
||||
|
||||
download_file "$BOOTSTRAP_BASE_URL/$BOOTSTRAP_FILE" "$outfile"
|
||||
info "Bootstrap download complete."
|
||||
|
||||
info "Downloading checksums..."
|
||||
download_file "$BOOTSTRAP_BASE_URL/${BOOTSTRAP_FILE}.md5" "$md5file"
|
||||
download_file "$BOOTSTRAP_BASE_URL/${BOOTSTRAP_FILE}.sha256" "$sha256file"
|
||||
|
||||
# Verify checksums
|
||||
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
|
||||
|
||||
# Clean up checksum files
|
||||
rm -f "$md5file" "$sha256file"
|
||||
|
||||
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 "$@"
|
||||
@@ -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 hushd.exe hush-cli.exe hush-tx.exe
|
||||
CC="${CC} -g " CXX="${CXX} -g " make V=1 dragonxd.exe dragonx-cli.exe dragonx-tx.exe
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#!/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 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"
|
||||
#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"
|
||||
FILE="$DIR.tar"
|
||||
TIME=$(perl -e 'print time')
|
||||
|
||||
@@ -23,12 +24,13 @@ 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 hushd hush-cli hush-tx hush-arrakis-chain ../$BUILD
|
||||
cp dragonxd dragonx-cli dragonx-tx ../$BUILD
|
||||
cd ../$BUILD
|
||||
strip hushd hush-cli hush-tx
|
||||
strip dragonxd dragonx-cli dragonx-tx
|
||||
cd ..
|
||||
tar -f $FILE -c hush-$VERSION-linux-amd64/*
|
||||
tar -f $FILE -c dragonx-$VERSION-linux-amd64/*
|
||||
gzip -9 $FILE
|
||||
sha256sum *.gz
|
||||
du -sh *.gz
|
||||
|
||||
554
util/test_block_validation.py
Normal file
554
util/test_block_validation.py
Normal file
@@ -0,0 +1,554 @@
|
||||
#!/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())
|
||||
210
util/test_diff1_block.py
Executable file
210
util/test_diff1_block.py
Executable file
@@ -0,0 +1,210 @@
|
||||
#!/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