diff --git a/.gitignore b/.gitignore index cdbb87517..1cc352ee3 100644 --- a/.gitignore +++ b/.gitignore @@ -131,7 +131,6 @@ src/cc/rogue/rogue src/cc/rogue/rogue.so src/cc/rogue/test.zip -src/cc/dapps/a.out src/checkfile src/foo.zip @@ -154,14 +153,15 @@ src/rogue.scr src/cc/rogue/confdefs.h src/cc/rogue/x64 -src/cc/dapps/a.out src/Makefile.in doc/man/Makefile.in Makefile.in src/libcc.so src/libcc.dll +src/libcc.dylib src/cc/customcc.so src/cc/customcc.dll +src/cc/customcc.dylib src/HUSH3_7776 REGTEST_7776 src/cc/librogue.so diff --git a/Dockerfile.compat b/Dockerfile.compat index 27a4a3b0f..fc35cc26e 100644 --- a/Dockerfile.compat +++ b/Dockerfile.compat @@ -1,4 +1,8 @@ -FROM ubuntu:20.04 +# Base image is parameterised so one Dockerfile can produce binaries for several +# glibc floors: docker build --build-arg BASE_IMAGE=ubuntu:18.04 ... +# The default is unchanged, so `./build.sh --linux-compat` behaves exactly as before. +ARG BASE_IMAGE=ubuntu:20.04 +FROM ${BASE_IMAGE} ENV DEBIAN_FRONTEND=noninteractive @@ -23,6 +27,17 @@ RUN rm -rf /build/depends/built /build/depends/work \ && rm -rf /build/src/cc/*.o /build/src/cc/*.a \ && rm -f /build/config.status /build/config.log +# The build context excludes .git (see .dockerignore), so genbuild.sh cannot derive +# a version and would stamp the binaries "-unk". build.sh computes the real one +# on the host and passes it in here. +ARG BUILD_DESC= +ENV DRAGONX_BUILD_DESC=${BUILD_DESC} + +RUN if [ -z "$DRAGONX_BUILD_DESC" ]; then \ + echo "WARNING: no BUILD_DESC build-arg -- binaries will be stamped -unk." >&2; \ + echo " Prefer ./build.sh --linux-compat, or pass --build-arg BUILD_DESC=..." >&2; \ + fi + RUN cd /build && ./util/build.sh --disable-tests -j$(nproc) # Strip binaries inside the container so extracted files are already small diff --git a/build.sh b/build.sh index 9c24d4db5..e39c4a400 100755 --- a/build.sh +++ b/build.sh @@ -6,10 +6,34 @@ set -eu -o pipefail -VERSION="1.0.3" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RELEASE_DIR="$SCRIPT_DIR/release" +# Derive the release version from configure.ac instead of hardcoding it here. +# A stale literal names the release directories after the wrong version while the +# binaries inside report the real one: this said 1.0.3 while the tree was already +# 1.1.0, so `./build.sh --all-release` would have produced +# release/dragonx-1.0.3-/ full of binaries announcing 1.1.0. +# Mirrors configure.ac's _CLIENT_VERSION_SUFFIX m4 exactly: +# build < 25 -> beta(build+1) build < 50 -> rc(build-24) +# build == 50 -> plain release build > 50 -> point release (build-50) +_acdef() { sed -n "s/^define(_CLIENT_VERSION_$1, *\([0-9]\{1,\}\))/\1/p" "$SCRIPT_DIR/configure.ac"; } +_V_MAJOR="$(_acdef MAJOR)" +_V_MINOR="$(_acdef MINOR)" +_V_REVISION="$(_acdef REVISION)" +_V_BUILD="$(_acdef BUILD)" +if [ -z "$_V_MAJOR" ] || [ -z "$_V_MINOR" ] || [ -z "$_V_REVISION" ] || [ -z "$_V_BUILD" ]; then + echo "ERROR: could not read the version from $SCRIPT_DIR/configure.ac" >&2 + echo " refusing to build a release whose directory name would be wrong." >&2 + exit 1 +fi +if [ "$_V_BUILD" -lt 25 ]; then _V_SUFFIX="$_V_REVISION-beta$((_V_BUILD + 1))" +elif [ "$_V_BUILD" -lt 50 ]; then _V_SUFFIX="$_V_REVISION-rc$((_V_BUILD - 24))" +elif [ "$_V_BUILD" -eq 50 ]; then _V_SUFFIX="$_V_REVISION" +else _V_SUFFIX="$_V_REVISION-$((_V_BUILD - 50))" +fi +VERSION="$_V_MAJOR.$_V_MINOR.$_V_SUFFIX" + # Parse release flags BUILD_LINUX_RELEASE=0 BUILD_WIN_RELEASE=0 @@ -140,7 +164,34 @@ if [ $BUILD_LINUX_COMPAT -eq 1 ] || [ $BUILD_LINUX_RELEASE -eq 1 ] || [ $BUILD_W 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" . + # .dockerignore excludes .git, so genbuild.sh inside the container cannot + # derive the version and would stamp the binaries "-unk". Compute it on the + # host, mirroring util/genbuild.sh exactly, and pass it in via --build-arg. + # NB: build.sh runs under `set -eu -o pipefail`, so every git call here must be + # non-fatal -- a source tarball, a machine without git, or a branch whose only + # reachable tags are lightweight (v1.0.1-v1.0.3) would otherwise abort the build. + git diff >/dev/null 2>&1 || true # refresh index: touched-but-unmodified are not dirty + COMPAT_BUILD_DESC="" + COMPAT_RAWDESC=$(git describe --abbrev=0 2>/dev/null || true) + if [ -n "$COMPAT_RAWDESC" ] \ + && [ "$(git rev-parse HEAD 2>/dev/null)" = "$(git rev-list -1 "$COMPAT_RAWDESC" 2>/dev/null)" ] \ + && git diff-index --quiet HEAD -- 2>/dev/null; then + COMPAT_BUILD_DESC="$COMPAT_RAWDESC" + else + COMPAT_SUFFIX=$(git rev-parse --short HEAD 2>/dev/null || true) + if [ -n "$COMPAT_SUFFIX" ]; then + git diff-index --quiet HEAD -- 2>/dev/null || COMPAT_SUFFIX="$COMPAT_SUFFIX-dirty" + COMPAT_BUILD_DESC="v$VERSION-$COMPAT_SUFFIX" + fi + fi + if [ -n "$COMPAT_BUILD_DESC" ]; then + echo "Stamping container build as: $COMPAT_BUILD_DESC" + else + echo "Warning: no usable git metadata; container binaries will be stamped -unk" + fi + $DOCKER_CMD build -f Dockerfile.compat \ + --build-arg BUILD_DESC="$COMPAT_BUILD_DESC" \ + -t "$DOCKER_IMAGE" . echo "Extracting binaries from Docker image..." CONTAINER_ID=$($DOCKER_CMD create "$DOCKER_IMAGE") diff --git a/configure.ac b/configure.ac index d0316ec49..4aab55486 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 1) dnl Must be kept in sync with src/clientversion.h , ugh! -define(_CLIENT_VERSION_MINOR, 0) -define(_CLIENT_VERSION_REVISION, 3) +define(_CLIENT_VERSION_MINOR, 3) +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))) @@ -833,6 +833,22 @@ AM_CONDITIONAL([TARGET_DARWIN], [test x$TARGET_OS = xdarwin]) AM_CONDITIONAL([BUILD_DARWIN], [test x$BUILD_OS = xdarwin]) AM_CONDITIONAL([TARGET_LINUX], [test x$TARGET_OS = xlinux]) AM_CONDITIONAL([TARGET_WINDOWS], [test x$TARGET_OS = xwindows]) + +dnl mingw ld is single-pass: bracket the internal static archives in a link group +dnl so it re-scans and resolves the cross-references DragonX added between them +dnl (libbitcoin_util/common objects using UniValue; util<->common mutual deps). +dnl Delivered via AC_SUBST (not an automake conditional) so automake does not +dnl reject the linker flags inside _LDADD. Empty elsewhere (macOS ld64 rejects the +dnl flag; GNU ld on Linux re-scans archives already). +if test "x$TARGET_OS" = "xwindows"; then + LINK_GROUP_START="-Wl,--start-group" + LINK_GROUP_END="-Wl,--end-group" +else + LINK_GROUP_START="" + LINK_GROUP_END="" +fi +AC_SUBST(LINK_GROUP_START) +AC_SUBST(LINK_GROUP_END) AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) AM_CONDITIONAL([ENABLE_MINING],[test x$enable_mining = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$BUILD_TEST = xyes]) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index cf12082c5..9f8ecba11 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,89 @@ +dragonx (1.3.0) stable; urgency=medium + + * RandomX stratum mining support: the daemon can serve stratum clients + directly, with a reference miner behind the stratummine RPC for testing. + * Stratum fixes found by audit: a malformed 63-character job_id no longer + aborts the daemon; each block is paid to the miner that actually found it + rather than to whichever client asked for work first; and a low-difficulty + share is now rejected before it costs a RandomX hash. + * Fix -connect never dialing its targets, so a node pinned to specific peers + reaches them instead of silently falling through to peer discovery. + * Remove roughly 7,100 lines of dead code, including the CBOPRET price + validation in the coinbase check, whose guard could not be true on any + chain, and adaptive-PoW difficulty logic that DragonX does not enable. + * Honor a command-line -rpcpassword across restarts, and fix a misspelled + -rpcusername key that silently discarded the configured RPC user. + * Default -checkpoints off on regtest so an isolated node leaves initial + block download, instead of staying in IBD forever and disabling every + operation gated on it. + * Release cs_main and the mempool lock on the miner's isStake error paths; + the leak presented as a permanent stall rather than a slow response. + * Windows cross-build: the mingw target links and finds librustzcash, and a + fs::path::c_str() regression in init no longer breaks the build. + * Stamp container builds with the real version instead of "-unk". + * Repair the qa/rpc-tests harness far enough to start a DragonX node and + build the shared test chain; it previously started mainnet nodes. + + -- DragonX Developers Mon, 31 Aug 2026 03:46:19 +0000 + +dragonx (1.2.0) stable; urgency=medium + + * Auto-shield matured coinbase into a wallet-owned Sapling address on a block + interval. The destination is derived from the HD seed at m/32'/coin'/i' and + is the lowest index inside -mnemonicsaplinggap, so a bare seed-phrase restore + re-derives it; auto-shielding refuses to run rather than send anywhere a + restore would not find. Enabled only when the seed's provenance is known to be + recoverable, so upgraded wallets stay opted out until the operator says + otherwise. + * Create new wallets from a BIP39 seed phrase by default, byte-compatible with + SilentDragonXLite. z_exportmnemonic returns the phrase; -mnemonic restores + from it. + * New RPC z_autoshieldstatus reports whether auto-shielding is on, the resolved + destination, the HD seed's provenance, and why it is off when it is off. + * Bound each auto-shield round to 400 inputs and correct the transaction size + estimate to account for all three Sapling output descriptions, and lock the + selected coins for the duration of proof building so a concurrent + z_shieldcoinbase or z_sendmany cannot select them too. + * Repair, rather than reject, an hdchain record truncated by an older wallet + build. Previously one address generated under a pre-1.1.0 binary left the + wallet unopenable with "Wallet corrupted"; the record is now completed and + rewritten, and the error text names the seed-phrase remedy when it genuinely + cannot be recovered. + * Clear a stale sweep flag that could otherwise leave sweeping, consolidation + and auto-shielding permanently disabled together, and stop the async queue + silently discarding operations at shutdown while reporting success. + * Peer discovery: seed from the round-robin DNS record seed.dragonx.is rather + than three hostnames that no longer resolve, count DNS-seeded addresses so + the fixed-seed fallback is no longer triggered spuriously, give every entry + in the compiled-in seed list its P2P port, and stop non-DRAGONX smart chains + inheriting DragonX's seeds. Adds two seed nodes in new regions. + * Derive the release version from configure.ac in build.sh instead of a + hardcoded literal, and document container-based release builds in + doc/build-containers.md. + + -- DragonX Developers Tue, 25 Aug 2026 19:45:00 +0000 + +dragonx (1.1.0) stable; urgency=medium + + * Extend DRAGONX checkpoints to height 3,226,000, enabling the existing + RandomX skip below the last in-index checkpoint. Blocks requiring a + RandomX verify drop from ~391,000 to ~5,500; measured 91.3 blk/s below + the checkpoint vs 4.7 above on identical hardware. + * Verify each block's RandomX solution once per connect instead of twice, + and only when CheckBlockHeader actually performed the verification + (future-timestamped blocks keep their check). + * Build RandomX with ARCH=default rather than ARCH=native, so binaries are + not silently tuned to the build machine's CPU. A Zen4 build previously + emitted AVX-512 into librandomx.a, which cannot run on the seed fleet or + on older user CPUs. + * Remove a hardcoded git commit id that made builds without git metadata + report themselves as an unrelated 2018 commit. + * Includes the accumulated dev-branch work since 1.0.2: parallel RandomX + pre-verification, adaptive -dbcache, Sapling witness desync fix, BIP39 + seed phrases, chain-level Sapling turnstile, and the audit fixes. + + -- DragonX Developers Sun, 23 Aug 2026 10:41:53 -0500 + dragonx (1.0.3) stable; urgency=medium * IBD/sync speedups: parallel RandomX pre-verification, adaptive -dbcache, P2P download fixes @@ -8,6 +94,22 @@ dragonx (1.0.3) stable; urgency=medium -- DragonX Tue, 07 Jul 2026 05:49:59 +0200 +dragonx (1.0.2) stable; urgency=medium + + * Fix Sapling pool persistence, and report the block subsidy and total fees + in the getblock RPC + * Fix the Windows bootstrap script and add a mirror fallback + * Fix the Windows build + * Fix the macOS Sequoia build with GCC 15 + + -- DragonX Thu, 19 Mar 2026 10:09:18 -0500 + +dragonx (1.0.1) stable; urgency=medium + + * Fix a fresh-sync failure at the difficulty reset height 2838976 + + -- DragonX Thu, 12 Mar 2026 01:25:21 -0500 + dragonx (1.0.0) stable; urgency=medium * Initial release of DragonX, forked from Hush Full Node @@ -15,7 +117,7 @@ dragonx (1.0.0) stable; urgency=medium * RandomX proof-of-work, 36-second block time, fully shielded transactions * New binary names: dragonxd, dragonx-cli, dragonx-tx - -- DragonX Mon, 03 Mar 2026 00:00:00 +0000 + -- DragonX Tue, 10 Mar 2026 19:39:55 -0500 hush (3.10.5) stable; urgency=medium diff --git a/contrib/debian/dragonx.manpages b/contrib/debian/dragonx.manpages index f4a5b20c9..0c914c93e 100644 --- a/contrib/debian/dragonx.manpages +++ b/contrib/debian/dragonx.manpages @@ -1,3 +1,3 @@ -DEBIAN/manpages/dragonx-cli.1 -DEBIAN/manpages/dragonx-tx.1 -DEBIAN/manpages/dragonxd.1 +doc/man/dragonxd.1 +doc/man/dragonx-cli.1 +doc/man/dragonx-tx.1 diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index b4f366273..2b6dfd5c0 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -177,7 +177,7 @@ def main(): g.write('\n') with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: process_nodes(g, f, 'chainparams_seed_test') - g.write('#endif // HUSH_CHAINPARAMSSEEDS_H\n') + g.write('#endif // DRAGONX_CHAINPARAMSSEEDS_H\n') if __name__ == '__main__': main() diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index 2516ae38e..efa242101 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -1,14 +1,23 @@ +# generate-seeds.py expects : (see its docstring). Without the port it +# emits 0, and every fixed seed becomes unconnectable -- which is what shipped: +# the whole chainparams_seed_main array carried 0x00,0x00 as the port. # node1.dragonx.is -212.56.41.63 +212.56.41.63:21768 # node2.dragonx.is -194.140.198.176 +194.140.198.176:21768 # node3.dragonx.is -212.56.41.47 +212.56.41.47:21768 # node4.dragonx.is -144.126.147.165 +144.126.147.165:21768 # node5.dragonx.is -176.126.87.241 +176.126.87.241:21768 + +# node6.dragonx.is +13.140.58.251:21768 + +# node7.dragonx.is +5.104.83.100:21768 diff --git a/depends/packages/librustzcash.mk b/depends/packages/librustzcash.mk index 864536d5b..f7cfd1a4d 100644 --- a/depends/packages/librustzcash.mk +++ b/depends/packages/librustzcash.mk @@ -53,6 +53,6 @@ endif define $(package)_stage_cmds mkdir $($(package)_staging_dir)$(host_prefix)/lib/ && \ mkdir $($(package)_staging_dir)$(host_prefix)/include/ && \ - cp $($(package)_library_file) $($(package)_staging_dir)$(host_prefix)/lib/ && \ + cp $($(package)_library_file) $($(package)_staging_dir)$(host_prefix)/lib/librustzcash.a && \ cp librustzcash/include/librustzcash.h $($(package)_staging_dir)$(host_prefix)/include/ endef diff --git a/doc/build-containers.md b/doc/build-containers.md new file mode 100644 index 000000000..1c8e7f70b --- /dev/null +++ b/doc/build-containers.md @@ -0,0 +1,223 @@ +# Building release binaries in containers + +Release binaries must be built in a container based on an **old** Linux distribution. +This document is written to be executed, by a person or an agent, on a machine that +has nothing set up yet. + +--- + +## 1. Why this exists + +glibc compatibility runs one way only. A binary linked against glibc 2.35 demands +symbol versions that glibc 2.31 does not have, and refuses to start. A binary linked +against glibc 2.29 runs on 2.29, 2.31 and 2.35 alike. + +Measured on the actual fleet, 2026-08-25: + +| binary | max GLIBC required | runs on | +|---|---|---| +| what all four 20.04 seeds run today (`v1.0.3-d159e7208`) | `GLIBC_2.29` | 18.04, 20.04, 22.04 | +| anything built on seed 176 today (Ubuntu 22.04) | `GLIBC_2.34` | 22.04 only | + +The second binary will not start on four of our own five seeds. The loader reports: + +``` +/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found +/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.30' not found +``` + +Nothing new is being *called*. glibc 2.34 merged libpthread and libdl into libc and +re-versioned every `pthread_*`, `dlsym` and `dladdr` symbol; 2.33 replaced the old +`__xstat` inlines with real `stat`/`fstat`/`lstat64`. All of those functions exist in +2.31 under older tags. Building against older headers is the entire fix. + +**Do not try to solve this with full static linking.** The daemon calls `getaddrinfo`, +`gethostbyname` and `getnameinfo`, and it must resolve `node1..node5.dragonx.is`, which +are hard-coded and injected into `-addnode` on every node. Under a fully static glibc +binary those go through NSS, which `dlopen`s `libnss_dns.so.2` at run time — it either +fails or silently requires the target to have the same glibc you linked against, which +defeats the purpose. + +--- + +## 2. What already exists in this repo + +Do not write a new build system. Two pieces are already here: + +- **`Dockerfile.compat`** — an Ubuntu base image that installs the toolchain, copies the + tree, **deletes any host-built `depends/` and object files**, runs `./util/build.sh`, + and strips the three binaries. +- **`./build.sh --linux-compat`** — builds that image, creates a throwaway container, + copies `dragonxd`, `dragonx-cli` and `dragonx-tx` out into + `release/dragonx--linux-amd64-ubuntu2004/`, adds `bootstrap-dragonx.sh`, + `asmap.dat` and the two sapling params, fixes ownership, and prints the binary's + maximum required GLIBC version. + +The base image is parameterised via `ARG BASE_IMAGE` (default `ubuntu:20.04`), so the +same Dockerfile can target several glibc floors. + +--- + +## 3. Prerequisites + +Docker (the scripted path uses `docker` specifically; podman works for the manual path +if you alias or substitute it). + +```sh +sudo apt-get update +sudo apt-get install -y docker.io git +sudo usermod -aG docker "$USER" # then log out and back in, or every command needs sudo +``` + +Budget, measured on a 4-core box: + +| resource | needs | +|---|---| +| disk | ~15 GB free (the `depends/` tree alone is ~1.6 GB per target, plus image layers) | +| RAM | 4 GB minimum, 8 GB comfortable — the link step is the peak | +| time | **1–2 hours per base image on first build.** `depends/` builds boost, BDB, wolfssl, libevent, libsodium, libcurl and rust from source. Later builds reuse Docker layer cache unless the tree changed. | + +`depends/` downloads and builds its own rust toolchain, so the host's rust (or absence +of it) is irrelevant. + +--- + +## 4. Build one target + +```sh +git clone https://git.dragonx.is/DragonX/dragonx +cd dragonx +git checkout + +./build.sh --linux-compat +``` + +Output lands in `release/dragonx--linux-amd64-ubuntu2004/` and the script +prints the max GLIBC at the end. `` is read from `configure.ac`, not +hardcoded, so it always matches what the binaries report. + +--- + +## 5. Build several targets + +```sh +for BASE in ubuntu:18.04 ubuntu:20.04 ubuntu:22.04; do + TAG="dragonx-compat-${BASE#ubuntu:}" + TAG="${TAG//./}" + docker build --build-arg "BASE_IMAGE=$BASE" -f Dockerfile.compat -t "$TAG" . + + OUT="release/dragonx-$(grep -oP 'define\(_CLIENT_VERSION_MAJOR, \K[0-9]+' configure.ac).$(grep -oP 'define\(_CLIENT_VERSION_MINOR, \K[0-9]+' configure.ac).$(grep -oP 'define\(_CLIENT_VERSION_REVISION, \K[0-9]+' configure.ac)-linux-amd64-${BASE#ubuntu:}" + mkdir -p "$OUT" + CID=$(docker create "$TAG") + for b in dragonxd dragonx-cli dragonx-tx; do docker cp "$CID:/build/src/$b" "$OUT/$b"; done + docker rm "$CID" >/dev/null + cp util/bootstrap-dragonx.sh contrib/asmap/asmap.dat sapling-output.params sapling-spend.params "$OUT/" 2>/dev/null || true +done +``` + +### Which base to choose + +| base | glibc it provides | default GCC | verdict | +|---|---|---|---| +| `ubuntu:18.04` | 2.27 | 7 | **Verify before relying on it.** The tree is built with `-std=c++17`; GCC 7's C++17 support is incomplete and its cmake (3.10) may be too old for RandomX. Attempt only if you need to reach 18.04 users, and treat a successful build as the proof. | +| `ubuntu:20.04` | 2.31 | 9 | **Recommended floor.** GCC 9 covers C++17 fully. Evidence it works: the binary the fleet runs today requires only `GLIBC_2.29`, i.e. the code touches nothing newer, so a 20.04 build reaches 18.04 machines anyway. | +| `ubuntu:22.04` | 2.35 | 11 | **Do not ship this.** It is what we already have and what excludes four of our own seeds. Useful only for development. | + +Ubuntu 20.04 left standard support in April 2025, which is precisely why it belongs in +a container on a patched host rather than on a build box someone has to maintain. + +--- + +## 6. Verify — this step is not optional + +A build that silently targets the wrong glibc looks completely normal until a user +reports that nothing starts. + +```sh +BIN=release/dragonx--linux-amd64-ubuntu2004/dragonxd + +# The ceiling. Must be <= the glibc of the OLDEST system you intend to support. +objdump -p "$BIN" | grep -oE 'GLIBC_2\.[0-9]+' | sort -t. -k2 -n | tail -1 +objdump -p "$BIN" | grep -oE 'GLIBCXX_3\.4\.[0-9]+' | sort -t. -k3 -n | tail -1 + +# If the ceiling is too high, this names the symbols responsible. +readelf --dyn-syms --wide "$BIN" | grep -E '@GLIBC_2\.(3[2-9])' +``` + +Expected for a 20.04 build: `GLIBC_2.29` or lower, `GLIBCXX_3.4.26` or lower. + +Then actually run it somewhere old. A ceiling check proves the loader will resolve the +symbols; it does not prove the binary works. `./dragonxd --version` on a real 20.04 box +is a ten-second confirmation. + +--- + +## 7. Traps + +Each of these has cost real time. + +**`ETXTBSY` when installing over a running daemon.** `cp` onto the binary fails with +"Text file busy" *even after the process has exited* — `pgrep` returning nothing is not +sufficient, the kernel still holds the text mapping. Stage into the same directory and +`mv` (rename is not blocked), allow ~10 s to settle, and **sha256-verify the installed +file before starting it**. A failed copy that goes unnoticed leaves the old binary +running and looks like a successful deploy. + +**Never touch `configure.ac` in a configured tree.** Even `cp`-ing back a byte-identical +copy updates its mtime, which makes `make` regenerate `aclocal.m4` and `configure` and +then re-run `configure`, which fails with `libdb_cxx headers missing` because the +depends prefix is not on the command line. If it happens: confirm +`git diff --quiet HEAD -- configure.ac`, then restore mtime order oldest-to-newest with +one-second gaps — `configure.ac`/`Makefile.am`, then `aclocal.m4`, then +`configure`/`Makefile.in`, then `config.status`, then `Makefile`. Inside a container +this cannot happen, which is one more reason to build there. + +**Never blind-`touch` a path that might not exist.** `touch src/config/hush-config.h` +silently *creates* an empty stray file; the real header is `bitcoin-config.h`. Check +`git status` after any timestamp surgery. + +**RandomX must be built with `ARCH=default`.** `util/build.sh` already passes it and the +comment there explains why: `ARCH=native` tunes to the build machine, and a build on an +AVX-512 host emitted 746 `zmm` instructions into `librandomx.a`, which `SIGILL`s on the +entire fleet. If you ever invoke cmake by hand, pass `-DARCH=default`. + +**Strip before distributing.** Unstripped is ~220 MB, stripped ~16 MB. `Dockerfile.compat` +already strips inside the container. + +**`util/build-win.sh` discards every argument.** There is no `"$@"` handling in it, so +`-j$(nproc)` and `--disable-tests` are dropped on the floor and the Windows build is +single-threaded. Expect it to be far slower than you planned. + +**Windows also needs `-Wa,-mbig-obj` and `-DARCH=default`.** Both are in +`util/build-win.sh` today. The mingw flag was missing from `dev` for a month; without it +the cross-compile fails at link because boost-heavy translation units exceed the +PE/COFF section limit. Do not lose it on a re-branch. + +**macOS cannot be containerised.** `util/build-mac.sh` is a native-Mac script, and there +is no darwin cross-compile path in `depends/` at all: `hosts/darwin.mk` wants +`native_cctools`, which has no package definition, and there is no SDK in the tree. It +also hardcodes an Intel Homebrew GCC path, so it produces x86_64 only — no arm64, no +universal binary. macOS needs a real Mac. + +**The `contrib/gitian-descriptors/` files are not a build path.** They are unmodified +upstream Bitcoin files (`name: "bitcoin-win-0.11"`, suite `trusty`) with zero DragonX +content. Ignore them. + +--- + +## 8. Handoff checklist + +- [ ] Docker installed, user in the `docker` group, ~15 GB free +- [ ] Correct tag or branch checked out, tree clean (`git status`) +- [ ] Version in `configure.ac` is the one you intend to release +- [ ] `./build.sh --linux-compat` completes +- [ ] GLIBC ceiling is **2.31 or lower** (2.29 expected) +- [ ] GLIBCXX ceiling is **3.4.28 or lower** (3.4.26 expected) +- [ ] `dragonxd --version` runs on a real machine of the oldest supported distro +- [ ] Binaries stripped, `release/` contains the bootstrap script, `asmap.dat` and both sapling params +- [ ] sha256 recorded for each artifact + +One more thing that is not a build step but belongs in the same conversation: the +in-app daemon updater refuses any release without a detached signature +(`kDaemonRequireSignature = true`). Publishing checksums alone means no existing user +can update in place. diff --git a/doc/man/dragonx-cli.1 b/doc/man/dragonx-cli.1 index 43b43f2cc..849ef114a 100644 --- a/doc/man/dragonx-cli.1 +++ b/doc/man/dragonx-cli.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1. -.TH DRAGONX "1" "July 2026" "DragonX RPC client version v1.0.3-95aeaed0c-dirty" "User Commands" +.TH DRAGONX-CLI "1" "August 2026" "dragonx-cli v1.3.0" "User Commands" .SH NAME -DragonX \- manual page for DragonX RPC client version v1.0.3-95aeaed0c-dirty +dragonx-cli \- manual page for dragonx-cli v1.3.0 .SH DESCRIPTION -DragonX RPC client version v1.0.3\-95aeaed0c\-dirty +DragonX RPC client version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . @@ -70,25 +70,22 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default: .IP Read extra arguments from standard input, one per line until EOF/Ctrl\-D (recommended for sensitive information such as passphrases) -.PP +.SH COPYRIGHT + In order to ensure you are adequately protecting your privacy when using DragonX, please see . -.SH COPYRIGHT -Copyright \(co 2024\-2026 The DragonX Developers -.PP -.br -Copyright \(co 2016\-2024 Duke Leto and The Hush Developers -.PP -.br -Copyright \(co 2016\-2020 jl777 and SuperNET developers -.PP -.br -Copyright \(co 2016\-2018 The Zcash developers -.PP -.br -Copyright \(co 2009\-2014 The Bitcoin Core developers -.PP + +Copyright (C) 2024-2026 The DragonX Developers + +Copyright (C) 2016-2024 Duke Leto and The Hush Developers + +Copyright (C) 2016-2020 jl777 and SuperNET developers + +Copyright (C) 2016-2018 The Zcash developers + +Copyright (C) 2009-2014 The Bitcoin Core developers + This is experimental Free Software! Fuck Yeah!!!!! -.PP + Distributed under the GPLv3 software license, see the accompanying file COPYING -or . +or . diff --git a/doc/man/dragonx-tx.1 b/doc/man/dragonx-tx.1 index 7b5bf014c..126f7b444 100644 --- a/doc/man/dragonx-tx.1 +++ b/doc/man/dragonx-tx.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1. -.TH DRAGONX-TX "1" "July 2026" "dragonx-tx v1.0.3-4caf2fc68" "User Commands" +.TH DRAGONX-TX "1" "August 2026" "dragonx-tx v1.3.0" "User Commands" .SH NAME -dragonx-tx \- DragonX transaction utility +dragonx-tx \- manual page for dragonx-tx v1.3.0 .SH DESCRIPTION -hush\-tx utility version v1.0.3\-4caf2fc68 +hush\-tx utility version v1.3.0 .SS "Usage:" .TP hush\-tx [options] [commands] @@ -84,3 +84,22 @@ Load JSON file FILENAME into register NAME set=NAME:JSON\-STRING .IP Set register NAME to given JSON\-STRING +.SH COPYRIGHT + +In order to ensure you are adequately protecting your privacy when using +DragonX, please see . + +Copyright (C) 2024-2026 The DragonX Developers + +Copyright (C) 2016-2024 Duke Leto and The Hush Developers + +Copyright (C) 2016-2020 jl777 and SuperNET developers + +Copyright (C) 2016-2018 The Zcash developers + +Copyright (C) 2009-2014 The Bitcoin Core developers + +This is experimental Free Software! Fuck Yeah!!!!! + +Distributed under the GPLv3 software license, see the accompanying file COPYING +or . diff --git a/doc/man/dragonxd.1 b/doc/man/dragonxd.1 index 079f9229a..9fd809079 100644 --- a/doc/man/dragonxd.1 +++ b/doc/man/dragonxd.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1. -.TH DRAGONX "1" "July 2026" "DragonX Daemon version v1.0.3-4caf2fc68" "User Commands" +.TH DRAGONXD "1" "August 2026" "dragonxd v1.3.0" "User Commands" .SH NAME -DragonX \- manual page for DragonX Daemon version v1.0.3-4caf2fc68 +dragonxd \- manual page for dragonxd v1.3.0 .SH DESCRIPTION -DragonX Daemon version v1.0.3\-4caf2fc68 +DragonX Daemon version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . @@ -52,7 +52,7 @@ Specify directory to be used when exporting data .HP \fB\-dbcache=\fR .IP -Set database cache size in megabytes (4 to 16384). Default: adaptive \- +Set database cache size in megabytes (4 to 65536). Default: adaptive \- uses most free RAM to speed up initial block download (far fewer UTXO flushes to disk) and automatically shrinks if other applications need memory, always leaving a reserve free. Setting @@ -88,8 +88,8 @@ leave that many cores free, default: 0) \fB\-randomxverifythreads=\fR .IP Number of threads for parallel RandomX PoW pre\-verification of -post\-checkpoint blocks during sync (0 = inline only, max 16, -default: same as \fB\-par\fR) +post\-checkpoint blocks during network sync; no effect on reindex +(0 = inline only, max 16, default: same as \fB\-par\fR) .HP \fB\-pid=\fR .IP @@ -361,15 +361,18 @@ exposes the seed to your shell history and process list. \fB\-mnemonic=\fR .IP Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible -with SilentDragonXLite (English, no passphrase). WARNING: exposes -the phrase to your shell history and process list; prefer -DRAGONX.conf with tight permissions. +with SilentDragonXLite (English, no passphrase; cross\-wallet +restore parity is mainnet\-only \fB\-\-\fR testnet/regtest derive a +different HD coin_type). WARNING: exposes the phrase to your +shell history and process list; prefer DRAGONX.conf with tight +permissions. .HP \fB\-usemnemonic\fR .IP Create new wallets from a fresh BIP39 seed phrase so the 24 words can be -exported (z_exportmnemonic) and used in SilentDragonXLite -(default: 0) +exported (z_exportmnemonic) and used in SilentDragonXLite. Set to +0 for a raw random seed with no recovery phrase; existing wallets +are never changed (default: 1) .HP \fB\-hdtransparentgaplimit=\fR .IP @@ -427,6 +430,40 @@ Enable sweeping to an external wallet (default false) .IP Addresses to exclude from sweeping (default none) .HP +\fB\-autoshield\fR +.IP +Automatically shield matured coinbase (mining rewards) into a +seed\-derived wallet z\-address (default: true for wallets created +or restored by this software, false when the HD seed provenance +is unknown). No\-op when not mining or wallet is locked. +.HP +\fB\-autoshieldinterval\fR +.IP +Block interval between automatic coinbase\-shielding rounds (default: 25, +min 5) +.HP +\fB\-autoshieldaddress=\fR +.IP +Destination Sapling z\-address for auto\-shielded coinbase (default: reuse +or create a wallet z\-address). Must be spendable by this wallet. +.HP +\fB\-autoshieldfee\fR +.IP +Fee in puposhis for automatic coinbase\-shielding transactions (default: +10000) +.HP +\fB\-sietch\-min\-zouts=\fR +.IP +Minimum number of shielded (Sapling) outputs Sietch adds to each +z_sendmany transaction as decoys, strengthening +amount/linkability privacy. Higher values add privacy at the cost +of larger transactions (default: 7, clamped to the range 3\-50) +.HP +\fB\-autoshieldminutxos\fR +.IP +Only auto\-shield once at least this many matured coinbase UTXOs exist +(default: 1) +.HP \fB\-deletetx\fR .IP Enable Old Transaction Deletion @@ -446,7 +483,7 @@ Keep transactions for at least blocks (default: 10000) .HP \fB\-paytxfee=\fR .IP -Fee (in HUSH/kB) to add to transactions you send (default: 0.00) +Fee (in DRAGONX/kB) to add to transactions you send (default: 0.00) .HP \fB\-keepnotewitnesscache\fR .IP @@ -485,7 +522,7 @@ mined will become invalid (default: 200) .HP \fB\-maxtxfee=\fR .IP -Maximum total fees (in HUSH) to use in a single wallet transaction; +Maximum total fees (in DRAGONX) to use in a single wallet transaction; setting this too low may abort large transactions (default: 0.10) .HP \fB\-upgradewallet\fR @@ -554,8 +591,8 @@ Prepend debug output with timestamp (default: 1) .HP \fB\-minrelaytxfee=\fR .IP -Fees (in HUSH/kB) smaller than this are considered zero fee for relaying -(default: 0.000001) +Fees (in DRAGONX/kB) smaller than this are considered zero fee for +relaying (default: 0.000001) .HP \fB\-printtoconsole\fR .IP @@ -680,6 +717,11 @@ Stratum server options: .IP Enable stratum server (default: off) .HP +\fB\-stratumtarget=\fR +.IP +Pool share target (64\-hex, big\-endian; larger = easier). Default is the +diff\-1 target. Useful for solo/low\-difficulty mining. +.HP \fB\-stratumaddress=\fR
.IP Mining address to use when special address of 'x' is sent by miner @@ -804,25 +846,22 @@ Starting supply, default is 10 \fB\-ac_txpow\fR .IP Enforce transaction\-rate limit, default 0 -.PP +.SH COPYRIGHT + In order to ensure you are adequately protecting your privacy when using DragonX, please see . -.SH COPYRIGHT -Copyright \(co 2024\-2026 The DragonX Developers -.PP -.br -Copyright \(co 2016\-2024 Duke Leto and The Hush Developers -.PP -.br -Copyright \(co 2016\-2020 jl777 and SuperNET developers -.PP -.br -Copyright \(co 2016\-2018 The Zcash developers -.PP -.br -Copyright \(co 2009\-2014 The Bitcoin Core developers -.PP + +Copyright (C) 2024-2026 The DragonX Developers + +Copyright (C) 2016-2024 Duke Leto and The Hush Developers + +Copyright (C) 2016-2020 jl777 and SuperNET developers + +Copyright (C) 2016-2018 The Zcash developers + +Copyright (C) 2009-2014 The Bitcoin Core developers + This is experimental Free Software! Fuck Yeah!!!!! -.PP + Distributed under the GPLv3 software license, see the accompanying file COPYING -or . +or . diff --git a/doc/man/hush-cli.html b/doc/man/hush-cli.html deleted file mode 100644 index 0101fa93a..000000000 --- a/doc/man/hush-cli.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - -HUSH-CLI - - - - -

HUSH-CLI

- -NAME
-DESCRIPTION
-Usage:
-OPTIONS
-COPYRIGHT
- -
- - -

NAME - -

- - -

hush-cli - -manual page for hush-cli v3.10.4

- -

DESCRIPTION - -

- - -

Hush RPC client -version v3.10.4-7e63e2f01-dirty

- -

In order to -ensure you are adequately protecting your privacy when using -Hush, please see <https://hush.is/security/>.

- -

Usage: - -

- - -

hush-cli -[options] <command> [params]

- -

Send command to Hush

- -

hush-cli [options] help

- -

List commands

- -

hush-cli [options] help -<command>

- -

Get help for a command

- -

OPTIONS - -

- - - - - - - -
- - -

-?

-
- -

This help -message

- - - - - - -
- - -

-conf=<file>

-
- -

Specify -configuration file (default: HUSH3.conf)

- - - - - - -
- - -

-datadir=<dir>

-
- -

Specify data -directory (this path cannot use ’˜’)

- - - - - - -
- - -

-testnet

-
- -

Use the test -network

- - - - - - -
- - -

-regtest

-
- -

Enter -regression test mode, which uses a special chain in which -blocks can be solved instantly. This is intended for -regression testing tools and app development.

- - - - - - -
- - - -

-rpcconnect=<ip>

-
- -

Send commands -to node running on <ip> (default: 127.0.0.1)

- - - - - - -
- - - -

-rpcport=<port>

-
- -

Connect to -JSON-RPC on <port> (default: 18030 )

- - - - - - -
- - -

-rpcwait

-
- -

Wait for RPC -server to start

- - - - - - -
- - - -

-rpcuser=<user>

-
- -

Username for -JSON-RPC connections

- - - - - - -
- - - -

-rpcpassword=<pw>

-
- -

Password for -JSON-RPC connections

- - - - - - -
- - - -

-rpcclienttimeout=<n>

-
- -

Timeout in -seconds during HTTP requests, or 0 for no timeout. (default: -900)

- - - - - - -
- - -

-stdin

-
- -

Read extra -arguments from standard input, one per line until EOF/Ctrl-D -(recommended for sensitive information such as -passphrases)

- -

COPYRIGHT - -

- - -

In order to -ensure you are adequately protecting your privacy when using -Hush, please see <https://hush.is/security/>.

- -

Copyright (C) -2016-2025 Duke Leto and The Hush Developers

- -

Copyright (C) -2016-2020 jl777 and SuperNET developers

- -

Copyright (C) -2016-2018 The Zcash developers

- -

Copyright (C) -2009-2014 The Bitcoin Core developers

- -

This is -experimental Free Software! Fuck Yeah!!!!!

- -

Distributed -under the GPLv3 software license, see the accompanying file -COPYING or -<https://www.gnu.org/licenses/gpl-3.0.en.html>.

-
- - diff --git a/doc/man/hushd.html b/doc/man/hushd.html deleted file mode 100644 index 0086e76e4..000000000 --- a/doc/man/hushd.html +++ /dev/null @@ -1,2681 +0,0 @@ - - - - - - - - - -HUSHD - - - - -

HUSHD

- -NAME
-DESCRIPTION
-Usage:
-OPTIONS
-COPYRIGHT
- -
- - -

NAME - -

- - -

hushd - manual -page for hushd v3.10.4

- -

DESCRIPTION - -

- - -

Hush Daemon -version v3.10.4-7e63e2f01-dirty

- -

In order to -ensure you are adequately protecting your privacy when using -Hush, please see <https://hush.is/security/>.

- -

Usage: - -

- - -

hushd -[options]

- -

Start a Hush Daemon

- -

OPTIONS - -

- - - - - - - -
- - -

-?

-
- -

This help -message

- - - - - - -
- - - -

-blocknotify=<cmd>

-
- -

Execute command -when the best block changes (%s in cmd is replaced by block -hash)

- - - - - - -
- - - -

-checkblocks=<n>

-
- -

How many blocks -to check at startup (default: 288, 0 = all)

- - - - - - -
- - - -

-checklevel=<n>

-
- -

How thorough -the block verification of -checkblocks is (0-4, -default: 3)

- - - - - - -
- - - -

-clientname=<SomeName>

-
- -

Full node -client name, default ’GoldenSandtrout’

- - - - - - -
- - -

-conf=<file>

-
- -

Specify -configuration file (default: HUSH3.conf)

- - - - - - -
- - -

-daemon

-
- -

Run in the -background as a daemon and accept commands

- - - - - - -
- - -

-datadir=<dir>

-
- -

Specify data -directory (this path cannot use ’˜’)

- - - - - - -
- - - -

-exportdir=<dir>

-
- -

Specify -directory to be used when exporting data

- - - - - - -
- - -

-dbcache=<n>

-
- -

Set database -cache size in megabytes (4 to 16384, default: 512)

- - - - - - -
- - - -

-loadblock=<file>

-
- -

Imports blocks -from external blk000??.dat file on startup

- - - - - - -
- - - -

-maxdebugfilesize=<n>

-
- -

Set the max -size of the debug.log file (default: 15)

- - - - - - -
- - - -

-maxorphantx=<n>

-
- -

Keep at most -<n> unconnectable transactions in memory (default: -100)

- - - - - - -
- - -

-maxreorg=<n>

-
- -

Specify the -maximum length of a blockchain re-organization

- - - - - - -
- - - -

-mempooltxinputlimit=<n>

-
- - -

[DEPRECATED/IGNORED] -Set the maximum number of transparent inputs in a -transaction that the mempool will accept (default: 0 = no -limit applied)

- - - - - - -
- - -

-par=<n>

-
- -

Set the number -of script verification threads (-8 to 16, 0 = auto, -<0 = leave that many cores free, default: 0)

- - - - - - -
- - -

-pid=<file>

-
- -

Specify pid -file (default: hushd.pid)

- - - - - - -
- - - -

-txexpirynotify=<cmd>

-
- -

Execute command -when transaction expires (%s in cmd is replaced by -transaction id)

- - - - - - -
- - -

-prune=<n>

-
- -

Reduce storage -requirements by pruning (deleting) old blocks. This mode -disables wallet support and is incompatible with --txindex. Warning: Reverting this setting requires -re-downloading the entire blockchain. (default: 0 = disable -pruning blocks, >550 = target size in MiB to use for -block files)

- - - - - - -
- - -

-reindex

-
- -

Rebuild block -chain index from current blk000??.dat files on startup

- - - - - - -
- - -

-sysperms

-
- -

Create new -files with system default permissions, instead of umask 077 -(only effective with disabled wallet functionality)

- - - - - - -
- - -

-txindex

-
- -

Maintain a full -transaction index, used by the getrawtransaction rpc call -(default: 0)

- - - - - - -
- - -

-txsend=<cmd>

-
- -

Execute command -to send a transaction instead of broadcasting (%s in cmd is -replaced by transaction hex)

- - - - - - -
- - -

-addressindex

-
- -

Maintain a full -address index, used to query for the balance, txids and -unspent outputs for addresses (default: 0)

- - - - - - -
- - -

-timestampindex

-
- -

Maintain a -timestamp index for block hashes, used to query blocks -hashes by a range of timestamps (default: 0)

- - - - - - -
- - -

-spentindex

-
- -

Maintain a full -spent index, used to query the spending txid and input index -for an outpoint (default: 0)

- - - - - - -
- - -

-zindex

-
- -

Maintain extra -statistics about shielded transactions and payments -(default: 0)

- -

Connection -options:

- - - - - - -
- - -

-addnode=<ip>

-
- -

Add a node to -connect to and attempt to keep the connection open

- - - - - - -
- - -

-asmap=<file>

-
- -

Specify ASN -mapping used for bucketing of the peers (default: -asmap.dat). Relative paths will be prefixed by the -net-specific datadir location.

- - - - - - -
- - -

-banscore=<n>

-
- -

Threshold for -disconnecting misbehaving peers (default: 100)

- - - - - - -
- - -

-bantime=<n>

-
- -

Number of -seconds to keep misbehaving peers from reconnecting -(default: 86400)

- - - - - - -
- - -

-bind=<addr>

-
- -

Bind to given -address and always listen on it. Use [host]:port notation -for IPv6

- - - - - - -
- - -

-connect=<ip>

-
- -

Connect only to -the specified node(s)

- - - - - - -
- - -

-discover

-
- -

Discover own IP -addresses (default: 1 when listening and no --externalip or -proxy)

- - - - - - -
- - -

-dns

-
- -

Allow DNS -lookups for -addnode, -seednode and --connect (default: 1)

- - - - - - -
- - -

-dnsseed

-
- -

Query for peer -addresses via DNS lookup, if low on addresses (default: 1 -unless -connect)

- - - - - - -
- - - -

-externalip=<ip>

-
- -

Specify your -own public address

- - - - - - -
- - -

-forcednsseed

-
- -

Always query -for peer addresses via DNS lookup (default: 0)

- - - - - - -
- - -

-listen

-
- -

Accept -connections from outside (default: 1 if no -proxy or --connect)

- - - - - - -
- - -

-listenonion

-
- -

Automatically -create Tor hidden service (default: 1)

- - - - - - -
- - - -

-maxconnections=<n>

-
- -

Maintain at -most <n> connections to peers (default: 384)

- - - - - - -
- - - -

-maxreceivebuffer=<n>

-
- -

Maximum -per-connection receive buffer, <n>*1000 bytes -(default: 5000)

- - - - - - -
- - - -

-maxsendbuffer=<n>

-
- -

Maximum -per-connection send buffer, <n>*1000 bytes (default: -1000)

- - - - - - -
- - - -

-onion=<ip:port>

-
- -

Use separate -SOCKS5 proxy to reach peers via Tor hidden services -(default: -proxy)

- - - - - - -
- - -

-nspv_msg

-
- -

Enable NSPV -messages processing (default: true when --ac_private=1, otherwise false)

- - - - - - -
- - - -

-i2psam=<ip:port>

-
- -

I2P SAM proxy -to reach I2P peers and accept I2P connections (default: -none)

- - - - - - -
- - -

-i2pacceptincoming

-
- -

If set and --i2psam is also set then incoming I2P connections are -accepted via the SAM proxy. If this is not set but --i2psam is set then only outgoing connections will be -made to the I2P network. Ignored if -i2psam is not -set. Listening for incoming I2P connections is done through -the SAM proxy, not by binding to a local address and port -(default: 1)

- - - - - - -
- - -

-onlynet=<net>

-
- -

Only connect to -nodes in network <net> (ipv4, ipv6, onion or i2p)

- - - - - - -
- - -

-disableipv4

-
- -

Disable Ipv4 -network connections (default: 0)

- - - - - - -
- - -

-disableipv6

-
- -

Disable Ipv6 -network connections (default: 0)

- - - - - - -
- - -

-clearnet

-
- -

Enable clearnet -connections. Setting to 0 will disable clearnet and use sane -defaults for Tor/i2p (default: 1)

- - - - - - -
- - -

-permitbaremultisig

-
- -

Relay non-P2SH -multisig (default: 1)

- - - - - - -
- - -

-peerbloomfilters

-
- -

Support -filtering of blocks and transaction with Bloom filters -(default: 1)

- - - - - - -
- - -

-port=<port>

-
- -

Listen for -connections on <port> (default: 55555 or testnet: -55420)

- - - - - - -
- - - -

-proxy=<ip:port>

-
- -

Connect through -SOCKS5 proxy

- - - - - - -
- - -

-proxyrandomize

-
- -

Randomize -credentials for every proxy connection. This enables Tor -stream isolation (default: 1)

- - - - - - -
- - -

-seednode=<ip>

-
- -

Connect to a -node to retrieve peer addresses, and disconnect

- - - - - - -
- - -

-timeout=<n>

-
- -

Specify -connection timeout in milliseconds (minimum: 1, default: -60000)

- - - - - - -
- - - -

-torcontrol=<ip>:<port>

-
- -

Tor control -port to use if onion listening enabled (default: -127.0.0.1:9051)

- - - - - - -
- - - -

-torpassword=<pass>

-
- -

Tor control -port password (default: empty)

- - - - - - -
- - -

-tls=<option>

-
- -

Specify TLS -usage (default: 1 => enabled and required); Cannot be -turned off.

- - - - - - -
- - -

-tlsvalidate=<0 or -1>

-
- -

Connect to -peers only with valid certificates (default: 0)

- - - - - - -
- - - -

-tlskeypath=<path>

-
- -

Full path to a -private key

- - - - - - -
- - - -

-tlskeypwd=<password>

-
- -

Password for a -private key encryption (default: not set, i.e. private key -will be stored unencrypted)

- - - - - - -
- - - -

-tlscertpath=<path>

-
- -

Full path to a -certificate

- - - - - - -
- - - -

-tlstrustdir=<path>

-
- -

Full path to a -trusted certificates directory

- - - - - - -
- - - -

-allowbind=<addr>

-
- -

Bind to given -address and allowlist peers connecting to it. Use -[host]:port notation for IPv6

- - - - - - -
- - - -

-allowlist=<netmask>

-
- -

Allowlist peers -connecting from the given netmask or IP address. Can be -specified multiple times. Allowlisted peers cannot be DoS -banned and their transactions are always relayed, even if -they are already in the mempool, useful e.g. for a -gateway

- -

Wallet -options:

- - - - - - -
- - -

-disablewallet

-
- -

Do not load the -wallet and disable wallet RPC calls

- - - - - - -
- - -

-keypool=<n>

-
- -

Set key pool -size to <n> (default: 100)

- - - - - - -
- - -

-consolidation

-
- -

Enable auto -Sapling note consolidation (default: false)

- - - - - - -
- - - -

-consolidationinterval

-
- -

Block interval -between consolidations (default: 25)

- - - - - - -
- - - -

-consolidatesaplingaddress=<zaddr>

-
- -

Specify Sapling -Address to Consolidate. (default: all)

- - - - - - -
- - -

-consolidationtxfee

-
- -

Fee amount in -Puposhis used send consolidation transactions. (default -10000)

- - - - - - -
- - -

-zsweep

-
- -

Enable zaddr -sweeping, automatically move all shielded funds to a one -address once per X blocks

- - - - - - -
- - - -

-zsweepaddress=<zaddr>

-
- -

Specify the -shielded address where swept funds will be sent)

- - - - - - -
- - -

-zsweepfee

-
- -

Fee amount in -puposhis used send sweep transactions. (default 10000)

- - - - - - -
- - -

-zsweepinterval

-
- -

Sweep shielded -funds every X blocks (default 5)

- - - - - - -
- - -

-zsweepmaxinputs

-
- -

Maximum number -of shielded inputs to sweep per transaction (default 8)

- - - - - - -
- - -

-zsweepexternal

-
- -

Enable sweeping -to an external wallet (default false)

- - - - - - -
- - -

-zsweepexclude

-
- -

Addresses to -exclude from sweeping (default none)

- - - - - - -
- - -

-deletetx

-
- -

Enable Old -Transaction Deletion

- - - - - - -
- - -

-deleteinterval

-
- -

Delete -transaction every <n> blocks during inital block -download (default: 1000)

- - - - - - -
- - -

-keeptxnum

-
- -

Keep the last -<n> transactions (default: 200)

- - - - - - -
- - -

-keeptxfornblocks

-
- -

Keep -transactions for at least <n> blocks (default: -10000)

- - - - - - -
- - - -

-paytxfee=<amt>

-
- -

Fee (in -HUSH/kB) to add to transactions you send (default: 0.00)

- - - - - - -
- - - -

-keepnotewitnesscache

-
- -

Keep partial -Sapling Note Witness cache. Must be used with --rescanheight to find missing cache items.

- - - - - - -
- - -

-rescan

-
- -

Rescan the -block chain for missing wallet transactions on startup

- - - - - - -
- - -

-rescanheight

-
- -

Rescan from -specified height when rescan=1 on startup

- - - - - - -
- - -

-salvagewallet

-
- -

Attempt to -recover private keys from a corrupt wallet.dat on -startup

- - - - - - -
- - - -

-sendfreetransactions

-
- -

Send -transactions as zero-fee transactions if possible (default: -0)

- - - - - - -
- - -

-spendzeroconfchange

-
- -

Spend -unconfirmed change when sending transactions (default: -1)

- - - - - - -
- - - -

-txconfirmtarget=<n>

-
- -

If paytxfee is -not set, include enough fee so transactions begin -confirmation on average within n blocks (default: 2)

- - - - - - -
- - -

-txexpirydelta

-
- -

Set the number -of blocks after which a transaction that has not been mined -will become invalid (default: 200)

- - - - - - -
- - - -

-maxtxfee=<amt>

-
- -

Maximum total -fees (in HUSH) to use in a single wallet transaction; -setting this too low may abort large transactions (default: -0.10)

- - - - - - -
- - -

-upgradewallet

-
- -

Upgrade wallet -to latest format on startup

- - - - - - -
- - -

-wallet=<file>

-
- -

Specify wallet -file absolute path or a path relative to the data directory -(default: wallet.dat)

- - - - - - -
- - -

-walletbroadcast

-
- -

Make the wallet -broadcast transactions (default: 1)

- - - - - - -
- - - -

-walletnotify=<cmd>

-
- -

Execute command -when a wallet transaction changes (%s in cmd is replaced by -TxID)

- - - - - - -
- - - -

-allowlistaddress=<Raddress>

-
- -

Enable the -wallet filter for notary nodes and add one Raddress to the -allowlist of the wallet filter. If -allowlistaddress= -is used, then the wallet filter is automatically activated. -Several Raddresses can be defined using several --allowlistaddress= (similar to -addnode). The -wallet filter will filter the utxo to only ones coming from -my own Raddress (derived from pubkey) and each Raddress -defined using -allowlistaddress= this option is -mostly for Notary Nodes).

- - - - - - -
- - - -

-zapwallettxes=<mode>

-
- -

Delete all -wallet transactions and only recover those parts of the -blockchain through -rescan on startup (1 = keep tx -meta data e.g. account owner and payment request -information, 2 = drop tx meta data)

- - -

Debugging/Testing -options:

- - - - - - -
- - - -

-debug=<category>

-
- -

Output -debugging information (default: 0, supplying -<category> is optional). If <category> is not -supplied or if <category> = 1, output all debugging -information. <category> can be: addrman, bench, -coindb, db, deletetx, estimatefee, http, libevent, lock, -mempool, net, tls, partitioncheck, pow, proxy, prune, rand, -randomx, reindex, rpc, selectcoins, stratum, tor, zrpc, -zrpcunsafe (implies zrpc).

- - - - - - -
- - - -

-experimentalfeatures

-
- -

Enable use of -experimental features

- - - - - - -
- - -

-help-debug

-
- -

Show all -debugging options (usage: --help -help-debug)

- - - - - - -
- - -

-logips

-
- -

Include IP -addresses in debug output (default: 0)

- - - - - - -
- - -

-logtimestamps

-
- -

Prepend debug -output with timestamp (default: 1)

- - - - - - -
- - - -

-minrelaytxfee=<amt>

-
- -

Fees (in -HUSH/kB) smaller than this are considered zero fee for -relaying (default: 0.000001)

- - - - - - -
- - -

-printtoconsole

-
- -

Send -trace/debug info to console instead of debug.log file

- - - - - - -
- - -

-shrinkdebugfile

-
- -

Shrink -debug.log file on client startup (default: 1 when no --debug)

- - - - - - -
- - -

-testnet

-
- -

Use the test -network

- -

Node relay -options:

- - - - - - -
- - -

-datacarrier

-
- -

Relay and mine -data carrier transactions (default: 1)

- - - - - - -
- - -

-datacarriersize

-
- -

Maximum size of -data in data carrier transactions we relay and mine -(default: 8192)

- -

Block creation -options:

- - - - - - -
- - - -

-blockminsize=<n>

-
- -

Set minimum -block size in bytes (default: 0)

- - - - - - -
- - - -

-blockmaxsize=<n>

-
- -

Set maximum -block size in bytes (default: 2000000)

- - - - - - -
- - - -

-blockprioritysize=<n>

-
- -

Set maximum -size of high-priority/low-fee transactions in bytes -(default: 1000000)

- -

Mining -options:

- - - - - - -
- - -

-gen

-
- -

Mine/generate -coins (default: 0)

- - - - - - -
- - - -

-genproclimit=<n>

-
- -

Set the number -of threads for coin mining if enabled (-1 = all -cores, default: 0)

- - - - - - -
- - - -

-equihashsolver=<name>

-
- -

Specify the -Equihash solver to be used if enabled (default: -"default")

- - - - - - -
- - - -

-mineraddress=<addr>

-
- -

Send mined -coins to a specific single address

- - - - - - -
- - -

-minetolocalwallet

-
- -

Require that -mined blocks use a coinbase address in the local wallet -(default: 1)

- -

RPC server -options:

- - - - - - -
- - -

-server

-
- -

Accept command -line and JSON-RPC commands

- - - - - - -
- - -

-rest

-
- -

Accept public -REST requests (default: 0)

- - - - - - -
- - - -

-rpcbind=<addr>

-
- -

Bind to given -address to listen for JSON-RPC connections. Use [host]:port -notation for IPv6. This option can be specified multiple -times (default: bind to all interfaces)

- - - - - - -
- - - -

-rpcuser=<user>

-
- -

Username for -JSON-RPC connections

- - - - - - -
- - - -

-rpcpassword=<pw>

-
- -

Password for -JSON-RPC connections

- - - - - - -
- - - -

-rpcport=<port>

-
- -

Listen for -JSON-RPC connections on <port> (default: 0 or testnet: -10000)

- - - - - - -
- - - -

-rpcallowip=<ip>

-
- -

Allow JSON-RPC -connections 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

- - - - - - -
- - - -

-rpcthreads=<n>

-
- -

Set the number -of threads to service RPC calls (default: 8)

- -

Metrics Options -(only if -daemon and -printtoconsole are not -set):

- - - - - - -
- - -

-showmetrics

-
- -

Show metrics on -stdout (default: 1 if running in a console, 0 otherwise)

- - - - - - -
- - -

-metricsui

-
- -

Set to 1 for a -persistent metrics screen, 0 for sequential metrics output -(default: 1 if running in a console, 0 otherwise)

- - - - - - -
- - -

-metricsrefreshtime

-
- -

Number of -seconds between metrics refreshes (default: 1 if running in -a console, 600 otherwise)

- -

Stratum server -options:

- - - - - - -
- - -

-stratum

-
- -

Enable stratum -server (default: off)

- - - - - - -
- - - -

-stratumaddress=<address>

-
- -

Mining address -to use when special address of ’x’ is sent by -miner (default: none)

- - - - - - -
- - - -

-stratumbind=<ipaddr>

-
- -

Bind to given -address to listen for Stratum work requests. Use [host]:port -notation for IPv6. This option can be specified multiple -times (default: bind to all interfaces)

- - - - - - -
- - - -

-stratumport=<port>

-
- -

Listen for -Stratum work requests on <port> (default: 19031 or -testnet: 19031)

- - - - - - -
- - - -

-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

- -

Hush Arrakis -Chain options:

- - - - - - -
- - -

-ac_algo

-
- -

Choose PoW -mining algorithm, either ’equihash’ or -’randomx’. default is Equihash (200,9)

- - - - - - -
- - -

-ac_blocktime

-
- -

Block time in -seconds, default is 60

- - - - - - -
- - -

-ac_beam

-
- -

BEAM -integration

- - - - - - -
- - -

-ac_burn

-
- -

Allow sending -funds to the transparent burn address when --ac_private=1

- - - - - - -
- - -

-ac_minopreturnfee

-
- -

OP_RETURN -minimum fee per tx, regardless of tx size, default is 1 -coin

- - - - - - -
- - -

-ac_coda

-
- -

CODA -integration

- - - - - - -
- - -

-ac_decay

-
- -

Percentage of -block reward decrease at each halving

- - - - - - -
- - -

-ac_end

-
- -

Block height at -which block rewards will end

- - - - - - -
- - -

-ac_eras

-
- -

Block reward -eras

- - - - - - -
- - -

-ac_founders

-
- -

Number of -blocks between founders reward payouts

- - - - - - -
- - -

-ac_halving

-
- -

Number of -blocks between each block reward halving

- - - - - - -
- - -

-ac_name

-
- -

Name of asset -chain

- - - - - - -
- - -

-ac_notarypay

-
- -

Pay notaries, -default 0

- - - - - - -
- - -

-ac_perc

-
- -

Percentage of -block rewards paid to the founder

- - - - - - -
- - -

-ac_private

-
- -

Shielded -transactions only (except coinbase + notaries), default is -0

- - - - - - -
- - -

-ac_pubkey

-
- -

Public key for -receiving payments on the network

- - - - - - -
- - -

-ac_public

-
- -

Transparent -transactions only, default 0

- - - - - - -
- - -

-ac_randomx_interval

-
- -

Controls how -often the RandomX key block will change, default is 1024

- - - - - - -
- - -

-ac_randomx_lag

-
- -

Sets the number -of RandomX blocks to wait before updating the key block, -default is 64

- - - - - - -
- - -

-ac_reward

-
- -

Block reward in -satoshis, default is 0

- - - - - - -
- - -

-ac_script

-
- -

P2SH/multisig -address to receive founders rewards

- - - - - - -
- - -

-ac_supply

-
- -

Starting -supply, default is 10

- - - - - - -
- - -

-ac_txpow

-
- -

Enforce -transaction-rate limit, default 0

- -

COPYRIGHT - -

- - -

In order to -ensure you are adequately protecting your privacy when using -Hush, please see <https://hush.is/security/>.

- -

Copyright (C) -2016-2025 Duke Leto and The Hush Developers

- -

Copyright (C) -2016-2020 jl777 and SuperNET developers

- -

Copyright (C) -2016-2018 The Zcash developers

- -

Copyright (C) -2009-2014 The Bitcoin Core developers

- -

This is -experimental Free Software! Fuck Yeah!!!!!

- -

Distributed -under the GPLv3 software license, see the accompanying file -COPYING or -<https://www.gnu.org/licenses/gpl-3.0.en.html>.

-
- - diff --git a/doc/release-process.md b/doc/release-process.md index c31733483..e89a038f9 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -8,32 +8,32 @@ It is best to keep doc/relnotes/README.md up to date as changes and bug fixes ar ## Branch model -Development happens on the `dev` branch. Releases are cut on the default branch, `dragonx`. There is no `master` branch. Code changes should land on `dev` first and undergo testing before being merged into `dragonx`. +Development happens on the `dev` branch. Releases are cut on the default branch, `master`. Code changes should land on `dev` first and undergo testing before being merged into `master`. -## Check for changes on dragonx that should be on dev +## Check for changes on master that should be on dev -Occasionally trivial changes are made directly on the `dragonx` branch, such as documentation changes. In theory, no code changes should happen on `dragonx` without being on `dev` first, but it's better to be safe than sorry. We want the `dev` branch which undergoes testing to be as close as possible to what the `dragonx` branch will become, so we don't want to merge `dev` into `dragonx` and just assume everything works. So it's best to merge the `dragonx` branch into `dev` just before merging the `dev` branch into `dragonx`. +Occasionally trivial changes are made directly on the `master` branch, such as documentation changes. In theory, no code changes should happen on `master` without being on `dev` first, but it's better to be safe than sorry. We want the `dev` branch which undergoes testing to be as close as possible to what the `master` branch will become, so we don't want to merge `dev` into `master` and just assume everything works. So it's best to merge the `master` branch into `dev` just before merging the `dev` branch into `master`. -To check if the `dragonx` branch has any changes that the `dev` branch does not: +To check if the `master` branch has any changes that the `dev` branch does not: ``` # this assumes you are working with https://git.dragonx.is/DragonX/dragonx as your remote git checkout dev git pull # make sure dev is up to date -git checkout dragonx -git pull # make sure dragonx is up to date -git diff dev...dragonx # look at the set of changes which exist in dragonx but not dev +git checkout master +git pull # make sure master is up to date +git diff dev...master # look at the set of changes which exist in master but not dev ``` -If the last command has no output, congrats, there is nothing to do. If the last command has output, then you should merge `dragonx` into `dev`: +If the last command has no output, congrats, there is nothing to do. If the last command has output, then you should merge `master` into `dev`: ``` git checkout dev -git merge dragonx +git merge master git push origin dev ``` -Use the `--no-ff` flag when merging `dev` into `dragonx` for a release (see below). The `--no-ff` flag makes sure to make a merge commit, no matter what, even if a "fast forward" could be done. For those in the future looking back, it's much better to see evidence of when branches were merged. +Use the `--no-ff` flag when merging `dev` into `master` for a release (see below). The `--no-ff` flag makes sure to make a merge commit, no matter what, even if a "fast forward" could be done. For those in the future looking back, it's much better to see evidence of when branches were merged. ### Git Issues @@ -66,6 +66,7 @@ Install deps on Linux: - Run "make seeds" - Commit the result - Update version in configure.ac and src/clientversion.h to update the dragonxd version + - **The new version MUST be higher than every version already tagged**, including tags that were never built or published. Check with `git tag -l --sort=-v:refname | head`. Two trees stamped with the same `CLIENT_VERSION` are indistinguishable on the wire, in `getnetworkinfo`, and to the wallet's in-app updater — and a published archive that does not match its tag destroys the only provenance check users have. - In src/clientversion.h you update `CLIENT_VERSION_*` variables. Usually you will just update `CLIENT_VERSION_REVISION` - If there is a consensus change, it may be a good idea to update `CLIENT_VERSION_MINOR` or `CLIENT_VERSION_MAJOR` - To make a pre-release "beta" you can modify `CLIENT_VERSION_BUILD` but that is rarely done. @@ -97,17 +98,27 @@ Install deps on Linux: - Try to generate checkpoints as close to the release as possible, so you can have a recent block height be protected. - For instance, don't update checkpoints and then do a release a month later. You can always update checkpoint data again or multiple times - Update doc/relnotes/README.md - - To get the stats of file changes: `git diff --stat dragonx...dev` + - To get the stats of file changes: `git diff --stat master...dev` - Do a fresh clone and fresh sync with new checkpoints - Stop node, wait 20 minutes, and then do a partial sync with new checkpoints - - Merge dev into dragonx: `git checkout dev && git pull && git checkout dragonx && git pull && git merge --no-ff dev && git push` + - Merge dev into master: `git checkout dev && git pull && git checkout master && git pull && git merge --no-ff dev && git push` - The above command makes sure that your local dev branch is up to date before doing anything - The above command will not merge if "git pull" creates a merge conflict - The above command will not push if there is a problem with merging dev - - Make Gitea release with git tag from the dragonx branch (make sure to merge dev in first) + - Make Gitea release with git tag from the master branch (make sure to merge dev in first) - Make sure git tag starts with a `v` such as `v1.0.3` - - Use util/gen-linux-binary-release.sh to make a Linux release binary - - Upload Linux binary to Gitea release and add SHA256 sum + - **The tag MUST be annotated** (`git tag -a v1.3.0 -m 'DragonX v1.3.0'`), not lightweight. `util/genbuild.sh` calls `git describe` *without* `--tags`, which only ever sees annotated tags — a lightweight tag makes the build stamp itself `v-` instead of the release version. v1.0.0 through v1.0.3 are lightweight, which is why their builds are labelled that way. + - Verify before building: `git describe` must print exactly the tag, with no `--g` suffix. + - Use `./build.sh` (container-based, see doc/build-containers.md) or util/gen-linux-binary-release.sh to make a Linux release binary + - **Sign every archive and publish the signatures.** This step is mandatory and was missing from this document until v1.3.0 — its absence is why v1.1.0 and v1.2.0 were tagged but never became installable releases. + - The wallet's in-app daemon updater pins an ed25519 public key in `ObsidianDragon/src/util/daemon_updater.h` and sets `kDaemonRequireSignature = true`. **An update is refused outright unless a valid `.sig` is published beside the archive.** No signature means every existing user silently stays on their old daemon. + - Sign with `ObsidianDragon/scripts/sign-daemon-release.sh`: + - `scripts/sign-daemon-release.sh sign ...` produces `.sig` (base64 of a detached 64-byte ed25519 signature over the exact archive bytes) + - or `scripts/sign-daemon-release.sh release ` to zip, sign, and print the checksum table in one step + - Keep the secret key offline, mode 600. The matching base64 public key must already be pinned in `kDaemonSignaturePublicKeyBase64`. + - Upload each Linux binary archive **and its `.sig`** to the Gitea release + - **Paste the SHA-256 checksum table into the release body** as markdown rows of the form `| .zip | `` |`. The updater parses this table and will not install an archive that is absent from it. + - Confirm the release is actually consumable before announcing it: the updater looks for an asset whose name contains `"-" + platformToken + ".zip"` (`linux-amd64`, `macos`, `win64`). An archive named for a distro variant instead of the platform token is invisible to it. - Create an x86 Debian package for the release: - Edit contrib/debian/changelog to add information about the new release - Use `util/build-debian-package.sh` to make an x86 Debian package for the release @@ -115,7 +126,7 @@ Install deps on Linux: - `lintian` is an optional dependency, it's not needed to build the .deb - Upload .deb to Gitea release - Add SHA256 checksum of .deb to release - - Use util/build-debian-package-ARM.sh (does this still work?) to make an ARM Debian package for the release + - ARM Debian package: `util/build-debian-package-ARM.sh` is referenced here historically but **is not present in the tree**. Skip, or restore the script first. - Upload the debian packages to the Gitea release page, with SHA256 sums ## Platform-specific notes diff --git a/qa/r4-salvaged-wallet-harness.sh b/qa/r4-salvaged-wallet-harness.sh new file mode 100755 index 000000000..5ca5c35e4 --- /dev/null +++ b/qa/r4-salvaged-wallet-harness.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# r4 now-tier acceptance harness. MUST run inside a network namespace: the v1.0.x binaries +# predate the regtest seed-injection fix and would otherwise dial the live DragonX network. +set -u +OLD=/home/dev/dragonx/release/dragonx-1.0.1-linux-amd64 +NEW=/home/dev/dragonx-dev/src +ROOT=/tmp/claude-1000/-home-dev/45a644d6-ea0b-4b7c-8ec7-6ccb1a64afa7/scratchpad/r4lab +PASS=0; FAIL=0 +ok(){ echo " PASS $1"; PASS=$((PASS+1)); } +no(){ echo " FAIL $1"; FAIL=$((FAIL+1)); } + +# --- hard guardrail: refuse to run unisolated --- +if [ "$(ip route show 2>/dev/null | wc -l)" != "0" ]; then + echo "REFUSING: not in an isolated netns (routes present). Run under: unshare -rn"; exit 90 +fi +ip link set lo up 2>/dev/null +echo "isolation: $(ip route show | wc -l) routes, $(ip -o link | wc -l) interface(s)" + +conf(){ printf 'regtest=1\nrpcuser=t\nrpcpassword=t\nlisten=0\ndnsseed=0\n' > "$1/DRAGONX.conf"; } +start(){ # $1=bindir $2=datadir $3=extra + "$1/dragonxd" -regtest -datadir="$2" -connect=0 -listen=0 -dnsseed=0 $3 -daemon >/dev/null 2>&1 + for i in $(seq 40); do "$1/dragonx-cli" -regtest -datadir="$2" -rpcuser=t -rpcpassword=t getblockcount >/dev/null 2>&1 && return 0; sleep 2; done + return 1; } +cli(){ "$1/dragonx-cli" -regtest -datadir="$2" -rpcuser=t -rpcpassword=t "${@:3}" 2>&1; } +stopn(){ cli "$1" "$2" stop >/dev/null 2>&1; sleep 6; } + +rm -rf "$ROOT"; mkdir -p "$ROOT" + +echo; echo "### build victim wallets with the OLD binary (v1.0.1) ###" +mkdir -p "$ROOT/base/regtest"; conf "$ROOT/base/regtest" +start "$OLD" "$ROOT/base/regtest" "" || { echo "old node failed to start"; exit 91; } +[ "$(cli "$OLD" "$ROOT/base/regtest" getconnectioncount)" = "0" ] && ok "victim-maker has 0 peers (isolated)" || no "victim-maker NOT isolated -- ABORT" +cli "$OLD" "$ROOT/base/regtest" getnewaddress >/dev/null +cli "$OLD" "$ROOT/base/regtest" z_getnewaddress >/dev/null +ZBEFORE=$(cli "$OLD" "$ROOT/base/regtest" z_listaddresses | tr -d ' \n') +stopn "$OLD" "$ROOT/base/regtest" + +for v in C A B; do cp -a "$ROOT/base" "$ROOT/$v"; done + +# A = salvaged by v1.0.1 (drops hdchain). B = A then USED on v1.0.1 (persists a bogus chain). +start "$OLD" "$ROOT/A/regtest" "-salvagewallet" && stopn "$OLD" "$ROOT/A/regtest" +start "$OLD" "$ROOT/B/regtest" "-salvagewallet" && stopn "$OLD" "$ROOT/B/regtest" +start "$OLD" "$ROOT/B/regtest" "" && { cli "$OLD" "$ROOT/B/regtest" z_getnewaddress >/dev/null; stopn "$OLD" "$ROOT/B/regtest"; } + +echo " A hdchain records: $(strings "$ROOT/A/regtest/regtest/wallet.dat" | grep -c hdchain) (expect 0)" +echo " B hdchain records: $(strings "$ROOT/B/regtest/regtest/wallet.dat" | grep -c hdchain) (expect >=1)" + +echo; echo "### open each on the NEW binary ###" +for v in C A B; do + D="$ROOT/$v/regtest"; L="$D/regtest/debug.log" + WDAT="$D/regtest/wallet.dat" + [ -f "$WDAT" ] || { echo " FAIL $v: wallet.dat not found at $WDAT"; exit 92; } + # NOT a byte-identical check: normal startup (keypool top-up, bestblock) rewrites wallet.dat for + # ANY wallet, healthy ones included -- verified with a control. The precise claim is that the + # degraded path never SYNTHESISES an hdchain record, so count that instead. + HD1=$(strings "$WDAT" | grep -c hdchain) + : > "$L" 2>/dev/null + if start "$NEW" "$D" "-exportdir=$D/exp"; then + STARTED=yes; DEG=$(grep -c "DEGRADED" "$L" 2>/dev/null) + MISS=$(grep -c "hdchain record is missing" "$L" 2>/dev/null) + MISM=$(grep -c "does not belong to this wallet" "$L" 2>/dev/null) + if [ "$v" = "A" ]; then + mkdir -p "$D/exp"; EXP=$(cli "$NEW" "$D" z_exportwallet r4dump 2>&1 | head -1) + DUMP=$(find "$D" -name 'r4dump' 2>/dev/null | head -1) + ZNEW=$(cli "$NEW" "$D" z_getnewaddress); TNEW=$(cli "$NEW" "$D" getnewaddress) + fi + stopn "$NEW" "$D" + else STARTED=no; DEG=0; MISS=0; MISM=0; fi + HD2=$(strings "$WDAT" | grep -c hdchain) + + case $v in + C) [ "$STARTED" = yes ] && ok "C healthy wallet opens" || no "C healthy wallet failed to open" + [ "$DEG" = "0" ] && ok "C no false positive (not flagged degraded)" || no "C FALSE POSITIVE: healthy wallet flagged" ;; + A) [ "$STARTED" = yes ] && ok "A salvaged wallet opens (was DB_CORRUPT before)" || no "A salvaged wallet still refuses to open" + [ "$MISS" -ge 1 ] && ok "A flagged: hdchain missing" || no "A not flagged as missing-hdchain" + [ "$HD1" = "0" ] && [ "$HD2" = "0" ] && ok "A no hdchain synthesised (degraded path persists nothing)" || no "A hdchain record appeared ($HD1 -> $HD2)" + echo "$ZNEW" | grep -qi 'error' && ok "A z_getnewaddress refused cleanly (derivation gated)" || no "A z_getnewaddress derived anyway: $ZNEW" + echo "$TNEW" | grep -qiE '^R[a-zA-Z0-9]+$' && ok "A getnewaddress still works (legacy random t-key)" || no "A getnewaddress broke: $TNEW" + if [ -n "${DUMP:-}" ] && [ -f "$DUMP" ]; then + grep -qE '^# HDSeed=[0-9a-f]' "$DUMP" && no "E z_exportwallet emitted an HDSeed line on a degraded wallet" || ok "E z_exportwallet emitted no bogus HDSeed line" + else echo " SKIP E (no dump produced: $EXP)"; fi ;; + B) [ "$STARTED" = yes ] && ok "B poisoned wallet opens" || no "B poisoned wallet failed to open" + [ "$MISM" -ge 1 ] && ok "B CASE-3 DETECTOR FIRED (seedFp mismatch)" || no "B case-3 detector did NOT fire" ;; + esac +done +echo; echo "### $PASS passed, $FAIL failed ###" +exit $FAIL diff --git a/qa/rpc-tests/getblocktemplate.py b/qa/rpc-tests/getblocktemplate.py index ed67dd52f..cfec0ae1f 100755 --- a/qa/rpc-tests/getblocktemplate.py +++ b/qa/rpc-tests/getblocktemplate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying diff --git a/qa/rpc-tests/getblocktemplate_longpoll.py b/qa/rpc-tests/getblocktemplate_longpoll.py index b836fe8e5..3d7a43159 100755 --- a/qa/rpc-tests/getblocktemplate_longpoll.py +++ b/qa/rpc-tests/getblocktemplate_longpoll.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -52,7 +52,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework): ''' def run_test(self): - print "Warning: this test will take about 70 seconds in the best case. Be patient." + print("Warning: this test will take about 70 seconds in the best case. Be patient.") self.nodes[0].generate(10) templat = self.nodes[0].getblocktemplate() longpollid = templat['longpollid'] diff --git a/qa/rpc-tests/getblocktemplate_proposals.py b/qa/rpc-tests/getblocktemplate_proposals.py index a0426d35b..faa675762 100755 --- a/qa/rpc-tests/getblocktemplate_proposals.py +++ b/qa/rpc-tests/getblocktemplate_proposals.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException +from test_framework.util import initialize_chain_clean, start_node from binascii import a2b_hex, b2a_hex from hashlib import sha256 @@ -69,14 +70,43 @@ def genmrklroot(leaflist): cur = n return cur[0] +# --------------------------------------------------------------------------- +# Sapling v4 transaction layout. +# +# This test was written against the pre-Overwinter serialization, where a tx +# began with a 4-byte nVersion immediately followed by the vin count, so the +# first input's prevout hash lived at offset 4+1. DragonX transactions are +# Sapling v4: 4-byte header (nVersion | fOverwintered) + 4-byte nVersionGroupId +# + vin count, so the prevout hash starts 4 bytes further in. Poking the old +# offset corrupts nVersionGroupId and every proposal below just comes back +# "Block decode failed" instead of exercising any consensus rule. +CB_PREVOUT_OFF = 4+4+1 +# Likewise the tx no longer ends at nLockTime: nExpiryHeight (4), valueBalance +# (8) and the empty vShieldedSpend/vShieldedOutput/vJoinSplit counts (1 each) +# trail it, so nLockTime is the 4 bytes at [-19:-15]. +TX_TAIL_AFTER_LOCKTIME = 4+8+1+1+1 + +def tx_seq_off(tx): + """Offset of the first input's nSequence in a Sapling v4 tx.""" + scriptlen_off = CB_PREVOUT_OFF + 32 + 4 # after prevout hash + prevout.n + return scriptlen_off + 1 + tx[scriptlen_off] + +def tx_vout0_value_off(tx): + """Offset of the first output's 8-byte value in a Sapling v4 tx.""" + return tx_seq_off(tx) + 4 + 1 # after nSequence + vout count + def template_to_bytes(tmpl, txlist): blkver = pack(' makes init.cpp soft-set -listen=0 ("parameter interaction: -connect + # set -> setting -listen=0"), so cache node0 never opened its p2p port and nodes 1-3 could + # never sync to it -- initialize_chain() then hung forever in sync_blocks(). Pass -listen + # and -bind explicitly (an explicit arg beats SoftSetBoolArg) and keep the loopback bind so + # the cache nodes stay off the public network. + args = [ os.getenv("BITCOIND", "src/dragonxd"), "-regtest", "-connect=0", "-keypool=1", "-datadir="+datadir, "-discover=0", + "-listen=1", "-bind=127.0.0.1", "-dnsseed=0" ] + _am = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat") + if os.path.exists(_am): + args.append("-asmap=" + os.path.realpath(_am)) if i > 0: args.append("-connect=127.0.0.1:"+str(p2p_port(0))) bitcoind_processes[i] = subprocess.Popen(args) - cmd = os.getenv("BITCOINCLI", "hush-cli") + cmd = os.getenv("BITCOINCLI", "src/dragonx-cli") cmd_args = cmd + " -datadir="+datadir + " -rpcwait getblockcount" if os.getenv("PYTHON_DEBUG", ""): print("initialize_chain: hushd started, calling: " + cmd_args) @@ -180,10 +204,17 @@ def initialize_chain(test_dir): wait_bitcoinds() for i in range(4): print("Cleaning up cache dir files") - os.remove(log_filename("cache", i, "debug.log")) - os.remove(log_filename("cache", i, "db.log")) - os.remove(log_filename("cache", i, "peers.dat")) - os.remove(log_filename("cache", i, "fee_estimates.dat")) + # log_filename() points at /node/regtest, but that IS the -datadir we passed; + # dragonxd writes its logs/peers.dat one level deeper, into the net-specific + # /regtest subdir (same datadir-vs-netdir split that forced -asmap to be + # absolute in start_node). Try both, and tolerate files a node never created. + for name in ("debug.log", "db.log", "peers.dat", "fee_estimates.dat"): + for cand in (log_filename("cache", i, os.path.join("regtest", name)), + log_filename("cache", i, name)): + try: + os.remove(cand) + except OSError: + pass for i in range(4): from_dir = os.path.join("cache", "node"+str(i)) @@ -227,9 +258,10 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= """ print("Starting node " + str(i) + " in dir " + dirname) datadir = os.path.join(dirname, "node"+str(i), "regtest") + if extra_args is None: extra_args = [] # creating special config if len(extra_args) > 0 and extra_args[0] == '-ac_name=ZZZ': - configpath = datadir + "/ZZZ.conf" + configpath = datadir + "/DRAGONX.conf" with open(configpath, "w+") as config: config.write("rpcuser=hush\n") config.write("rpcpassword=puppy\n") @@ -247,16 +279,30 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= print("Done writing to %s" % configpath) if binary is None: - binary = os.getenv("BITCOIND", "src/hushd") - args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ] + binary = os.getenv("BITCOIND", "src/dragonxd") + # -regtest MUST be a command-line flag. DragonX ignores "regtest=1" in the conf file, so + # without this the node silently runs on MAINNET: it loads the real genesis, dials the real + # seeds and starts syncing the live chain into the test datadir (observed: 196k blocks and + # 679MB before a test timed out). -connect=0 keeps the regtest node off the public network. + args = [ binary, "-regtest", "-connect=0", "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ] + # -asmap relative paths are resolved against the NET-SPECIFIC datadir (init.cpp), which for + # regtest is /regtest -- so a copy sitting in is never found. Pass an + # absolute path; without it dragonxd exits with "Could not find any asmap file!". + _asmap = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat") + if os.path.exists(_asmap): + args.append("-asmap=" + os.path.realpath(_asmap)) if extra_args is not None: args.extend(extra_args) print("args=" + ' '.join(args)) bitcoind_processes[i] = subprocess.Popen(args) devnull = open("/dev/null", "w+") - cmd = os.getenv("BITCOINCLI", "src/hush-cli") + cmd = os.getenv("BITCOINCLI", "src/dragonx-cli") print("cmd=" + cmd) - args = [ extra_args[0], "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ] + # The CLI only needs the datadir: initialize_datadir() already wrote DRAGONX.conf there + # with the right rpcport/user/password. The old form passed extra_args[0] as argv[0] and + # replayed daemon-only flags at the CLI, which only worked for the -ac_name=ZZZ assetchain + # tests and broke every test that passes no extra_args. + args = [ "-regtest", "-datadir="+datadir ] cmd_args = ' '.join(args) + " -rpcwait getblockcount " if os.getenv("PYTHON_DEBUG", ""): print("start_node: hushd started, calling : " + cmd + " " + cmd_args) @@ -266,18 +312,22 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= import time time.sleep(2) subprocess.check_call(strcmd, shell=True); - #subprocess.check_call([ os.getenv("BITCOINCLI", "hush-cli"), "-datadir="+datadir] + + #subprocess.check_call([ os.getenv("BITCOINCLI", "dragonx-cli"), "-datadir="+datadir] + # _rpchost_to_args(rpchost) + # ["-rpcwait", "-rpcport=6438", "getblockcount"], stdout=devnull) if os.getenv("PYTHON_DEBUG", ""): print("start_node: calling hush-cli -rpcwait getblockcount returned") devnull.close() - port = extra_args[3] - #port = rpc_port(i) + # Port comes from the same helper initialize_datadir() used, except for the assetchain + # tests which pass it positionally as extra_args[3] == "-rpcport=NNNN". + if len(extra_args) > 3 and str(extra_args[0]) == '-ac_name=ZZZ': + port = extra_args[3][9:] + else: + port = str(rpc_port(i)) #print("port=%s" % port) username = rpc_username() password = rpc_password() - url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port[9:]) + url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port) print("connecting to " + url) if timewait is not None: proxy = AuthServiceProxy(url, timeout=timewait) @@ -315,7 +365,7 @@ def stop_nodes(nodes): del nodes[:] # Emptying array closes connections as a side effect def set_node_times(nodes, t): - print("Setting nodes time to " + t) + print("Setting nodes time to " + str(t)) for node in nodes: node.setmocktime(t) diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index 8c159e50d..843cf2041 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -20,8 +20,20 @@ class WalletTest (BitcoinTestFramework): print("Initializing test directory "+self.options.tmpdir) initialize_chain_clean(self.options.tmpdir, 4) + # PORT NOTE (DragonX/regtest, not a change of test intent): + # test_framework.start_node() hardcodes "-connect=0", and init.cpp turns that into + # "-connect set -> setting -listen=0". With listening off the nodes never bind their + # p2p port, so connect_nodes_bi() connects nothing at all (its version==0 poll loop + # exits immediately because there are no local peers) and the first sync_all() hangs + # forever. Passing -bind forces -listen back to 1. -dnsseed=0 keeps these regtest + # nodes from dialing the live DragonX network. + NET_ARGS = ["-listen=1", "-bind=127.0.0.1", "-dnsseed=0"] + + def net_args(self, n, extra=None): + return [list(self.NET_ARGS) + list(extra or []) for _ in range(n)] + def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3)) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) @@ -29,7 +41,7 @@ class WalletTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(4) self.sync_all() @@ -106,7 +118,7 @@ class WalletTest (BitcoinTestFramework): signed_tx = self.nodes[2].signrawtransaction(raw_tx) try: self.nodes[2].sendrawtransaction(signed_tx["hex"]) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("absurdly high fees" in errorString) assert("900000000 > 190000" in errorString) @@ -186,7 +198,7 @@ class WalletTest (BitcoinTestFramework): txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) sync_mempools(self.nodes) - self.nodes.append(start_node(3, self.options.tmpdir)) + self.nodes.append(start_node(3, self.options.tmpdir, list(self.NET_ARGS))) connect_nodes_bi(self.nodes, 0, 3) sync_blocks(self.nodes) @@ -227,7 +239,7 @@ class WalletTest (BitcoinTestFramework): #do some -walletbroadcast tests stop_nodes(self.nodes) wait_bitcoinds() - self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]]) + self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3, ["-walletbroadcast=0"])) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) @@ -256,7 +268,7 @@ class WalletTest (BitcoinTestFramework): #restart the nodes with -walletbroadcast=1 stop_nodes(self.nodes) wait_bitcoinds() - self.nodes = start_nodes(3, self.options.tmpdir) + self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3)) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) @@ -290,7 +302,7 @@ class WalletTest (BitcoinTestFramework): num_t_recipients = 3000 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_t_recipients): + for i in range(0,num_t_recipients): newtaddr = self.nodes[2].getnewaddress() recipients.append({"address":newtaddr, "amount":amount_per_recipient}) @@ -305,7 +317,7 @@ class WalletTest (BitcoinTestFramework): try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("Too many outputs, size of raw transaction" in errorString) @@ -314,10 +326,10 @@ class WalletTest (BitcoinTestFramework): num_z_recipients = 50 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_t_recipients): + for i in range(0,num_t_recipients): newtaddr = self.nodes[2].getnewaddress() recipients.append({"address":newtaddr, "amount":amount_per_recipient}) - for i in xrange(0,num_z_recipients): + for i in range(0,num_z_recipients): newzaddr = self.nodes[2].z_getnewaddress() recipients.append({"address":newzaddr, "amount":amount_per_recipient}) @@ -327,7 +339,7 @@ class WalletTest (BitcoinTestFramework): try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("size of raw transaction would be larger than limit" in errorString) @@ -335,12 +347,12 @@ class WalletTest (BitcoinTestFramework): num_z_recipients = 100 amount_per_recipient = Decimal('0.00000001') errorString = '' - for i in xrange(0,num_z_recipients): + for i in range(0,num_z_recipients): newzaddr = self.nodes[2].z_getnewaddress() recipients.append({"address":newzaddr, "amount":amount_per_recipient}) try: self.nodes[0].z_sendmany(myzaddr, recipients) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert("Invalid parameter, too many zaddr outputs" in errorString) @@ -426,7 +438,7 @@ class WalletTest (BitcoinTestFramework): errorString = "" try: txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Invalid amount" in errorString, True) @@ -434,7 +446,7 @@ class WalletTest (BitcoinTestFramework): errorString = "" try: self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("not an integer" in errorString, True) @@ -448,9 +460,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) # This fee is larger than the default fee and since amount=0 @@ -462,7 +474,7 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert('Small transaction amount' in errorString) @@ -475,9 +487,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) # Make sure amount=0, fee=0 transaction are valid to add to mempool @@ -490,9 +502,9 @@ class WalletTest (BitcoinTestFramework): try: myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee) assert(myopid) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] - print errorString + print(errorString) assert(False) diff --git a/qa/rpc-tests/wallet_listnotes.py b/qa/rpc-tests/wallet_listnotes.py index 7d7fc38b3..cc847c881 100755 --- a/qa/rpc-tests/wallet_listnotes.py +++ b/qa/rpc-tests/wallet_listnotes.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying diff --git a/qa/rpc-tests/wallet_mergetoaddress.py b/qa/rpc-tests/wallet_mergetoaddress.py index 452f031d9..5cbf6b9c6 100755 --- a/qa/rpc-tests/wallet_mergetoaddress.py +++ b/qa/rpc-tests/wallet_mergetoaddress.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2017 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -32,7 +32,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(1) do_not_shield_taddr = self.nodes[0].getnewaddress() @@ -81,7 +81,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress("*", myzaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON value is not an array as expected" in errorString, True) @@ -90,7 +90,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[2].z_mergetoaddress([mytaddr], myzaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Could not find any funds to merge" in errorString, True) @@ -98,7 +98,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) @@ -106,7 +106,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001')) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) @@ -114,7 +114,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, 999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient funds" in errorString, True) @@ -122,7 +122,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of UTXOs cannot be negative" in errorString, True) @@ -130,7 +130,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -138,7 +138,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of notes cannot be negative" in errorString, True) @@ -146,7 +146,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -154,7 +154,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework): try: self.nodes[0].z_mergetoaddress([mytaddr], mytaddr) assert(False) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Destination address is also the only source address, and all its funds are already merged" in errorString, True) diff --git a/qa/rpc-tests/wallet_nullifiers.py b/qa/rpc-tests/wallet_nullifiers.py index d7b039b97..4d4cd8f35 100755 --- a/qa/rpc-tests/wallet_nullifiers.py +++ b/qa/rpc-tests/wallet_nullifiers.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -7,15 +7,71 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_true, bitcoind_processes, \ - connect_nodes_bi, start_node, start_nodes, wait_and_assert_operationid_status + connect_nodes_bi, initialize_chain_clean, p2p_port, start_node, start_nodes, \ + sync_blocks, wait_and_assert_operationid_status from decimal import Decimal class WalletNullifiersTest (BitcoinTestFramework): + # The framework default setup_chain() calls initialize_chain(), which pre-builds a + # 200-block chain in a *relative* "cache/" directory shared by every test process + # running out of this tree. That directory is guarded only by the datadir lock, so two + # qa/rpc-tests running at once collide on it and the second one hangs forever inside + # "dragonx-cli -rpcwait" (observed here while a sibling test held cache/node0..3). + # Build the identical pre-condition -- 4 nodes, two rounds of 25 blocks each, i.e. 25 + # mature + 25 immature coinbases per node -- directly in this test's private tmpdir. + # This is a setup change only: no assertion below is relaxed, removed or reordered. + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + # Three networking facts about this daemon force extra flags here. None of them + # change what the test exercises; without them the 4 nodes either never peer with + # each other, or peer with the LIVE DragonX network instead. + # + # 1. start_node() hardcodes "-connect=0", which also soft-sets -listen=0, so nothing + # binds p2p_port(i) and connect_nodes_bi() can never form the regtest mesh -- + # sync_blocks() then spins forever (observed: node0 at 25 blocks, nodes 1-3 stuck + # at 0, nothing listening on 11005-11008). -listen=1 -bind=127.0.0.1 restores the + # mesh and keeps it on loopback. + # 2. hush_args() appends node1..node10.dragonx.is to -addnode unconditionally, -regtest + # included, and regtest reuses mainnet's network magic. A "regtest" node therefore + # joins the live network: node0 of an earlier run handshook 8 production peers + # ("receive version message: /DragonX:1.0.3/ ... blocks=3254266") and ingested their + # headers. -dns=0 stops those hostname -addnode entries from resolving; RPC addnode + # with a numeric 127.0.0.1:port is unaffected. + # 3. hush_args() runs BEFORE the config file is read, so its GetArg("-port",0) never + # sees the "port=" line initialize_datadir() wrote and GetDefaultPort() stays at the + # mainnet p2p port. "-connect=0" is then parsed as the address 0.0.0.0:, i.e. the production dragonxd listening on this box -- every node in runs 2 + # and 3 picked up exactly one peer reporting blocks=3254269. Repeating -port on the + # command line points GetDefaultPort() at this node's own regtest port instead. + # + # -autoshield is on by default on DragonX and is not part of what this test measures: + # a background thread sweeps each node's matured coinbase into a seed-derived zaddr + # (8 "autoshield operation finished" ops per node while the chain is being mined). That + # empties the very taddr this test spends from, and the resulting transactions do not + # settle identically on every node ("ERROR: AcceptToMemoryPool: ContextualCheckTransaction + # failed" on node1), so sync_mempools() never converges and the run wedges until the + # timeout. Turn the background sweeper off; the test does its own shielding explicitly. + def net_args(self, i): + return ['-listen=1', '-bind=127.0.0.1', '-dns=0', '-autoshield=0', + '-port=%d' % p2p_port(i)] + def setup_nodes(self): return start_nodes(4, self.options.tmpdir, - extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4) + extra_args=[['-experimentalfeatures', '-developerencryptwallet'] + + self.net_args(i) for i in range(4)]) + + def setup_network(self, split = False): + super().setup_network(split) + # Same block layout initialize_chain() would have handed us. + for _ in range(2): + for peer in range(4): + self.nodes[peer].generate(25) + sync_blocks(self.nodes) + self.sync_all() def run_test (self): # add zaddr to node 0 @@ -25,7 +81,7 @@ class WalletNullifiersTest (BitcoinTestFramework): mytaddr = self.nodes[0].getnewaddress() recipients = [] recipients.append({"address":myzaddr0, "amount":Decimal('10.0')-Decimal('0.0001')}) # utxo amount less fee - + wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(mytaddr, recipients), timeout=120) self.sync_all() @@ -44,7 +100,7 @@ class WalletNullifiersTest (BitcoinTestFramework): bitcoind_processes[1].wait() # restart node 1 - self.nodes[1] = start_node(1, self.options.tmpdir) + self.nodes[1] = start_node(1, self.options.tmpdir, self.net_args(1)) connect_nodes_bi(self.nodes, 0, 1) connect_nodes_bi(self.nodes, 1, 2) self.sync_all() @@ -52,7 +108,7 @@ class WalletNullifiersTest (BitcoinTestFramework): # send node 0 zaddr to note 2 zaddr recipients = [] recipients.append({"address":myzaddr, "amount":7.0}) - + wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(myzaddr0, recipients), timeout=120) self.sync_all() @@ -97,7 +153,7 @@ class WalletNullifiersTest (BitcoinTestFramework): mytaddr1 = self.nodes[1].getnewaddress() recipients = [] recipients.append({"address":mytaddr1, "amount":1.0}) - + wait_and_assert_operationid_status(self.nodes[1], self.nodes[1].z_sendmany(myzaddr, recipients), timeout=120) self.sync_all() diff --git a/qa/rpc-tests/wallet_persistence.py b/qa/rpc-tests/wallet_persistence.py index a3b7a8c72..1b24564a2 100755 --- a/qa/rpc-tests/wallet_persistence.py +++ b/qa/rpc-tests/wallet_persistence.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -13,6 +13,29 @@ from test_framework.util import ( ) from decimal import Decimal +def get_value_pool(pools, pool_id): + """ + Return the valuePools entry with the given id, or None if this chain does + not have that pool. DragonX's getblockchaininfo only reports the Sapling + pool (no Sprout history exists on this chain), so the pools list can no + longer be indexed positionally the way the upstream test did. + """ + for pool in pools: + if pool['id'] == pool_id: + return pool + return None + +def assert_pool_values(pools, sprout_value, sapling_value): + sprout = get_value_pool(pools, 'sprout') + if sprout is not None: + assert_equal(sprout['chainValue'], sprout_value) + else: + # No Sprout pool at all is the same statement as "the Sprout pool holds nothing" + assert_equal(sprout_value, Decimal('0')) + sapling = get_value_pool(pools, 'sapling') + assert_true(sapling is not None, "Sapling value pool missing from getblockchaininfo") + assert_equal(sapling['chainValue'], sapling_value) + class WalletPersistenceTest (BitcoinTestFramework): def setup_chain(self): @@ -20,8 +43,20 @@ class WalletPersistenceTest (BitcoinTestFramework): initialize_chain_clean(self.options.tmpdir, 3) def setup_network(self, split=False): + # -listen=1/-bind: the framework's start_node() passes -connect=0, and DragonX (like + # Bitcoin) reacts to -connect by soft-setting -listen=0. A non-listening node can never + # accept the "addnode 127.0.0.1:" that connect_nodes_bi() issues, so without this + # the three nodes stay isolated and sync_all() spins forever. -bind keeps the listener on + # loopback so a regtest node never becomes reachable from the public internet. self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[[ + '-listen=1', + '-bind=127.0.0.1', + # -dns=0: DragonX appends node1..node10.dragonx.is to -addnode for every chain + # named DRAGONX (hush_utils.h), and -connect=0 does not suppress -addnode. Without + # this a regtest node dials the LIVE DragonX network and is fed mainnet headers. + # The addnode calls connect_nodes_bi() makes use literal IPs, so they still work. + '-dns=0', '-nuparams=5ba81b19:100', # Overwinter '-nuparams=76b809bb:201', # Sapling ]] * 3) @@ -69,12 +104,11 @@ class WalletPersistenceTest (BitcoinTestFramework): # Verify shielded balance assert_equal(self.nodes[0].z_getbalance(sapling_addr), Decimal('20')) - + # Verify size of shielded pools pools = self.nodes[0].getblockchaininfo()['valuePools'] - assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout - assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling - + assert_pool_values(pools, Decimal('0'), Decimal('20')) + # Restart the nodes stop_nodes(self.nodes) wait_bitcoinds() @@ -82,8 +116,7 @@ class WalletPersistenceTest (BitcoinTestFramework): # Verify size of shielded pools pools = self.nodes[0].getblockchaininfo()['valuePools'] - assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout - assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling + assert_pool_values(pools, Decimal('0'), Decimal('20')) # Node 0 sends some shielded funds to Node 1 dest_addr = self.nodes[1].z_getnewaddress('sapling') diff --git a/qa/rpc-tests/wallet_protectcoinbase.py b/qa/rpc-tests/wallet_protectcoinbase.py index c23f67567..0613a1b9c 100755 --- a/qa/rpc-tests/wallet_protectcoinbase.py +++ b/qa/rpc-tests/wallet_protectcoinbase.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -6,7 +6,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException -from test_framework.mininode import COIN from test_framework.util import assert_equal, initialize_chain_clean, \ start_nodes, connect_nodes_bi, wait_and_assert_operationid_status @@ -14,6 +13,20 @@ import sys import timeit from decimal import Decimal +# Upstream imported this from test_framework.mininode, which is still python2 and +# fails to even parse under python3 (0x100000000L literals). mininode is a p2p +# serialisation module this test does not otherwise use, so rather than drag a +# large unrelated port into test_framework/ the one constant needed is inlined. +# Same value as test_framework/mininode.py:52. +COIN = 100000000 # 1 DRGX in puposhis + +# DragonX has no Sprout pool: getblockchaininfo/getblock only ever emit a +# "sapling" entry in valuePools (see rpc/blockchain.cpp), and z_getnewaddress +# only makes Sapling addresses. The shielded value this test moves therefore +# lands in the Sapling pool, so every check that upstream made against 'sprout' +# is made against 'sapling' here. The assertion itself is unchanged. +SHIELDED_POOL = 'sapling' + def check_value_pool(node, name, total): value_pools = node.getblockchaininfo()['valuePools'] found = False @@ -42,7 +55,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(4) @@ -59,17 +72,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): assert_equal(self.nodes[2].getbalance(), 0) assert_equal(self.nodes[3].getbalance(), 0) - check_value_pool(self.nodes[0], 'sprout', 0) - check_value_pool(self.nodes[1], 'sprout', 0) - check_value_pool(self.nodes[2], 'sprout', 0) - check_value_pool(self.nodes[3], 'sprout', 0) + check_value_pool(self.nodes[0], SHIELDED_POOL, 0) + check_value_pool(self.nodes[1], SHIELDED_POOL, 0) + check_value_pool(self.nodes[2], SHIELDED_POOL, 0) + check_value_pool(self.nodes[3], SHIELDED_POOL, 0) # Send will fail because we are enforcing the consensus rule that # coinbase utxos can only be sent to a zaddr. errorString = "" try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True) @@ -88,18 +101,18 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # as it's currently not possible to specify a change address in z_sendmany. recipients = [] recipients.append({"address":myzaddr, "amount":Decimal('1.23456789')}) - + myopid = self.nodes[0].z_sendmany(mytaddr, recipients) error_result = wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "wallet does not allow any change", 10) # Test that the returned status object contains a params field with the operation's input parameters assert_equal(error_result["method"], "z_sendmany") params = error_result["params"] - assert_equal(params["fee"], Decimal('0.0001')) # default - assert_equal(params["minconf"], Decimal('1')) # default + assert_equal(Decimal(params["fee"]), Decimal('0.0001')) # default + assert_equal(Decimal(params["minconf"]), Decimal('1')) # default assert_equal(params["fromaddress"], mytaddr) assert_equal(params["amounts"][0]["address"], myzaddr) - assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789')) + assert_equal(Decimal(params["amounts"][0]["amount"]), Decimal('1.23456789')) # Add viewing key for myzaddr to Node 3 myviewingkey = self.nodes[0].z_exportviewingkey(myzaddr) @@ -169,14 +182,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): assert_equal(Decimal(resp["private"]), Decimal('19.9999')) assert_equal(Decimal(resp["total"]), Decimal('39.9999')) - # The Sprout value pool should reflect the send - sproutvalue = shieldvalue - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + # The shielded value pool should reflect the send + shieldedvalue = shieldvalue + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) # A custom fee of 0 is okay. Here the node will send the note value back to itself. recipients = [] recipients.append({"address":myzaddr, "amount": Decimal('19.9999')}) - myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('0.0')) + # NB: the fee is passed as a JSON number, not a Decimal. authproxy serialises + # Decimal as a JSON *string* and z_sendmany reads the fee with params[3].get_real(), + # which only accepts VNUM -- see port notes. The value is unchanged. + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, 0.0) mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid) self.sync_all() self.nodes[1].generate(1) @@ -186,8 +202,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): assert_equal(Decimal(resp["private"]), Decimal('19.9999')) assert_equal(Decimal(resp["total"]), Decimal('39.9999')) - # The Sprout value pool should be unchanged - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + # The shielded value pool should be unchanged + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) # convert note to transparent funds unshieldvalue = Decimal('10.0') @@ -206,12 +222,12 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): self.sync_all() # check balances - sproutvalue -= unshieldvalue + Decimal('0.0001') + shieldedvalue -= unshieldvalue + Decimal('0.0001') resp = self.nodes[0].z_gettotalbalance() assert_equal(Decimal(resp["transparent"]), Decimal('30.0')) assert_equal(Decimal(resp["private"]), Decimal('9.9998')) assert_equal(Decimal(resp["total"]), Decimal('39.9998')) - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) # z_sendmany will return an error if there is transparent change output considered dust. # UTXO selection in z_sendmany sorts in ascending order, so smallest utxos are consumed first. @@ -226,7 +242,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): errorString = "" try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient funds" in errorString, True) @@ -241,7 +257,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # Send will fail because of insufficient funds unless sender uses coinbase utxos try: self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True) @@ -256,7 +272,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # Note that regtest chainparams does not require standard tx, so setting the amount to be # less than the dust threshold, e.g. 0.00000001 will not result in mempool rejection. start_time = timeit.default_timer() - for i in xrange(0,num_t_recipients): + for i in range(0,num_t_recipients): newtaddr = self.nodes[2].getnewaddress() recipients.append({"address":newtaddr, "amount":amount_per_recipient}) elapsed = timeit.default_timer() - start_time @@ -287,28 +303,30 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): # check balance node2balance = amount_per_recipient * num_t_recipients - sproutvalue -= node2balance + Decimal('0.0001') + shieldedvalue -= node2balance + Decimal('0.0001') assert_equal(self.nodes[2].getbalance(), node2balance) - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) # Send will fail because fee is negative try: self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than MAX_MONEY + errorString = "" try: - self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001')) - except JSONRPCException,e: + self.nodes[0].z_sendmany(myzaddr, recipients, 1, float(Decimal('21000000.00000001'))) + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Send will fail because fee is larger than sum of outputs + errorString = "" try: - self.nodes[0].z_sendmany(myzaddr, recipients, 1, (amount_per_recipient * num_t_recipients) + Decimal('0.00000001')) - except JSONRPCException,e: + self.nodes[0].z_sendmany(myzaddr, recipients, 1, float((amount_per_recipient * num_t_recipients) + Decimal('0.00000001'))) + except JSONRPCException as e: errorString = e.error['message'] assert_equal("is greater than the sum of outputs" in errorString, True) @@ -334,10 +352,10 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): send_amount = num_recipients * amount_per_recipient custom_fee = Decimal('0.00012345') zbalance = self.nodes[0].z_getbalance(myzaddr) - for i in xrange(0,num_recipients): + for i in range(0,num_recipients): newzaddr = self.nodes[2].z_getnewaddress() recipients.append({"address":newzaddr, "amount":amount_per_recipient}) - myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, custom_fee) + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, float(custom_fee)) wait_and_assert_operationid_status(self.nodes[0], myopid) self.sync_all() self.nodes[1].generate(1) @@ -353,8 +371,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework): resp = self.nodes[0].z_getbalance(myzaddr) assert_equal(Decimal(resp), zbalance - custom_fee - send_amount) - sproutvalue -= custom_fee - check_value_pool(self.nodes[0], 'sprout', sproutvalue) + shieldedvalue -= custom_fee + check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue) notes = self.nodes[0].z_listunspent(1, 99999, False, [myzaddr]) sum_of_notes = sum([note["amount"] for note in notes]) diff --git a/qa/rpc-tests/wallet_sapling.py b/qa/rpc-tests/wallet_sapling.py index d7beed817..81b429e89 100755 --- a/qa/rpc-tests/wallet_sapling.py +++ b/qa/rpc-tests/wallet_sapling.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2018 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -8,21 +8,99 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException from test_framework.util import ( assert_equal, - start_nodes, + initialize_chain_clean, + p2p_port, + set_node_times, + start_node, + sync_blocks, wait_and_assert_operationid_status, ) +import os +import stat + from decimal import Decimal # Test wallet behaviour with Sapling addresses class WalletSaplingTest(BitcoinTestFramework): + def setup_chain(self): + # The shared initialize_chain() cache builder in test_framework/util.py has not been + # repaired for DragonX (it spawns the cache nodes by bare name off PATH, drives the + # CLI without -regtest so it dials the assetchain RPC port, and deletes debug.log + # from the non-net-specific datadir). Build the same starting state here instead -- + # see _generate_starting_chain() -- so this test does not depend on it. + print("Initializing test directory " + self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + # !!! test_framework/util.py:start_node() hardcodes "-connect=0" into every regtest + # node's argv. Modern Bitcoin Core special-cases that value to mean "make no automatic + # connections", but THIS codebase does not (net.cpp ThreadOpenConnections just iterates + # mapMultiArgs["-connect"]), so "0" is dialled as a hostname: it resolves to 0.0.0.0, + # which on Linux connects to localhost on Params().GetDefaultPort() -- 21768, the live + # DRAGONX p2p port. Observed directly: a regtest node started by the unmodified + # framework peered with the production dragonxd on this host and with seven public + # mainnet nodes (heights ~3.25M) and began ingesting mainnet headers. "-connect" also + # soft-sets "-listen=0", so the framework's own connect_nodes_bi() can never establish + # the local links a multi-node test needs. + # + # Both problems are in the shared framework, which this port is not allowed to touch, so + # they are worked around per-node here: the daemon is launched through a tiny wrapper + # that strips the "-connect=0" argument, and the real topology/listening flags are passed + # as extra_args (which start_node appends after its own). + def _daemon_wrapper(self): + srcdir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "..", "src", "dragonxd") + path = os.path.join(self.options.tmpdir, "dragonxd-no-connect0") + with open(path, "w") as f: + f.write("#!/usr/bin/env bash\n") + f.write("args=()\n") + f.write('for a in "$@"; do\n') + f.write(' if [ "$a" = "-connect=0" ]; then continue; fi\n') + f.write(' args+=("$a")\n') + f.write("done\n") + f.write('exec %s "${args[@]}"\n' % os.path.realpath(srcdir)) + os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) + return path + def setup_nodes(self): - return start_nodes(4, self.options.tmpdir, [[ - #'-nuparams=5ba81b19:201', # Overwinter - #'-nuparams=76b809bb:203', # Sapling - #'-experimentalfeatures', '-zmergetoaddress', - ]] * 4) + binary = self._daemon_wrapper() + nodes = [] + for i in range(4): + extra_args = [ + #'-nuparams=5ba81b19:201', # Overwinter + #'-nuparams=76b809bb:203', # Sapling + #'-experimentalfeatures', '-zmergetoaddress', + # Listen on this test's PID-keyed port so the nodes can actually peer with + # each other, and only ever dial each other -- never the public network. + '-listen=1', + '-bind=127.0.0.1', + '-port=%d' % p2p_port(i), + ] + ['-connect=127.0.0.1:%d' % p2p_port(j) for j in range(4) if j != i] + nodes.append(start_node(i, self.options.tmpdir, extra_args, binary=binary)) + return nodes + + def setup_network(self, split=False): + super(WalletSaplingTest, self).setup_network(split) + self._generate_starting_chain() + + def _generate_starting_chain(self): + # Equivalent of test_framework.util.initialize_chain(): a 200-block chain where each + # of the 4 nodes mined 25 blocks twice, so every node holds 25 mature and 25 immature + # coinbases. Block timestamps are 10 minutes apart starting 1 Jan 2014, as there. + block_time = 1388534400 + for _round in range(2): + for peer in range(4): + for _j in range(25): + set_node_times(self.nodes, block_time) + self.nodes[peer].generate(1) + block_time += 10 * 75 + # Must sync before next peer starts generating blocks + sync_blocks(self.nodes) + # Drop back to wall-clock time: initialize_chain() stops the cache nodes and the test + # then runs against freshly started nodes that have no mocktime set. + set_node_times(self.nodes, 0) + self.sync_all() def run_test(self): # Sanity-check the test harness diff --git a/qa/rpc-tests/wallet_shieldcoinbase.py b/qa/rpc-tests/wallet_shieldcoinbase.py index cd2601103..8577ff530 100755 --- a/qa/rpc-tests/wallet_shieldcoinbase.py +++ b/qa/rpc-tests/wallet_shieldcoinbase.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2017 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -22,8 +22,24 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): initialize_chain_clean(self.options.tmpdir, 4) def setup_network(self, split=False): - args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] - args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + # DragonX-specific environment flags. None of these change what the test + # asserts; without them the test cannot run at all on this daemon: + # -listen=1/-bind=127.0.0.1: the framework's start_node() always passes + # -connect=0, and AppInit2 soft-sets -listen=0 whenever -connect is + # present, so the nodes never listen and connect_nodes_bi() silently + # builds an empty topology (sync_all() then spins forever). An explicit + # -listen=1 beats the SoftSetBoolArg. + # -dnsseed=0: chainparams_commandline() keeps DRAGONX's DNS seeds and + # overwrites pchMessageStart with the DRAGONX chain magic on *every* + # network including regtest, so a regtest node otherwise dials and + # handshakes with live mainnet peers (observed: 8 mainnet peers, + # blocks=3254237, feeding mainnet headers into the regtest node). + # -autoshield=0: DragonX auto-shields matured coinbase every 25 blocks + # by default, which would race the manual z_shieldcoinbase calls under + # test and move the balances this test checks. + isolate = ['-listen=1', '-bind=127.0.0.1', '-dnsseed=0', '-autoshield=0'] + args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] + isolate + args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + isolate if self.addr_type != 'sprout': nu = [ '-nuparams=5ba81b19:0', # Overwinter @@ -42,7 +58,7 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(1) self.sync_all() @@ -73,42 +89,42 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): self.nodes[2].importaddress(mytaddr) try: self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Could not find any coinbase funds to shield" in errorString, True) # Shielding will fail because fee is negative try: self.nodes[0].z_shieldcoinbase("*", myzaddr, -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Shielding will fail because fee is larger than MAX_MONEY try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001')) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Amount out of range" in errorString, True) # Shielding will fail because fee is larger than sum of utxos try: self.nodes[0].z_shieldcoinbase("*", myzaddr, 999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Insufficient coinbase funds" in errorString, True) # Shielding will fail because limit parameter must be at least 0 try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True) # Shielding will fail because limit parameter is absurdly large try: self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999) - except JSONRPCException,e: + except JSONRPCException as e: errorString = e.error['message'] assert_equal("JSON integer out of range" in errorString, True) @@ -214,3 +230,13 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework): sync_mempools(self.nodes[:2]) self.nodes[1].generate(1) self.sync_all() + +if __name__ == '__main__': + # Upstream (Zcash/Hush) ran this test twice: once with Sprout zaddrs and once + # with Sapling. DragonX has no Sprout support at all -- z_getnewaddress only + # accepts "sapling" or "amnesia" (src/wallet/rpcwallet.cpp z_getnewaddress), + # so WalletShieldCoinbaseTest('sprout') cannot even allocate its target + # address. The sprout-only branches inside run_test() are kept intact for + # reference but only the sapling variant is executed. + print("Running for sapling...") + WalletShieldCoinbaseTest('sapling').main() diff --git a/qa/rpc-tests/wallet_treestate.py b/qa/rpc-tests/wallet_treestate.py index 6d32ea9eb..3ef3fe0af 100755 --- a/qa/rpc-tests/wallet_treestate.py +++ b/qa/rpc-tests/wallet_treestate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2016 The Zcash developers # Distributed under the GPLv3 software license, see the accompanying @@ -20,7 +20,16 @@ class WalletTreeStateTest (BitcoinTestFramework): # Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true. def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[['-regtestprotectcoinbase','-debug=zrpc']] * 3 ) + # -listen=1 and -dns=0 are DragonX-specific harness requirements, not part of the + # original test: start_node() passes -connect=0, which trips the "-connect set -> + # setting -listen=0" parameter interaction, so without -listen=1 the three nodes + # cannot open the p2p links connect_nodes_bi() asks for (node1/node2 stay at height + # 0 forever and sync_all() can never converge). -dns=0 blocks the unconditional + # node1..node10.dragonx.is -addnode injection in hush_args(), which otherwise dials + # the real DragonX seed nodes from regtest and floods these nodes with mainnet headers. + self.nodes = start_nodes(3, self.options.tmpdir, + extra_args=[['-regtestprotectcoinbase','-debug=zrpc', + '-listen=1','-dns=0']] * 3 ) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) @@ -28,7 +37,7 @@ class WalletTreeStateTest (BitcoinTestFramework): self.sync_all() def run_test (self): - print "Mining blocks..." + print("Mining blocks...") self.nodes[0].generate(100) self.sync_all() @@ -79,7 +88,7 @@ class WalletTreeStateTest (BitcoinTestFramework): myopid = self.nodes[0].z_sendmany(myzaddr, recipients) # Wait for Tx 2 to begin executing... - for x in xrange(1, 60): + for x in range(1, 60): results = self.nodes[0].z_getoperationstatus([myopid]) status = results[0]["status"] if status == "executing": diff --git a/qa/rpc-tests/walletbackup.py b/qa/rpc-tests/walletbackup.py index 7b5154608..efb27ecb4 100755 --- a/qa/rpc-tests/walletbackup.py +++ b/qa/rpc-tests/walletbackup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2016-2024 The Hush developers # Copyright (c) 2014 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying @@ -37,8 +37,8 @@ and confirm again balances are correct. from test_framework.test_framework import BitcoinTestFramework from test_framework.authproxy import JSONRPCException from test_framework.util import assert_equal, initialize_chain_clean, \ - start_nodes, start_node, connect_nodes, stop_node, \ - sync_blocks, sync_mempools + start_nodes, start_node, connect_nodes, \ + sync_blocks, sync_mempools, bitcoind_processes import os import shutil @@ -48,6 +48,46 @@ import logging logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) +# Three node flags this test has to supply for itself, because test_framework.util.start_node() +# cannot give it a usable isolated regtest network on DragonX: +# +# -listen=1: start_node() passes "-connect=0" to every node. In DragonX (as in Bitcoin) the +# presence of a -connect argument SoftSetBoolArg()s -listen to false, so no test node ever +# binds its p2p port and connect_nodes() cannot build the loopback topology this test needs. +# Verified on a live run: only the RPC ports were listening and getpeerinfo showed zero +# 127.0.0.1 peers on all four nodes. SoftSetBoolArg does not override an explicit value. +# +# -dns=0: hush_args() unconditionally appends node1..node10.dragonx.is to -addnode whenever the +# chain name is DRAGONX, regardless of network, and -connect=0 does not suppress it. On a live +# run every "regtest" node ended up with 7-8 established connections to production mainnet +# nodes on port 21768, was flooded with mainnet headers ("AcceptBlockHeader: hashPrevBlock ... +# not found"), and one of them aborted on CheckBlockIndex(). -dns=0 stops the hostnames from +# resolving while leaving the literal 127.0.0.1:PORT addnodes connect_nodes() uses intact. +# +# -allowlist=127.0.0.1: "-connect=0" does not mean "no connections". The daemon resolves the +# literal "0" to 0.0.0.0 and dials it on the chain's default p2p port, i.e. 127.0.0.1:21768 -- +# which on a machine that also runs a real node is the PRODUCTION daemon (observed: +# ESTAB 127.0.0.1:41282 -> 127.0.0.1:21768 from every test node, peer subver /DragonX:1.0.3/, +# startingheight 3254254). That peer is outbound, so it is the node's only preferred-download +# peer; main.cpp:8398 then computes fFetch=false for every inbound peer, and node3 -- which in +# this test's topology is dialed by everyone and dials no one -- never downloads an announced +# block, so sync_blocks() hangs forever. Allowlisting loopback makes inbound test peers +# preferred-download too (main.cpp:381), which restores block propagation. +LISTEN = "-listen=1" +NODNS = "-dns=0" +ALLOWLIST = "-allowlist=127.0.0.1" + + +def stop_node_and_reap(node, i): + # Equivalent to test_framework.util.stop_node(), which cannot be called: it does + # print("Stopping node " + i) with the int index that every caller passes, which raises + # TypeError. Reimplemented here rather than editing shared framework code other tests use. + print("Stopping node %d" % i) + node.stop() + bitcoind_processes[i].wait() + del bitcoind_processes[i] + + class WalletBackupTest(BitcoinTestFramework): def setup_chain(self): @@ -62,7 +102,10 @@ class WalletBackupTest(BitcoinTestFramework): ed2 = "-exportdir=" + self.options.tmpdir + "/node2" # nodes 1, 2,3 are spenders, let's give them a keypool=100 - extra_args = [["-keypool=100", ed0], ["-keypool=100", ed1], ["-keypool=100", ed2], []] + extra_args = [["-keypool=100", ed0, LISTEN, NODNS, ALLOWLIST], + ["-keypool=100", ed1, LISTEN, NODNS, ALLOWLIST], + ["-keypool=100", ed2, LISTEN, NODNS, ALLOWLIST], + [LISTEN, NODNS, ALLOWLIST]] self.nodes = start_nodes(4, self.options.tmpdir, extra_args) connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) @@ -95,18 +138,18 @@ class WalletBackupTest(BitcoinTestFramework): # As above, this mirrors the original bash test. def start_three(self): - self.nodes[0] = start_node(0, self.options.tmpdir) - self.nodes[1] = start_node(1, self.options.tmpdir) - self.nodes[2] = start_node(2, self.options.tmpdir) + self.nodes[0] = start_node(0, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) + self.nodes[1] = start_node(1, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) + self.nodes[2] = start_node(2, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST]) connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) connect_nodes(self.nodes[2], 3) connect_nodes(self.nodes[2], 0) def stop_three(self): - stop_node(self.nodes[0], 0) - stop_node(self.nodes[1], 1) - stop_node(self.nodes[2], 2) + stop_node_and_reap(self.nodes[0], 0) + stop_node_and_reap(self.nodes[1], 1) + stop_node_and_reap(self.nodes[2], 2) def erase_three(self): os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat") diff --git a/src/Makefile.am b/src/Makefile.am index 86d199a52..96b509435 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -243,6 +243,7 @@ BITCOIN_CORE_H = \ wallet/asyncrpcoperation_mergetoaddress.h \ wallet/asyncrpcoperation_saplingconsolidation.h \ wallet/asyncrpcoperation_sweep.h \ + wallet/asyncrpcoperation_autoshieldcoinbase.h \ wallet/asyncrpcoperation_sendmany.h \ wallet/asyncrpcoperation_shieldcoinbase.h \ wallet/crypter.h \ @@ -321,6 +322,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/asyncrpcoperation_mergetoaddress.cpp \ wallet/asyncrpcoperation_saplingconsolidation.cpp \ wallet/asyncrpcoperation_sweep.cpp \ + wallet/asyncrpcoperation_autoshieldcoinbase.cpp \ wallet/asyncrpcoperation_sendmany.cpp \ wallet/asyncrpcoperation_shieldcoinbase.cpp \ wallet/crypter.cpp \ @@ -487,7 +489,7 @@ if TARGET_WINDOWS dragonxd_SOURCES += bitcoind-res.rc endif -dragonxd_LDADD = \ +dragonxd_LDADD = $(LINK_GROUP_START) \ $(LIBBITCOIN_SERVER) \ $(LIBCURL) \ $(LIBBITCOIN_COMMON) \ @@ -525,6 +527,8 @@ if TARGET_LINUX dragonxd_LDADD += libcc.so $(LIBSECP256K1) endif +dragonxd_LDADD += $(LINK_GROUP_END) + # [+] Decker: use static linking for libstdc++.6.dylib, libgomp.1.dylib, libgcc_s.1.dylib if TARGET_DARWIN dragonxd_LDFLAGS += -static-libgcc @@ -551,7 +555,7 @@ if TARGET_WINDOWS dragonx_cli_SOURCES += bitcoin-cli-res.rc endif -dragonx_cli_LDADD = \ +dragonx_cli_LDADD = $(LINK_GROUP_START) \ $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ @@ -564,8 +568,10 @@ dragonx_cli_LDADD = \ $(LIBBITCOIN_CRYPTO) \ $(LIBZCASH_LIBS) +dragonx_cli_LDADD += $(LINK_GROUP_END) + if ENABLE_WALLET -wallet_utility_LDADD = \ +wallet_utility_LDADD = $(LINK_GROUP_START) \ libbitcoin_wallet.a \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_CRYPTO) \ @@ -577,6 +583,7 @@ wallet_utility_LDADD = \ $(LIBZCASH) \ $(LIBZCASH_LIBS)\ $(LIBRANDOMX) +wallet_utility_LDADD += $(LINK_GROUP_END) endif # hush-tx binary # @@ -589,7 +596,7 @@ if TARGET_WINDOWS dragonx_tx_SOURCES += bitcoin-tx-res.rc endif -dragonx_tx_LDADD = \ +dragonx_tx_LDADD = $(LINK_GROUP_START) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ @@ -600,7 +607,7 @@ dragonx_tx_LDADD = \ $(LIBZCASH_LIBS) \ $(LIBRANDOMX) -dragonx_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) +dragonx_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) $(LINK_GROUP_END) # Zcash Protocol Primitives libzcash_a_SOURCES = \ diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index fa9d47448..2f3100f11 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -13,7 +13,8 @@ hush_gtest_SOURCES = \ gtest/utils.cpp \ gtest/test_randomx_preverify.cpp \ gtest/test_hdtransparent.cpp \ - gtest/test_mnemonic_compat.cpp + gtest/test_mnemonic_compat.cpp \ + gtest/test_stratum_jobid.cpp hush_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES) hush_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) diff --git a/src/addrman.cpp b/src/addrman.cpp index ae5d5a50d..3a8fc0777 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -473,11 +473,6 @@ CAddrInfo CAddrMan::Select_(bool newOnly) if (size() == 0) return CAddrInfo(); - // Track number of attempts to find a table entry, before giving up to avoid infinite loop - const int kMaxRetries = 200000; // magic number so unit tests can pass - const int kRetriesBetweenSleep = 1000; - const int kRetrySleepInterval = 100; // milliseconds - if (newOnly && nNew == 0) return CAddrInfo(); @@ -485,89 +480,72 @@ CAddrInfo CAddrMan::Select_(bool newOnly) if (!newOnly && (nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) { // use a tried node - double fChanceFactor = 1.0; - double fReachableFactor = 1.0; - double fJustTried = 1.0; - while (1) { - if (ShutdownRequested()) //break loop on shutdown request - return CAddrInfo(); - - int i = 0; - int nKBucket = RandomInt(ADDRMAN_TRIED_BUCKET_COUNT); - int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); - while (vvTried[nKBucket][nKBucketPos] == -1) { - nKBucket = (nKBucket + insecure_rand()) % ADDRMAN_TRIED_BUCKET_COUNT; - nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; - if (i++ > kMaxRetries) - return CAddrInfo(); - if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull()) - MilliSleep(kRetrySleepInterval); - } - int nId = vvTried[nKBucket][nKBucketPos]; - // assert(mapInfo.count(nId) == 1); - if(mapInfo.count(nId) != 1) { - fprintf(stderr,"%s: Could not find tried node with nId=%d=vvTried[%d][%d], mapInfo.count(%d)=%lu\n", __func__, nId, nKBucket, nKBucketPos, nId, mapInfo.count(nId) ); - continue; - } - - CAddrInfo& info = mapInfo[nId]; - if (info.IsReachableNetwork()) { - //deprioritize unreachable networks - fReachableFactor = 0.25; - } - if (info.IsJustTried()) { - //deprioritize entries just tried - fJustTried = 0.10; - } - if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) - return info; - fChanceFactor *= 1.2; - } + return SelectFromTable_(vvTried, ADDRMAN_TRIED_BUCKET_COUNT, "tried"); } else { // use a new node - double fChanceFactor = 1.0; - double fReachableFactor = 1.0; - double fJustTried = 1.0; - while (1) { - if (ShutdownRequested()) //break loop on shutdown request - return CAddrInfo(); - - int i = 0; - int nUBucket = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); - int nUBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); - while (vvNew[nUBucket][nUBucketPos] == -1) { - nUBucket = (nUBucket + insecure_rand()) % ADDRMAN_NEW_BUCKET_COUNT; - nUBucketPos = (nUBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; - if (i++ > kMaxRetries) - return CAddrInfo(); - if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull()) - MilliSleep(kRetrySleepInterval); - } - int nId = vvNew[nUBucket][nUBucketPos]; - - if(mapInfo.count(nId) != 1) { - fprintf(stderr,"%s: Could not find new node with nId=%d=vvNew[%d][%d], mapInfo.count(%d)=%lu\n", __func__, nId, nUBucket, nUBucketPos, nId, mapInfo.count(nId) ); - continue; - } - // assert(mapInfo.count(nId) == 1); - CAddrInfo& info = mapInfo[nId]; - if (info.IsReachableNetwork()) { - //deprioritize unreachable networks - fReachableFactor = 0.25; - } - if (info.IsJustTried()) { - //deprioritize entries just tried - fJustTried = 0.10; - } - if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) - return info; - fChanceFactor *= 1.2; - } + return SelectFromTable_(vvNew, ADDRMAN_NEW_BUCKET_COUNT, "new"); } return CAddrInfo(); } +// Random-walk one addrman bucket table (tried or new) and return an accepted peer, +// applying the reachable/just-tried deprioritization and the growing chance factor. +// Extracted verbatim from Select_'s two previously copy-pasted branches; the only +// differences were the table (vvTried/vvNew), its bucket count, and the log label. +CAddrInfo CAddrMan::SelectFromTable_(int (*vvTable)[ADDRMAN_BUCKET_SIZE], int nBucketCount, const char *tableName) +{ + // Track number of attempts to find a table entry, before giving up to avoid infinite loop + const int kMaxRetries = 200000; // magic number so unit tests can pass + const int kRetriesBetweenSleep = 1000; + const int kRetrySleepInterval = 100; // milliseconds + + // Peer-selection tuning factors (networking heuristics, not consensus). + const double kChanceFactorGrowth = 1.2; + const double kUnreachableDeprioritize = 0.25; + const double kJustTriedDeprioritize = 0.10; + const int kChanceScale = 1 << 30; + + double fChanceFactor = 1.0; + double fReachableFactor = 1.0; + double fJustTried = 1.0; + while (1) { + if (ShutdownRequested()) //break loop on shutdown request + return CAddrInfo(); + + int i = 0; + int nKBucket = RandomInt(nBucketCount); + int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); + while (vvTable[nKBucket][nKBucketPos] == -1) { + nKBucket = (nKBucket + insecure_rand()) % nBucketCount; + nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; + if (i++ > kMaxRetries) + return CAddrInfo(); + if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull()) + MilliSleep(kRetrySleepInterval); + } + int nId = vvTable[nKBucket][nKBucketPos]; + // assert(mapInfo.count(nId) == 1); + if(mapInfo.count(nId) != 1) { + fprintf(stderr,"%s: Could not find %s node with nId=%d=vvTable[%d][%d], mapInfo.count(%d)=%lu\n", __func__, tableName, nId, nKBucket, nKBucketPos, nId, mapInfo.count(nId) ); + continue; + } + + CAddrInfo& info = mapInfo[nId]; + if (info.IsReachableNetwork()) { + //deprioritize unreachable networks + fReachableFactor = kUnreachableDeprioritize; + } + if (info.IsJustTried()) { + //deprioritize entries just tried + fJustTried = kJustTriedDeprioritize; + } + if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale) + return info; + fChanceFactor *= kChanceFactorGrowth; + } +} + #ifdef DEBUG_ADDRMAN int CAddrMan::Check_() { diff --git a/src/addrman.h b/src/addrman.h index ba37eba23..0795e5aac 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -300,6 +300,10 @@ protected: //! Select an address to connect to, if newOnly is set to true, only the new table is selected from. CAddrInfo Select_(bool newOnly); + //! Random-walk one bucket table (tried or new) and return an accepted peer. + //! Shared implementation for Select_'s two (previously copy-pasted) branches. + CAddrInfo SelectFromTable_(int (*vvTable)[ADDRMAN_BUCKET_SIZE], int nBucketCount, const char *tableName); + //! Wraps GetRandInt to allow tests to override RandomInt and make it deterministic. virtual int RandomInt(int nMax); diff --git a/src/asyncrpcoperation.cpp b/src/asyncrpcoperation.cpp index c0f5f4c0a..0579ca1f6 100644 --- a/src/asyncrpcoperation.cpp +++ b/src/asyncrpcoperation.cpp @@ -18,6 +18,7 @@ ******************************************************************************/ #include "asyncrpcoperation.h" +#include #include #include @@ -58,6 +59,31 @@ AsyncRPCOperation::AsyncRPCOperation(const AsyncRPCOperation& o) : { } +// Shared error mapping for every async op's main(): rethrow the in-flight +// exception and translate it to this operation's error code/message. Keeping +// it here means the mapping is edited in one place, not copy-pasted into six. +void AsyncRPCOperation::set_error_from_current_exception() +{ + try { + throw; + } catch (const UniValue& objError) { + set_error_code(find_value(objError, "code").get_int()); + set_error_message(find_value(objError, "message").get_str()); + } catch (const runtime_error& e) { + set_error_code(-1); + set_error_message("runtime error: " + string(e.what())); + } catch (const logic_error& e) { + set_error_code(-1); + set_error_message("logic error: " + string(e.what())); + } catch (const exception& e) { + set_error_code(-1); + set_error_message("general exception: " + string(e.what())); + } catch (...) { + set_error_code(-2); + set_error_message("unknown error"); + } +} + AsyncRPCOperation& AsyncRPCOperation::operator=( const AsyncRPCOperation& other ) { this->id_ = other.id_; this->creation_time_ = other.creation_time_; diff --git a/src/asyncrpcoperation.h b/src/asyncrpcoperation.h index 8bef0ec08..f3a5dbf10 100644 --- a/src/asyncrpcoperation.h +++ b/src/asyncrpcoperation.h @@ -147,6 +147,11 @@ protected: std::lock_guard guard(lock_); this->error_message_ = errorMessage; } + + // Map the in-flight (rethrown) exception to error_code_/error_message_. Called from + // every async op's main() catch(...) so the UniValue/runtime/logic/exception mapping + // lives in one place instead of being copy-pasted into all six operations. + void set_error_from_current_exception(); void set_result(UniValue v) { std::lock_guard guard(lock_); diff --git a/src/asyncrpcqueue.cpp b/src/asyncrpcqueue.cpp index 543951e18..ce356c146 100644 --- a/src/asyncrpcqueue.cpp +++ b/src/asyncrpcqueue.cpp @@ -96,18 +96,21 @@ void AsyncRPCQueue::run(size_t workerId) { * * Don't use std::make_shared(). */ -void AsyncRPCQueue::addOperation(const std::shared_ptr &ptrOperation) { +bool AsyncRPCQueue::addOperation(const std::shared_ptr &ptrOperation) { std::lock_guard guard(lock_); - // Don't add if queue is closed or finishing + // Don't add if queue is closed or finishing. Report it: silently dropping the + // operation made callers announce work that would never run. + // (isClosed/isFinishing read atomics, so calling them under the guard is safe.) if (isClosed() || isFinishing()) { - return; + return false; } AsyncRPCOperationId id = ptrOperation->getId(); operation_map_.emplace(id, ptrOperation); operation_id_queue_.push(id); this->condition_.notify_one(); + return true; } /** diff --git a/src/asyncrpcqueue.h b/src/asyncrpcqueue.h index 1ebc6c1ee..099b31706 100644 --- a/src/asyncrpcqueue.h +++ b/src/asyncrpcqueue.h @@ -63,7 +63,12 @@ public: size_t getOperationCount() const; std::shared_ptr getOperationForId(AsyncRPCOperationId) const; std::shared_ptr popOperationForId(AsyncRPCOperationId); - void addOperation(const std::shared_ptr &ptrOperation); + // Returns false if the queue is closed or finishing, in which case the + // operation was NOT queued and will never run. Callers must react: a caller + // that ignores this both reports success for work that will not happen and + // leaves any state it set for the operation (running flags, coin locks) + // stranded for the life of the process. + bool addOperation(const std::shared_ptr &ptrOperation); std::vector getAllOperationIds() const; private: diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 84e4c0846..367355419 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -257,14 +257,18 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) event_base_dispatch(base.get()); if (response.status == 0) { + // Report the port we ACTUALLY dialled. ASSETCHAINS_RPCPORT is a separate global that is + // initialised to the mainnet default at the top of this file and never assigned here, so + // using it made every failure claim port 21769 no matter what -rpcport was given -- which + // reads as "your -rpcport was ignored" and sends you chasing a config bug that isn't there. throw CConnectionFailed(strprintf("couldn't connect to server at port %d : %s (code %d)\n(make sure server is running and you are connecting to the correct RPC port)", - ASSETCHAINS_RPCPORT, http_errorstring(response.error), response.error)); + port, http_errorstring(response.error), response.error)); } else if (response.status == HTTP_UNAUTHORIZED) { throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) { throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); } else if (response.body.empty()) { - throw std::runtime_error(strprintf("no response from server at port %d", ASSETCHAINS_RPCPORT )); + throw std::runtime_error(strprintf("no response from server at port %d", port)); } // Parse reply diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 0c7737234..eaf325b92 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -41,7 +41,11 @@ /// \cond INTERNAL #define CC_MAXVINS 1024 -#define CC_REQUIREMENTS_MSG (HUSH_NSPV_SUPERLITE?"to use CC contracts you need to nspv_login first\n":"to use CC contracts, you need to launch daemon with valid -pubkey= for an address in your wallet\n") +// NOTE: CryptoConditions (CC) contracts are inherited from the Komodo/Hush lineage and are +// largely vestigial on DragonX (ac_private=1 fully-shielded chain). This user-facing message +// still describes the legacy prerequisites for using CC contracts (nspv_login in superlite +// mode, or launching dragonxd with a valid -pubkey=). +#define CC_REQUIREMENTS_MSG (HUSH_NSPV_SUPERLITE?"to use CC contracts you need to nspv_login first\n":"to use CC contracts, you need to launch dragonxd with valid -pubkey= for an address in your wallet\n") #define SMALLVAL 0.000000000000001 #define SATOSHIDEN ((uint64_t)100000000L) @@ -58,7 +62,8 @@ struct CC_utxo /// \endcond -/// CC contract (Antara module) info structure that contains data used for signing and validation of cc contract transactions +/// CC (CryptoConditions) contract info structure that contains data used for signing and validation of cc contract transactions. +/// NOTE: the CC framework (historically called "Antara modules" in the Komodo/Hush lineage) is largely vestigial on DragonX. struct CCcontract_info { uint8_t evalcode; //!< cc contract eval code, set by CCinit function @@ -101,7 +106,7 @@ struct CCcontract_info bool(*validate)(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn); /// checks if the value of evalcode in cp object is present in the scriptSig parameter, - /// that is, the vin for this scriptSig will be validated by the cc contract (Antara module) defined by the eval code in this CCcontract_info object + /// that is, the vin for this scriptSig will be validated by the cc contract defined by the eval code in this CCcontract_info object /// @param scriptSig scriptSig to check\n /// Example: /// \code @@ -283,7 +288,7 @@ bool ExtractTokensCCVinPubkeys(const CTransaction &tx, std::vector &vin /// cp = CCinit(&C, EVAL_ASSETS); /// CPubKey ccAssetsPk = GetUnspendable(cp, ccAssetsPriv); /// \endcode -/// Now ccAssetsPk has Antara 'Assets' module global pubkey and ccAssetsPriv has its publicly available private key +/// Now ccAssetsPk has the 'Assets' CC module global pubkey and ccAssetsPriv has its publicly available private key CPubKey GetUnspendable(struct CCcontract_info *cp,uint8_t *unspendablepriv); // CCutils @@ -373,7 +378,7 @@ int64_t CCfullsupply(uint256 tokenid); /// @returns true if success bool Getscriptaddress(char *destaddr,const CScript &scriptPubKey); -/// Returns my pubkey, that is set by -pubkey hushd parameter +/// Returns my pubkey, that is set by the -pubkey dragonxd parameter /// @returns public key as byte array std::vector Mypubkey(); @@ -404,8 +409,8 @@ extern std::vector NULL_pubkeys; //!< constant value for use in functio std::string FinalizeCCTx(uint64_t skipmask,struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey mypk,uint64_t txfee,CScript opret,std::vector pubkeys = NULL_pubkeys); /// FinalizeCCTx is a very useful function that will properly sign both CC and normal inputs, adds normal change and might add an opreturn output. -/// This allows for Antara module transaction creation rpc functions to create an CMutableTransaction object, add the appropriate vins and vouts to it and use FinalizeCCTx to properly sign the transaction. -/// By using -addressindex=1 of hushd daemon, it allows tracking of all the CC addresses. +/// This allows for CC module transaction creation rpc functions to create an CMutableTransaction object, add the appropriate vins and vouts to it and use FinalizeCCTx to properly sign the transaction. +/// By using -addressindex=1 of the dragonxd daemon, it allows tracking of all the CC addresses. /// /// For signing the vins the function builds several default probe scriptPubKeys and checks them against the referred previous transactions (vintx) vouts. /// For cryptocondition vins the function creates a basic set of probe cryptconditions with mypk and module global pubkey, both for coins and tokens cases. @@ -473,7 +478,7 @@ int64_t AddNormalinputs(CMutableTransaction &mtx,CPubKey mypk,int64_t total,int3 int64_t AddNormalinputsLocal(CMutableTransaction &mtx,CPubKey mypk,int64_t total,int32_t maxinputs); /// AddNormalinputs2 adds normal (not cc) inputs to the transaction object vin array for the specified total amount using utxos on my pubkey's TX_PUBKEY address (my pubkey is set by -pubkey command line parameter), to fund the transaction. -/// 'My pubkey' is the -pubkey parameter of hushd. +/// 'My pubkey' is the -pubkey parameter of dragonxd. /// @param mtx mutable transaction object /// @param total amount of inputs to add. If total equals to 0 the function does not add inputs but returns amount of all available normal inputs in the wallet /// @param maxinputs maximum number of inputs to add diff --git a/src/cc/CCtx.cpp b/src/cc/CCtx.cpp index 9ea6fcee2..1c43ecb55 100644 --- a/src/cc/CCtx.cpp +++ b/src/cc/CCtx.cpp @@ -72,11 +72,6 @@ int32_t CC_vinselect(int32_t *aboveip,int64_t *abovep,int32_t *belowip,int64_t * abovei = belowi = -1; for (above=below=i=0; i 200 ) { - // if ( (rand() % 100) < 90 ) - // continue; - //} if ( (atx_value= utxos[i].nValue) <= 0 ) continue; if ( atx_value == value ) @@ -103,13 +98,11 @@ int32_t CC_vinselect(int32_t *aboveip,int64_t *abovep,int32_t *belowip,int64_t * belowi = i; } } - //printf("value %.8f gap %.8f abovei.%d %.8f belowi.%d %.8f\n",dstr(value),dstr(gap),abovei,dstr(above),belowi,dstr(below)); } *aboveip = abovei; *abovep = above; *belowip = belowi; *belowp = below; - //printf("above.%d below.%d\n",abovei,belowi); if ( abovei >= 0 && belowi >= 0 ) { if ( above < (below >> 1) ) @@ -127,8 +120,6 @@ int64_t AddNormalinputsLocal(CMutableTransaction &mtx,CPubKey mypk,int64_t total if ( HUSH_NSPV_SUPERLITE ) return(NSPV_AddNormalinputs(mtx,mypk,total,maxinputs,&NSPV_U)); - // if (mypk != pubkey2pk(Mypubkey())) //remote superlite mypk, do not use wallet since it is not locked for non-equal pks (see rpcs with nspv support)! - // return(AddNormalinputs3(mtx, mypk, total, maxinputs)); #ifdef ENABLE_WALLET assert(pwalletMain != NULL); @@ -150,7 +141,6 @@ int64_t AddNormalinputsLocal(CMutableTransaction &mtx,CPubKey mypk,int64_t total vout = out.i; if ( myGetTransaction(txid,tx,hashBlock) != 0 && tx.vout.size() > 0 && vout < tx.vout.size() && tx.vout[vout].scriptPubKey.IsPayToCryptoCondition() == 0 ) { - //fprintf(stderr,"check %.8f to vins array.%d of %d %s/v%d\n",(double)out.tx->vout[out.i].nValue/COIN,n,maxutxos,txid.GetHex().c_str(),(int32_t)vout); if ( mtx.vin.size() > 0 ) { for (i=0; inValue = out.tx->vout[out.i].nValue; up->vout = vout; sum += up->nValue; - //fprintf(stderr,"add %.8f to vins array.%d of %d\n",(double)up->nValue/COIN,n,maxutxos); if ( n >= maxinputs || sum >= total ) break; } @@ -207,14 +196,12 @@ int64_t AddNormalinputsLocal(CMutableTransaction &mtx,CPubKey mypk,int64_t total remains -= up->nValue; utxos[ind] = utxos[--n]; memset(&utxos[n],0,sizeof(utxos[n])); - //fprintf(stderr,"totalinputs %.8f vs total %.8f i.%d vs max.%d\n",(double)totalinputs/COIN,(double)total/COIN,i,maxinputs); if ( totalinputs >= total || (i+1) >= maxinputs ) break; } free(utxos); if ( totalinputs >= total ) { - //fprintf(stderr,"return totalinputs %.8f\n",(double)totalinputs/COIN); return(totalinputs); } #endif @@ -252,7 +239,6 @@ int64_t AddNormalinputsRemote(CMutableTransaction &mtx, CPubKey mypk, int64_t to continue; if ( myGetTransaction(txid,tx,hashBlock) != 0 && tx.vout.size() > 0 && vout < tx.vout.size() && tx.vout[vout].scriptPubKey.IsPayToCryptoCondition() == 0 ) { - //fprintf(stderr,"check %.8f to vins array.%d of %d %s/v%d\n",(double)out.tx->vout[out.i].nValue/COIN,n,maxutxos,txid.GetHex().c_str(),(int32_t)vout); if ( mtx.vin.size() > 0 ) { for (i=0; inValue = it->second.satoshis; up->vout = vout; sum += up->nValue; - //fprintf(stderr,"add %.8f to vins array.%d of %d\n",(double)up->nValue/COIN,n,maxutxos); if ( n >= maxinputs || sum >= total ) break; } @@ -308,14 +293,12 @@ int64_t AddNormalinputsRemote(CMutableTransaction &mtx, CPubKey mypk, int64_t to remains -= up->nValue; utxos[ind] = utxos[--n]; memset(&utxos[n],0,sizeof(utxos[n])); - //fprintf(stderr,"totalinputs %.8f vs total %.8f i.%d vs max.%d\n",(double)totalinputs/COIN,(double)total/COIN,i,maxinputs); if ( totalinputs >= total || (i+1) >= maxinputs ) break; } free(utxos); if ( totalinputs >= total ) { - //fprintf(stderr,"return totalinputs %.8f\n",(double)totalinputs/COIN); return(totalinputs); } return(0); diff --git a/src/cc/CCutils.cpp b/src/cc/CCutils.cpp index 6e3f2f735..6cdf0e66b 100644 --- a/src/cc/CCutils.cpp +++ b/src/cc/CCutils.cpp @@ -20,6 +20,7 @@ #include "CCinclude.h" #include "hush_structs.h" #include "key_io.h" +#include "util.h" #ifdef TESTMODE #define MIN_NON_NOTARIZED_CONFIRMS 2 @@ -46,7 +47,6 @@ int32_t has_opret(const CTransaction &tx, uint8_t evalcode) int i = 0; for ( auto vout : tx.vout ) { - //fprintf(stderr, "[txid.%s] 1.%i 2.%i 3.%i 4.%i\n",tx.GetHash().GetHex().c_str(), vout.scriptPubKey[0], vout.scriptPubKey[1], vout.scriptPubKey[2], vout.scriptPubKey[3]); if ( vout.scriptPubKey.size() > 3 && vout.scriptPubKey[0] == OP_RETURN && vout.scriptPubKey[2] == evalcode ) return i; i++; @@ -88,7 +88,6 @@ bool CheckTxFee(const CTransaction &tx, uint64_t txfee, uint32_t height, uint64_ actualtxfee = valuein-tx.GetValueOut(); if ( actualtxfee > txfee ) { - //fprintf(stderr, "actualtxfee.%li vs txfee.%li\n", actualtxfee, txfee); return false; } return true; @@ -112,7 +111,6 @@ bool Getscriptaddress(char *destaddr,const CScript &scriptPubKey) return(true); } } - //fprintf(stderr,"ExtractDestination failed\n"); return(false); } @@ -202,17 +200,17 @@ bool hush_txnotarizedconfirmed(uint256 txid) { if ( NSPV_myGetTransaction(txid,tx,hashBlock,txheight,currentheight) == 0 ) { - fprintf(stderr,"hush_txnotarizedconfirmed cant find txid %s\n",txid.ToString().c_str()); + LogPrintf("hush_txnotarizedconfirmed cant find txid %s\n",txid.ToString().c_str()); return(0); } else if (txheight<=0) { - fprintf(stderr,"hush_txnotarizedconfirmed no txheight.%d for txid %s\n",txheight,txid.ToString().c_str()); + LogPrintf("hush_txnotarizedconfirmed no txheight.%d for txid %s\n",txheight,txid.ToString().c_str()); return(0); } else if (txheight>currentheight) { - fprintf(stderr,"hush_txnotarizedconfirmed backwards heights for txid %s hts.(%d %d)\n",txid.ToString().c_str(),txheight,currentheight); + LogPrintf("hush_txnotarizedconfirmed backwards heights for txid %s hts.(%d %d)\n",txid.ToString().c_str(),txheight,currentheight); return(0); } confirms=1 + currentheight - txheight; @@ -221,24 +219,24 @@ bool hush_txnotarizedconfirmed(uint256 txid) { if ( myGetTransaction(txid,tx,hashBlock) == 0 ) { - fprintf(stderr,"hush_txnotarizedconfirmed cant find txid %s\n",txid.ToString().c_str()); + LogPrintf("hush_txnotarizedconfirmed cant find txid %s\n",txid.ToString().c_str()); return(0); } else if ( hashBlock == zeroid ) { - fprintf(stderr,"hush_txnotarizedconfirmed no hashBlock for txid %s\n",txid.ToString().c_str()); + LogPrintf("hush_txnotarizedconfirmed no hashBlock for txid %s\n",txid.ToString().c_str()); return(0); } else if ( (pindex= hush_blockindex(hashBlock)) == 0 || (txheight= pindex->GetHeight()) <= 0 ) { - fprintf(stderr,"hush_txnotarizedconfirmed no txheight.%d %p for txid %s\n",txheight,pindex,txid.ToString().c_str()); + LogPrintf("hush_txnotarizedconfirmed no txheight.%d %p for txid %s\n",txheight,pindex,txid.ToString().c_str()); return(0); } else if ( (pindex= chainActive.LastTip()) == 0 || pindex->GetHeight() < txheight ) { - fprintf(stderr,"hush_txnotarizedconfirmed backwards heights for txid %s hts.(%d %d)\n",txid.ToString().c_str(),txheight,(int32_t)pindex->GetHeight()); + LogPrintf("hush_txnotarizedconfirmed backwards heights for txid %s hts.(%d %d)\n",txid.ToString().c_str(),txheight,(int32_t)pindex->GetHeight()); return(0); - } + } confirms=1 + pindex->GetHeight() - txheight; } diff --git a/src/cc/cclib.cpp b/src/cc/cclib.cpp index cf0dfcfe2..95dc25026 100644 --- a/src/cc/cclib.cpp +++ b/src/cc/cclib.cpp @@ -25,7 +25,6 @@ #include "main.h" #include "chain.h" #include "core_io.h" -#define FAUCET2SIZE COIN #define EVAL_FAUCET2 EVAL_FIRSTUSER #ifdef BUILD_CUSTOMCC diff --git a/src/cc/customcc.dylib b/src/cc/customcc.dylib deleted file mode 100644 index dc1730032..000000000 Binary files a/src/cc/customcc.dylib and /dev/null differ diff --git a/src/cc/dapps/Makefile b/src/cc/dapps/Makefile deleted file mode 100644 index 0489d37a9..000000000 --- a/src/cc/dapps/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2016-2024 The Hush Developers -# Distributed under the GPLv3 software license, see the accompanying -# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -# Just type make to compile all dat dapp code, fellow cypherpunkz - -# we no longer build zmigrate by default, nobody uses that fucking code -all: hushdex - -hushdex: - $(CC) hushdex.c -o hushdex -lm - -# Just for historical knowledge, to study how fucking stupid -# ZEC+KMD were to still support sprout, to this day!!!!!!!! -# Hush leads the entire world into the future, sans Sprout turdz -zmigrate: - $(CC) zmigrate.c -o zmigrate -lm - -clean: - rm zmigrate - diff --git a/src/cc/dapps/README.md b/src/cc/dapps/README.md deleted file mode 100644 index 86930e87a..000000000 --- a/src/cc/dapps/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# CryptoCondition dApps - -## Compiling - -To compile all dapps in this directory: - - make - -## zmigrate - Sprout to Sapling Migration dApp - -This tool converts Sprout zaddress funds into Sapling funds in a new Sapling address. -This is not applicable to HUSH3, since we have no Sprout funds, but left for historical -purposes. - -### Usage - - ./zmigrate COIN zsaplingaddr - -The above command may need to be run multiple times to complete the process. - -This CLI implementation will be called by GUI wallets, average users do not -need to worry about using this low-level tool. - -## HushDEX - -HushDEX forked from the Subatomic Decentralized App (dapp) and we focus purely -on privacy coin swaps, and specifically, shielded swaps between Zcash Protocol -coins. These are called z-swaps. - -### Z-swap example - -Alice has 1 ZEC and wants to trade it for 5 HUSH, since she hears HushChat is -pretty awesome and ZEC just goes down in price, always. We represent this in -a diagram like this - - Alice (ZEC) <> Bob (HUSH) - -HushDEX is only concerns with Sapling shielded addresses (zaddrs) which start -with `zs1`. Even though ZEC supports Sprout addresses (which start with `zc`), -they cannot be used on HushDEX. Sprout is unsupported on HushDEX. - -So Alice must make sure her ZEC is in a Sapling zaddr, and then she can use -HushDEX on her computer, to z-swap with Bob, in a decentralized way, with -no centralized service. The system is not completely trustless, users must -trust the developers and miners on the relevant chains to not do nefarious -things. There is no central authority to decide who gets to do what, it's -peer-to-peer like BitTorrent or Tor. - -### Privacy Features of Z-Swaps - - * No KYC - * We will not feed the identity theft industry any more free data - * No IP address limiting - * It is trivial to pay for an IP address from any country in the world - * Alice's address never appears in public data - * Bob's address never appears in public data - * Consequently, Alice and Bob's address cannot be searched for on an explorer - * Since you can't see the address of any transaction, you cannot infer if - the same address appears as sender or receiver in many transactions. - * The amount of the transaction, how much ZEC and how much HUSH, is unknown - * It could be pennies or millions - * The exchange rate of the transaction never appears on the blockchain - * The exchange rate will be leaked to the network p2p layer, but it is never - recorded in blockchain history. If you are not there to record it, it is gone. - * Realistically, it's simple to run a malicious node which records all exchange rates - and so we assume an adversary does this - * Since the exchange rate of ZEC/HUSH is already public data, this is not considered valuable - information leakage. We are leaking the differential of CEX ZEC/HUSH exchange ratio to - this DEX's ratio. - * Adversaries watching all possible public data can infer exchange ratios but no amounts - or addresses, which is considered a massive blow against blockchain analysis. - diff --git a/src/cc/dapps/cJSON.c b/src/cc/dapps/cJSON.c deleted file mode 100644 index 555d04993..000000000 --- a/src/cc/dapps/cJSON.c +++ /dev/null @@ -1,1201 +0,0 @@ -// Copyright (c) 2016-2024 The Hush developers -// Distributed under the GPLv3 software license, see the accompanying -// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -/* - Copyright (c) 2009 Dave Gamble - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/* cJSON */ -/* JSON parser in C. */ -#include - -#include "../includes/cJSON.h" - -#ifndef DBL_EPSILON -#define DBL_EPSILON 2.2204460492503131E-16 -#endif - -static const char *ep; - -long stripquotes(char *str) -{ - long len,offset; - if ( str == 0 ) - return(0); - len = strlen(str); - if ( str[0] == '"' && str[len-1] == '"' ) - { - str[len-1] = 0; - offset = 1; - } - else offset = 0; - return(offset); -} - -const char *cJSON_GetErrorPtr(void) {return ep;} - -static int32_t cJSON_strcasecmp(const char *s1,const char *s2) -{ - if (!s1) return (s1==s2)?0:1;if (!s2) return 1; - for(; tolower((int32_t)(*s1)) == tolower((int32_t)(*s2)); ++s1, ++s2) if(*s1 == 0) return 0; - return tolower((int32_t)(*(const unsigned char *)s1)) - tolower((int32_t)(*(const unsigned char *)s2)); -} - -void *LP_alloc(uint64_t len); -void LP_free(void *ptr); -static void *(*cJSON_malloc)(size_t sz) = (void *)malloc;//LP_alloc; -static void (*cJSON_free)(void *ptr) = free;//LP_free; - -static void *cJSON_mallocstr(int32_t len) -{ - return(cJSON_malloc(len)); -} - -static char **cJSON_mallocptrs(int32_t num,char **space,int32_t max) -{ - if ( num < max ) - return(space); - else return(cJSON_malloc(num * sizeof(char *))); -} - -static void *cJSON_mallocnode() -{ - return(cJSON_malloc(sizeof(cJSON))); -} - -static void cJSON_freeptrs(char **ptrs,int32_t num,char **space) -{ - if ( ptrs != space ) - cJSON_free(ptrs); -} - -static void cJSON_freestr(char *str) -{ - cJSON_free(str); -} - -static void cJSON_freenode(cJSON *item) -{ - cJSON_free(item); -} - -static char* cJSON_strdup(const char* str) -{ - size_t len; - char* copy; - - len = strlen(str) + 1; - if (!(copy = (char*)cJSON_mallocstr((int32_t)len+1))) return 0; - memcpy(copy,str,len); - return copy; -} - -void cJSON_InitHooks(cJSON_Hooks* hooks) -{ - if (!hooks) { /* Reset hooks */ - cJSON_malloc = malloc; - cJSON_free = free; - return; - } - - cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; - cJSON_free = (hooks->free_fn)?hooks->free_fn:free; -} - -/* Internal constructor. */ -static cJSON *cJSON_New_Item(void) -{ - cJSON* node = (cJSON*)cJSON_mallocnode(); - if (node) memset(node,0,sizeof(cJSON)); - return node; -} - -/* Delete a cJSON structure. */ -void cJSON_Delete(cJSON *c) -{ - cJSON *next; - while (c) - { - next=c->next; - if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); - if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_freestr(c->valuestring); - if (c->string) cJSON_freestr(c->string); - cJSON_freenode(c); - c=next; - } -} - -/* Parse the input text to generate a number, and populate the result into item. */ -static const char *parse_number(cJSON *item,const char *num) -{ - double n=0,sign=1,scale=0;int32_t subscale=0,signsubscale=1; - - if (*num=='-') sign=-1,num++; /* Has sign? */ - if (*num=='0') num++; /* is zero */ - if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ - if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ - if (*num=='e' || *num=='E') /* Exponent? */ - { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ - while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ - } - - n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ - - item->valuedouble=n; - item->valueint=(int64_t)n; - item->type=cJSON_Number; - return num; -} - -/* Render the number nicely from the given item into a string. */ -static char *print_number(cJSON *item) -{ - char *str; - double d = item->valuedouble; - if ( fabs(((double)item->valueint) - d) <= DBL_EPSILON && d >= (1. - DBL_EPSILON) && d < (1LL << 62) )//d <= INT_MAX && d >= INT_MIN ) - { - str = (char *)cJSON_mallocstr(24); /* 2^64+1 can be represented in 21 chars + sign. */ - if ( str != 0 ) - sprintf(str,"%lld",(long long)item->valueint); - } - else - { - str = (char *)cJSON_mallocstr(66); /* This is a nice tradeoff. */ - if ( str != 0 ) - { - if ( fabs(floor(d) - d) <= DBL_EPSILON && fabs(d) < 1.0e60 ) - sprintf(str,"%.0f",d); - //else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); - else - sprintf(str,"%.8f",d); - } - } - return str; -} - -static unsigned parse_hex4(const char *str) -{ - unsigned h=0; - if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; - h=h<<4;str++; - if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; - h=h<<4;str++; - if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; - h=h<<4;str++; - if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0; - return h; -} - -/* Parse the input text into an unescaped cstring, and populate item. */ -static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; -static const char *parse_string(cJSON *item,const char *str) -{ - const char *ptr=str+1;char *ptr2;char *out;int32_t len=0;unsigned uc,uc2; - if (*str!='\"') {ep=str;return 0;} /* not a string! */ - - while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; // Skip escaped quotes - - out=(char*)cJSON_mallocstr(len+2); /* This is how long we need for the string, roughly. */ - if (!out) return 0; - - ptr=str+1;ptr2=out; - while (*ptr!='\"' && *ptr) - { - if (*ptr!='\\') - { - if ( *ptr == '%' && is_hexstr((char *)&ptr[1],2) && isprint(_decode_hex((char *)&ptr[1])) != 0 ) - *ptr2++ = _decode_hex((char *)&ptr[1]), ptr += 3; - else *ptr2++ = *ptr++; - } - else - { - ptr++; - switch (*ptr) - { - case 'b': *ptr2++='\b'; break; - case 'f': *ptr2++='\f'; break; - case 'n': *ptr2++='\n'; break; - case 'r': *ptr2++='\r'; break; - case 't': *ptr2++='\t'; break; - case 'u': // transcode utf16 to utf8 - uc=parse_hex4(ptr+1);ptr+=4; // get the unicode char - - if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; // check for invalid - - if (uc>=0xD800 && uc<=0xDBFF) // UTF16 surrogate pairs - { - if (ptr[1]!='\\' || ptr[2]!='u') break; // missing second-half of surrogate. - uc2=parse_hex4(ptr+3);ptr+=6; - if (uc2<0xDC00 || uc2>0xDFFF) break; // invalid second-half of surrogate - uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); - } - - len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; - - switch (len) { - case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; - case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; - case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; - case 1: *--ptr2 =(uc | firstByteMark[len]); - } - ptr2+=len; - break; - default: *ptr2++=*ptr; break; - } - ptr++; - } - } - *ptr2=0; - if (*ptr=='\"') ptr++; - item->valuestring=out; - item->type=cJSON_String; - return ptr; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static char *print_string_ptr(const char *str) -{ - const char *ptr;char *ptr2,*out;int32_t len=0;unsigned char token; - - if (!str) return cJSON_strdup(""); - ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} - - out=(char*)cJSON_mallocstr(len+3+1); - if (!out) return 0; - - ptr2=out;ptr=str; - *ptr2++='\"'; - while (*ptr) - { - if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; - else - { - *ptr2++='\\'; - switch (token=*ptr++) - { - case '\\': *ptr2++='\\'; break; - case '\"': *ptr2++='\"'; break; - case '\b': *ptr2++='b'; break; - case '\f': *ptr2++='f'; break; - case '\n': *ptr2++='n'; break; - case '\r': *ptr2++='r'; break; - case '\t': *ptr2++='t'; break; - default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ - } - } - } - *ptr2++='\"';*ptr2++=0; - return out; -} -/* Invote print_string_ptr (which is useful) on an item. */ -static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} - -/* Predeclare these prototypes. */ -static const char *parse_value(cJSON *item,const char *value); -static char *print_value(cJSON *item,int32_t depth,int32_t fmt); -static const char *parse_array(cJSON *item,const char *value); -static char *print_array(cJSON *item,int32_t depth,int32_t fmt); -static const char *parse_object(cJSON *item,const char *value); -static char *print_object(cJSON *item,int32_t depth,int32_t fmt); - -/* Utility to jump whitespace and cr/lf */ -static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} - -/* Parse an object - create a new root, and populate. */ -cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int32_t require_null_terminated) -{ - const char *end=0; - cJSON *c=cJSON_New_Item(); - ep=0; - if (!c) return 0; /* memory fail */ - - end=parse_value(c,skip(value)); - if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */ - - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} - if (return_parse_end) *return_parse_end=end; - return c; -} -/* Default options for cJSON_Parse */ -cJSON *cJSON_Parse(const char *value) -{ - return(cJSON_ParseWithOpts(value,0,0)); -} - -/* Render a cJSON item/entity/structure to text. */ -char *cJSON_Print(cJSON *item) -{ - return(print_value(item,0,1)); -} -char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} - -/* Parser core - when encountering text, process appropriately. */ -static const char *parse_value(cJSON *item,const char *value) -{ - if (!value) return 0; /* Fail on null. */ - if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } - if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } - if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } - if (*value=='\"') { return parse_string(item,value); } - if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } - if (*value=='[') { return parse_array(item,value); } - if (*value=='{') { return parse_object(item,value); } - - ep=value;return 0; /* failure. */ -} - -/* Render a value to text. */ -static char *print_value(cJSON *item,int32_t depth,int32_t fmt) -{ - char *out=0; - if (!item) return 0; - switch ((item->type)&255) - { - case cJSON_NULL: out=cJSON_strdup("null"); break; - case cJSON_False: out=cJSON_strdup("false");break; - case cJSON_True: out=cJSON_strdup("true"); break; - case cJSON_Number: out=print_number(item);break; - case cJSON_String: out=print_string(item);break; - case cJSON_Array: out=print_array(item,depth,fmt);break; - case cJSON_Object: out=print_object(item,depth,fmt);break; - } - return out; -} - -/* Build an array from input text. */ -static const char *parse_array(cJSON *item,const char *value) -{ - cJSON *child; - if (*value!='[') {ep=value;return 0;} /* not an array! */ - - item->type=cJSON_Array; - value=skip(value+1); - if (*value==']') return value+1; /* empty array. */ - - item->child=child=cJSON_New_Item(); - if (!item->child) return 0; /* memory fail */ - value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ - if (!value) return 0; - - while (*value==',') - { - cJSON *new_item; - if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ - child->next=new_item;new_item->prev=child;child=new_item; - value=skip(parse_value(child,skip(value+1))); - if (!value) return 0; /* memory fail */ - } - - if (*value==']') return value+1; /* end of array */ - ep=value;return 0; /* malformed. */ -} - -/* Render an array to text */ -static char *print_array(cJSON *item,int32_t depth,int32_t fmt) -{ - char **entries,*space_entries[512]; - char *out=0,*ptr,*ret;int32_t len=5; - cJSON *child=item->child; - int32_t numentries=0,i=0,fail=0; - - /* How many entries in the array? */ - while (child) numentries++,child=child->next; - /* Explicitly handle numentries==0 */ - if (!numentries) - { - out=(char*)cJSON_mallocstr(3+1); - if (out) strcpy(out,"[]"); - return out; - } - /* Allocate an array to hold the values for each */ - entries=cJSON_mallocptrs(1+numentries,space_entries,sizeof(space_entries)/sizeof(*space_entries)); - if (!entries) return 0; - memset(entries,0,numentries*sizeof(char*)); - /* Retrieve all the results: */ - child=item->child; - while (child && !fail) - { - ret=print_value(child,depth+1,fmt); - entries[i++]=ret; - if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; - child=child->next; - } - - /* If we didn't fail, try to malloc the output string */ - if (!fail) out=(char*)cJSON_mallocstr(len+1); - /* If that fails, we fail. */ - if (!out) fail=1; - - /* Handle failure. */ - if (fail) - { - for (i=0;itype=cJSON_Object; - value=skip(value+1); - if (*value=='}') return value+1; /* empty array. */ - - item->child=child=cJSON_New_Item(); - if (!item->child) return 0; - value=skip(parse_string(child,skip(value))); - if (!value) return 0; - child->string=child->valuestring;child->valuestring=0; - if (*value!=':') {ep=value;return 0;} /* fail! */ - value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ - if (!value) return 0; - - while (*value==',') - { - cJSON *new_item; - if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ - child->next=new_item;new_item->prev=child;child=new_item; - value=skip(parse_string(child,skip(value+1))); - if (!value) return 0; - child->string=child->valuestring;child->valuestring=0; - if (*value!=':') {ep=value;return 0;} /* fail! */ - value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ - if (!value) return 0; - } - - if (*value=='}') return value+1; /* end of array */ - ep=value;return 0; /* malformed. */ -} - -/* Render an object to text. */ -static char *print_object(cJSON *item,int32_t depth,int32_t fmt) -{ - char **entries=0,**names=0,*space_entries[512],*space_names[512]; - char *out=0,*ptr,*ret,*str;int32_t len=7,i=0,j; - cJSON *child=item->child,*firstchild; - int32_t numentries=0,fail=0; - // Count the number of entries - firstchild = child; - while ( child ) - { - numentries++; - child = child->next; - if ( child == firstchild ) - { - printf("cJSON infinite loop detected\n"); - break; - } - } - /* Explicitly handle empty object case */ - if (!numentries) - { - out=(char*)cJSON_mallocstr(fmt?depth+4+1:3+1); - if (!out) return 0; - ptr=out;*ptr++='{'; - if (fmt) {*ptr++='\n';for (i=0;ichild;depth++;if (fmt) len+=depth; - while ( child ) - { - names[i]=str=print_string_ptr(child->string); - entries[i++]=ret=print_value(child,depth,fmt); - if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; - child=child->next; - if ( child == firstchild ) - break; - } - - /* Try to allocate the output string */ - if (!fail) out=(char*)cJSON_mallocstr(len+1); - if (!out) fail=1; - - /* Handle failure */ - if (fail) - { - for (i=0;ichild;int32_t i=0;while(c)i++,c=c->next;return i;} -cJSON *cJSON_GetArrayItem(cJSON *array,int32_t item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} -cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) { if ( object == 0 ) return(0); cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} - -/* Utility for array list handling. */ -static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} -/* Utility for handling references. */ -static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} - -/* Add item to array/object. */ -void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} -void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} -void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} -void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} - -cJSON *cJSON_DetachItemFromArray(cJSON *array,int32_t which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; - if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c==array->child) array->child=c->next;c->prev=c->next=0;return c;} -void cJSON_DeleteItemFromArray(cJSON *array,int32_t which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} -cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int32_t i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} -void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} - -/* Replace array/object items with new ones. */ -void cJSON_ReplaceItemInArray(cJSON *array,int32_t which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; - newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; - if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} -void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int32_t i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} - -/* Create basic types: */ -cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} -cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} -cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} -cJSON *cJSON_CreateBool(int32_t b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} -cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int64_t)num;}return item;} -cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} - -/* Create Arrays: */ -cJSON *cJSON_CreateIntArray(int64_t *numbers,int32_t count) {int32_t i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} -cJSON *cJSON_CreateFloatArray(float *numbers,int32_t count) {int32_t i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} -cJSON *cJSON_CreateDoubleArray(double *numbers,int32_t count) {int32_t i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} -cJSON *cJSON_CreateStringArray(char **strings,int32_t count) {int32_t i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} - -/* Duplication */ -cJSON *cJSON_Duplicate(cJSON *item,int32_t recurse) -{ - cJSON *newitem,*cptr,*nptr=0,*newchild; - /* Bail on bad ptr */ - if (!item) return 0; - /* Create new item */ - newitem=cJSON_New_Item(); - if (!newitem) return 0; - /* Copy over all vars */ - newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble; - if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}} - if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}} - /* If non-recursive, then we're done! */ - if (!recurse) return newitem; - /* Walk the ->next chain for the child. */ - cptr=item->child; - while (cptr) - { - newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) {cJSON_Delete(newitem);return 0;} - if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */ - cptr=cptr->next; - } - return newitem; -} - -void cJSON_Minify(char *json) -{ - char *into=json; - while (*json) - { - if (*json==' ') json++; - else if (*json=='\t') json++; // Whitespace characters. - else if (*json=='\r') json++; - else if (*json=='\n') json++; - else if (*json=='/' && json[1]=='/') while (*json && *json!='\n') json++; // double-slash comments, to end of line. - else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;} // multiline comments. - else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} // string literals, which are \" sensitive. - else *into++=*json++; // All other characters. - } - *into=0; // and null-terminate. -} - -// the following written by jl777 -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - -void copy_cJSON(struct destbuf *dest,cJSON *obj) -{ - char *str; - int i; - long offset; - dest->buf[0] = 0; - if ( obj != 0 ) - { - str = cJSON_Print(obj); - if ( str != 0 ) - { - offset = stripquotes(str); - //strcpy(dest,str+offset); - for (i=0; ibuf[i]= str[offset+i]) == 0 ) - break; - dest->buf[i] = 0; - free(str); - } - } -} - -void copy_cJSON2(char *dest,int32_t maxlen,cJSON *obj) -{ - struct destbuf tmp; - maxlen--; - dest[0] = 0; - if ( maxlen > sizeof(tmp.buf) ) - maxlen = sizeof(tmp.buf); - copy_cJSON(&tmp,obj); - if ( strlen(tmp.buf) < maxlen ) - strcpy(dest,tmp.buf); - else dest[0] = 0; -} - -int64_t _get_cJSON_int(cJSON *json) -{ - struct destbuf tmp; - if ( json != 0 ) - { - copy_cJSON(&tmp,json); - if ( tmp.buf[0] != 0 ) - return(calc_nxt64bits(tmp.buf)); - } - return(0); -} - -int64_t get_cJSON_int(cJSON *json,char *field) -{ - cJSON *numjson; - if ( json != 0 ) - { - numjson = cJSON_GetObjectItem(json,field); - if ( numjson != 0 ) - return(_get_cJSON_int(numjson)); - } - return(0); -} - -int64_t _conv_cJSON_float(cJSON *json) -{ - int64_t conv_floatstr(char *); - struct destbuf tmp; - if ( json != 0 ) - { - copy_cJSON(&tmp,json); - return(conv_floatstr(tmp.buf)); - } - return(0); -} - -int64_t conv_cJSON_float(cJSON *json,char *field) -{ - if ( json != 0 ) - return(_conv_cJSON_float(cJSON_GetObjectItem(json,field))); - return(0); -} - -int32_t extract_cJSON_str(char *dest,int32_t max,cJSON *json,char *field) -{ - int32_t safecopy(char *dest,char *src,long len); - char *str; - cJSON *obj; - int32_t len; - long offset; - dest[0] = 0; - obj = cJSON_GetObjectItem(json,field); - if ( obj != 0 ) - { - str = cJSON_Print(obj); - offset = stripquotes(str); - len = safecopy(dest,str+offset,max); - free(str); - return(len); - } - return(0); -} - -cJSON *gen_list_json(char **list) -{ - cJSON *array,*item; - array = cJSON_CreateArray(); - while ( list != 0 && *list != 0 && *list[0] != 0 ) - { - item = cJSON_CreateString(*list++); - cJSON_AddItemToArray(array,item); - } - return(array); -} - -uint64_t get_API_nxt64bits(cJSON *obj) -{ - uint64_t nxt64bits = 0; - struct destbuf tmp; - if ( obj != 0 ) - { - if ( is_cJSON_Number(obj) != 0 ) - return((uint64_t)obj->valuedouble); - copy_cJSON(&tmp,obj); - nxt64bits = calc_nxt64bits(tmp.buf); - } - return(nxt64bits); -} -uint64_t j64bits(cJSON *json,char *field) { if ( field == 0 ) return(get_API_nxt64bits(json)); return(get_API_nxt64bits(cJSON_GetObjectItem(json,field))); } -uint64_t j64bitsi(cJSON *json,int32_t i) { return(get_API_nxt64bits(cJSON_GetArrayItem(json,i))); } - -uint64_t get_satoshi_obj(cJSON *json,char *field) -{ - int32_t i,n; - uint64_t prev,satoshis,mult = 1; - struct destbuf numstr,checkstr; - cJSON *numjson; - numjson = cJSON_GetObjectItem(json,field); - copy_cJSON(&numstr,numjson); - satoshis = prev = 0; mult = 1; n = (int32_t)strlen(numstr.buf); - for (i=n-1; i>=0; i--,mult*=10) - { - satoshis += (mult * (numstr.buf[i] - '0')); - if ( satoshis < prev ) - printf("get_satoshi_obj numstr.(%s) i.%d prev.%llu vs satoshis.%llu\n",numstr.buf,i,(unsigned long long)prev,(unsigned long long)satoshis); - prev = satoshis; - } - sprintf(checkstr.buf,"%llu",(long long)satoshis); - if ( strcmp(checkstr.buf,numstr.buf) != 0 ) - { - printf("SATOSHI GREMLIN?? numstr.(%s) -> %.8f -> (%s)\n",numstr.buf,dstr(satoshis),checkstr.buf); - } - return(satoshis); -} - -void add_satoshis_json(cJSON *json,char *field,uint64_t satoshis) -{ - cJSON *obj; - char numstr[64]; - sprintf(numstr,"%lld",(long long)satoshis); - obj = cJSON_CreateString(numstr); - cJSON_AddItemToObject(json,field,obj); - if ( satoshis != get_satoshi_obj(json,field) ) - printf("error adding satoshi obj %ld -> %ld\n",(unsigned long)satoshis,(unsigned long)get_satoshi_obj(json,field)); -} - -char *cJSON_str(cJSON *json) -{ - if ( json != 0 && is_cJSON_String(json) != 0 ) - return(json->valuestring); - return(0); -} - -void jadd(cJSON *json,char *field,cJSON *item) { if ( json != 0 )cJSON_AddItemToObject(json,field,item); } -void jaddstr(cJSON *json,char *field,char *str) { if ( json != 0 && str != 0 ) cJSON_AddItemToObject(json,field,cJSON_CreateString(str)); } -void jaddnum(cJSON *json,char *field,double num) { if ( json != 0 )cJSON_AddItemToObject(json,field,cJSON_CreateNumber(num)); } -void jadd64bits(cJSON *json,char *field,uint64_t nxt64bits) { char numstr[64]; sprintf(numstr,"%llu",(long long)nxt64bits), jaddstr(json,field,numstr); } -void jaddi(cJSON *json,cJSON *item) { if ( json != 0 ) cJSON_AddItemToArray(json,item); } -void jaddistr(cJSON *json,char *str) { if ( json != 0 ) cJSON_AddItemToArray(json,cJSON_CreateString(str)); } -void jaddinum(cJSON *json,double num) { if ( json != 0 ) cJSON_AddItemToArray(json,cJSON_CreateNumber(num)); } -void jaddi64bits(cJSON *json,uint64_t nxt64bits) { char numstr[64]; sprintf(numstr,"%llu",(long long)nxt64bits), jaddistr(json,numstr); } -char *jstr(cJSON *json,char *field) { if ( json == 0 ) return(0); if ( field == 0 ) return(cJSON_str(json)); return(cJSON_str(cJSON_GetObjectItem(json,field))); } - -char *jstri(cJSON *json,int32_t i) { return(cJSON_str(cJSON_GetArrayItem(json,i))); } -char *jprint(cJSON *json,int32_t freeflag) -{ - char *str; - /*static portable_mutex_t mutex; static int32_t initflag; - if ( initflag == 0 ) - { - portable_mutex_init(&mutex); - initflag = 1; - }*/ - if ( json == 0 ) - return(clonestr((char *)"{}")); - //portable_mutex_lock(&mutex); - //usleep(5000); - str = cJSON_Print(json), _stripwhite(str,' '); - if ( freeflag != 0 ) - free_json(json); - //portable_mutex_unlock(&mutex); - return(str); -} - -bits256 get_API_bits256(cJSON *obj) -{ - bits256 hash; char *str; - memset(hash.bytes,0,sizeof(hash)); - if ( obj != 0 ) - { - if ( is_cJSON_String(obj) != 0 && (str= obj->valuestring) != 0 && strlen(str) == 64 ) - decode_hex(hash.bytes,sizeof(hash),str); - } - return(hash); -} -bits256 jbits256(cJSON *json,char *field) { if ( field == 0 ) return(get_API_bits256(json)); return(get_API_bits256(json != 0 ? cJSON_GetObjectItem(json,field) : 0)); } -bits256 jbits256i(cJSON *json,int32_t i) { return(get_API_bits256(cJSON_GetArrayItem(json,i))); } -void jaddbits256(cJSON *json,char *field,bits256 hash) { char str[65]; bits256_str(str,hash), jaddstr(json,field,str); } -void jaddibits256(cJSON *json,bits256 hash) { char str[65]; bits256_str(str,hash), jaddistr(json,str); } - -char *get_cJSON_fieldname(cJSON *obj) -{ - if ( obj != 0 ) - { - if ( obj->string != 0 ) - return(obj->string); - if ( obj->child != 0 && obj->child->string != 0 ) - return(obj->child->string); - } - return((char *)""); -} - -int32_t jnum(cJSON *obj,char *field) -{ - char *str; int32_t polarity = 1; - if ( field != 0 ) - obj = jobj(obj,field); - if ( obj != 0 ) - { - if ( is_cJSON_Number(obj) != 0 ) - return(obj->valuedouble); - else if ( is_cJSON_String(obj) != 0 && (str= jstr(obj,0)) != 0 ) - { - if ( str[0] == '-' ) - polarity = -1, str++; - return(polarity * (int32_t)calc_nxt64bits(str)); - } - } - return(0); -} - -void ensure_jsonitem(cJSON *json,char *field,char *value) -{ - cJSON *obj; - if ( json != 0 ) - { - obj = cJSON_GetObjectItem(json,field); - if ( obj == 0 ) - cJSON_AddItemToObject(json,field,cJSON_CreateString(value)); - else cJSON_ReplaceItemInObject(json,field,cJSON_CreateString(value)); - } -} - -int32_t in_jsonarray(cJSON *array,char *value) -{ - int32_t i,n; - struct destbuf remote; - if ( array != 0 && is_cJSON_Array(array) != 0 ) - { - n = cJSON_GetArraySize(array); - for (i=0; i= range ) - x = (range - 1); - return((int32_t)x); -} - -int32_t get_API_int(cJSON *obj,int32_t val) -{ - struct destbuf buf; - if ( obj != 0 ) - { - if ( is_cJSON_Number(obj) != 0 ) - return((int32_t)obj->valuedouble); - copy_cJSON(&buf,obj); - val = myatoi(buf.buf,0); - if ( val < 0 ) - val = 0; - } - return(val); -} -int32_t jint(cJSON *json,char *field) { if ( json == 0 ) return(0); if ( field == 0 ) return(get_API_int(json,0)); return(get_API_int(cJSON_GetObjectItem(json,field),0)); } -int32_t jinti(cJSON *json,int32_t i) { if ( json == 0 ) return(0); return(get_API_int(cJSON_GetArrayItem(json,i),0)); } - -uint32_t get_API_uint(cJSON *obj,uint32_t val) -{ - struct destbuf buf; - if ( obj != 0 ) - { - if ( is_cJSON_Number(obj) != 0 ) - return((uint32_t)obj->valuedouble); - copy_cJSON(&buf,obj); - val = myatoi(buf.buf,0); - } - return(val); -} -uint32_t juint(cJSON *json,char *field) { if ( json == 0 ) return(0); if ( field == 0 ) return(get_API_uint(json,0)); return(get_API_uint(cJSON_GetObjectItem(json,field),0)); } -uint32_t juinti(cJSON *json,int32_t i) { if ( json == 0 ) return(0); return(get_API_uint(cJSON_GetArrayItem(json,i),0)); } - -double get_API_float(cJSON *obj) -{ - double val = 0.; - struct destbuf buf; - if ( obj != 0 ) - { - if ( is_cJSON_Number(obj) != 0 ) - return(obj->valuedouble); - copy_cJSON(&buf,obj); - val = atof(buf.buf); - } - return(val); -} - -double jdouble(cJSON *json,char *field) -{ - if ( json != 0 ) - { - if ( field == 0 ) - return(get_API_float(json)); - else return(get_API_float(cJSON_GetObjectItem(json,field))); - } else return(0.); -} - -double jdoublei(cJSON *json,int32_t i) -{ - if ( json != 0 ) - return(get_API_float(cJSON_GetArrayItem(json,i))); - else return(0.); -} - -cJSON *jobj(cJSON *json,char *field) { if ( json != 0 ) return(cJSON_GetObjectItem(json,field)); return(0); } - -void jdelete(cJSON *json,char *field) -{ - if ( jobj(json,field) != 0 ) - cJSON_DeleteItemFromObject(json,field); -} - -cJSON *jduplicate(cJSON *json) { return(cJSON_Duplicate(json,1)); } - -cJSON *jitem(cJSON *array,int32_t i) { if ( array != 0 && is_cJSON_Array(array) != 0 && cJSON_GetArraySize(array) > i ) return(cJSON_GetArrayItem(array,i)); return(0); } -cJSON *jarray(int32_t *nump,cJSON *json,char *field) -{ - cJSON *array; - if ( json != 0 ) - { - if ( field == 0 ) - array = json; - else array = cJSON_GetObjectItem(json,field); - if ( array != 0 && is_cJSON_Array(array) != 0 && (*nump= cJSON_GetArraySize(array)) > 0 ) - return(array); - } - *nump = 0; - return(0); -} - -int32_t expand_nxt64bits(char *NXTaddr,uint64_t nxt64bits) -{ - int32_t i,n; - uint64_t modval; - char rev[64]; - for (i=0; nxt64bits!=0; i++) - { - modval = nxt64bits % 10; - rev[i] = (char)(modval + '0'); - nxt64bits /= 10; - } - n = i; - for (i=0; i= 22 ) - { - printf("calc_nxt64bits: illegal NXTaddr.(%s) too long\n",NXTaddr); - return(0); - } - else if ( strcmp(NXTaddr,"0") == 0 || strcmp(NXTaddr,"false") == 0 ) - { - // printf("zero address?\n"); getchar(); - return(0); - } - if ( NXTaddr[0] == '-' ) - polarity = -1, NXTaddr++, n--; - mult = 1; - lastval = 0; - for (i=n-1; i>=0; i--,mult*=10) - { - c = NXTaddr[i]; - if ( c < '0' || c > '9' ) - { - //printf("calc_nxt64bits: illegal char.(%c %d) in (%s).%d\n",c,c,NXTaddr,(int32_t)i); -#ifdef __APPLE__ - //while ( 1 ) - { - //sleep(60); - //printf("calc_nxt64bits: illegal char.(%c %d) in (%s).%d\n",c,c,NXTaddr,(int32_t)i); - } -#endif - return(0); - } - nxt64bits += mult * (c - '0'); - if ( nxt64bits < lastval ) - printf("calc_nxt64bits: warning: 64bit overflow %llx < %llx\n",(long long)nxt64bits,(long long)lastval); - lastval = nxt64bits; - } - while ( *NXTaddr == '0' && *NXTaddr != 0 ) - NXTaddr++; - if ( cmp_nxt64bits(NXTaddr,nxt64bits) != 0 ) - printf("error calculating nxt64bits: %s -> %llx -> %s\n",NXTaddr,(long long)nxt64bits,nxt64str(nxt64bits)); - if ( polarity < 0 ) - return(-(int64_t)nxt64bits); - return(nxt64bits); -} - -cJSON *addrs_jsonarray(uint64_t *addrs,int32_t num) -{ - int32_t j; cJSON *array; - array = cJSON_CreateArray(); - for (j=0; jtype = cJSON_Array; -//#ifdef CJSON_GARBAGECOLLECTION -// cJSON_register(item); -//#endif - return(item); -} - -cJSON *cJSON_CreateObject(void) -{ - cJSON *item = cJSON_New_Item(); - if ( item ) - item->type = cJSON_Object; -//#ifdef CJSON_GARBAGECOLLECTION -// cJSON_register(item); -//#endif - return item; -} - -void free_json(cJSON *item) -{ -//#ifdef CJSON_GARBAGECOLLECTION -// cJSON_unregister(item); -//#endif - if ( item != 0 ) - cJSON_Delete(item); -} diff --git a/src/cc/dapps/dappinc.h b/src/cc/dapps/dappinc.h deleted file mode 100644 index e9afd7fdc..000000000 --- a/src/cc/dapps/dappinc.h +++ /dev/null @@ -1,1597 +0,0 @@ -// Copyright (c) 2016-2024 The Hush developers -// Distributed under the GPLv3 software license, see the accompanying -// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -/****************************************************************************** - * Copyright © 2014-2020 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ -#include -#include -#include -#include -#include "cJSON.c" - -bits256 zeroid; - -int32_t unstringbits(char *buf,uint64_t bits) -{ - int32_t i; - for (i=0; i<8; i++,bits>>=8) - if ( (buf[i]= (char)(bits & 0xff)) == 0 ) - break; - buf[i] = 0; - return(i); -} - -uint64_t stringbits(char *str) -{ - uint64_t bits = 0; - if ( str == 0 ) - return(0); - int32_t i,n = (int32_t)strlen(str); - if ( n > 8 ) - n = 8; - for (i=n-1; i>=0; i--) - bits = (bits << 8) | (str[i] & 0xff); - //printf("(%s) -> %llx %llu\n",str,(long long)bits,(long long)bits); - return(bits); -} - -char hexbyte(int32_t c) -{ - c &= 0xf; - if ( c < 10 ) - return('0'+c); - else if ( c < 16 ) - return('a'+c-10); - else return(0); -} - -int32_t _unhex(char c) -{ - if ( c >= '0' && c <= '9' ) - return(c - '0'); - else if ( c >= 'a' && c <= 'f' ) - return(c - 'a' + 10); - else if ( c >= 'A' && c <= 'F' ) - return(c - 'A' + 10); - return(-1); -} - -int32_t is_hexstr(char *str,int32_t n) -{ - int32_t i; - if ( str == 0 || str[0] == 0 ) - return(0); - for (i=0; str[i]!=0; i++) - { - if ( n > 0 && i >= n ) - break; - if ( _unhex(str[i]) < 0 ) - break; - } - if ( n == 0 ) - return(i); - return(i == n); -} - -int32_t unhex(char c) -{ - int32_t hex; - if ( (hex= _unhex(c)) < 0 ) - { - //printf("unhex: illegal hexchar.(%c)\n",c); - } - return(hex); -} - -unsigned char _decode_hex(char *hex) { return((unhex(hex[0])<<4) | unhex(hex[1])); } - -int32_t decode_hex(unsigned char *bytes,int32_t n,char *hex) -{ - int32_t adjust,i = 0; - //printf("decode.(%s)\n",hex); - if ( is_hexstr(hex,n) <= 0 ) - { - memset(bytes,0,n); - return(n); - } - if ( hex[n-1] == '\n' || hex[n-1] == '\r' ) - hex[--n] = 0; - if ( hex[n-1] == '\n' || hex[n-1] == '\r' ) - hex[--n] = 0; - if ( n == 0 || (hex[n*2+1] == 0 && hex[n*2] != 0) ) - { - if ( n > 0 ) - { - bytes[0] = unhex(hex[0]); - printf("decode_hex n.%d hex[0] (%c) -> %d hex.(%s) [n*2+1: %d] [n*2: %d %c] len.%ld\n",n,hex[0],bytes[0],hex,hex[n*2+1],hex[n*2],hex[n*2],(long)strlen(hex)); - } - bytes++; - hex++; - adjust = 1; - } else adjust = 0; - if ( n > 0 ) - { - for (i=0; i>4) & 0xf); - hexbytes[i*2 + 1] = hexbyte(message[i] & 0xf); - //printf("i.%d (%02x) [%c%c]\n",i,message[i],hexbytes[i*2],hexbytes[i*2+1]); - } - hexbytes[len*2] = 0; - //printf("len.%ld\n",len*2+1); - return((int32_t)len*2+1); -} - -long _stripwhite(char *buf,int accept) -{ - int32_t i,j,c; - if ( buf == 0 || buf[0] == 0 ) - return(0); - for (i=j=0; buf[i]!=0; i++) - { - buf[j] = c = buf[i]; - if ( c == accept || (c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\b') ) - j++; - } - buf[j] = 0; - return(j); -} - -char *clonestr(char *str) -{ - char *clone; - if ( str == 0 || str[0]==0) - { - printf("warning cloning nullstr.%p\n",str); - //#ifdef __APPLE__ - // while ( 1 ) sleep(1); - //#endif - str = (char *)""; - } - clone = (char *)malloc(strlen(str)+16); - strcpy(clone,str); - return(clone); -} - -int32_t safecopy(char *dest,char *src,long len) -{ - int32_t i = -1; - if ( src != 0 && dest != 0 && src != dest ) - { - if ( dest != 0 ) - memset(dest,0,len); - for (i=0; i0; i--) - str[i] = str[i-1]; - str[0] = '/'; - str[n+1] = 0; - }*/ -#endif - return(str); -#endif -} - -void *loadfile(char *fname,uint8_t **bufp,long *lenp,long *allocsizep) -{ - FILE *fp; - long filesize,buflen = *allocsizep; - uint8_t *buf = *bufp; - *lenp = 0; - if ( (fp= fopen(portable_path(fname),"rb")) != 0 ) - { - fseek(fp,0,SEEK_END); - filesize = ftell(fp); - if ( filesize == 0 ) - { - fclose(fp); - *lenp = 0; - //printf("loadfile null size.(%s)\n",fname); - return(0); - } - if ( filesize > buflen ) - { - *allocsizep = filesize; - *bufp = buf = (uint8_t *)realloc(buf,(long)*allocsizep+64); - } - rewind(fp); - if ( buf == 0 ) - printf("Null buf ???\n"); - else - { - if ( fread(buf,1,(long)filesize,fp) != (unsigned long)filesize ) - printf("error reading filesize.%ld\n",(long)filesize); - buf[filesize] = 0; - } - fclose(fp); - *lenp = filesize; - //printf("loaded.(%s)\n",buf); - } //else printf("OS_loadfile couldnt load.(%s)\n",fname); - return(buf); -} - -void *filestr(long *allocsizep,char *_fname) -{ - long filesize = 0; char *fname,*buf = 0; void *retptr; - *allocsizep = 0; - fname = malloc(strlen(_fname)+1); - strcpy(fname,_fname); - retptr = loadfile(fname,(uint8_t **)&buf,&filesize,allocsizep); - free(fname); - return(retptr); -} - -char *send_curl(char *url,char *fname) -{ - long fsize; char curlstr[1024]; - sprintf(curlstr,"curl --url \"%s\" > %s",url,fname); - system(curlstr); - return(filestr(&fsize,fname)); -} - -cJSON *get_urljson(char *url,char *fname) -{ - char *jsonstr; cJSON *json = 0; - if ( (jsonstr= send_curl(url,fname)) != 0 ) - { - //printf("(%s) -> (%s)\n",url,jsonstr); - json = cJSON_Parse(jsonstr); - free(jsonstr); - } - return(json); -} - -////////////////////////////////////////////// -// start of dapp -////////////////////////////////////////////// -int md_unlink(char *file) -{ -#ifdef _WIN32 - _chmod(file, 0600); - return( _unlink(file) ); -#else - return(unlink(file)); -#endif -} - -char *REFCOIN_CLI,DPOW_pubkeystr[67],DPOW_secpkeystr[67],DPOW_handle[67],DPOW_recvaddr[64],DPOW_recvZaddr[128]; - -cJSON *get_hushcli(char *refcoin,char **retstrp,char *acname,char *method,char *arg0,char *arg1,char *arg2,char *arg3,char *arg4,char *arg5,char *arg6) -{ - long fsize; cJSON *retjson = 0; char cmdstr[32768],*jsonstr,fname[32768]; - sprintf(fname,"/tmp/notarizer_%s_%d",method,(rand() >> 17) % 10000); - if ( acname[0] != 0 ) { - if ( refcoin[0] != 0 && strcmp(refcoin,"HUSH3") != 0 && strcmp(refcoin,acname) != 0 ) - printf("unexpected: refcoin.(%s) acname.(%s)\n",refcoin,acname); - sprintf(cmdstr,"hush-arrakis-chain -ac_name=%s %s %s %s %s %s %s %s %s > %s\n",acname,method,arg0,arg1,arg2,arg3,arg4,arg5,arg6,fname); - } - else if ( strcmp(refcoin,"HUSH3") == 0 ) - sprintf(cmdstr,"hush-cli %s %s %s %s %s %s %s %s > %s\n",method,arg0,arg1,arg2,arg3,arg4,arg5,arg6,fname); - else if ( REFCOIN_CLI != 0 && REFCOIN_CLI[0] != 0 ) - { - sprintf(cmdstr,"%s %s %s %s %s %s %s %s %s > %s\n",REFCOIN_CLI,method,arg0,arg1,arg2,arg3,arg4,arg5,arg6,fname); - //printf("ref.(%s) REFCOIN_CLI (%s)\n",refcoin,cmdstr); - } - //fprintf(stderr,"system(%s)\n",cmdstr); - system(cmdstr); - *retstrp = 0; - if ( (jsonstr= filestr(&fsize,fname)) != 0 ) - { - jsonstr[strlen(jsonstr)-1]='\0'; - //fprintf(stderr,"%s -> jsonstr.(%s)\n",cmdstr,jsonstr); - if ( (jsonstr[0] != '{' && jsonstr[0] != '[') || (retjson= cJSON_Parse(jsonstr)) == 0 ) - *retstrp = jsonstr; - else free(jsonstr); - md_unlink(fname); - } //else fprintf(stderr,"system(%s) -> NULL\n",cmdstr); - return(retjson); -} - -cJSON *hushdex_cli(char *clistr,char **retstrp,char *method,char *arg0,char *arg1,char *arg2,char *arg3,char *arg4,char *arg5,char *arg6) -{ - long fsize; cJSON *retjson = 0; char cmdstr[32768],*jsonstr,fname[32768]; - //TODO: fix this shitty insecure jl777 fucktwattery - sprintf(fname,"/tmp/hushdex_%s_%d",method,(rand() >> 17) % 10000); - sprintf(cmdstr,"%s %s %s %s %s %s %s %s %s > %s\n",clistr,method,arg0,arg1,arg2,arg3,arg4,arg5,arg6,fname); - //fprintf(stderr,"system(%s)\n",cmdstr); - system(cmdstr); - *retstrp = 0; - if ( (jsonstr= filestr(&fsize,fname)) != 0 ) - { - jsonstr[strlen(jsonstr)-1]='\0'; - //fprintf(stderr,"%s -> jsonstr.(%s)\n",cmdstr,jsonstr); - if ( (jsonstr[0] != '{' && jsonstr[0] != '[') || (retjson= cJSON_Parse(jsonstr)) == 0 ) - *retstrp = jsonstr; - else free(jsonstr); - md_unlink(fname); - } //else fprintf(stderr,"system(%s) -> NULL\n",cmdstr); - return(retjson); -} - -bits256 hushbroadcast(char *refcoin,char *acname,cJSON *hexjson) -{ - char *hexstr,*retstr,str[65]; cJSON *retjson; bits256 txid; - memset(txid.bytes,0,sizeof(txid)); - if ( (hexstr= jstr(hexjson,"hex")) != 0 ) - { - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"sendrawtransaction",hexstr,"","","","","","")) != 0 ) - { - //fprintf(stderr,"broadcast.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(txid.bytes,32,retstr); - } - fprintf(stderr,"broadcast %s txid.(%s)\n",strlen(acname)>0?acname:refcoin,bits256_str(str,txid)); - free(retstr); - } - } - return(txid); -} - -bits256 sendtoaddress(char *refcoin,char *acname,char *destaddr,int64_t satoshis,char *oprethexstr) -{ - char numstr[32],*retstr,str[65]; cJSON *retjson; bits256 txid; - memset(txid.bytes,0,sizeof(txid)); - sprintf(numstr,"%.8f",(double)satoshis/SATOSHIDEN); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"sendtoaddress",destaddr,numstr,"false","","",oprethexstr,"")) != 0 ) - { - fprintf(stderr,"unexpected sendrawtransaction json.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(txid.bytes,32,retstr); - } - fprintf(stderr,"sendtoaddress %s %.8f txid.(%s)\n",destaddr,(double)satoshis/SATOSHIDEN,bits256_str(str,txid)); - free(retstr); - } - return(txid); -} - -bits256 tokentransfer(char *refcoin,char *acname,char *tokenid,char *destpub,int64_t units) -{ - char numstr[32],*retstr,str[65]; cJSON *retjson; bits256 txid; - memset(txid.bytes,0,sizeof(txid)); - sprintf(numstr,"%llu",(long long)units); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"tokentransfer",tokenid,destpub,numstr,"","","","")) != 0 ) - { - txid = hushbroadcast(refcoin,acname,retjson); - fprintf(stderr,"tokentransfer returned (%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"tokentransfer.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(txid); -} - -char *get_tokenaddress(char *refcoin,char *acname,char *tokenaddr) -{ - char *retstr,*str; cJSON *retjson; - tokenaddr[0] = 0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"tokenaddress","","","","","","","")) != 0 ) - { - if ( (str= jstr(retjson,"myCCAddress(Tokens)")) != 0 ) - { - strcpy(tokenaddr,str); - fprintf(stderr,"tokenaddress returned (%s)\n",tokenaddr); - free_json(retjson); - return(tokenaddr); - } - free_json(retjson); - } - else if ( retstr != 0 ) - { - //fprintf(stderr,"tokentransfer.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -int64_t get_tokenbalance(char *refcoin,char *acname,char *tokenid) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"tokenbalance",tokenid,"","","","","","")) != 0 ) - { - amount = j64bits(retjson,"balance"); - fprintf(stderr,"tokenbalance %llu\n",(long long)amount); - free_json(retjson); - } - else if ( retstr != 0 ) - { - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - free(retstr); - } - return (amount); -} - -cJSON *get_decodescript(char *refcoin,char *acname,char *script) -{ - cJSON *retjson; char *retstr; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"decodescript",script,"","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_decodescript.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -char *get_createmultisig2(char *refcoin,char *acname,char *msigaddr,char *redeemscript,char *pubkeyA,char *pubkeyB) -{ - //char para 2 '["02c3af47b51a506b08b4ededb156cb4c3f9db9e0ac7ad27b8623c08a056fdcc220", "038e61fbface549a850862f12ed99b7cbeef5c2bd2d8f1daddb34809416f0259e1"]' - cJSON *retjson; char *retstr,*str,params[256]; int32_t height=0; - msigaddr[0] = 0; - redeemscript[0] = 0; - sprintf(params,"'[\"%s\", \"%s\"]'",pubkeyA,pubkeyB); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"createmultisig","2",params,"","","","","")) != 0 ) - { - if ( (str= jstr(retjson,"address")) != 0 ) - strcpy(msigaddr,str); - if ( (str= jstr(retjson,"redeemScript")) != 0 ) - strcpy(redeemscript,str); - free_json(retjson); - if ( msigaddr[0] != 0 && redeemscript[0] != 0 ) - return(msigaddr); - else return(0); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"%s get_createmultisig2.(%s) error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -int32_t get_coinheight(char *refcoin,char *acname,bits256 *blockhashp) -{ - cJSON *retjson; char *retstr; int32_t height=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockchaininfo","","","","","","","")) != 0 ) - { - height = jint(retjson,"blocks"); - *blockhashp = jbits256(retjson,"bestblockhash"); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"%s get_coinheight.(%s) error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(height); -} - -bits256 get_coinblockhash(char *refcoin,char *acname,int32_t height) -{ - cJSON *retjson; char *retstr,heightstr[32]; bits256 hash; - memset(hash.bytes,0,sizeof(hash)); - sprintf(heightstr,"%d",height); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockhash",heightstr,"","","","","","")) != 0 ) - { - fprintf(stderr,"unexpected blockhash json.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(hash.bytes,32,retstr); - } - free(retstr); - } - return(hash); -} - -bits256 get_coinmerkleroot(char *refcoin,char *acname,bits256 blockhash,uint32_t *blocktimep) -{ - cJSON *retjson; char *retstr,str[65]; bits256 merkleroot; - memset(merkleroot.bytes,0,sizeof(merkleroot)); - *blocktimep = 0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockheader",bits256_str(str,blockhash),"","","","","","")) != 0 ) - { - merkleroot = jbits256(retjson,"merkleroot"); - *blocktimep = juint(retjson,"time"); - //fprintf(stderr,"got merkleroot.(%s)\n",bits256_str(str,merkleroot)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"%s %s get_coinmerkleroot error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(merkleroot); -} - -uint32_t get_heighttime(char *refcoin,char *acname,int32_t height) -{ - bits256 blockhash; uint32_t blocktime; - blockhash = get_coinblockhash(refcoin,acname,height); - get_coinmerkleroot(refcoin,acname,blockhash,&blocktime); - return(blocktime); -} - -int32_t get_coinheader(char *refcoin,char *acname,bits256 *blockhashp,bits256 *merklerootp,int32_t prevheight) -{ - int32_t height = 0; char str[65]; bits256 bhash; uint32_t blocktime; - if ( prevheight == 0 ) - height = get_coinheight(refcoin,acname,&bhash) - 20; - else height = prevheight + 1; - if ( height > 0 ) - { - *blockhashp = get_coinblockhash(refcoin,acname,height); - if ( bits256_nonz(*blockhashp) != 0 ) - { - *merklerootp = get_coinmerkleroot(refcoin,acname,*blockhashp,&blocktime); - if ( bits256_nonz(*merklerootp) != 0 ) - return(height); - } - } - memset(blockhashp,0,sizeof(*blockhashp)); - memset(merklerootp,0,sizeof(*merklerootp)); - return(0); -} - -cJSON *get_rawmempool(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getrawmempool","","","","","","","")) != 0 ) - { - //printf("mempool.(%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_rawmempool.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_addressutxos(char *refcoin,char *acname,char *coinaddr) -{ - cJSON *retjson; char *retstr,jsonbuf[256]; - if ( refcoin[0] != 0 && strcmp(refcoin,"HUSH3") != 0 ) - printf("warning: assumes %s has addressindex enabled\n",refcoin); - sprintf(jsonbuf,"{\\\"addresses\\\":[\\\"%s\\\"]}",coinaddr); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getaddressutxos",jsonbuf,"","","","","","")) != 0 ) - { - //printf("addressutxos.(%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_addressutxos.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_rawtransaction(char *refcoin,char *acname,bits256 txid) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getrawtransaction",bits256_str(str,txid),"1","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_rawtransaction.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_z_viewtransaction(char *refcoin,char *acname,bits256 txid) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_viewtransaction",bits256_str(str,txid),"","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_z_viewtransaction.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_listunspent(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"listunspent","","","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_listunspent.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_getinfo(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getinfo","","","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_getinfo.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_listunspent(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_listunspent","","","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_listunspent.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_listoperationids(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_listoperationids","","","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_listoperationids.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_getoperationstatus(char *refcoin,char *acname,char *opid) -{ - cJSON *retjson; char *retstr,str[65],params[512]; - sprintf(params,"'[\"%s\"]'",opid); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getoperationstatus",params,"","","","","","")) != 0 ) - { - //printf("got status (%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_getoperationstatus.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_getoperationresult(char *refcoin,char *acname,char *opid) -{ - cJSON *retjson; char *retstr,str[65],params[512]; - sprintf(params,"'[\"%s\"]'",opid); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getoperationresult",params,"","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_getoperationresult.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -int32_t validateaddress(char *refcoin,char *acname,char *depositaddr, char* compare) -{ - cJSON *retjson; char *retstr; int32_t res=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"validateaddress",depositaddr,"","","","","","")) != 0 ) - { - if (is_cJSON_True(jobj(retjson,compare)) != 0 ) res=1; - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"validateaddress.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return (res); -} - -int32_t z_validateaddress(char *refcoin,char *acname,char *depositaddr, char *compare) -{ - cJSON *retjson; char *retstr; int32_t res=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_validateaddress",depositaddr,"","","","","","")) != 0 ) - { - if (is_cJSON_True(jobj(retjson,compare)) != 0 ) - res=1; - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_validateaddress.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return (res); -} - -int64_t get_getbalance(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getbalance","","","","","","","")) != 0 ) - { - fprintf(stderr,"get_getbalance.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - amount = atof(retstr) * SATOSHIDEN; - sprintf(cmpstr,"%.8f",dstr(amount)); - if ( strcmp(retstr,cmpstr) != 0 ) - amount++; - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - free(retstr); - } - return (amount); -} - -int64_t z_getbalance(char *refcoin,char *acname,char *coinaddr) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getbalance",coinaddr,"","","","","","")) != 0 ) - { - fprintf(stderr,"z_getbalance.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - amount = atof(retstr) * SATOSHIDEN; - sprintf(cmpstr,"%.8f",dstr(amount)); - if ( strcmp(retstr,cmpstr) != 0 ) - amount++; - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - free(retstr); - } - return (amount); -} - -int32_t z_exportkey(char *privkey,char *refcoin,char *acname,char *zaddr) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - privkey[0] = 0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_exportkey",zaddr,"","","","","","")) != 0 ) - { - fprintf(stderr,"z_exportkey.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - return(-1); - } - else if ( retstr != 0 ) - { - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - strcpy(privkey,retstr); - free(retstr); - return(0); - } - return(-1); -} - -int32_t getnewaddress(char *coinaddr,char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr; int64_t amount=0; int32_t retval = -1; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getnewaddress","","","","","","","")) != 0 ) - { - fprintf(stderr,"getnewaddress.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - strcpy(coinaddr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int32_t z_getnewaddress(char *coinaddr,char *refcoin,char *acname,char *typestr) -{ - cJSON *retjson; char *retstr; int64_t amount=0; int32_t retval = -1; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getnewaddress",typestr,"","","","","","")) != 0 ) - { - fprintf(stderr,"z_getnewaddress.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - strcpy(coinaddr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int64_t find_onetime_amount(char *coinstr,char *coinaddr) -{ - cJSON *array,*item; int32_t i,n; char *addr; int64_t amount = 0; - coinaddr[0] = 0; - if ( (array= get_listunspent(coinstr,"")) != 0 ) - { - //printf("got listunspent.(%s)\n",jprint(array,0)); - if ( (n= cJSON_GetArraySize(array)) > 0 ) - { - for (i=0; i 0 ) - { - for (i=0; i %s\n",coinstr,acname,srcaddr,params); - if ( (retjson= get_hushcli(coinstr,&retstr,acname,"z_sendmany",addr,params,"","","","","")) != 0 ) - { - printf("unexpected json z_sendmany.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_sendmany.(%s) -> opid.(%s)\n",coinstr,retstr); - strcpy(opidstr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int32_t z_mergetoaddress(char *opidstr,char *coinstr,char *acname,char *destaddr) -{ - cJSON *retjson; char *retstr,addr[128],*opstr; int32_t retval = -1; - sprintf(addr,"[\\\"ANY_SPROUT\\\"]"); - if ( (retjson= get_hushcli(coinstr,&retstr,acname,"z_mergetoaddress",addr,destaddr,"","","","","")) != 0 ) - { - if ( (opstr= jstr(retjson,"opid")) != 0 ) - strcpy(opidstr,opstr); - retval = jint(retjson,"remainingNotes"); - fprintf(stderr,"%s\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_mergetoaddress.(%s) -> opid.(%s)\n",coinstr,retstr); - strcpy(opidstr,retstr); - free(retstr); - } - return(retval); -} - -int32_t empty_mempool(char *coinstr,char *acname) -{ - cJSON *array; int32_t n; - if ( (array= get_rawmempool(coinstr,acname)) != 0 ) - { - if ( (n= cJSON_GetArraySize(array)) > 0 ) - return(0); - free_json(array); - return(1); - } - return(-1); -} - -cJSON *getinputarray(int64_t *totalp,cJSON *unspents,int64_t required) -{ - cJSON *vin,*item,*vins = cJSON_CreateArray(); int32_t i,n,v; int64_t satoshis; bits256 txid; - *totalp = 0; - if ( (n= cJSON_GetArraySize(unspents)) > 0 ) - { - for (i=0; i= required ) - break; - } - } - } - return(vins); -} - -int32_t tx_has_voutaddress(char *refcoin,char *acname,bits256 txid,char *coinaddr) -{ - cJSON *txobj,*vouts,*vout,*vins,*vin,*sobj,*addresses; char *addr,str[65]; int32_t i,j,n,numarray,retval = 0, hasvout=0; - if ( (txobj= get_rawtransaction(refcoin,acname,txid)) != 0 ) - { - if ( (vouts= jarray(&numarray,txobj,"vout")) != 0 ) - { - for (i=0; i 0 ) - { - for (i=0; i 0 ) - { - for (j=0; j 0 && strcmp(vinaddr,cmpaddr) == 0 ) - return(0); - printf("mismatched vinaddr.(%s) vs %s\n",vinaddr,cmpaddr); - } - } - return(-1); -} - -int32_t txid_in_vins(char *refcoin,bits256 txid,bits256 cmptxid) -{ - cJSON *txjson,*vins,*vin; int32_t numvins,v,vinvout; bits256 vintxid; char str[65]; - if ( (txjson= get_rawtransaction(refcoin,"",txid)) != 0 ) - { - if ( (vins= jarray(&numvins,txjson,"vin")) != 0 ) - { - for (v=0; v n.%d retval.%d\n",tagA,tagB,pubkeystr,n,retval); - } - free_json(retjson); - } - return(retval); -} - -int32_t dpow_hasmessage(char *payload,char *tagA,char *tagB,char *pubkeystr) -{ - cJSON *retjson,*item,*array; char *retstr,*pstr; int32_t i,n,retval = 0; - if ( (retjson= get_hushcli((char *)"",&retstr,DEXP2P_CHAIN,"DEX_list","0","0",tagA,tagB,pubkeystr,"","")) != 0 ) - { - if ( (array= jarray(&n,retjson,"matches")) != 0 ) - { - for (i=0; i 0 ) - { - ptrs = calloc(n,sizeof(*ptrs)); - for (i=0; ishorthash = juint(item,"id"); - ptrs[m]->jsonstr = ptr; - strcpy(ptrs[m]->senderpub,senderpub); - m++; - } - } - } - *nump = m; - } - free_json(retjson); - } - return(ptrs); -} diff --git a/src/cc/dapps/dappstd.c b/src/cc/dapps/dappstd.c deleted file mode 100644 index 3279e9927..000000000 --- a/src/cc/dapps/dappstd.c +++ /dev/null @@ -1,1114 +0,0 @@ -// Copyright (c) 2016-2024 The Hush developers -// Distributed under the GPLv3 software license, see the accompanying -// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - -// requires CHAINNAME and GAMEMAIN() to be #defined - -#include -#include -#include -#include -#include -#include -#include - -extern struct games_state globalR; -void *gamesiterate(struct games_state *rs); - -char USERPASS[8192]; uint16_t GAMES_PORT; -char Gametxidstr[67]; -char *clonestr(char *str); - -#define MAXSTR 1024 -char whoami[MAXSTR]; - -#define SMALLVAL 0.000000000000001 -#define SATOSHIDEN ((uint64_t)100000000L) -#define dstr(x) ((double)(x) / SATOSHIDEN) -#define HUSH_SMART_CHAIN_MAXLEN 65 -char ASSETCHAINS_SYMBOL[HUSH_SMART_CHAIN_MAXLEN],IPADDRESS[100]; - -#ifndef _BITS256 -#define _BITS256 -union _bits256 { uint8_t bytes[32]; uint16_t ushorts[16]; uint32_t uints[8]; uint64_t ulongs[4]; uint64_t txid; }; -typedef union _bits256 bits256; -#endif - -#ifdef _WIN32 -#ifdef _MSC_VER -int gettimeofday(struct timeval * tp, struct timezone * tzp) -{ - // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's - static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); - - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; - - GetSystemTime(&system_time); - SystemTimeToFileTime(&system_time, &file_time); - time = ((uint64_t)file_time.dwLowDateTime); - time += ((uint64_t)file_time.dwHighDateTime) << 32; - - tp->tv_sec = (long)((time - EPOCH) / 10000000L); - tp->tv_usec = (long)(system_time.wMilliseconds * 1000); - return 0; -} -#endif // _MSC_VER -#endif - -double OS_milliseconds() -{ - struct timeval tv; double millis; -#ifdef __MINGW32__ - mingw_gettimeofday(&tv,NULL); -#else - gettimeofday(&tv,NULL); -#endif - millis = ((double)tv.tv_sec * 1000. + (double)tv.tv_usec / 1000.); - //printf("tv_sec.%ld usec.%d %f\n",tv.tv_sec,tv.tv_usec,millis); - return(millis); -} - -int32_t _unhex(char c) -{ - if ( c >= '0' && c <= '9' ) - return(c - '0'); - else if ( c >= 'a' && c <= 'f' ) - return(c - 'a' + 10); - else if ( c >= 'A' && c <= 'F' ) - return(c - 'A' + 10); - return(-1); -} - -int32_t is_hexstr(char *str,int32_t n) -{ - int32_t i; - if ( str == 0 || str[0] == 0 ) - return(0); - for (i=0; str[i]!=0; i++) - { - if ( n > 0 && i >= n ) - break; - if ( _unhex(str[i]) < 0 ) - break; - } - if ( n == 0 ) - return(i); - return(i == n); -} - -int32_t unhex(char c) -{ - int32_t hex; - if ( (hex= _unhex(c)) < 0 ) - { - //printf("unhex: illegal hexchar.(%c)\n",c); - } - return(hex); -} - -unsigned char _decode_hex(char *hex) { return((unhex(hex[0])<<4) | unhex(hex[1])); } - -int32_t decode_hex(uint8_t *bytes,int32_t n,char *hex) -{ - int32_t adjust,i = 0; - //printf("decode.(%s)\n",hex); - if ( is_hexstr(hex,n) <= 0 ) - { - memset(bytes,0,n); - return(n); - } - if ( hex[n-1] == '\n' || hex[n-1] == '\r' ) - hex[--n] = 0; - if ( n == 0 || (hex[n*2+1] == 0 && hex[n*2] != 0) ) - { - if ( n > 0 ) - { - bytes[0] = unhex(hex[0]); - printf("decode_hex n.%d hex[0] (%c) -> %d hex.(%s) [n*2+1: %d] [n*2: %d %c] len.%ld\n",n,hex[0],bytes[0],hex,hex[n*2+1],hex[n*2],hex[n*2],(long)strlen(hex)); - } - bytes++; - hex++; - adjust = 1; - } else adjust = 0; - if ( n > 0 ) - { - for (i=0; i>4) & 0xf); - hexbytes[i*2 + 1] = hexbyte(message[i] & 0xf); - //printf("i.%d (%02x) [%c%c]\n",i,message[i],hexbytes[i*2],hexbytes[i*2+1]); - } - hexbytes[len*2] = 0; - //printf("len.%ld\n",len*2+1); - return((int32_t)len*2+1); -} - -char *bits256_str(char hexstr[65],bits256 x) -{ - init_hexbytes_noT(hexstr,x.bytes,sizeof(x)); - return(hexstr); -} - -long _stripwhite(char *buf,int accept) -{ - int32_t i,j,c; - if ( buf == 0 || buf[0] == 0 ) - return(0); - for (i=j=0; buf[i]!=0; i++) - { - buf[j] = c = buf[i]; - if ( c == accept || (c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\b') ) - j++; - } - buf[j] = 0; - return(j); -} - -char *parse_conf_line(char *line,char *field) -{ - line += strlen(field); - for (; *line!='='&&*line!=0; line++) - break; - if ( *line == 0 ) - return(0); - if ( *line == '=' ) - line++; - while ( line[strlen(line)-1] == '\r' || line[strlen(line)-1] == '\n' || line[strlen(line)-1] == ' ' ) - line[strlen(line)-1] = 0; - //printf("LINE.(%s)\n",line); - _stripwhite(line,0); - return(clonestr(line)); -} - -int32_t safecopy(char *dest,char *src,long len) -{ - int32_t i = -1; - if ( src != 0 && dest != 0 && src != dest ) - { - if ( dest != 0 ) - memset(dest,0,len); - for (i=0; i buflen ) - { - *allocsizep = filesize; - *bufp = buf = (uint8_t *)realloc(buf,(long)*allocsizep+64); - } - rewind(fp); - if ( buf == 0 ) - printf("Null buf ???\n"); - else - { - if ( fread(buf,1,(long)filesize,fp) != (unsigned long)filesize ) - printf("error reading filesize.%ld\n",(long)filesize); - buf[filesize] = 0; - } - fclose(fp); - *lenp = filesize; - //printf("loaded.(%s)\n",buf); - } //else printf("OS_loadfile couldnt load.(%s)\n",fname); - return(buf); -} - -uint8_t *OS_fileptr(long *allocsizep,char *fname) -{ - long filesize = 0; uint8_t *buf = 0; void *retptr; - *allocsizep = 0; - retptr = OS_loadfile(fname,&buf,&filesize,allocsizep); - return((uint8_t *)retptr); -} - -struct MemoryStruct { char *memory; size_t size; }; -struct return_string { char *ptr; size_t len; }; - -// return data from the server -#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) -#define CURL_GLOBAL_SSL (1<<0) -#define CURL_GLOBAL_WIN32 (1<<1) - - -/************************************************************************ - * - * Initialize the string handler so that it is thread safe - * - ************************************************************************/ - -void init_string(struct return_string *s) -{ - s->len = 0; - s->ptr = (char *)calloc(1,s->len+1); - if ( s->ptr == NULL ) - { - fprintf(stderr,"init_string malloc() failed\n"); - exit(-1); - } - s->ptr[0] = '\0'; -} - -/************************************************************************ - * - * Use the "writer" to accumulate text until done - * - ************************************************************************/ - -size_t accumulatebytes(void *ptr,size_t size,size_t nmemb,struct return_string *s) -{ - size_t new_len = s->len + size*nmemb; - s->ptr = (char *)realloc(s->ptr,new_len+1); - if ( s->ptr == NULL ) - { - fprintf(stderr, "accumulate realloc() failed\n"); - exit(-1); - } - memcpy(s->ptr+s->len,ptr,size*nmemb); - s->ptr[new_len] = '\0'; - s->len = new_len; - return(size * nmemb); -} - -/************************************************************************ - * - * return the current system time in milliseconds - * - ************************************************************************/ - -#define EXTRACT_BITCOIND_RESULT // if defined, ensures error is null and returns the "result" field -#ifdef EXTRACT_BITCOIND_RESULT - -/************************************************************************ - * - * perform post processing of the results - * - ************************************************************************/ - -char *post_process_bitcoind_RPC(char *debugstr,char *command,char *rpcstr,char *params) -{ - long i,j,len; char *retstr = 0; cJSON *json,*result,*error; - //printf("<<<<<<<<<<< bitcoind_RPC: %s post_process_bitcoind_RPC.%s.[%s]\n",debugstr,command,rpcstr); - if ( command == 0 || rpcstr == 0 || rpcstr[0] == 0 ) - { - if ( strcmp(command,"signrawtransaction") != 0 ) - printf("<<<<<<<<<<< bitcoind_RPC: %s post_process_bitcoind_RPC.%s.[%s]\n",debugstr,command,rpcstr); - return(rpcstr); - } - json = cJSON_Parse(rpcstr); - if ( json == 0 ) - { - printf("<<<<<<<<<<< bitcoind_RPC: %s post_process_bitcoind_RPC.%s can't parse.(%s) params.(%s)\n",debugstr,command,rpcstr,params); - free(rpcstr); - return(0); - } - result = cJSON_GetObjectItem(json,"result"); - error = cJSON_GetObjectItem(json,"error"); - if ( error != 0 && result != 0 ) - { - if ( (error->type&0xff) == cJSON_NULL && (result->type&0xff) != cJSON_NULL ) - { - retstr = cJSON_Print(result); - len = strlen(retstr); - if ( retstr[0] == '"' && retstr[len-1] == '"' ) - { - for (i=1,j=0; itype&0xff) != cJSON_NULL || (result->type&0xff) != cJSON_NULL ) - { - if ( strcmp(command,"signrawtransaction") != 0 ) - printf("<<<<<<<<<<< bitcoind_RPC: %s post_process_bitcoind_RPC (%s) error.%s\n",debugstr,command,rpcstr); - } - free(rpcstr); - } else retstr = rpcstr; - free_json(json); - //fprintf(stderr,"<<<<<<<<<<< bitcoind_RPC: postprocess returns.(%s)\n",retstr); - return(retstr); -} -#endif - -#ifdef _WIN32 -#ifdef _MSC_VER -#define sleep(x) Sleep(1000*(x)) -#endif -#endif - -/************************************************************************ - * - * perform the query - * - ************************************************************************/ - -char *bitcoind_RPC(char **retstrp,char *debugstr,char *url,char *userpass,char *command,char *params) -{ - static int didinit,count,count2; static double elapsedsum,elapsedsum2; - struct curl_slist *headers = NULL; struct return_string s; CURLcode res; CURL *curl_handle; - char *bracket0,*bracket1,*databuf = 0; long len; int32_t specialcase,numretries; double starttime; - if ( didinit == 0 ) - { - didinit = 1; - curl_global_init(CURL_GLOBAL_ALL); //init the curl session - } - numretries = 0; - if ( debugstr != 0 && strcmp(debugstr,"BTCD") == 0 && command != 0 && strcmp(command,"SuperNET") == 0 ) - specialcase = 1; - else specialcase = 0; - if ( url[0] == 0 ) - strcpy(url,"http://127.0.0.1:7876/nxt"); - if ( specialcase != 0 && 0 ) - printf("<<<<<<<<<<< bitcoind_RPC: debug.(%s) url.(%s) command.(%s) params.(%s)\n",debugstr,url,command,params); -try_again: - if ( retstrp != 0 ) - *retstrp = 0; - starttime = OS_milliseconds(); - curl_handle = curl_easy_init(); - init_string(&s); - headers = curl_slist_append(0,"Expect:"); - - curl_easy_setopt(curl_handle,CURLOPT_USERAGENT,"mozilla/4.0");//"Mozilla/4.0 (compatible; )"); - curl_easy_setopt(curl_handle,CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl_handle,CURLOPT_URL, url); - curl_easy_setopt(curl_handle,CURLOPT_WRITEFUNCTION, (void *)accumulatebytes); // send all data to this function - curl_easy_setopt(curl_handle,CURLOPT_WRITEDATA, &s); // we pass our 's' struct to the callback - curl_easy_setopt(curl_handle,CURLOPT_NOSIGNAL, 1L); // supposed to fix "Alarm clock" and long jump crash - curl_easy_setopt(curl_handle,CURLOPT_NOPROGRESS, 1L); // no progress callback - if ( strncmp(url,"https",5) == 0 ) - { - curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYPEER,0); - curl_easy_setopt(curl_handle,CURLOPT_SSL_VERIFYHOST,0); - } - if ( userpass != 0 ) - curl_easy_setopt(curl_handle,CURLOPT_USERPWD, userpass); - databuf = 0; - if ( params != 0 ) - { - if ( command != 0 && specialcase == 0 ) - { - len = strlen(params); - if ( len > 0 && params[0] == '[' && params[len-1] == ']' ) { - bracket0 = bracket1 = (char *)""; - } - else - { - bracket0 = (char *)"["; - bracket1 = (char *)"]"; - } - - databuf = (char *)malloc(256 + strlen(command) + strlen(params)); - sprintf(databuf,"{\"id\":\"jl777\",\"method\":\"%s\",\"params\":%s%s%s}",command,bracket0,params,bracket1); - //printf("url.(%s) userpass.(%s) databuf.(%s)\n",url,userpass,databuf); - // - } //else if ( specialcase != 0 ) fprintf(stderr,"databuf.(%s)\n",params); - curl_easy_setopt(curl_handle,CURLOPT_POST,1L); - if ( databuf != 0 ) - curl_easy_setopt(curl_handle,CURLOPT_POSTFIELDS,databuf); - else curl_easy_setopt(curl_handle,CURLOPT_POSTFIELDS,params); - } - //laststart = milliseconds(); - res = curl_easy_perform(curl_handle); - curl_slist_free_all(headers); - curl_easy_cleanup(curl_handle); - if ( databuf != 0 ) // clean up temporary buffer - { - free(databuf); - databuf = 0; - } - if ( res != CURLE_OK ) - { - numretries++; - if ( specialcase != 0 ) - { - printf("<<<<<<<<<<< bitcoind_RPC.(%s): BTCD.%s timeout params.(%s) s.ptr.(%s) err.%d\n",url,command,params,s.ptr,res); - free(s.ptr); - return(0); - } - else if ( numretries >= 1 ) - { - //printf("Maximum number of retries exceeded!\n"); - free(s.ptr); - return(0); - } - if ( (rand() % 1000) == 0 ) - printf( "curl_easy_perform() failed: %s %s.(%s %s), retries: %d\n",curl_easy_strerror(res),debugstr,url,command,numretries); - free(s.ptr); - sleep((1< (%s)\n",params,s.ptr); - count2++; - elapsedsum2 += (OS_milliseconds() - starttime); - if ( (count2 % 10000) == 0) - printf("%d: ave %9.6f | elapsed %.3f millis | NXT calls.(%s) cmd.(%s)\n",count2,elapsedsum2/count2,(double)(OS_milliseconds() - starttime),url,command); - return(s.ptr); - } - } - printf("bitcoind_RPC: impossible case\n"); - free(s.ptr); - return(0); -} - -static size_t WriteMemoryCallback(void *ptr,size_t size,size_t nmemb,void *data) -{ - size_t realsize = (size * nmemb); - struct MemoryStruct *mem = (struct MemoryStruct *)data; - mem->memory = (char *)((ptr != 0) ? realloc(mem->memory,mem->size + realsize + 1) : malloc(mem->size + realsize + 1)); - if ( mem->memory != 0 ) - { - if ( ptr != 0 ) - memcpy(&(mem->memory[mem->size]),ptr,realsize); - mem->size += realsize; - mem->memory[mem->size] = 0; - } - //printf("got %d bytes\n",(int32_t)(size*nmemb)); - return(realsize); -} - -char *curl_post(CURL **cHandlep,char *url,char *userpass,char *postfields,char *hdr0,char *hdr1,char *hdr2,char *hdr3) -{ - struct MemoryStruct chunk; CURL *cHandle; long code; struct curl_slist *headers = 0; - if ( (cHandle= *cHandlep) == NULL ) - *cHandlep = cHandle = curl_easy_init(); - else curl_easy_reset(cHandle); - //#ifdef DEBUG - //curl_easy_setopt(cHandle,CURLOPT_VERBOSE, 1); - //#endif - curl_easy_setopt(cHandle,CURLOPT_USERAGENT,"mozilla/4.0");//"Mozilla/4.0 (compatible; )"); - curl_easy_setopt(cHandle,CURLOPT_SSL_VERIFYPEER,0); - //curl_easy_setopt(cHandle,CURLOPT_SSLVERSION,1); - curl_easy_setopt(cHandle,CURLOPT_URL,url); - curl_easy_setopt(cHandle,CURLOPT_CONNECTTIMEOUT,10); - if ( userpass != 0 && userpass[0] != 0 ) - curl_easy_setopt(cHandle,CURLOPT_USERPWD,userpass); - if ( postfields != 0 && postfields[0] != 0 ) - { - curl_easy_setopt(cHandle,CURLOPT_POST,1); - curl_easy_setopt(cHandle,CURLOPT_POSTFIELDS,postfields); - } - if ( hdr0 != NULL && hdr0[0] != 0 ) - { - //printf("HDR0.(%s) HDR1.(%s) HDR2.(%s) HDR3.(%s)\n",hdr0!=0?hdr0:"",hdr1!=0?hdr1:"",hdr2!=0?hdr2:"",hdr3!=0?hdr3:""); - headers = curl_slist_append(headers,hdr0); - if ( hdr1 != 0 && hdr1[0] != 0 ) - headers = curl_slist_append(headers,hdr1); - if ( hdr2 != 0 && hdr2[0] != 0 ) - headers = curl_slist_append(headers,hdr2); - if ( hdr3 != 0 && hdr3[0] != 0 ) - headers = curl_slist_append(headers,hdr3); - } //headers = curl_slist_append(0,"Expect:"); - if ( headers != 0 ) - curl_easy_setopt(cHandle,CURLOPT_HTTPHEADER,headers); - //res = curl_easy_perform(cHandle); - memset(&chunk,0,sizeof(chunk)); - curl_easy_setopt(cHandle,CURLOPT_WRITEFUNCTION,WriteMemoryCallback); - curl_easy_setopt(cHandle,CURLOPT_WRITEDATA,(void *)&chunk); - curl_easy_perform(cHandle); - curl_easy_getinfo(cHandle,CURLINFO_RESPONSE_CODE,&code); - if ( headers != 0 ) - curl_slist_free_all(headers); - if ( code != 200 ) - printf("(%s) server responded with code %ld (%s)\n",url,code,chunk.memory); - return(chunk.memory); -} - -uint16_t _hush_userpass(char *username, char *password, FILE *fp) -{ - char *rpcuser,*rpcpassword,*str,*ipaddress,line[8192]; uint16_t port = 0; - rpcuser = rpcpassword = 0; - username[0] = password[0] = 0; - while ( fgets(line,sizeof(line),fp) != 0 ) - { - if ( line[0] == '#' ) - continue; - //printf("line.(%s) %p %p\n",line,strstr(line,(char *)"rpcuser"),strstr(line,(char *)"rpcpassword")); - if ( (str= strstr(line,(char *)"rpcuser")) != 0 ) - rpcuser = parse_conf_line(str,(char *)"rpcuser"); - else if ( (str= strstr(line,(char *)"rpcpassword")) != 0 ) - rpcpassword = parse_conf_line(str,(char *)"rpcpassword"); - else if ( (str= strstr(line,(char *)"rpcport")) != 0 ) - { - port = atoi(parse_conf_line(str,(char *)"rpcport")); - //fprintf(stderr,"rpcport.%u in file\n",port); - } - else if ( (str= strstr(line,(char *)"ipaddress")) != 0 ) - { - ipaddress = parse_conf_line(str,(char *)"ipaddress"); - strcpy(IPADDRESS,ipaddress); - } - } - if ( rpcuser != 0 && rpcpassword != 0 ) - { - strcpy(username,rpcuser); - strcpy(password,rpcpassword); - } - //printf("rpcuser.(%s) rpcpassword.(%s) %u ipaddress.%s\n",rpcuser,rpcpassword,port,ipaddress); - if ( rpcuser != 0 ) - free(rpcuser); - if ( rpcpassword != 0 ) - free(rpcpassword); - return(port); -} - -uint16_t hush_userpass(char *userpass,char *symbol) -{ - FILE *fp; uint16_t port = 0; char fname[512],username[512],password[512],confname[HUSH_SMART_CHAIN_MAXLEN]; - userpass[0] = 0; - sprintf(confname,"%s.conf",symbol); - //hush_statefname(fname,symbol,confname); - if ( (fp= fopen(confname,"rb")) != 0 ) - { - port = _hush_userpass(username,password,fp); - sprintf(userpass,"%s:%s",username,password); - if ( strcmp(symbol,ASSETCHAINS_SYMBOL) == 0 ) - strcpy(USERPASS,userpass); - fclose(fp); - } - return(port); -} - -#define is_cJSON_True(json) ((json) != 0 && ((json)->type & 0xff) == cJSON_True) - -char *hush_issuemethod(char *userpass,char *method,char *params,uint16_t port) -{ - //static void *cHandle; - char url[512],*retstr=0,*retstr2=0,postdata[8192]; - if ( params == 0 || params[0] == 0 ) - params = (char *)"[]"; - if ( strlen(params) < sizeof(postdata)-128 ) - { - sprintf(url,(char *)"http://%s:%u",IPADDRESS,port); - sprintf(postdata,"{\"method\":\"%s\",\"params\":%s}",method,params); - //printf("[%s] (%s) postdata.(%s) params.(%s) USERPASS.(%s)\n",ASSETCHAINS_SYMBOL,url,postdata,params,USERPASS); - retstr2 = bitcoind_RPC(&retstr,(char *)"debug",url,userpass,method,params); - //retstr = curl_post(&cHandle,url,USERPASS,postdata,0,0,0,0); - } - return(retstr2); -} - -int32_t games_sendrawtransaction(char *rawtx) -{ - char *params,*retstr,*hexstr; cJSON *retjson,*resobj; int32_t retval = -1; - params = (char *)malloc(strlen(rawtx) + 16); - sprintf(params,"[\"%s\"]",rawtx); - if ( (retstr= hush_issuemethod(USERPASS,(char *)"sendrawtransaction",params,GAMES_PORT)) != 0 ) - { - if ( 0 ) // causes 4th level crash - { - static FILE *fp; - if ( fp == 0 ) - fp = fopen("games.sendlog","wb"); - if ( fp != 0 ) - { - fprintf(fp,"%s\n",retstr); - fflush(fp); - } - } - if ( (retjson= cJSON_Parse(retstr)) != 0 ) - { - if ( (resobj= jobj(retjson,(char *)"result")) != 0 ) - { - if ( (hexstr= jstr(resobj,0)) != 0 && is_hexstr(hexstr,64) == 64 ) - retval = 0; - } - free_json(retjson); - } - - /* log sendrawtx result in file */ - - /* - FILE *debug_file; - debug_file = fopen("tx_debug.log", "a"); - fprintf(debug_file, "%s\n", retstr); - fflush(debug_file); - fclose(debug_file); - */ - - free(retstr); - } - free(params); - return(retval); -} - -int32_t games_progress(struct games_state *rs,int32_t waitflag,uint64_t seed,gamesevent *keystrokes,int32_t num) -{ - char cmd[16384],hexstr[16384],params[32768],*retstr,*errstr,*rawtx; int32_t i,len,retflag = -1; cJSON *retjson,*resobj; - if ( rs->guiflag != 0 && Gametxidstr[0] != 0 ) - { - if ( rs->keystrokeshex != 0 ) - { - if ( games_sendrawtransaction(rs->keystrokeshex) == 0 ) - { - if ( waitflag == 0 ) - return(0); - else if ( 0 ) - { - while ( games_sendrawtransaction(rs->keystrokeshex) == 0 ) - { - //fprintf(stderr,"pre-rebroadcast\n"); - sleep(10); - } - } - } - free(rs->keystrokeshex), rs->keystrokeshex = 0; - } - memset(hexstr,0,sizeof(hexstr)); - for (i=0; ikeystrokeshex != 0 ) - free(rs->keystrokeshex); - if ( (errstr= jstr(resobj,(char *)"error")) == 0 ) - { - rs->keystrokeshex = (char *)malloc(strlen(rawtx)+1); - strcpy(rs->keystrokeshex,rawtx); - retflag = 1; - } else fprintf(stderr,"error sending keystrokes tx\n"), sleep(1); - //fprintf(stderr,"set keystrokestx <- %s\n",rs->keystrokeshex); - } - free_json(retjson); - } - free(retstr); - } - } - return(retflag); -} - -int32_t gamesfname(char *fname,uint64_t seed,int32_t counter) -{ - sprintf(fname,"%s.%llu.%d",GAMENAME,(long long)seed,counter); - return(0); -} - -int32_t flushkeystrokes_local(struct games_state *rs,int32_t waitflag) -{ -#ifdef STANDALONE - char fname[1024]; FILE *fp; int32_t i,retflag = -1; - rs->counter++; - gamesfname(fname,rs->origseed,rs->counter); - if ( (fp= fopen(fname,"wb")) != 0 ) - { - if ( fwrite(rs->buffered,sizeof(*rs->buffered),rs->num,fp) == rs->num ) - { - rs->num = 0; - retflag = 0; - fclose(fp); - gamesfname(fname,rs->origseed,rs->counter+1); - if ( (fp= fopen(fname,"wb")) != 0 ) // truncate next file - fclose(fp); - //fprintf(stderr,"savefile <- %s retflag.%d\n",fname,retflag); - //} - } else fprintf(stderr,"error writing (%s)\n",fname); - } else fprintf(stderr,"error creating (%s)\n",fname); - return(retflag); -#else - return(0); -#endif -} - -#ifndef STANDALONE -// stubs for inside daemon - -int32_t games_progress(struct games_state *rs,int32_t waitflag,uint64_t seed,char *keystrokes,int32_t num) -{ - return(0); -} - -int32_t games_setplayerdata(struct games_state *rs,char *gametxidstr) -{ - return(-1); -} -#endif - -int32_t flushkeystrokes(struct games_state *rs,int32_t waitflag) -{ - if ( rs->num > 0 ) - { - if ( games_progress(rs,waitflag,rs->origseed,rs->buffered,rs->num) > 0 ) - { - flushkeystrokes_local(rs,waitflag); - memset(rs->buffered,0,sizeof(rs->buffered)); - } - } - return(0); -} - -void gamesbailout(struct games_state *rs) -{ - flushkeystrokes(rs,1); -} - -#ifdef _WIN32 -#ifdef _MSC_VER -#define sleep(x) Sleep(1000*(x)) -#endif -#endif - -long get_filesize(FILE *fp) -{ - long fsize,fpos = ftell(fp); - fseek(fp,0,SEEK_END); - fsize = ftell(fp); - fseek(fp,fpos,SEEK_SET); - return(fsize); -} - -gamesevent *games_keystrokesload(int32_t *numkeysp,uint64_t seed,int32_t counter) -{ - char fname[1024]; gamesevent *keystrokes = 0; FILE *fp; long fsize; int32_t i,num = 0; - *numkeysp = 0; - while ( 1 ) - { - gamesfname(fname,seed,counter); - //printf("check (%s)\n",fname); - if ( (fp= fopen(fname,"rb")) == 0 ) - break; - if ( (fsize= get_filesize(fp)) <= 0 ) - { - fclose(fp); - //printf("fsize.%ld\n",fsize); - break; - } - if ( (keystrokes= (gamesevent *)realloc(keystrokes,sizeof(*keystrokes)*num+fsize)) == 0 ) - { - fprintf(stderr,"error reallocating keystrokes\n"); - fclose(fp); - return(0); - } - if ( fread(&keystrokes[num],1,fsize,fp) != fsize ) - { - fprintf(stderr,"error reading keystrokes from (%s)\n",fname); - fclose(fp); - free(keystrokes); - return(0); - } - fclose(fp); - num += (int32_t)(fsize / sizeof(gamesevent)); - //for (i=0; i 0 ) - { - sprintf(fname,"%s.%llu.player",GAMENAME,(long long)seed); - if ( (fp=fopen(fname,"rb")) != 0 ) - { - if ( fread(&P,1,sizeof(P),fp) > 0 ) - { - //printf("max size player\n"); - player = &P; - } - fclose(fp); - } - games_replay2(0,seed,keystrokes,num,player,sleeptime); - mvaddstr(LINES - 2, 0, (char *)"replay completed"); - endwin(); - games_exit(); - } - if ( keystrokes != 0 ) - free(keystrokes); - return(num); -} - -int32_t games_setplayerdata(struct games_state *rs,char *gametxidstr) -{ - char cmd[32768]; int32_t i,n,retval=-1; char params[1024],*filestr=0,*pname,*statusstr,*datastr,fname[128]; long allocsize; cJSON *retjson,*array,*item,*resultjson; - if ( rs->guiflag == 0 ) - return(-1); - if ( gametxidstr == 0 || *gametxidstr == 0 ) - return(retval); - if ( 0 ) - { - sprintf(fname,"%s.gameinfo",gametxidstr); - sprintf(cmd,"./hush-cli -ac_name=%s cclib gameinfo 17 \\\"[%%22%s%%22]\\\" > %s",ASSETCHAINS_SYMBOL,gametxidstr,fname); - if ( system(cmd) != 0 ) - fprintf(stderr,"error issuing (%s)\n",cmd); - else filestr = (char *)OS_fileptr(&allocsize,fname); - } - else - { - sprintf(params,"[\"gameinfo\",\"17\",\"[%%22%s%%22]\"]",gametxidstr); - filestr = hush_issuemethod(USERPASS,(char *)"cclib",params,GAMES_PORT); - } - if ( filestr != 0 ) - { - if ( (retjson= cJSON_Parse(filestr)) != 0 && (resultjson= jobj(retjson,(char *)"result")) != 0 ) - { - //fprintf(stderr,"gameinfo.(%s)\n",jprint(resultjson,0)); - if ( (array= jarray(&n,resultjson,(char *)"players")) != 0 ) - { - for (i=0; iP,(int32_t)strlen(datastr)/2,datastr); - fprintf(stderr,"set pname[%s] %s\n",pname==0?"":pname,jprint(item,0)); - rs->restoring = 1; - } - } - } - } - } - free_json(retjson); - } - free(filestr); - } - return(retval); -} - -#ifdef _WIN32 -#ifdef _MSC_VER -__inline int msver(void) { - switch (_MSC_VER) { - case 1500: return 2008; - case 1600: return 2010; - case 1700: return 2012; - case 1800: return 2013; - case 1900: return 2015; - //case 1910: return 2017; - default: return (_MSC_VER / 100); - } -} - -static inline bool is_x64(void) { -#if defined(__x86_64__) || defined(_WIN64) || defined(__aarch64__) - return 1; -#elif defined(__amd64__) || defined(__amd64) || defined(_M_X64) || defined(_M_IA64) - return 1; -#else - return 0; -#endif -} - -#define BUILD_DATE __DATE__ " " __TIME__ -#endif // _WIN32 -#endif // _MSC_VER - -int main(int argc, char **argv) -{ - uint64_t seed; FILE *fp = 0; int32_t i,j,c; char userpass[8192]; -#ifdef _WIN32 -#ifdef _MSC_VER - printf("*** games for Windows [ Build %s ] ***\n", BUILD_DATE); - const char* arch = is_x64() ? "64-bits" : "32-bits"; - printf(" Built with VC++ %d (%ld) %s\n\n", msver(), _MSC_FULL_VER, arch); -#endif -#endif - - for (i=j=0; argv[0][i]!=0&&jbase : &mp->rel; - if ( coin[0] == 0 ) - return(coin); - if ( (external= jarray(&n,SUBATOMIC_json,"externalcoins")) != 0 && n > 0 ) - { - for (i=0; icli) ) - { - ptr->isexternal = 1; - strcpy(ptr->cli,clistr); - //fprintf(stderr,"found external coin %s %s\n",coin,clistr); - } - } - } - if ( coin[0] == '#' ) - { - strcpy(ptr->coinstr,coin); - strcpy(ptr->acname,""); - ptr->isfile = 1; - return(coin); - } - else if ( coin[0] != 'z' ) - { - for (i=1; coin[i]!=0; i++) - if ( coin[i] == '.' ) - { - dpow_tokenregister(ptr->tokenid,0,coin,0); - if ( ptr->tokenid[0] != 0 ) - { - strcpy(tmpstr,coin); - tmpstr[i] = 0; - //fprintf(stderr,"found a tokenmap %s -> %s %s\n",coin,tmpstr,ptr->tokenid); - ptr->istoken = 1; - strcpy(ptr->acname,coin); - strcpy(ptr->coinstr,""); - return(tmpstr); - } - } - if ( ptr->isexternal == 0 ) - { - strcpy(ptr->acname,coin); - strcpy(ptr->coinstr,""); - strcpy(ptr->acname,""); - } - else - { - strcpy(ptr->coinstr,coin); - strcpy(ptr->acname,""); - } - return(coin); - } - else - { - for (i=1; coin[i]!=0; i++) - if ( isupper(coin[i]) == 0 ) - return(coin); - ptr->iszaddr = 1; - return(coin+1); - } -} - -int32_t hushdex_zonly(struct coininfo *coin) -{ - if ( strcmp(coin->coin,"HUSH3") == 0 ) - return(1); - - if ( strcmp(coin->coin,"HUSHFILE") == 0 ) - return(1); - - return 0; -} - -// //////////////////////////////// the four key functions needed to support a new item for hushdexs - -int64_t _hushdex_getbalance(struct coininfo *coin) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - if ( (retjson= hushdex_cli(coin->cli,&retstr,"getbalance","","","","","","","")) != 0 ) - { - fprintf(stderr,"_hushdex_getbalance.(%s) %s returned json!\n",coin->coinstr,coin->cli); - free_json(retjson); - } - else if ( retstr != 0 ) - { - amount = atof(retstr) * SATOSHIDEN; - sprintf(cmpstr,"%.8f",dstr(amount)); - if ( strcmp(retstr,cmpstr) != 0 ) - amount++; - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - free(retstr); - } - return (amount); -} - -bits256 _hushdex_sendtoaddress(struct coininfo *coin,char *destaddr,int64_t satoshis) -{ - char numstr[32],*retstr,str[65]; cJSON *retjson; bits256 txid; - memset(txid.bytes,0,sizeof(txid)); - sprintf(numstr,"%.8f",(double)satoshis/SATOSHIDEN); - if ( (retjson= hushdex_cli(coin->cli,&retstr,"sendtoaddress",destaddr,numstr,"false","","","","")) != 0 ) - { - fprintf(stderr,"unexpected _hushdex_sendtoaddress json.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(txid.bytes,32,retstr); - } - fprintf(stderr,"_hushdex_sendtoaddress %s %.8f txid.(%s)\n",destaddr,(double)satoshis/SATOSHIDEN,bits256_str(str,txid)); - free(retstr); - } - return(txid); -} - -cJSON *_hushdex_rawtransaction(struct coininfo *coin,bits256 txid) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= hushdex_cli(coin->cli,&retstr,"getrawtransaction",bits256_str(str,txid),"1","","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"_hushdex_rawtransaction.(%s) %s error.(%s)\n",coin->coin,coin->name,retstr); - free(retstr); - } - return(0); -} - -int64_t hushdex_getbalance(struct coininfo *coin) -{ - char *coinstr,*acname=""; FILE *fp; int64_t retval = 0; - coinstr = coin->coin; - if ( coin->isfile != 0 ) - { - if ( (fp= fopen(coin->name+1,"rb")) != 0 ) // if alice, add bob pubkey to fname - { - fclose(fp); - retval = SATOSHIDEN; - } - return(retval); - } else if ( hushdex_zonly(coin) != 0 ) { - return(z_getbalance(coinstr,acname,DPOW_recvZaddr)); - } -} - -bits256 hushdex_coinpayment(uint32_t origid,int32_t OTCmode,struct coininfo *coin,char *destaddr,uint64_t paytoshis,char *memostr,char *destpub,char *senderpub) -{ - bits256 txid; char opidstr[128],opretstr[32],str[65],*status,*coinstr,*acname=""; cJSON *retjson,*retjson2,*item,*res; int32_t i,pending=0; - memset(&txid,0,sizeof(txid)); - if ( OTCmode == 0 ) - { - fprintf(stderr,"micropayment channels are not supported yet\n"); - return(txid); - } - if ( coin->isfile != 0 ) - { - fprintf(stderr,"start broadcast of (%s)\n",coin->coin+1); - if ( (retjson= dpow_publish(SUBATOMIC_PRIORITY,coin->coin+1)) != 0 ) // spawn thread - { - sprintf(opretstr,"%08x",juint(retjson,"id")); - sprintf(opidstr,"%u",origid); - if ( (retjson2= dpow_broadcast(SUBATOMIC_PRIORITY,opretstr,"inbox",opidstr,senderpub,"","")) != 0 ) - free_json(retjson2); - fprintf(stderr,"broadcast file.(%s) and send id.%u to alice (%s)\n",coin->coin+1,juint(retjson,"id"),jprint(retjson,0)); - txid = jbits256(retjson,"filehash"); - free_json(retjson); - } - fprintf(stderr,"end broadcast of (%s) to %s\n",coin->coin+1,senderpub); - return(txid); - } - else if ( hushdex_zonly(coin) != 0 ) - { - if ( memostr[0] == 0 ) - memostr = "beef"; - z_sendmany(opidstr,"",coin->coin,DPOW_recvZaddr,destaddr,paytoshis,memostr); - for (i=0; icoin,opidstr)) != 0 ) - { - item = jitem(retjson,0); - if ( (status= jstr(item,"status")) != 0 ) - { - if ( strcmp(status,"executing") == 0 ) - pending++; - else - { - res = jobj(item,"result"); - txid = jbits256(res,"txid"); - //fprintf(stderr,"got Ztx txid.%s\n",bits256_str(str,txid)); - free_json(retjson); - break; - } - /*else if ( clearresults != 0 ) - { - if ( (result= z_getoperationresult(coinstr,"",jstri(array,i))) != 0 ) - { - free_json(result); - } - }*/ - } - free_json(retjson); - } - sleep(1); - } - if ( i == 60 ) - printf("%u timed out waiting for opid to finish\n",origid); - } - else - { - coinstr = coin->coin; - if ( coin->istoken != 0 ) - txid = tokentransfer(coinstr,acname,coin->tokenid,destpub,paytoshis/SATOSHIDEN); - else if ( coin->isexternal == 0 ) - { - sprintf(opretstr,"%08x",origid); - txid = sendtoaddress(coinstr,acname,destaddr,paytoshis,opretstr); - } else txid = _hushdex_sendtoaddress(coin,destaddr,paytoshis); - printf("%u got txid.%s\n",origid,bits256_str(str,txid)); - } - return(txid); -} - -cJSON *hushdex_txidwait(struct coininfo *coin,bits256 txid,char *hexstr,int32_t numseconds,char *senderpub) -{ - int32_t i,zflag; char *coinstr,str[65],*acname=""; cJSON *rawtx; bits256 z; bits256 filehash; - memset(&z,0,sizeof(z)); - if ( memcmp(&z,&txid,sizeof(txid)) == 0 ) - return(0); - if ( hexstr != 0 && hexstr[0] != 0 ) // probably not worth doing and zaddr is a problem to decode - { - // compare against txid - // if matches, sendrawtransaction if OTC mode, decoode and return if channels mode - } - zflag = (hushdex_zonly(coin) != 0); - coinstr = coin->coin; - for (i=0; iisfile != 0 ) - { - if ( (rawtx= dpow_subscribe(SUBATOMIC_PRIORITY,coin->coin+1,senderpub)) != 0 ) - { - filehash = jbits256(rawtx,"filehash"); - if ( memcmp(&filehash,&txid,sizeof(filehash)) != 0 ) - { - fprintf(stderr,"waiting (%s) (%s)\n",coin->coin+1,jprint(rawtx,0)); - free_json(rawtx); - rawtx = 0; - } else return(rawtx); - } - } - else if ( zflag != 0 ) - rawtx = get_z_viewtransaction(coinstr,acname,txid); - else if ( coin->isexternal == 0 ) - rawtx = get_rawtransaction(coinstr,acname,txid); - else rawtx = _hushdex_rawtransaction(coin,txid); - if ( rawtx != 0 ) - return(rawtx); - sleep(1); - } - printf("%s/%s timeout waiting for %s\n",coin->name,coin->coin,bits256_str(str,txid)); - return(0); -} - -int64_t hushdex_verifypayment(struct coininfo *coin,cJSON *rawtx,uint64_t destsatoshis,char *destaddr,bits256 txid) -{ - int32_t i,n,m,valid=0; bits256 tokenid,filehash,checkhash; cJSON *array,*item,*sobj,*a; char *addr,*acname,*coinstr,tokenaddr[64],*hex; uint8_t hexbuf[512],pub33[33]; uint64_t netval,recvsatoshis = 0; - if ( coin->isfile != 0 ) - { - filehash = jbits256(rawtx,"filehash"); - checkhash = jbits256(rawtx,"checkhash"); - if ( memcmp(&txid,&filehash,sizeof(txid)) == 0 && memcmp(&txid,&checkhash,sizeof(txid)) == 0 ) - { - fprintf(stderr,"verified file is matching the filehash (%s)\n",jprint(rawtx,0)); - return(SATOSHIDEN); - } else return(0); - } - else if ( hushdex_zonly(coin) != 0 ) - { - if ( (array= jarray(&n,rawtx,"outputs")) != 0 && n > 0 ) - { - for (i=0; iistoken != 0 ) - { - if ( (array= jarray(&n,rawtx,"vout")) != 0 && n > 0 ) - { - item = jitem(array,0); - if ( (sobj= jobj(item,"scriptPubKey")) != 0 && (a= jarray(&m,sobj,"addresses")) != 0 && m == 1 ) - { - coinstr = coin->coin; - if ( get_tokenaddress(coinstr,acname,tokenaddr) != 0 ) - { - //fprintf(stderr,"tokenaddr.%s\n",tokenaddr); - if ( (addr= jstri(a,0)) != 0 && strcmp(addr,tokenaddr) == 0 ) - recvsatoshis += SATOSHIDEN * (uint64_t)(jdouble(item,"value")*SATOSHIDEN + 0.000000004999); - else fprintf(stderr,"miscompare (%s) vs %s\n",jprint(sobj,0),addr); - } - } - item = jitem(array,n-1); - if ( (sobj= jobj(item,"scriptPubKey")) != 0 && (hex= jstr(sobj,"hex")) != 0 && (m= is_hexstr(hex,0)) > 1 && m/2 < sizeof(hexbuf) ) - { - m >>= 1; - decode_hex(hexbuf,m,hex); - decode_hex(tokenid.bytes,32,coin->tokenid); - decode_hex(pub33,33,DPOW_secpkeystr); - // opret 69len EVAL_TOKENS 't' tokenid 1 33 pub33 - if ( hexbuf[0] == 0x6a && hexbuf[1] == 0x45 && hexbuf[2] == 0xf2 && hexbuf[3] == 't' && memcmp(&hexbuf[4],&tokenid,sizeof(tokenid)) == 0 && hexbuf[4+32] == 1 && hexbuf[4+32+1] == 33 && memcmp(&hexbuf[4+32+2],pub33,33) == 0 ) - { - valid = 1; - //fprintf(stderr,"validated it is a token transfer!\n"); - } else fprintf(stderr,"need to validate tokentransfer.(%s) %s %d\n",hex,DPOW_secpkeystr,memcmp(&hexbuf[4+32+2],pub33,33) == 0); - //6a 45 f2 74 2b1feef719ecb526b07416dd432bce603ac6dc8bfe794cddf105cb52f6aae3cd 01 21 02b27de3ee5335518b06f69f4fbabb029cfc737613b100996841d5532b324a5a61 - - } - recvsatoshis *= valid; - } - } - else - { - if ( (array= jarray(&n,rawtx,"vout")) != 0 && n > 0 ) - { - for (i=0; iorigid = origid; - HASH_ADD(hh,Messages,origid,sizeof(origid),mp); - return(mp); -} - -int32_t hushdex_status(struct msginfo *mp,int32_t status) -{ - static FILE *fp; - if ( fp == 0 ) - { - int32_t i,oid,s,n,num,count; struct msginfo *m; long fsize; - if ( (fp= fopen("SUBATOMIC.DB","rb+")) == 0 ) - { - if ( (fp= fopen("SUBATOMIC.DB","wb")) == 0 ) - { - fprintf(stderr,"cant create SUBATOMIC.DB\n"); - exit(-1); - } - } - else - { - fseek(fp,0,SEEK_END); - fsize = ftell(fp); - if ( (fsize % (sizeof(uint32_t)*2)) != 0 ) - { - fprintf(stderr,"SUBATOMIC.DB illegal filesize.%ld\n",fsize); - exit(-1); - } - n = (int32_t)(fsize / (sizeof(uint32_t)*2)); - rewind(fp); - for (i=num=count=0; i SUBATOMIC_CLOSED ) - { - fprintf(stderr,"SUBATOMIC.DB corrupted at filepos.%ld: illegal status.%d\n",ftell(fp),s); - exit(-1); - } - //fprintf(stderr,"%u <- %d\n",oid,s); - if ( (m= hushdex_find(oid)) == 0 ) - { - m = hushdex_add(oid); - count++; - } - if ( s > m->status ) - { - m->status = s; - num++; - } - } - fprintf(stderr,"initialized %d messages, updated %d out of total.%d\n",count,num,n); - } - } - if ( mp->status >= status ) - return(-1); - if ( fwrite(&mp->origid,1,sizeof(mp->origid),fp) != sizeof(mp->origid) || fwrite(&status,1,sizeof(status),fp) != sizeof(status) ) - fprintf(stderr,"error updating SUBATOMIC.DB, risk of double spends\n"); - fflush(fp); - mp->status = status; - return(0); -} - -struct msginfo *hushdex_tracker(uint32_t origid) -{ - struct msginfo *mp; - if ( (mp= hushdex_find(origid)) == 0 ) - { - mp = hushdex_add(origid); - hushdex_status(mp,0); - } - return(mp); -} - -char *hushdex_hexstr(char *jsonstr) -{ - char *hexstr; int32_t i,c,n = (int32_t)strlen(jsonstr); - hexstr = malloc(2*n + 3); - strcpy(hexstr,jsonstr); - for (i=0; iorigid); - jaddnum(item,"price",mp->price); - jaddnum(item,"openrequest",mp->openrequestid); - jaddstr(item,"base",mp->base.name); - jaddstr(item,"basecoin",mp->base.coin); - jadd64bits(item,"basesatoshis",mp->base.satoshis); - jadd64bits(item,"basetxfee",mp->base.txfee); - jadd64bits(item,"maxbaseamount",mp->base.maxamount); - jaddstr(item,"rel",mp->rel.name); - jaddstr(item,"relcoin",mp->rel.coin); - jadd64bits(item,"relsatoshis",mp->rel.satoshis); - jadd64bits(item,"reltxfee",mp->rel.txfee); - jadd64bits(item,"maxrelamount",mp->rel.maxamount); - jaddstr(item,"alice",mp->alice.pubkey); - jaddstr(item,"alicesecp",mp->alice.secp); - jaddstr(item,"bob",mp->bob.pubkey); - jaddstr(item,"bobsecp",mp->bob.secp); - if ( hushdex_zonly(&mp->rel) != 0 ) - jaddstr(item,"bobZaddr",mp->bob.recvZaddr); - else jaddstr(item,"bobaddr",mp->bob.recvaddr); - if ( mp->rel.istoken != 0 ) - jaddstr(item,"bobtoken",mp->rel.tokenid); - if ( hushdex_zonly(&mp->base) != 0 ) - jaddstr(item,"aliceZaddr",mp->alice.recvZaddr); - else jaddstr(item,"aliceaddr",mp->alice.recvaddr); - if ( mp->base.istoken != 0 ) - jaddstr(item,"alicetoken",mp->base.tokenid); - return(item); -} - -uint64_t hushdex_orderbook_mpset(struct msginfo *mp,char *basecheck) -{ - cJSON *retjson; char *tagA,*tagB,*senderpub,*str,tmpstr[32]; int32_t matches=0; double volA,volB; int64_t txfee=0; - strcpy(mp->base.name,basecheck); - strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,basecheck)); - mp->rel.txfee = hushdex_txfee(mp->rel.coin); - if ( (retjson= dpow_get(mp->origid)) != 0 ) - { - //fprintf(stderr,"dpow_get.(%s) (%s/%s)\n",jprint(retjson,0),mp->base.coin,mp->rel.coin); - if ( (senderpub= jstr(retjson,"senderpub")) != 0 && is_hexstr(senderpub,0) == 66 && (tagA= jstr(retjson,"tagA")) != 0 && (tagB= jstr(retjson,"tagB")) != 0 && strncmp(tagB,mp->rel.name,strlen(mp->rel.name)) == 0 && strlen(tagA) < sizeof(mp->base.name) ) - { - strcpy(mp->base.name,tagA); - strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,tagA)); - if ( basecheck[0] == 0 || strncmp(basecheck,tagA,strlen(basecheck)) == 0 ) - matches = 1; - else if ( strcmp(tagA,mp->base.name) == 0 ) - matches = 1; - else if ( mp->bobflag != 0 && tagA[0] == '#' && strcmp(mp->base.name,"#allfiles") == 0 ) - matches = 1; - if ( matches != 0 ) - { - if ( (str= jstr(retjson,"decrypted")) != 0 && strlen(str) < 128 ) - strcpy(mp->payload,str); - mp->locktime = juint(retjson,"timestamp") + SUBATOMIC_LOCKTIME; - mp->base.txfee = hushdex_txfee(mp->base.coin); - strcpy(mp->senderpub,senderpub); - volB = jdouble(retjson,"amountB"); - volA = jdouble(retjson,"amountA"); - mp->base.maxamount = volA*SATOSHIDEN + 0.0000000049999; - mp->rel.maxamount = volB*SATOSHIDEN + 0.0000000049999; - if ( 0 && mp->rel.istoken == 0 ) - txfee = mp->rel.txfee; - if ( mp->base.maxamount != 0 && mp->rel.maxamount != 0 && volA > SMALLVAL && volB > SMALLVAL && mp->rel.satoshis <= mp->rel.maxamount ) - { - mp->price = volA / volB; - mp->base.satoshis = (mp->rel.satoshis - txfee) * mp->price; - //fprintf(stderr,"base satoshis.%llu\n",(long long)mp->base.satoshis); - } else fprintf(stderr,"%u rel %llu vs (%llu %llu)\n",mp->origid,(long long)mp->rel.satoshis,(long long)mp->base.maxamount,(long long)mp->rel.maxamount); - } else printf("%u didnt match (%s) tagA.%s %s, tagB.%s %s %d %d\n",mp->origid,basecheck,tagA,mp->base.name,tagB,mp->rel.name,tagA[0] == '#', strcmp(mp->base.name,"#allfiles") == 0); - } else printf("%u didnt compare tagA.%s %s, tagB.%s %s\n",mp->origid,tagA,mp->base.name,tagB,mp->rel.name); - free_json(retjson); - } - return(mp->base.satoshis); -} - -char *randhashstr(char *str) -{ - bits256 rands; int32_t i; - for (i=0; i<32; i++) - rands.bytes[i] = rand() >> 17; - bits256_str(str,rands); - return(str); -} - -void hushdex_extrafields(cJSON *dest,cJSON *src) -{ - char *str; - if ( (str= jstr(src,"approval")) != 0 ) - jaddstr(dest,"approval",str); - if ( (str= jstr(src,"opened")) != 0 ) - jaddstr(dest,"opened",str); - if ( (str= jstr(src,"payamount")) != 0 ) - jaddstr(dest,"payamount",str); - if ( (str= jstr(src,"destaddr")) != 0 ) - jaddstr(dest,"destaddr",str); - if ( (str= jstr(src,"bobpayment")) != 0 ) - jaddstr(dest,"bobpayment",str); - if ( (str= jstr(src,"alicepayment")) != 0 ) - jaddstr(dest,"alicepayment",str); - if ( (str= jstr(src,"bobaddr")) != 0 ) - jaddstr(dest,"bobaddr",str); - if ( (str= jstr(src,"bobZaddr")) != 0 ) - jaddstr(dest,"bobZaddr",str); - if ( (str= jstr(src,"aliceaddr")) != 0 ) - jaddstr(dest,"aliceaddr",str); - if ( (str= jstr(src,"aliceZaddr")) != 0 ) - jaddstr(dest,"aliceZaddr",str); - if ( (str= jstr(src,"alicetoken")) != 0 ) - jaddstr(dest,"alicetoken",str); - if ( (str= jstr(src,"bobtoken")) != 0 ) - jaddstr(dest,"bobtoken",str); -} - -char *hushdex_submit(cJSON *argjson,int32_t tobob) -{ - char *jsonstr,*hexstr; - jaddnum(argjson,"tobob",tobob != 0); - jsonstr = jprint(argjson,1); - hexstr = hushdex_hexstr(jsonstr); - free(jsonstr); - return(hexstr); -} - -#define SCRIPT_OP_IF 0x63 -#define SCRIPT_OP_ELSE 0x67 -#define SCRIPT_OP_DUP 0x76 -#define SCRIPT_OP_ENDIF 0x68 -#define SCRIPT_OP_TRUE 0x51 -#define SCRIPT_OP_2 0x52 -#define SCRIPT_OP_3 0x53 -#define SCRIPT_OP_DROP 0x75 -#define SCRIPT_OP_EQUALVERIFY 0x88 -#define SCRIPT_OP_HASH160 0xa9 -#define SCRIPT_OP_EQUAL 0x87 -#define SCRIPT_OP_CHECKSIG 0xac -#define SCRIPT_OP_CHECKMULTISIG 0xae -#define SCRIPT_OP_CHECKMULTISIGVERIFY 0xaf -#define SCRIPT_OP_CHECKLOCKTIMEVERIFY 0xb1 - -int32_t hushdex_redeemscript(char *redeemscript,uint32_t locktime,char *pubkeyA,char *pubkeyB) // not needed -{ - // if ( refund ) OP_HASH160 <2of2 multisig hash> OP_EQUAL // standard multisig - // else CLTV OP_DROP OP_CHECKSIG // standard spend - uint8_t pubkeyAbytes[33],pubkeyBbytes[33],hex[4096]; int32_t i,n = 0; - decode_hex(pubkeyAbytes,33,pubkeyA); - decode_hex(pubkeyBbytes,33,pubkeyB); - hex[n++] = SCRIPT_OP_IF; - hex[n++] = SCRIPT_OP_2; - hex[n++] = 33, memcpy(&hex[n],pubkeyAbytes,33), n += 33; - hex[n++] = 33, memcpy(&hex[n],pubkeyBbytes,33), n += 33; - hex[n++] = SCRIPT_OP_2; - hex[n++] = SCRIPT_OP_CHECKMULTISIG; - hex[n++] = SCRIPT_OP_ELSE; - hex[n++] = 4; - hex[n++] = locktime & 0xff, locktime >>= 8; - hex[n++] = locktime & 0xff, locktime >>= 8; - hex[n++] = locktime & 0xff, locktime >>= 8; - hex[n++] = locktime & 0xff; - hex[n++] = SCRIPT_OP_CHECKLOCKTIMEVERIFY; - hex[n++] = SCRIPT_OP_DROP; - hex[n++] = 33; memcpy(&hex[n],pubkeyAbytes,33); n += 33; - hex[n++] = SCRIPT_OP_CHECKSIG; - hex[n++] = SCRIPT_OP_ENDIF; - for (i=0; i>4) & 0xf); - redeemscript[i*2 + 1] = hexbyte(hex[i] & 0xf); - } - redeemscript[n*2] = 0; - /*tmpbuf[0] = SCRIPT_OP_HASH160; - tmpbuf[1] = 20; - calc_OP_HASH160(scriptPubKey,tmpbuf+2,redeemscript); - tmpbuf[22] = SCRIPT_OP_EQUAL; - init_hexbytes_noT(scriptPubKey,tmpbuf,23); - if ( p2shaddr != 0 ) - { - p2shaddr[0] = 0; - if ( (btc_addr= base58_encode_check(addrtype,true,tmpbuf+2,20)) != 0 ) - { - if ( strlen(btc_addr->str) < 36 ) - strcpy(p2shaddr,btc_addr->str); - cstr_free(btc_addr,true); - } - }*/ - return(n); -} - -int32_t hushdex_approved(struct msginfo *mp,cJSON *approval,cJSON *msgjson,char *senderpub) -{ - char *hexstr,numstr[32],redeemscript[1024],*coin,*acname=""; cJSON *retjson,*decodejson; int32_t i,retval = 0; - hushdex_extrafields(approval,msgjson); - if ( mp->OTCmode == 0 ) - { - coin = (mp->bobflag != 0) ? mp->base.coin : mp->rel.coin; // the other side gets this coin - if ( get_createmultisig2(coin,acname,mp->msigaddr,mp->redeemscript,mp->alice.secp,mp->bob.secp) != 0 ) - { - hushdex_redeemscript(redeemscript,mp->locktime,mp->alice.secp,mp->bob.secp); - if ( (decodejson= get_decodescript(coin,acname,redeemscript)) != 0 ) - { - fprintf(stderr,"%s %s msigaddr.%s %s -> %s %s\n",mp->bobflag!=0?"bob":"alice",(mp->bobflag != 0) ? mp->base.coin : mp->rel.coin,mp->msigaddr,mp->redeemscript,redeemscript,jprint(decodejson,0)); - free(decodejson); - } - } - } - sprintf(numstr,"%u",mp->origid); - for (i=0; numstr[i]!=0; i++) - sprintf(&mp->approval[i<<1],"%02x",numstr[i]); - sprintf(&mp->approval[i<<1],"%02x",' '); - i++; - mp->approval[i<<1] = 0; - jaddstr(approval,"approval",mp->approval); - hexstr = hushdex_submit(approval,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"approved",senderpub,"","")) != 0 ) - { - if ( (mp->approvalid= juint(retjson,"id")) != 0 ) - retval = 1; - printf("%u approvalid.%u (%s)\n",mp->origid,mp->approvalid,senderpub); - hushdex_status(mp,SUBATOMIC_APPROVED); - free_json(retjson); - } - free(hexstr); - return(retval); -} - -int32_t hushdex_opened(struct msginfo *mp,cJSON *opened,cJSON *msgjson,char *senderpub) -{ - char *hexstr,channelstr[65]; cJSON *retjson; int32_t retval = 0; - hushdex_extrafields(opened,msgjson); - jaddstr(opened,"opened",randhashstr(channelstr)); - hexstr = hushdex_submit(opened,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"opened",senderpub,"","")) != 0 ) - { - if ( (mp->openedid= juint(retjson,"id")) != 0 ) - retval = 1; - printf("%u openedid.%u\n",mp->origid,mp->openedid); - hushdex_status(mp,SUBATOMIC_OPENED); - free_json(retjson); - } - free(hexstr); - return(retval); -} - -int32_t hushdex_payment(struct msginfo *mp,cJSON *payment,cJSON *msgjson,char *senderpub) -{ - bits256 txid; uint64_t paytoshis; cJSON *retjson; char numstr[32],*coin,*dest,*hexstr; int32_t retval = 0; - if ( mp->bobflag == 0 ) - { - coin = mp->rel.name; - paytoshis = mp->rel.satoshis; - if ( hushdex_zonly(&mp->rel) != 0 ) - dest = mp->bob.recvZaddr; - else dest = mp->bob.recvaddr; - sprintf(numstr,"%llu",(long long)paytoshis); - jaddstr(payment,"alicepays",numstr); - jaddstr(payment,"bobdestaddr",dest); - txid = hushdex_coinpayment(mp->origid,mp->OTCmode,&mp->rel,dest,paytoshis,mp->approval,mp->bob.secp,senderpub); - jaddbits256(payment,"alicepayment",txid); - mp->alicepayment = txid; - hexstr = 0; // get it from rawtransaction of txid - jaddstr(payment,"alicetx",hexstr); - } - else - { - coin = mp->base.name; - paytoshis = mp->base.satoshis; - if ( hushdex_zonly(&mp->base) != 0 ) - dest = mp->alice.recvZaddr; - else dest = mp->alice.recvaddr; - sprintf(numstr,"%llu",(long long)paytoshis); - jaddstr(payment,"bobpays",numstr); - jaddstr(payment,"alicedestaddr",dest); - txid = hushdex_coinpayment(mp->origid,mp->OTCmode,&mp->base,dest,paytoshis,mp->approval,mp->alice.secp,senderpub); - jaddbits256(payment,"bobpayment",txid); - mp->bobpayment = txid; - hexstr = 0; // get it from rawtransaction of txid - jaddstr(payment,"bobtx",hexstr); - } - hexstr = hushdex_submit(payment,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"payment",senderpub,"","")) != 0 ) - { - if ( (mp->paymentids[0]= juint(retjson,"id")) != 0 ) - retval = 1; - printf("%u: %.8f %s -> %s, paymentid[0] %u\n",mp->origid,dstr(paytoshis),coin,dest,mp->paymentids[0]); - hushdex_status(mp,SUBATOMIC_PAYMENT); - free_json(retjson); - } - free(hexstr); - return(retval); -} - -int32_t hushdex_paidinfull(struct msginfo *mp,cJSON *paid,cJSON *msgjson,char *senderpub) -{ - char *hexstr; cJSON *retjson; int32_t retval = 0; - jaddstr(paid,"paid","in full"); - hushdex_extrafields(paid,msgjson); - hexstr = hushdex_submit(paid,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"paid",senderpub,"","")) != 0 ) - { - if ( (mp->paidid= juint(retjson,"id")) != 0 ) - retval = 1; - printf("%u paidid.%u\n",mp->origid,mp->paidid); - hushdex_status(mp,SUBATOMIC_PAIDINFULL); - free_json(retjson); - } - free(hexstr); - return(retval); -} - -int32_t hushdex_closed(struct msginfo *mp,cJSON *closed,cJSON *msgjson,char *senderpub) -{ - char *hexstr; cJSON *retjson; int32_t retval = 0; - jaddnum(closed,"closed",mp->origid); - hushdex_extrafields(closed,msgjson); - hexstr = hushdex_submit(closed,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"closed",senderpub,"","")) != 0 ) - { - if ( (mp->closedid= juint(retjson,"id")) != 0 ) - retval = 1; - hushdex_status(mp,SUBATOMIC_CLOSED); - printf("%u closedid.%u\n",mp->origid,mp->closedid); - free_json(retjson); - } - free(hexstr); - return(retval); -} - -uint32_t hushdex_alice_openrequest(struct msginfo *origmp) -{ - struct msginfo *mp; cJSON *retjson,*openrequest; char *hexstr,*str,tmpstr[32]; - mp = hushdex_tracker(origmp->origid); - mp->origid = origmp->origid; - mp->rel.satoshis = origmp->rel.satoshis; - mp->rel.istoken = origmp->rel.istoken; - strcpy(mp->rel.tokenid,origmp->rel.tokenid); - strcpy(mp->rel.name,origmp->rel.name); - strcpy(mp->rel.coin,hushdex_checkname(tmpstr,mp,1,origmp->rel.name)); - strcpy(mp->alice.pubkey,DPOW_pubkeystr); - strcpy(mp->alice.secp,DPOW_secpkeystr); - strcpy(mp->alice.recvZaddr,DPOW_recvZaddr); - strcpy(mp->alice.recvaddr,DPOW_recvaddr); - printf("rel.%s/%s %s openrequest %u status.%d (%s/%s)\n",mp->rel.name,mp->rel.coin,mp->rel.tokenid,mp->origid,mp->status,mp->alice.recvaddr,mp->alice.recvZaddr); - if ( mp->status == 0 && hushdex_orderbook_mpset(mp,"") != 0 ) - { - strcpy(mp->bob.pubkey,mp->senderpub); - if ( hushdex_zonly(&mp->base) != 0 || hushdex_zonly(&mp->rel) != 0 ) - mp->OTCmode = 1; - else mp->OTCmode = SUBATOMIC_OTCDEFAULT; - strcpy(origmp->base.name,mp->base.name); - strcpy(origmp->base.coin,mp->base.coin); - origmp->base.istoken = mp->base.istoken; - strcpy(origmp->base.tokenid,mp->base.tokenid); - origmp->OTCmode = mp->OTCmode; - if ( mp->rel.istoken != 0 && ((mp->rel.satoshis % SATOSHIDEN) != 0 || mp->rel.iszaddr != 0) ) - { - printf("%u cant do zaddr or fractional rel %s.%s tokens %.8f\n",mp->origid,mp->rel.coin,mp->rel.tokenid,dstr(mp->rel.satoshis)); - return(0); - } - else if ( mp->base.istoken != 0 && ((mp->base.satoshis % SATOSHIDEN) != 0 || mp->base.iszaddr != 0 ) ) - { - printf("%u cant do zaddr or fractional base %s.%s tokens %.8f\n",mp->origid,mp->base.coin,mp->base.tokenid,dstr(mp->base.satoshis)); - return(0); - } - else if ( (openrequest= hushdex_mpjson(mp)) != 0 ) - { - hexstr = hushdex_submit(openrequest,!mp->bobflag); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,(char *)"inbox",(char *)"openrequest",mp->bob.pubkey,"","")) != 0 ) - { - mp->openrequestid = juint(retjson,"id"); - printf("%u openrequest.%u -> (%s)\n",mp->origid,mp->openrequestid,mp->bob.pubkey); - hushdex_status(mp,SUBATOMIC_OPENREQUEST); - free_json(retjson); - } - free(hexstr); - } - } - return(mp->openrequestid); -} - -void hushdex_bob_gotopenrequest(uint32_t inboxid,char *senderpub,cJSON *msgjson,char *basename,char *relname) -{ - struct msginfo *mp; cJSON *approval; int32_t origid; char *addr,tmpstr[32],*coin,*acname=""; - origid = juint(msgjson,"origid"); - mp = hushdex_tracker(origid); - strcpy(mp->base.name,basename); - strcpy(mp->base.coin,hushdex_checkname(tmpstr,mp,0,basename)); - strcpy(mp->rel.name,relname); - strcpy(mp->rel.coin,hushdex_checkname(tmpstr,mp,1,relname)); - mp->origid = origid; - mp->rel.satoshis = j64bits(msgjson,"relsatoshis"); - mp->bobflag = 1; - strcpy(mp->bob.pubkey,DPOW_pubkeystr); - strcpy(mp->bob.secp,DPOW_secpkeystr); - strcpy(mp->bob.recvZaddr,DPOW_recvZaddr); - strcpy(mp->bob.recvaddr,DPOW_recvaddr); - if ( (addr= jstr(msgjson,"aliceaddr")) != 0 ) - strcpy(mp->alice.recvaddr,addr); - if ( (addr= jstr(msgjson,"aliceZaddr")) != 0 ) - strcpy(mp->alice.recvZaddr,addr); - if ( (addr= jstr(msgjson,"alicesecp")) != 0 ) - strcpy(mp->alice.secp,addr); - if ( hushdex_zonly(&mp->base) != 0 || hushdex_zonly(&mp->rel) != 0 ) - mp->OTCmode = 1; - else mp->OTCmode = SUBATOMIC_OTCDEFAULT; - printf("%u got open request\n",mp->origid); - if ( mp->status == 0 && hushdex_orderbook_mpset(mp,basename) != 0 && (approval= hushdex_mpjson(mp)) != 0 ) - { - if ( mp->rel.istoken != 0 && ((mp->rel.satoshis % SATOSHIDEN) != 0 || mp->rel.iszaddr != 0) ) - { - printf("%u cant do zaddr or fractional rel %s.%s tokens %.8f\n",mp->origid,mp->rel.coin,mp->rel.tokenid,dstr(mp->rel.satoshis)); - hushdex_closed(mp,approval,msgjson,senderpub); - return; - } - else if ( mp->base.istoken != 0 && ((mp->base.satoshis % SATOSHIDEN) != 0 || mp->base.iszaddr != 0 ) ) - { - printf("%u cant do zaddr or fractional base %s.%s tokens %.8f\n",mp->origid,mp->base.coin,mp->base.tokenid,dstr(mp->base.satoshis)); - hushdex_closed(mp,approval,msgjson,senderpub); - return; - } - else if ( hushdex_getbalance(&mp->base) < mp->base.satoshis ) - { - printf("%u bob node low on %s funds! %.8f not enough for %.8f\n",mp->origid,mp->base.coin,dstr(hushdex_getbalance(&mp->base)),dstr(mp->base.satoshis)); - hushdex_closed(mp,approval,msgjson,senderpub); - } - else - { - printf("%u bob (%s/%s) gotopenrequest origid.%u status.%d (%s/%s) SENDERPUB.(%s)\n",mp->origid,mp->base.name,mp->rel.name,mp->origid,mp->status,mp->bob.recvaddr,mp->bob.recvZaddr,senderpub); - hushdex_approved(mp,approval,msgjson,senderpub); - } - } -} - -int32_t hushdex_channelapproved(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp) -{ - struct msginfo *mp; cJSON *approval; char *addr,*coin,*acname; int32_t retval = 0; - mp = hushdex_tracker(juint(msgjson,"origid")); - if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (approval= hushdex_mpjson(mp)) != 0 ) - { - printf("%u iambob.%d (%s/%s) channelapproved origid.%u status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->origid,mp->status); - if ( mp->bobflag == 0 && mp->status == SUBATOMIC_OPENREQUEST ) - { - if ( (addr= jstr(msgjson,"bobaddr")) != 0 ) - strcpy(mp->bob.recvaddr,addr); - if ( (addr= jstr(msgjson,"bobZaddr")) != 0 ) - strcpy(mp->bob.recvZaddr,addr); - if ( (addr= jstr(msgjson,"bobsecp")) != 0 ) - strcpy(mp->bob.secp,addr); - retval = hushdex_approved(mp,approval,msgjson,senderpub); - } - else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_APPROVED ) - retval = hushdex_opened(mp,approval,msgjson,senderpub); - } - return(retval); -} - -int32_t hushdex_incomingopened(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp) -{ - struct msginfo *mp; cJSON *payment; int32_t retval = 0; - mp = hushdex_tracker(juint(msgjson,"origid")); - if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (payment= hushdex_mpjson(mp)) != 0 ) - { - printf("%u iambob.%d (%s/%s) incomingchannel status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status); - if ( mp->bobflag == 0 && mp->status == SUBATOMIC_APPROVED ) - retval = hushdex_payment(mp,payment,msgjson,senderpub); - else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_OPENED ) - retval = 1; // nothing to do - } - return(retval); -} - -int32_t hushdex_incomingpayment(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp) -{ - static FILE *fp; - struct msginfo *mp; cJSON *pay,*rawtx,*retjson; bits256 txid; char str[65],*hexstr; int32_t retval = 0; - mp = hushdex_tracker(juint(msgjson,"origid")); - if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (pay= hushdex_mpjson(mp)) != 0 ) - { - printf("%u iambob.%d (%s/%s) incomingpayment status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status); - if ( mp->bobflag == 0 ) - { - txid = jbits256(msgjson,"bobpayment"); - jaddbits256(msgjson,"alicepayment",mp->alicepayment); - printf("%u alice waits for %s.%s to be in mempool (%.8f -> %s)\n",mp->origid,mp->base.name,bits256_str(str,txid),dstr(mp->base.satoshis),hushdex_zonly(&mp->base) == 0 ? mp->alice.recvaddr : mp->alice.recvZaddr); - hexstr = jstr(msgjson,"bobtx"); - if ( (rawtx= hushdex_txidwait(&mp->base,txid,hexstr,SUBATOMIC_TIMEOUT,senderpub)) != 0 ) - { - if ( hushdex_verifypayment(&mp->base,rawtx,mp->base.satoshis,hushdex_zonly(&mp->base) == 0 ? mp->alice.recvaddr : mp->alice.recvZaddr,txid) >= 0 ) - mp->gotpayment = 1; - free_json(rawtx); - } - if ( mp->gotpayment != 0 ) - { - printf("%u SWAP COMPLETE <<<<<<<<<<<<<<<<\n",mp->origid); - SUBATOMIC_retval = 0; - if ( mp->base.iszaddr == 0 ) - { - sprintf(str,"%u",mp->origid); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->alicepayment),(char *)"completed",str,DPOW_pubkeystr,"","")) != 0 ) - free_json(retjson); - } - } - else - { - printf("%u SWAP INCOMPLETE, waiting on %s.%s\n",mp->origid,mp->base.name,bits256_str(str,txid)); - if ( (fp= fopen("SUBATOMIC.incomplete","a+")) != 0 ) - { - char *jsonstr = jprint(msgjson,0); - fwrite(jsonstr,1,strlen(jsonstr),fp); - fputc('\n',fp); - fclose(fp); - free(jsonstr); - } - if ( mp->base.iszaddr == 0 ) - { - sprintf(str,"%u",mp->origid); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->alicepayment),(char *)"incomplete",str,DPOW_pubkeystr,"","")) != 0 ) - free_json(retjson); - } - hushdex_closed(mp,pay,msgjson,senderpub); - exit(-1); - } - } - if ( mp->gotpayment != 0 ) - retval = hushdex_paidinfull(mp,pay,msgjson,senderpub); - else - { - if ( mp->bobflag != 0 && mp->status == SUBATOMIC_OPENED ) - { - txid = jbits256(msgjson,"alicepayment"); - printf("%u bob waits for %s.%s to be in mempool (%.8f -> %s)\n",mp->origid,mp->rel.name,bits256_str(str,txid),dstr(mp->rel.satoshis),hushdex_zonly(&mp->rel) == 0 ? mp->bob.recvaddr : mp->bob.recvZaddr); - hexstr = jstr(msgjson,"alicetx"); - if ( (rawtx= hushdex_txidwait(&mp->rel,txid,hexstr,SUBATOMIC_TIMEOUT,senderpub)) != 0 ) - { - if ( hushdex_verifypayment(&mp->rel,rawtx,mp->rel.satoshis,hushdex_zonly(&mp->rel) == 0 ? mp->bob.recvaddr : mp->bob.recvZaddr,txid) >= 0 ) - mp->gotpayment = 1; - free_json(rawtx); - } - if ( mp->gotpayment != 0 ) - { - retval = hushdex_payment(mp,pay,msgjson,senderpub); - jaddbits256(msgjson,"bobpayment",mp->bobpayment); - if ( mp->rel.iszaddr == 0 ) - { - sprintf(str,"%u",mp->origid); - if ( (retjson= dpow_broadcast(SUBATOMIC_PRIORITY,bits256_str(str,mp->bobpayment),(char *)"completed",str,DPOW_pubkeystr,"","")) != 0 ) - free_json(retjson); - } - printf("%u SWAP COMPLETE <<<<<<<<<<<<<<<<\n",mp->origid); - if ( (fp= fopen("SUBATOMIC.proof","rb+")) == 0 ) - fp = fopen("SUBATOMIC.proof","wb"); - if ( fp != 0 ) - { - char *jsonstr = jprint(msgjson,0); - fseek(fp,0,SEEK_END); - fwrite(jsonstr,1,strlen(jsonstr),fp); - fputc('\n',fp); - fflush(fp); - free(jsonstr); - } - } else printf("%u SWAP INCOMPLETE: %s\n",mp->origid,jprint(msgjson,0)); - } - } - } - return(retval); -} - -int32_t hushdex_incomingfullypaid(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp) -{ - struct msginfo *mp; cJSON *closed; int32_t retval = 0; - mp = hushdex_tracker(juint(msgjson,"origid")); - if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (closed= hushdex_mpjson(mp)) != 0 ) - { - printf("%u iambob.%d (%s/%s) incomingfullypaid status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status); - // error check msgjson vs M - if ( mp->bobflag == 0 && mp->status == SUBATOMIC_PAIDINFULL ) - retval = hushdex_closed(mp,closed,msgjson,senderpub); - else if ( mp->bobflag != 0 && mp->status == SUBATOMIC_PAYMENT ) - retval = hushdex_paidinfull(mp,closed,msgjson,senderpub); - } - return(retval); -} - -int32_t hushdex_incomingclosed(uint32_t inboxid,char *senderpub,cJSON *msgjson,struct msginfo *origmp) -{ - struct msginfo *mp; cJSON *closed; int32_t retval = 0; - mp = hushdex_tracker(juint(msgjson,"origid")); - if ( hushdex_orderbook_mpset(mp,mp->base.name) != 0 && (closed= hushdex_mpjson(mp)) != 0 ) - { - printf("%u iambob.%d (%s/%s) incomingclose status.%d\n",mp->origid,mp->bobflag,mp->base.name,mp->rel.name,mp->status); - if ( mp->bobflag != 0 ) - dpow_cancel(mp->origid); - if ( mp->status < SUBATOMIC_CLOSED ) - { - retval = hushdex_closed(mp,closed,msgjson,senderpub); - hushdex_status(mp,SUBATOMIC_CLOSED); - } - retval = 1; - } - return(retval); -} - -int32_t hushdex_ismine(int32_t bobflag,cJSON *json,char *basename,char *relname) -{ - char *base,*rel; - if ( (base= jstr(json,"base")) != 0 && (rel= jstr(json,"rel")) != 0 ) - { - if ( strcmp(base,basename) == 0 && strcmp(rel,relname) == 0 ) - return(1); - if ( bobflag != 0 ) - { - if ( strcmp(basename,"#allfiles") == 0 && base[0] == '#' ) - return(1); - fprintf(stderr,"skip ismine (%s/%s) vs (%s/%s)\n",basename,relname,base,rel); - } - } - return(0); -} - -void hushdex_tokensregister(int32_t priority) -{ - char *token_name,*tokenid,existing[65]; cJSON *tokens,*token; int32_t i,numtokens; - if ( SUBATOMIC_json != 0 && (tokens= jarray(&numtokens,SUBATOMIC_json,"tokens")) != 0 ) - { - for (i=0; i 0 ) - { - for (j=0; j %s, %u %llu %u\n",mp->bobflag,mp->base.name,mp->rel.name,mp->origid,(long long)mp->rel.satoshis,mp->openrequestid); - while ( 1 ) - { - if ( msgs == 0 ) - { - sleep(1); - fflush(stdout); - if ( mp->bobflag != 0 ) - { - dpow_pubkeyregister(SUBATOMIC_PRIORITY); - hushdex_tokensregister(SUBATOMIC_PRIORITY); - hushdex_filesregister(SUBATOMIC_PRIORITY); - } - } - msgs = 0; - for (iter=0; iter<(int32_t)(sizeof(tagBs)/sizeof(*tagBs)); iter++) - { - tagB = tagBs[iter]; - if ( (ptrs= dpow_inboxcheck(&n,&stopats[iter],tagB)) != 0 ) - { - for (i=0; ijsonstr)) != 0 ) - { - if ( jint(inboxjson,"tobob") != mp->bobflag ) - continue; - if ( hushdex_ismine(mp->bobflag,inboxjson,mp->base.name,mp->rel.name) != 0 ) - { - if ( strcmp(tagB,"openrequest") == 0 && mp->bobflag != 0 ) - hushdex_bob_gotopenrequest(ptr->shorthash,ptr->senderpub,inboxjson,mp->base.name,mp->rel.name); - else if ( strcmp(tagB,"approved") == 0 ) - mask |= hushdex_channelapproved(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 0; - else if ( strcmp(tagB,"opened") == 0 ) - mask |= hushdex_incomingopened(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 1; - else if ( strcmp(tagB,"payment") == 0 ) - mask |= hushdex_incomingpayment(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 2; - else if ( strcmp(tagB,"paid") == 0 ) - mask |= hushdex_incomingfullypaid(ptr->shorthash,ptr->senderpub,inboxjson,mp) << 3; - else if ( strcmp(tagB,"closed") == 0 ) - mask |= hushdex_incomingclosed(ptr->shorthash,ptr->senderpub,inboxjson,mp) * 0x1f; - else fprintf(stderr,"iambob.%d unknown unexpected tagB.(%s)\n",mp->bobflag,tagB); - } - free_json(inboxjson); - } else fprintf(stderr,"hushdex iambob.%d loop got unparseable(%s)\n",mp->bobflag,ptr->jsonstr); - free(ptr); - ptrs[i] = 0; - } - } - free(ptrs); - } - } - if ( mp->bobflag == 0 && (mask & 0x1f) == 0x1f ) - { - printf("alice %u %llu %u finished\n",mp->origid,(long long)mp->rel.satoshis,mp->openrequestid); - break; - } - } -} - -int32_t main(int32_t argc,char **argv) -{ - char *fname = "hushdex.json"; - int32_t i,height; char *coin,*kcli,*hushdex,*hashstr,*acname=(char *)""; cJSON *retjson; bits256 blockhash; char checkstr[65],str[65],str2[65],tmpstr[32]; long fsize; struct msginfo M; - memset(&M,0,sizeof(M)); - srand((int32_t)time(NULL)); - if ( (hushdex= filestr(&fsize,fname)) == 0 ) - { - fprintf(stderr,"cant load %s file\n",fname); - exit(-1); - } - if ( (SUBATOMIC_json= cJSON_Parse(hushdex)) == 0 ) - { - fprintf(stderr,"cant parse hushdex.json file (%s)\n",hushdex); - exit(-1); - } - free(hushdex); - if ( argc >= 4 ) - { - if ( dpow_pubkey() < 0 ) - { - fprintf(stderr,"couldnt set pubkey for ZEX\n"); - return(-1); - } - coin = (char *)argv[1]; - if ( argv[2][0] != 0 ) { - REFCOIN_CLI = (char *)argv[2]; - } else { - acname = coin; - } - hashstr = (char *)argv[3]; - strcpy(M.rel.coin,hushdex_checkname(tmpstr,&M,1,coin)); - strcpy(M.rel.name,coin); - if ( argc == 4 && strlen(hashstr) == 64 ) // for blocknotify usage, seems not needed - { - height = get_coinheight(coin,acname,&blockhash); - bits256_str(checkstr,blockhash); - if ( strcmp(checkstr,hashstr) == 0 ) - { - fprintf(stderr,"%s: (%s) %s height.%d\n",coin,REFCOIN_CLI!=0?REFCOIN_CLI:"",checkstr,height); - if ( (retjson= dpow_ntzdata(coin,SUBATOMIC_PRIORITY,height,blockhash)) != 0 ) - free_json(retjson); - } else fprintf(stderr,"coin.%s (%s) %s vs %s, height.%d\n",coin,REFCOIN_CLI!=0?REFCOIN_CLI:"",checkstr,hashstr,height); - if ( strcmp("BTC",coin) != 0 ) - { - bits256 prevntzhash,ntzhash; int32_t prevntzheight,ntzheight; uint32_t ntztime,prevntztime; char hexstr[81]; cJSON *retjson2; - prevntzhash = dpow_ntzhash(coin,&prevntzheight,&prevntztime); - if ( (retjson= get_getinfo(coin,acname)) != 0 ) - { - ntzheight = juint(retjson,"notarized"); - ntzhash = jbits256(retjson,"notarizedhash"); - if ( ntzheight > prevntzheight ) - { - get_coinmerkleroot(coin,acname,ntzhash,&ntztime); - fprintf(stderr,"NOTARIZATION %s.%d %s t.%u\n",coin,ntzheight,bits256_str(str,ntzhash),ntztime); - bits256_str(hexstr,ntzhash); - sprintf(&hexstr[64],"%08x",ntzheight); - sprintf(&hexstr[72],"%08x",ntztime); - hexstr[80] = 0; - if ( (retjson2= dpow_broadcast(SUBATOMIC_PRIORITY,hexstr,coin,"notarizations",DPOW_pubkeystr,"","")) != 0 ) - free_json(retjson2); - } - else if ( ntzheight == prevntzheight && memcmp(&prevntzhash,&ntzhash,32) != 0 ) - fprintf(stderr,"NTZ ERROR %s.%d %s != %s\n",coin,ntzheight,bits256_str(str,ntzhash),bits256_str(str2,prevntzhash)); - free_json(retjson); - } - } - } - else if ( argc == 5 && atol(hashstr) > 10000 ) - { - char checkstr[32]; uint64_t mult = 1; - M.origid = (uint32_t)atol(hashstr); - sprintf(checkstr,"%u",M.origid); - if ( strcmp(checkstr,hashstr) == 0 ) // alice - { - M.rel.satoshis = (uint64_t)(atof(argv[4])*SATOSHIDEN+0.0000000049999); - for (i=0; M.rel.name[i]!=0; i++) - if ( M.rel.name[i] == '.' ) - { - mult = SATOSHIDEN; - break; - } - if ( hushdex_getbalance(&M.rel) < M.rel.satoshis/mult ) - { - fprintf(stderr,"not enough balance %s %.8f for %.8f\n",M.rel.coin,dstr(hushdex_getbalance(&M.rel)),dstr(M.rel.satoshis/mult)); - return(-1); - } - fprintf(stderr,"hushdex_channel_alice (%s/%s) %s %u with %.8f %llu\n",M.rel.name,M.rel.coin,hashstr,M.origid,atof(argv[4]),(long long)M.rel.satoshis); - dpow_pubkeyregister(SUBATOMIC_PRIORITY); - M.openrequestid = hushdex_alice_openrequest(&M); - if ( M.openrequestid != 0 ) - hushdex_loop(&M); - } else fprintf(stderr,"checkstr mismatch %s %s != %s\n",coin,hashstr,checkstr); - } - else - { - M.bobflag = 1; - strcpy(M.base.name,hashstr); - strcpy(M.base.coin,hushdex_checkname(tmpstr,&M,0,hashstr)); - hushdex_loop(&M); // while ( 1 ) loop for each relcoin -> basecoin - } - } - return(SUBATOMIC_retval); -} - diff --git a/src/cc/dapps/hushdex.json b/src/cc/dapps/hushdex.json deleted file mode 100644 index dbb7df058..000000000 --- a/src/cc/dapps/hushdex.json +++ /dev/null @@ -1,19 +0,0 @@ -{ -"authorized": [ - {"dukeleto":"030554bffcf6dfcb34a20c486ff0a5be5546b9cc16fba969216527263f8e98c4af" }, - {"gilardh":"020554bffcf6dfcb34a20c486ff5a5be5546b9cc06fba9692165272b3f8e98c448" }, - {"nhdigitalcash":"030554bffcf6dfcb34a20c086ff5a5be5546b9cc16fba9692105272b3f8e98c4a0" }, - {"miodrag":"02b25de3ee5335518b06f69f4fbabb029cfc737603b100996841d5532b324a5a61" } -], -"tokens":[ -], -"files":[ - {"filename":"hushd","prices":[{"HUSH":0.1}, {"ZEC":1}]} -], -"externalcoins":[ - { "BTC":"bitcoin-cli" }, - { "HUSH":"hush-cli" }, - { "ZEC":"zcash-cli" } -] -} - diff --git a/src/cc/dapps/makedapps b/src/cc/dapps/makedapps deleted file mode 100755 index 3a86f68da..000000000 --- a/src/cc/dapps/makedapps +++ /dev/null @@ -1,2 +0,0 @@ -gcc -o oraclefeed cc/dapps/oraclefeed.c -lm -gcc -o zmigrate cc/dapps/zmigrate.c -lm diff --git a/src/cc/dapps/zmigrate.c b/src/cc/dapps/zmigrate.c deleted file mode 100644 index 4ccbc3636..000000000 --- a/src/cc/dapps/zmigrate.c +++ /dev/null @@ -1,1529 +0,0 @@ -// Copyright (c) 2016-2024 The Hush developers -// Distributed under the GPLv3 software license, see the accompanying -// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - -#include -#include -#include -#include -#include "cJSON.c" - -/* -NOTE: HUSH nor any Hush Arrakis Chain has any sprout outputs. This code is kept for historical and educational purposes. - - z_migrate: the purpose of z_migrate is to make converting of all sprout outputs into sapling. the usage would be for the user to specify a sapling address and call z_migrate zsaddr, until it returns that there is nothing left to be done. - - its main functionality is quite similar to a z_mergetoaddress ANY_ZADDR -> onetime_taddr followed by a z_sendmany onetime_taddr -> zsaddr - - since the z_mergetoaddress will take time, it would just queue up an async operation. When it starts, it should see if there are any onetime_taddr with 10000.0001 funds in it, that is a signal for it to do the sapling tx and it can just do that without async as it is fast enough, especially with a taddr input. Maybe it limits itself to one, or it does all possible taddr -> sapling as fast as it can. either is fine as it will be called over and over anyway. - - It might be that there is nothing to do, but some operations are pending. in that case it would return such a status. as soon as the operation finishes, there would be more work to do. - - the amount sent to the taddr, should be 10000.0001 - - The GUI or user would be expected to generate a sapling address and then call z_migrate saplingaddr in a loop, until it returns that it is all done. this loop should pause for 10 seconds or so, if z_migrate is just waiting for opid to complete. - */ - -bits256 zeroid; - -char hexbyte(int32_t c) -{ - c &= 0xf; - if ( c < 10 ) - return('0'+c); - else if ( c < 16 ) - return('a'+c-10); - else return(0); -} - -int32_t _unhex(char c) -{ - if ( c >= '0' && c <= '9' ) - return(c - '0'); - else if ( c >= 'a' && c <= 'f' ) - return(c - 'a' + 10); - else if ( c >= 'A' && c <= 'F' ) - return(c - 'A' + 10); - return(-1); -} - -int32_t is_hexstr(char *str,int32_t n) -{ - int32_t i; - if ( str == 0 || str[0] == 0 ) - return(0); - for (i=0; str[i]!=0; i++) - { - if ( n > 0 && i >= n ) - break; - if ( _unhex(str[i]) < 0 ) - break; - } - if ( n == 0 ) - return(i); - return(i == n); -} - -int32_t unhex(char c) -{ - int32_t hex; - if ( (hex= _unhex(c)) < 0 ) - { - //printf("unhex: illegal hexchar.(%c)\n",c); - } - return(hex); -} - -unsigned char _decode_hex(char *hex) { return((unhex(hex[0])<<4) | unhex(hex[1])); } - -int32_t decode_hex(unsigned char *bytes,int32_t n,char *hex) -{ - int32_t adjust,i = 0; - //printf("decode.(%s)\n",hex); - if ( is_hexstr(hex,n) <= 0 ) - { - memset(bytes,0,n); - return(n); - } - if ( hex[n-1] == '\n' || hex[n-1] == '\r' ) - hex[--n] = 0; - if ( hex[n-1] == '\n' || hex[n-1] == '\r' ) - hex[--n] = 0; - if ( n == 0 || (hex[n*2+1] == 0 && hex[n*2] != 0) ) - { - if ( n > 0 ) - { - bytes[0] = unhex(hex[0]); - printf("decode_hex n.%d hex[0] (%c) -> %d hex.(%s) [n*2+1: %d] [n*2: %d %c] len.%ld\n",n,hex[0],bytes[0],hex,hex[n*2+1],hex[n*2],hex[n*2],(long)strlen(hex)); - } - bytes++; - hex++; - adjust = 1; - } else adjust = 0; - if ( n > 0 ) - { - for (i=0; i>4) & 0xf); - hexbytes[i*2 + 1] = hexbyte(message[i] & 0xf); - //printf("i.%d (%02x) [%c%c]\n",i,message[i],hexbytes[i*2],hexbytes[i*2+1]); - } - hexbytes[len*2] = 0; - //printf("len.%ld\n",len*2+1); - return((int32_t)len*2+1); -} - -long _stripwhite(char *buf,int accept) -{ - int32_t i,j,c; - if ( buf == 0 || buf[0] == 0 ) - return(0); - for (i=j=0; buf[i]!=0; i++) - { - buf[j] = c = buf[i]; - if ( c == accept || (c != ' ' && c != '\n' && c != '\r' && c != '\t' && c != '\b') ) - j++; - } - buf[j] = 0; - return(j); -} - -char *clonestr(char *str) -{ - char *clone; - if ( str == 0 || str[0]==0) - { - printf("warning cloning nullstr.%p\n",str); - //#ifdef __APPLE__ - // while ( 1 ) sleep(1); - //#endif - str = (char *)""; - } - clone = (char *)malloc(strlen(str)+16); - strcpy(clone,str); - return(clone); -} - -int32_t safecopy(char *dest,char *src,long len) -{ - int32_t i = -1; - if ( src != 0 && dest != 0 && src != dest ) - { - if ( dest != 0 ) - memset(dest,0,len); - for (i=0; i0; i--) - str[i] = str[i-1]; - str[0] = '/'; - str[n+1] = 0; - }*/ -#endif - return(str); -#endif -} - -void *loadfile(char *fname,uint8_t **bufp,long *lenp,long *allocsizep) -{ - FILE *fp; - long filesize,buflen = *allocsizep; - uint8_t *buf = *bufp; - *lenp = 0; - if ( (fp= fopen(portable_path(fname),"rb")) != 0 ) - { - fseek(fp,0,SEEK_END); - filesize = ftell(fp); - if ( filesize == 0 ) - { - fclose(fp); - *lenp = 0; - printf("loadfile null size.(%s)\n",fname); - return(0); - } - if ( filesize > buflen ) - { - *allocsizep = filesize; - *bufp = buf = (uint8_t *)realloc(buf,(long)*allocsizep+64); - } - rewind(fp); - if ( buf == 0 ) - printf("Null buf ???\n"); - else - { - if ( fread(buf,1,(long)filesize,fp) != (unsigned long)filesize ) - printf("error reading filesize.%ld\n",(long)filesize); - buf[filesize] = 0; - } - fclose(fp); - *lenp = filesize; - //printf("loaded.(%s)\n",buf); - } //else printf("OS_loadfile couldnt load.(%s)\n",fname); - return(buf); -} - -void *filestr(long *allocsizep,char *_fname) -{ - long filesize = 0; char *fname,*buf = 0; void *retptr; - *allocsizep = 0; - fname = malloc(strlen(_fname)+1); - strcpy(fname,_fname); - retptr = loadfile(fname,(uint8_t **)&buf,&filesize,allocsizep); - free(fname); - return(retptr); -} - -char *send_curl(char *url,char *fname) -{ - long fsize; char curlstr[1024]; - sprintf(curlstr,"curl --url \"%s\" > %s",url,fname); - system(curlstr); - return(filestr(&fsize,fname)); -} - -cJSON *get_urljson(char *url,char *fname) -{ - char *jsonstr; cJSON *json = 0; - if ( (jsonstr= send_curl(url,fname)) != 0 ) - { - //printf("(%s) -> (%s)\n",url,jsonstr); - json = cJSON_Parse(jsonstr); - free(jsonstr); - } - return(json); -} - -////////////////////////////////////////////// -// start of dapp -////////////////////////////////////////////// - -char *REFCOIN_CLI; - -cJSON *get_hushcli(char *refcoin,char **retstrp,char *acname,char *method,char *arg0,char *arg1,char *arg2,char *arg3) -{ - long fsize; cJSON *retjson = 0; char cmdstr[32768],*jsonstr,fname[256]; - sprintf(fname,"/tmp/zmigrate.%s",method); - if ( acname[0] != 0 ) - { - if ( refcoin[0] != 0 && strcmp(refcoin,"HUSH3") != 0 ) - printf("unexpected: refcoin.(%s) acname.(%s)\n",refcoin,acname); - sprintf(cmdstr,"./hush-cli -ac_name=%s %s %s %s %s %s > %s\n",acname,method,arg0,arg1,arg2,arg3,fname); - } - else if ( strcmp(refcoin,"HUSH3") == 0 ) - sprintf(cmdstr,"./hush-cli %s %s %s %s %s > %s\n",method,arg0,arg1,arg2,arg3,fname); - else if ( REFCOIN_CLI != 0 && REFCOIN_CLI[0] != 0 ) - { - sprintf(cmdstr,"%s %s %s %s %s %s > %s\n",REFCOIN_CLI,method,arg0,arg1,arg2,arg3,fname); - //printf("ref.(%s) REFCOIN_CLI (%s)\n",refcoin,cmdstr); - } - system(cmdstr); - *retstrp = 0; - if ( (jsonstr= filestr(&fsize,fname)) != 0 ) - { - jsonstr[strlen(jsonstr)-1]='\0'; - //fprintf(stderr,"%s -> jsonstr.(%s)\n",cmdstr,jsonstr); - if ( (jsonstr[0] != '{' && jsonstr[0] != '[') || (retjson= cJSON_Parse(jsonstr)) == 0 ) - *retstrp = jsonstr; - else free(jsonstr); - } - return(retjson); -} - -bits256 sendtoaddress(char *refcoin,char *acname,char *destaddr,int64_t satoshis) -{ - char numstr[32],*retstr,str[65]; cJSON *retjson; bits256 txid; - memset(txid.bytes,0,sizeof(txid)); - sprintf(numstr,"%.8f",(double)satoshis/SATOSHIDEN); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"sendtoaddress",destaddr,numstr,"","")) != 0 ) - { - fprintf(stderr,"unexpected sendrawtransaction json.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(txid.bytes,32,retstr); - } - fprintf(stderr,"sendtoaddress %s %.8f txid.(%s)\n",destaddr,(double)satoshis/SATOSHIDEN,bits256_str(str,txid)); - free(retstr); - } - return(txid); -} - -int32_t get_coinheight(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr; int32_t height=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockchaininfo","","","","")) != 0 ) - { - height = jint(retjson,"blocks"); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"%s get_coinheight.(%s) error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(height); -} - -bits256 get_coinblockhash(char *refcoin,char *acname,int32_t height) -{ - cJSON *retjson; char *retstr,heightstr[32]; bits256 hash; - memset(hash.bytes,0,sizeof(hash)); - sprintf(heightstr,"%d",height); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockhash",heightstr,"","","")) != 0 ) - { - fprintf(stderr,"unexpected blockhash json.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - if ( strlen(retstr) >= 64 ) - { - retstr[64] = 0; - decode_hex(hash.bytes,32,retstr); - } - free(retstr); - } - return(hash); -} - -bits256 get_coinmerkleroot(char *refcoin,char *acname,bits256 blockhash) -{ - cJSON *retjson; char *retstr,str[65]; bits256 merkleroot; - memset(merkleroot.bytes,0,sizeof(merkleroot)); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getblockheader",bits256_str(str,blockhash),"","","")) != 0 ) - { - merkleroot = jbits256(retjson,"merkleroot"); - //fprintf(stderr,"got merkleroot.(%s)\n",bits256_str(str,merkleroot)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"%s %s get_coinmerkleroot error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(merkleroot); -} - -int32_t get_coinheader(char *refcoin,char *acname,bits256 *blockhashp,bits256 *merklerootp,int32_t prevheight) -{ - int32_t height = 0; char str[65]; - if ( prevheight == 0 ) - height = get_coinheight(refcoin,acname) - 20; - else height = prevheight + 1; - if ( height > 0 ) - { - *blockhashp = get_coinblockhash(refcoin,acname,height); - if ( bits256_nonz(*blockhashp) != 0 ) - { - *merklerootp = get_coinmerkleroot(refcoin,acname,*blockhashp); - if ( bits256_nonz(*merklerootp) != 0 ) - return(height); - } - } - memset(blockhashp,0,sizeof(*blockhashp)); - memset(merklerootp,0,sizeof(*merklerootp)); - return(0); -} - -cJSON *get_rawmempool(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getrawmempool","","","","")) != 0 ) - { - //printf("mempool.(%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_rawmempool.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_addressutxos(char *refcoin,char *acname,char *coinaddr) -{ - cJSON *retjson; char *retstr,jsonbuf[256]; - if ( refcoin[0] != 0 && strcmp(refcoin,"HUSH3") != 0 ) - printf("warning: assumes %s has addressindex enabled\n",refcoin); - sprintf(jsonbuf,"{\\\"addresses\\\":[\\\"%s\\\"]}",coinaddr); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getaddressutxos",jsonbuf,"","","")) != 0 ) - { - //printf("addressutxos.(%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_addressutxos.(%s) error.(%s)\n",acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_rawtransaction(char *refcoin,char *acname,bits256 txid) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getrawtransaction",bits256_str(str,txid),"1","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_rawtransaction.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *get_listunspent(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"listunspent","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"get_listunspent.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_listunspent(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_listunspent","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_listunspent.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_listoperationids(char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr,str[65]; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_listoperationids","","","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_listoperationids.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_getoperationstatus(char *refcoin,char *acname,char *opid) -{ - cJSON *retjson; char *retstr,str[65],params[512]; - sprintf(params,"'[\"%s\"]'",opid); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getoperationstatus",params,"","","")) != 0 ) - { - //printf("got status (%s)\n",jprint(retjson,0)); - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_getoperationstatus.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -cJSON *z_getoperationresult(char *refcoin,char *acname,char *opid) -{ - cJSON *retjson; char *retstr,str[65],params[512]; - sprintf(params,"'[\"%s\"]'",opid); - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getoperationresult",params,"","","")) != 0 ) - { - return(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_getoperationresult.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return(0); -} - -int32_t validateaddress(char *refcoin,char *acname,char *depositaddr, char* compare) -{ - cJSON *retjson; char *retstr; int32_t res=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"validateaddress",depositaddr,"","","")) != 0 ) - { - if (is_cJSON_True(jobj(retjson,compare)) != 0 ) res=1; - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"validateaddress.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return (res); -} - -int32_t z_validateaddress(char *refcoin,char *acname,char *depositaddr, char *compare) -{ - cJSON *retjson; char *retstr; int32_t res=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_validateaddress",depositaddr,"","","")) != 0 ) - { - if (is_cJSON_True(jobj(retjson,compare)) != 0 ) - res=1; - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_validateaddress.(%s) %s error.(%s)\n",refcoin,acname,retstr); - free(retstr); - } - return (res); -} - -int64_t z_getbalance(char *refcoin,char *acname,char *coinaddr) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getbalance",coinaddr,"","","")) != 0 ) - { - fprintf(stderr,"z_getbalance.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - amount = atof(retstr) * SATOSHIDEN; - sprintf(cmpstr,"%.8f",dstr(amount)); - if ( strcmp(retstr,cmpstr) != 0 ) - amount++; - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - free(retstr); - } - return (amount); -} - -int32_t z_exportkey(char *privkey,char *refcoin,char *acname,char *zaddr) -{ - cJSON *retjson; char *retstr,cmpstr[64]; int64_t amount=0; - privkey[0] = 0; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_exportkey",zaddr,"","","")) != 0 ) - { - fprintf(stderr,"z_exportkey.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - return(-1); - } - else if ( retstr != 0 ) - { - //printf("retstr %s -> %.8f\n",retstr,dstr(amount)); - strcpy(privkey,retstr); - free(retstr); - return(0); - } - return(-1); -} - -int32_t getnewaddress(char *coinaddr,char *refcoin,char *acname) -{ - cJSON *retjson; char *retstr; int64_t amount=0; int32_t retval = -1; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"getnewaddress","","","","")) != 0 ) - { - fprintf(stderr,"getnewaddress.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - strcpy(coinaddr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int32_t z_getnewaddress(char *coinaddr,char *refcoin,char *acname,char *typestr) -{ - cJSON *retjson; char *retstr; int64_t amount=0; int32_t retval = -1; - if ( (retjson= get_hushcli(refcoin,&retstr,acname,"z_getnewaddress",typestr,"","","")) != 0 ) - { - fprintf(stderr,"z_getnewaddress.(%s) %s returned json!\n",refcoin,acname); - free_json(retjson); - } - else if ( retstr != 0 ) - { - strcpy(coinaddr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int64_t find_onetime_amount(char *coinstr,char *coinaddr) -{ - cJSON *array,*item; int32_t i,n; char *addr; int64_t amount = 0; - coinaddr[0] = 0; - if ( (array= get_listunspent(coinstr,"")) != 0 ) - { - //printf("got listunspent.(%s)\n",jprint(array,0)); - if ( (n= cJSON_GetArraySize(array)) > 0 ) - { - for (i=0; i 0 ) - { - for (i=0; i %s\n",srcaddr,params); - if ( (retjson= get_hushcli(coinstr,&retstr,acname,"z_sendmany",addr,params,"","")) != 0 ) - { - printf("unexpected json z_sendmany.(%s)\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_sendmany.(%s) -> opid.(%s)\n",coinstr,retstr); - strcpy(opidstr,retstr); - free(retstr); - retval = 0; - } - return(retval); -} - -int32_t z_mergetoaddress(char *opidstr,char *coinstr,char *acname,char *destaddr) -{ - cJSON *retjson; char *retstr,addr[128],*opstr; int32_t retval = -1; - sprintf(addr,"[\\\"ANY_SPROUT\\\"]"); - if ( (retjson= get_hushcli(coinstr,&retstr,acname,"z_mergetoaddress",addr,destaddr,"","")) != 0 ) - { - if ( (opstr= jstr(retjson,"opid")) != 0 ) - strcpy(opidstr,opstr); - retval = jint(retjson,"remainingNotes"); - fprintf(stderr,"%s\n",jprint(retjson,0)); - free_json(retjson); - } - else if ( retstr != 0 ) - { - fprintf(stderr,"z_mergetoaddress.(%s) -> opid.(%s)\n",coinstr,retstr); - strcpy(opidstr,retstr); - free(retstr); - } - return(retval); -} - -int32_t empty_mempool(char *coinstr,char *acname) -{ - cJSON *array; int32_t n; - if ( (array= get_rawmempool(coinstr,acname)) != 0 ) - { - if ( (n= cJSON_GetArraySize(array)) > 0 ) - return(0); - free_json(array); - return(1); - } - return(-1); -} - -cJSON *getinputarray(int64_t *totalp,cJSON *unspents,int64_t required) -{ - cJSON *vin,*item,*vins = cJSON_CreateArray(); int32_t i,n,v; int64_t satoshis; bits256 txid; - *totalp = 0; - if ( (n= cJSON_GetArraySize(unspents)) > 0 ) - { - for (i=0; i= required ) - break; - } - } - } - return(vins); -} - -int32_t tx_has_voutaddress(char *refcoin,char *acname,bits256 txid,char *coinaddr) -{ - cJSON *txobj,*vouts,*vout,*vins,*vin,*sobj,*addresses; char *addr,str[65]; int32_t i,j,n,numarray,retval = 0, hasvout=0; - if ( (txobj= get_rawtransaction(refcoin,acname,txid)) != 0 ) - { - if ( (vouts= jarray(&numarray,txobj,"vout")) != 0 ) - { - for (i=0; i 0 ) - { - for (i=0; i 0 ) - { - for (j=0; j 0 && strcmp(vinaddr,cmpaddr) == 0 ) - return(0); - printf("mismatched vinaddr.(%s) vs %s\n",vinaddr,cmpaddr); - } - } - return(-1); -} - -int32_t txid_in_vins(char *refcoin,bits256 txid,bits256 cmptxid) -{ - cJSON *txjson,*vins,*vin; int32_t numvins,v,vinvout; bits256 vintxid; char str[65]; - if ( (txjson= get_rawtransaction(refcoin,"",txid)) != 0 ) - { - if ( (vins= jarray(&numvins,txjson,"vin")) != 0 ) - { - for (v=0; vrefundvalue < 0 ) - return(-1); - - sprintf(url,"https://explorer.hush.is/api/addr/%s",item->destaddr); - if ( (retstr= send_curl(url,"/tmp/itemvalid")) != 0 ) - { - if ( (curljson= cJSON_Parse(retstr)) != 0 ) - { - if ( (txids= jarray(&numtxids,curljson,"transactions")) != 0 ) - { - for (i=0; itxid) == 0 ) - { - printf("found item->txid %s inside %s\n",bits256_str(str,item->txid),bits256_str(str2,txid)); - item->approved = 1; - break; - } - } - } - free_json(curljson); - } - printf("%s\n",retstr); - free(retstr); - } - if ( item->approved != 0 ) - return(1); - *waitingp = item->refundvalue; - return(-1); -} - -void scan_claims(int32_t issueflag,char *refcoin,int32_t batchid) -{ - char str[65]; int32_t i,num,numstolen=0,numcandidates=0,numinvalids=0,numrefunded=0,numwaiting=0; struct claimitem *item; int64_t batchmin,batchmax,waiting,refunded,possiblerefund=0,possiblestolen = 0,invalidsum=0,totalrefunded=0,waitingsum=0; - if ( batchid == 0 ) - { - batchmin = 0; - batchmax = 7 * SATOSHIDEN; - } - else if ( batchid == 1 ) - { - batchmin = 7 * SATOSHIDEN; - batchmax = 777 * SATOSHIDEN; - } - else if ( batchid == 2 ) - { - batchmin = 1;//777 * SATOSHIDEN; - batchmax = 5000 * SATOSHIDEN; - } - else if ( batchid == 3 ) - { - batchmin = 1;//117777 * SATOSHIDEN; - batchmax = 10000000 * SATOSHIDEN; - } - for (i=0; irefundvalue < batchmin || item->refundvalue >= batchmax ) - continue; - printf("check.%d %s %.8f vs refund %.8f -> %s\n",batchid,item->oldaddr,dstr(item->total),dstr(item->refundvalue),item->destaddr); - if ( itemvalid(refcoin,&refunded,&waiting,item) < 0 ) - { - if ( refunded != 0 ) - { - numrefunded++; - totalrefunded += refunded; - } - else if ( waiting != 0 ) - { - numwaiting++; - waitingsum += waiting; - } - else - { - invalidsum += item->refundvalue; - numinvalids++; - } - continue; - } - if ( item->total > item->refundvalue*1.1 + 10*SATOSHIDEN ) - { - printf("possible.%d stolen %s %.8f vs refund %.8f -> %.8f\n",batchid,item->oldaddr,dstr(item->total),dstr(item->refundvalue),dstr(item->total)-dstr(item->refundvalue)); - numstolen++; - possiblestolen += (item->total - item->refundvalue); - item->approved = 0; - } - else - { - printf("candidate.%d %s %.8f vs refund %.8f -> %s\n",batchid,item->oldaddr,dstr(item->total),dstr(item->refundvalue),item->destaddr); - numcandidates++; - possiblerefund += item->refundvalue; - } - } - printf("batchid.%d TOTAL exposure %d %.8f, possible refund %d %.8f, invalids %d %.8f, numrefunded %d %.8f, waiting %d %.8f\n",batchid,numstolen,dstr(possiblestolen),numcandidates,dstr(possiblerefund),numinvalids,dstr(invalidsum),numrefunded,dstr(totalrefunded),numwaiting,dstr(waitingsum)); - for (i=num=0; iapproved != 0 ) - { - printf("%d.%d: approved.%d %s %.8f vs refund %.8f -> %s\n",i,num,batchid,item->oldaddr,dstr(item->total),dstr(item->refundvalue),item->destaddr); - num++; - if ( issueflag != 0 ) - { - static FILE *fp; char cmd[1024]; - if ( fp == 0 ) - fp = fopen("refund.log","wb"); - genrefund(cmd,refcoin,item->txid,item->destaddr,item->refundvalue); - if ( fp != 0 ) - { - fprintf(fp,"%s,%s,%s,%s,%s,%.8f,%s\n",item->username,refcoin,bits256_str(str,item->txid),item->oldaddr,item->destaddr,dstr(item->refundvalue),cmd); - fflush(fp); - } - memset(&SECONDVIN,0,sizeof(SECONDVIN)); - SECONDVOUT = 1; -//printf(">>>>>>>>>>>>>>>>>> getchar after (%s)\n",cmd); -//getchar(); - } - } - } -} - -int32_t update_claimvalue(int32_t *disputedp,char *addr,int64_t amount,bits256 txid) -{ - int32_t i; struct claimitem *item; - *disputedp = 0; - for (i=0; irefundvalue = amount; - if ( bits256_nonz(item->txid) != 0 ) - printf("disputed.%d (%s) %s claimed %.8f vs %.8f\n",item->disputed,item->username,addr,dstr(item->total),dstr(amount)); - item->txid = txid; - if ( item->disputed != 0 ) - *disputedp = 1; - return(i); - } - } - return(-1); -} - -int64_t update_claimstats(char *username,char *oldaddr,char *destaddr,int64_t amount) -{ - int32_t i; struct claimitem *item; - printf("claim user.(%s) (%s) -> (%s) %.8f\n",username,oldaddr,destaddr,dstr(amount)); - for (i=0; idestaddr) != 0 )//|| strcmp(username,item->username) != 0 ) - { - item->disputed++; - printf("disputed.%d claim.%-4d: (%36s -> [%36s] %s) vs. (%36s -> [%36s] %s) \n",item->disputed,i,oldaddr,destaddr,username,item->oldaddr,item->destaddr,item->username); - } - item->numutxos++; - item->total += amount; - return(amount); - } - } - item = &CLAIMS[NUM_CLAIMS++]; - item->total = amount; - item->numutxos = 1; - strncpy(item->oldaddr,oldaddr,sizeof(item->oldaddr)); - strncpy(item->destaddr,destaddr,sizeof(item->destaddr)); - strncpy(item->username,username,sizeof(item->username)); - printf("new claim.%-4d: %36s %16.8f -> %36s %s\n",NUM_CLAIMS,oldaddr,dstr(amount),destaddr,username); - return(amount); -} - -int32_t update_addrstats(char *srcaddr,int64_t amount) -{ - int32_t i; struct addritem *item; - for (i=0; itotal = amount; - item->numutxos = 1; - strcpy(item->addr,srcaddr); - printf("%d new address %s\n",NUM_ADDRESSES,srcaddr); - return(-1); -} - -int64_t sum_of_vins(char *refcoin,int32_t *totalvinsp,int32_t *uniqaddrsp,bits256 txid) -{ - cJSON *txjson,*vins,*vin; char str[65],srcaddr[64]; int32_t i,numarray; int64_t amount,total = 0; - if ( (txjson= get_rawtransaction(refcoin,"",txid)) != 0 ) - { - if ( (vins= jarray(&numarray,txjson,"vin")) != 0) - { - for (i=0; i 0 ) - { - printf("%d.(%s)\n",numlines,buf); - str = buf; - n = i = 0; - memset(fields,0,sizeof(fields)); - while ( *str != 0 ) - { - if ( *str == ',' || *str == '\n' || *str == '\r' ) - { - fields[n][i] = 0; - i = 0; - if ( n > 1 ) - { - printf("(%16s) ",fields[n]); - } - n++; - if ( *str == '\n' || *str == '\r' ) - break; - } - if ( *str == ',' || *str == ' ' ) - str++; - else fields[n][i++] = *str++; - } - printf("%s\n",fields[1]); - total += update_claimstats(fields[1],fields[3],fields[5 + (strcmp("HUSH3",refcoin)==0)],atof(fields[4])*SATOSHIDEN + 0.0000000049); - numlines++; - } - fclose(fp); - } - printf("total claims %.8f\n",dstr(total)); -} - -int32_t main(int32_t argc,char **argv) -{ - char *coinstr,*acstr,*addr,buf[64],srcaddr[64],str[65]; cJSON *retjson,*item; int32_t i,n,disputed,numdisputed,numsmall=0,numpayouts=0,numclaims=0,num=0,totalvins=0,uniqaddrs=0; int64_t amount,total = 0,total2 = 0,payout,maxpayout,smallpayout=0,totalpayout = 0,totaldisputed = 0,totaldisputed2 = 0,fundingamount = 0; - if ( argc != 2 ) - { - printf("argc needs to be 2: coin\n"); - return(-1); - } - if ( strcmp(argv[1],"HUSH3") == 0 ) - { - REFCOIN_CLI = "./hush-cli"; - coinstr = clonestr("HUSH3"); - acstr = ""; - } - else - { - sprintf(buf,"./hush-cli -ac_name=%s",argv[1]); - REFCOIN_CLI = clonestr(buf); - coinstr = clonestr(argv[1]); - acstr = coinstr; - } - if ( 1 ) - { - sprintf(buf,"%s-Claims.csv",coinstr); - reconcile_claims(coinstr,buf); - for (i=0; i 0 ) - { - for (i=0; i fundingamount ) - { - fundingamount = amount; - SECONDVIN = jbits256(item,"txid"); - SECONDVOUT = jint(item,"vout"); - printf("set SECONDVIN to %s/v%d %.8f\n",bits256_str(str,SECONDVIN),SECONDVOUT,dstr(amount)); - } - continue; - } - if ( strcmp(coinstr,"HUSH3") == 0 && verify_vin(coinstr,jbits256(item,"txid"),0,"R9JCEd6xnCxNUSpLrHEWvzPSh7CNXm7z75") < 0 ) - { - printf("WARNING: imposter dust detected! %s\n",bits256_str(str,jbits256(item,"txid"))); - continue; - } - else if ( strcmp(coinstr,"HUSH3") != 0 && verify_vin(coinstr,jbits256(item,"txid"),0,"R9MUnxXijovvSeT9sFuUX23TiFtVvZEGjT") < 0 ) - { - printf("WARNING: imposter dust detected! %s\n",bits256_str(str,jbits256(item,"txid"))); - continue; - } - amount = (utxo_value(coinstr,srcaddr,jbits256(item,"txid"),0) - 20000) * SATOSHIDEN; - //printf("%d: %s claimvalue %.8f\n",num,srcaddr,dstr(amount)); - num++; - total2 += amount; - if ( update_claimvalue(&disputed,srcaddr,amount,jbits256(item,"txid")) >= 0 ) - { - if ( disputed != 0 ) - { - totaldisputed2 += amount; - numdisputed++; - } - else - { - numclaims++; - total += amount; - } - } - } - } - } - free_json(retjson); - printf("remaining refunds.%d %.8f, numclaims.%d %.8f, numdisputed.%d %.8f\n",num,dstr(total2),numclaims,dstr(total),numdisputed,dstr(totaldisputed2)); - } - //scan_claims(0,coinstr,0); - //scan_claims(0,coinstr,1); - //scan_claims(0,coinstr,2); - scan_claims(1,coinstr,3); - } - else if ( (retjson= get_listunspent(coinstr,acstr)) != 0 ) - { - if ( (n= cJSON_GetArraySize(retjson)) > 0 ) - { - for (i=0; i= SATOSHIDEN ) - { - payout = ADDRESSES[i].total / SATOSHIDEN; - if ( payout > maxpayout ) - maxpayout = payout; - totalpayout += payout; - numpayouts++; - //if ( payout >= 7 ) - //{ - // numsmall++; - //smallpayout += payout; - genpayout(coinstr,ADDRESSES[i].addr,payout); - //} - //printf("%-4d: %-64s numutxos.%-4lld %llu\n",i,ADDRESSES[i].addr,ADDRESSES[i].numutxos,(long long)payout); - } - } - printf("num.%d total %.8f vs vintotal %.8f, totalvins.%d uniqaddrs.%d:%d totalpayout %llu maxpayout %llu numpayouts.%d numsmall.%d %llu\n",num,dstr(total),dstr(total2),totalvins,uniqaddrs,NUM_ADDRESSES,(long long)totalpayout,(long long)maxpayout,numpayouts,numsmall,(long long)smallpayout); - } -} - -int32_t zmigratemain(int32_t argc,char **argv) -{ - char buf[1024],*zsaddr,*coinstr; - if ( argc != 3 ) - { - printf("argc needs to be 3\n"); - return(-1); - } - if ( strcmp(argv[1],"HUSH3") == 0 ) - { - REFCOIN_CLI = "./hush-cli"; - coinstr = clonestr("HUSH3"); - } - else - { - sprintf(buf,"./hush-cli -ac_name=%s",argv[1]); - REFCOIN_CLI = clonestr(buf); - coinstr = clonestr(argv[1]); - } - if ( argv[2][0] != 'z' || argv[2][1] != 's' ) - { - printf("invalid sapling address (%s)\n",argv[2]); - return(-2); - } - if ( z_validateaddress(coinstr,"",argv[2],"ismine") == 0 ) - { - printf("invalid sapling address (%s)\n",argv[2]); - return(-3); - } - zsaddr = clonestr(argv[2]); - printf("%s: %s %s\n",REFCOIN_CLI,coinstr,zsaddr); - uint32_t lastopid; char coinaddr[64],privkey[1024],zcaddr[128],opidstr[128]; int32_t finished; int64_t amount,stdamount,txfee; - //stdamount = 500 * SATOSHIDEN; - txfee = 10000; -again: - if ( z_getnewaddress(zcaddr,coinstr,"","sprout") == 0 ) - { - z_exportkey(privkey,coinstr,"",zcaddr); - printf("zcaddr.(%s) -> z_exportkey.(%s)\n",zcaddr,privkey); - while ( 1 ) - { - if ( have_pending_opid(coinstr,0) != 0 ) - { - sleep(10); - continue; - } - if ( z_mergetoaddress(opidstr,coinstr,"",zcaddr) <= 0 ) - break; - } - } - printf("start processing zmigrate\n"); - lastopid = (uint32_t)time(NULL); - finished = 0; - while ( 1 ) - { - if ( have_pending_opid(coinstr,0) != 0 ) - { - sleep(10); - continue; - } - if ( (amount= find_onetime_amount(coinstr,coinaddr)) > txfee ) - { - // find taddr with funds and send all to zsaddr - z_sendmany(opidstr,coinstr,"",coinaddr,zsaddr,amount-txfee); - lastopid = (uint32_t)time(NULL); - sleep(1); - continue; - } - if ( (amount= find_sprout_amount(coinstr,zcaddr)) > txfee ) - { - // generate taddr, send max of 10000.0001 - static int64_t lastamount,lastamount2,lastamount3,lastamount4,refamount = 5000 * SATOSHIDEN; - stdamount = refamount; - if ( amount == lastamount && amount == lastamount2 ) - { - stdamount /= 10; - if ( amount == lastamount3 && amount == lastamount4 ) - stdamount /= 10; - } - if ( stdamount < SATOSHIDEN ) - { - stdamount = SATOSHIDEN; - refamount = SATOSHIDEN * 50; - } - if ( stdamount < refamount ) - refamount = stdamount; - lastamount4 = lastamount3; - lastamount3 = lastamount2; - lastamount2 = lastamount; - lastamount = amount; - if ( amount > stdamount+2*txfee ) - amount = stdamount + 2*txfee; - if ( getnewaddress(coinaddr,coinstr,"") == 0 ) - { - z_sendmany(opidstr,coinstr,"",zcaddr,coinaddr,amount-txfee); - lastopid = (uint32_t)time(NULL); - } else printf("couldnt getnewaddress!\n"); - sleep(3); - continue; - } - if ( time(NULL) > lastopid+600 ) - break; - } - sleep(3); - printf("%s %s ALLDONE! taddr %.8f sprout %.8f mempool empty.%d\n",coinstr,zsaddr,dstr(find_onetime_amount(coinstr,coinaddr)),dstr(find_sprout_amount(coinstr,zcaddr)),empty_mempool(coinstr,"")); - sleep(3); - if ( find_onetime_amount(coinstr,coinaddr) == 0 && find_sprout_amount(coinstr,zcaddr) == 0 ) - { - printf("about to purge all opid results!. ctrl-C to abort, to proceed\n"); - getchar(); - have_pending_opid(coinstr,1); - } else goto again; - return(0); -} diff --git a/src/cc/eval.h b/src/cc/eval.h index d0be279d0..2ca194b61 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -80,21 +80,6 @@ public: bool Error(std::string s) { return state.Error(s); } bool Valid() { return true; } - /* - * Dispute a payout using a VM - */ - bool DisputePayout(AppVM &vm, std::vector params, const CTransaction &disputeTx, unsigned int nIn); - - /* - * Test an ImportPayout CC Eval condition - */ - bool ImportPayout(std::vector params, const CTransaction &importTx, unsigned int nIn); - - /* - * Import coin from another chain with same symbol - */ - bool ImportCoin(std::vector params, const CTransaction &importTx, unsigned int nIn); - /* * IO functions */ @@ -281,7 +266,6 @@ typedef std::pair TxProof; uint256 GetMerkleRoot(const std::vector& vLeaves); struct CCcontract_info *CCinit(struct CCcontract_info *cp,uint8_t evalcode); -bool ProcessCC(struct CCcontract_info *cp,Eval* eval, std::vector paramsNull, const CTransaction &tx, unsigned int nIn); #endif /* CC_EVAL_H */ diff --git a/src/chain.h b/src/chain.h index 69c66c674..aa039e83f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -35,7 +35,7 @@ extern bool fZindex; // These version thresholds control whether nSproutValue/nSaplingValue are // serialized in the block index. They must be <= CLIENT_VERSION or the // values will never be persisted, causing nChainSaplingValue to reset -// to 0 after node restart. DragonX CLIENT_VERSION is 1000350 (v1.0.3.50). +// to 0 after node restart. DragonX CLIENT_VERSION is 1030050 (v1.3.0.50). static const int SPROUT_VALUE_VERSION = 1000000; static const int SAPLING_VALUE_VERSION = 1000000; // Block-index records written at >= this version store nSaplingValue as a boost::optional @@ -113,10 +113,9 @@ enum BlockStatus: uint32_t { //! Scripts & signatures ok. Implies all parents are also at least SCRIPTS. BLOCK_VALID_SCRIPTS = 5, - // flag to check if contextual check block has passed in Accept block, if it has not check at connect block. - BLOCK_VALID_CONTEXT = 6, - //! All validity bits. + //! NOTE: the levels above are sequential VALUES occupying this 3-bit field, not independent + //! bits, so any flag stored in nStatus must live entirely outside this mask. BLOCK_VALID_MASK = BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS | BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS, @@ -129,9 +128,29 @@ enum BlockStatus: uint32_t { BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD, BLOCK_ACTIVATES_UPGRADE = 128, //! block activates a network upgrade - BLOCK_IN_TMPFILE = 256 + BLOCK_IN_TMPFILE = 256, + + //! ContextualCheckBlock already passed in AcceptBlock, so ConnectBlock may skip re-running it. + //! Was 6 until v1.3.0, which put it INSIDE BLOCK_VALID_MASK (1|2|3|4|5 == 7): `nStatus |= + //! BLOCK_VALID_CONTEXT` then overwrote the validity level rather than setting a flag, so a + //! block that was only written to disk read back as BLOCK_VALID_SCRIPTS and every later + //! RaiseValidity() silently no-opped. Detected by CheckBlockIndex's "CHAIN valid implies all + //! parents are CHAIN valid" assert, which aborts any node doing out-of-order block download + //! (regtest only, where fDefaultConsistencyChecks is true). Validity was only ever inflated, + //! never deflated, so ConnectBlock's full validation was never skipped -- see git history. + //! Legacy block indexes still carry the polluted low bits; they resolve on reindex, and until + //! then simply re-run the contextual check they used to skip. + BLOCK_VALID_CONTEXT = 512 }; +//! The validity level is a small integer packed into BLOCK_VALID_MASK, so every other nStatus flag +//! must be disjoint from it. Enforced here so this class of bug cannot be reintroduced silently. +static_assert((BLOCK_VALID_CONTEXT & BLOCK_VALID_MASK) == 0, "BLOCK_VALID_CONTEXT overlaps the validity-level field"); +static_assert((BLOCK_HAVE_MASK & BLOCK_VALID_MASK) == 0, "BLOCK_HAVE_MASK overlaps the validity-level field"); +static_assert((BLOCK_FAILED_MASK & BLOCK_VALID_MASK) == 0, "BLOCK_FAILED_MASK overlaps the validity-level field"); +static_assert((BLOCK_ACTIVATES_UPGRADE & BLOCK_VALID_MASK) == 0, "BLOCK_ACTIVATES_UPGRADE overlaps the validity-level field"); +static_assert((BLOCK_IN_TMPFILE & BLOCK_VALID_MASK) == 0, "BLOCK_IN_TMPFILE overlaps the validity-level field"); + //! Short-hand for the highest consensus validity we implement. //! Blocks with this validity are assumed to satisfy all consensus rules. static const BlockStatus BLOCK_VALID_CONSENSUS = BLOCK_VALID_SCRIPTS; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 683cb2a33..d56993c1c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -148,6 +148,12 @@ public: nMinerThreads = 0; nMaxTipAge = 24 * 60 * 60; nPruneAfterHeight = 100000; + // NOTE: These Equihash parameters and the literal Bitcoin genesis block below are + // inherited from the upstream (Zcash/Komodo) CMainParams and are NOT what DragonX + // mines under. DragonX is a RandomX CPU-mining chain whose real PoW and chain + // parameters are set for its SMART_CHAIN_SYMBOL at runtime (see hush_utils.h and + // chainparams_commandline()). They are retained here for upstream-diff hygiene and + // genesis fixity; do not "fix" them to RandomX values. const size_t N = 200, K = 9; BOOST_STATIC_ASSERT(equihash_parameters_acceptable(N, K)); nEquihashN = N; @@ -181,11 +187,29 @@ public: assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); vFixedSeeds.clear(); vSeeds.clear(); - // Hush Official DNS Seeds - vSeeds.push_back(CDNSSeedData("node1", "seed1.hush.is")); - vSeeds.push_back(CDNSSeedData("node2", "seed2.hush.is")); - // Community run DNS Seeds - vSeeds.push_back(CDNSSeedData("node3", "dns.leto.net")); + // DragonX DNS seeds. These must be names that actually resolve. + // + // An assetchain INHERITS these: chainparams_commandline() sets the port, magic, + // blocktime, upgrade heights and checkpoints, but never touches vSeeds or + // vFixedSeeds. DRAGONX therefore ran on Hush's seeds -- seed1.hush.is, + // seed2.hush.is and dns.leto.net -- and all three have no A records left, so DNS + // seeding silently returned zero addresses on every start. The only bootstrap + // path that worked was the node1..node5.dragonx.is -addnode injection in + // hush_utils.h, which is almost certainly why that injection exists. + // + // seed.dragonx.is is a round-robin A record over the seed set, so ONE lookup + // returns all of them and the set can be changed -- a node added, a node retired + // -- with a DNS edit instead of a release. That is the point of it: the previous + // arrangement hardcoded the seed list into the binary twice over (here and in the + // -addnode injection in hush_utils.h), so the network's entry points could only + // change by shipping a new version. + // + // node1/node5 stay as static fallbacks in case the round-robin record is ever + // mistyped or removed. They are the same hosts, so this is insurance against a + // DNS mistake rather than genuine redundancy. + vSeeds.push_back(CDNSSeedData("seed", "seed.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node1", "node1.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node5", "node5.dragonx.is")); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,60); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,85); @@ -515,15 +539,27 @@ void hush_setactivation(int32_t height) void *chainparams_commandline() { CChainParams::CCheckpointData checkpointData; - //if(fDebug) { - fprintf(stderr,"chainparams_commandline called with port=%u\n", ASSETCHAINS_P2PPORT); - //} + LogPrint("net", "chainparams_commandline called with port=%u\n", ASSETCHAINS_P2PPORT); if ( SMART_CHAIN_SYMBOL[0] != 0 ) { + // A smart chain inherits vSeeds/vFixedSeeds from the base network params, + // and nothing below ever touched them. That is how DRAGONX came to run on + // Hush's seeds -- seed1.hush.is and friends, long since removed from DNS -- + // for as long as it did. Seeds are per-chain by nature: an address that + // serves one chain is useless to another, and dialling it is at best wasted + // effort and at worst a peer that speaks a different protocol. + // + // DRAGONX keeps the seeds configured in CMainParams (which are its own). + // Every other chain starts empty and relies on -addnode/-connect, which is + // what an assetchain operator has to configure anyway. if (strcmp(SMART_CHAIN_SYMBOL,"HUSH3") == 0) { ASSETCHAINS_P2PPORT = 18030; } + if (strcmp(SMART_CHAIN_SYMBOL, "DRAGONX") != 0) { + pCurrentParams->ClearSeeds(); + } + if ( ASSETCHAINS_BLOCKTIME != 60 ) { pCurrentParams->consensus.nMaxFutureBlockTime = 7 * ASSETCHAINS_BLOCKTIME; // 7 blocks @@ -547,7 +583,7 @@ void *chainparams_commandline() { pCurrentParams->pchMessageStart[1] = (ASSETCHAINS_MAGIC >> 8) & 0xff; pCurrentParams->pchMessageStart[2] = (ASSETCHAINS_MAGIC >> 16) & 0xff; pCurrentParams->pchMessageStart[3] = (ASSETCHAINS_MAGIC >> 24) & 0xff; - fprintf(stderr,">>>>>>>>>> %s: p2p.%u rpc.%u magic.%08x %u %u coins\n",SMART_CHAIN_SYMBOL,ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT,ASSETCHAINS_MAGIC,ASSETCHAINS_MAGIC,(uint32_t)ASSETCHAINS_SUPPLY); + LogPrintf("%s: p2p port %u, rpc port %u, magic %08x, supply %u coins\n",SMART_CHAIN_SYMBOL,ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT,ASSETCHAINS_MAGIC,(uint32_t)ASSETCHAINS_SUPPLY); pCurrentParams->consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight = ASSETCHAINS_SAPLING; pCurrentParams->consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = ASSETCHAINS_OVERWINTER; @@ -5640,9 +5676,401 @@ void *chainparams_commandline() { (2836000, uint256S("0x00000000004f1a5b9b0fad39c6751db29b99bfcb045181b6077d791ee0cf91f2")) (2837000, uint256S("0x000000000027c61ed8745c18d6b00edec9414e30dd880d92d598a6a0ce0fc238")) (2838000, uint256S("0x00000000010947813b04f02da1166a07ba213369ec83695f4d8a6270c57f1141")) - ,(int64_t) 1770622731, // time of last checkpointed block - (int64_t) 2940000, // total txs - (double) 4576 // txs in the last day before block 2838000 + // Extended 2026-08-21: the table previously ended at 2,838,000, which is 976 + // blocks BELOW ASSETCHAINS_RANDOMX_VALIDATION (2,838,976), so the RandomX + // skip in RandomXValidationRequired() could never fire. Verified against 5 + // independent full nodes, both directions, all on the active chain. + (2839000, uint256S("0x036b5f8f60733ef985d422bbf2367822dd032a9440fd3524420d4ecfa21ba9f6")) + (2840000, uint256S("0x0007994f4c5a67c3030e41980e8f28de88f8c0095a5148393e3ae11fb7116071")) + (2841000, uint256S("0x0002d6ec728f3a1aa76ffcda9a0bd713328a1d192bc76374cf9d819af755161d")) + (2842000, uint256S("0x00002cb9b235571f8bdfe133ac25f12a05cb33a1c7894d4fe3cab0b7a46eb30d")) + (2843000, uint256S("0x000007ad1b64a69892768646121a5d675ea425fa5ad1147207c7b5c286ee2e0d")) + (2844000, uint256S("0x00000347a2169ad507a954c5529fbbe52ea19c9d0953005bb8964825b4944133")) + (2845000, uint256S("0x0000037a9cbecf55c5100c890a83b6fb8c0d4413d86ea0a45f86f0644709fb79")) + (2846000, uint256S("0x00000011d9e2ae53be284f718e22461ad28dfd4ee6e1b9ef98412f5b2f5831b1")) + (2847000, uint256S("0x000004a70b7f18cec9623e7c5460093264ba00a44e66ac8c0d2551a488216907")) + (2848000, uint256S("0x000005dadf5e3f8d4837da9580ca28934d70ff7cb63bc326df5f44f91bbc22fe")) + (2849000, uint256S("0x000002f92bdffcf58b8dca4382a66af71e06447045758dab93c39c77c1d9c91e")) + (2850000, uint256S("0x0000003db45806f522f1cbf059bab8ddb1a428bcc9063bced294c7bf0289a3c3")) + (2851000, uint256S("0x000005352fb09271bb1f804cd55c57cabefcfbf34dad29d0209f16c98903a836")) + (2852000, uint256S("0x00000123edae432f080e11dcf7a0718e9f144f571746b0ca902a2e2f81eecccc")) + (2853000, uint256S("0x000006ae53f9318417fc4df6d3672f2a88a3f67a7b3e77d9f833cc23778e023b")) + (2854000, uint256S("0x000004715fdde7854b5b227fb5cc1c4cf006935fd80ce8390b0178e4a7c6367c")) + (2855000, uint256S("0x000001b53c3d0689ae4df1ad2b5bbd5ae61b2a392b2abc25cb8d866bff4cafe9")) + (2856000, uint256S("0x00000011f6bb05a702ffc936985dabbbb983a771b97a6a518798781325adc400")) + (2857000, uint256S("0x0000034dc8e9de8530491ae750fca3d7ea5b8992165a13e16aa90a35b690fb53")) + (2858000, uint256S("0x0000002ba3bc10f84ebaab3713c58016ce5320b0a68efafc542aaa954d6d2d05")) + (2859000, uint256S("0x000000b2740c0b463d424bc5630ebb80fc01823c701859bd4a0b92161880f11d")) + (2860000, uint256S("0x0000037007777311e559e889930c7590814e28f0bd3794796658b6cf588a9c19")) + (2861000, uint256S("0x000002f1fb62662b6d62addea67d78e6dafa3ec0e50d8098d6f593434022b4e3")) + (2862000, uint256S("0x0000056b00954317d9e2518bf462e839ba8cfbd2c522e307414b75869ec1483b")) + (2863000, uint256S("0x00000444ea6f3ff4bdaa22f7d9fddd1001530b5d96172799560ceca2cb7c9936")) + (2864000, uint256S("0x00000379a4d7ed4096c2715f05c154a6c18f1322eae69f76c0566f88b6372f6c")) + (2865000, uint256S("0x000004a98a372678405acceb0fe311a10fd6a57fd512b057675546b687cb81c4")) + (2866000, uint256S("0x000000301204227ce755e160607d720619b0f06e12664cdae92abcf2dc500ef7")) + (2867000, uint256S("0x00000229bdbc6f916558d279de61cad703fa4842041d373cfe19e9c6f05e5d0e")) + (2868000, uint256S("0x000001511aa190c24e92a4149741038221fd6dd794680ae8972dea6ab7d73a14")) + (2869000, uint256S("0x000001209e63f22310be9844155012a7736a8134028ce2f1b536caa95a87455a")) + (2870000, uint256S("0x000001d04778f2526ee1427709e733843e18decf20ee71d482271f04152737ac")) + (2871000, uint256S("0x000000e9bcee55b40cf6b5105c707d48e2fdd852c38ab2d9994203de560e6c22")) + (2872000, uint256S("0x0000020f46c3732afe95b72159ea059a7fbe443e2a9978a951321ab77608ecba")) + (2873000, uint256S("0x000000be45b4ec2b826ed385e13602a4034857736278722455128e134c8ca072")) + (2874000, uint256S("0x000002605ca5037e8a5cdc19c0c418fdb0b922c3eec713aa0357f0077ddc0173")) + (2875000, uint256S("0x0000028d16a1dcfa320bb247e2fbd31c318df0e9f0362b177f56f0cd193b79ca")) + (2876000, uint256S("0x0000000289fccbfdd88bb3bb1f22c5c5d4fbb16690922fd1a2ba9002c12c4df1")) + (2877000, uint256S("0x000000a4625231d51e8c65eeb8c13352e50aba0f570d4497849fef94434f2cd6")) + (2878000, uint256S("0x000000080fdf691ba03c1ade593abd758c9683452aa195121029e44614daeff4")) + (2879000, uint256S("0x000000956fec52943b4577e2959dede6411997a2720965bfb615b6bdd8fbfe3a")) + (2880000, uint256S("0x000002799452afc6905050bf9f1e0688b644b118c78051836e19e3d959a62c6e")) + (2881000, uint256S("0x000000a55304c9b02690aa867ba4b775887384a323e71ddb9486234aab7337ae")) + (2882000, uint256S("0x000000770d3b0e3eef93d5d652c236b7ac993f102984161bad8fff57d4b97f87")) + (2883000, uint256S("0x000000f0cd04f12eb06e250f1f81556310d2d7607a8aa7b56dd9bc8aa0f6a7ab")) + (2884000, uint256S("0x0000001701df19e9b84d616b6b9e38c2b0d040cf53f8b5f300d7d62a0da705d8")) + (2885000, uint256S("0x000000d08bc242a49321452104c7797a03e1c62206a9549e801f60a120374359")) + (2886000, uint256S("0x0000005b2559414fbd1150b6c981eb33b79b09b99cf741c2f085bb7856779e4f")) + (2887000, uint256S("0x000000f806f6b02d5b1a9c0fbeccb8c8590b368f577f0171fa309db1927b9731")) + (2888000, uint256S("0x0000015e75de0201216b1c0761a0193f3590d4b980d7e2685dd8ae17ce2e6b59")) + (2889000, uint256S("0x0000017c84d2d3187ebc80680087af61ba582c4cd34fb171c174aa5f2e689632")) + (2890000, uint256S("0x0000013c9ed9f4eaa71e4d8da5c986c6d031e0907afd134e8f1268b899141a16")) + (2891000, uint256S("0x0000018144f06b18c18d26863a57d157ede9137876334afab3641647e13b94b8")) + (2892000, uint256S("0x000000d6605f31785f1a1f1afe6058fc8d438b67017e88c8c13eb438808af7c8")) + (2893000, uint256S("0x0000001fbcaf6af29a76c408290d9169a58ea696473c7379cd5c675ab80069dc")) + (2894000, uint256S("0x000000173a49f8d72af0d6377c8e219e8f7c6f16fd193414bd2252a2b0e6b17f")) + (2895000, uint256S("0x0000003100994bacedd566893497bcb50aa4533a2a2c8199b9390eb9535945c7")) + (2896000, uint256S("0x000000b9b69f0dcd15d8b6f44502bef17e92c595d6564d729490d40021c82dc0")) + (2897000, uint256S("0x000001126a35ef0fbd43ac5450efe13750de16bb86bc1d735008f2ba6ddf202f")) + (2898000, uint256S("0x000001105d958e9dd8208bf5ddf552a3ee175e00ed64175e4260bf227d7038e3")) + (2899000, uint256S("0x0000012bfbf4516bdf5ccbdb420d02a2abdfced13e00729bfa6504f113ace2a7")) + (2900000, uint256S("0x000001ad94f932e03125a5cb8b97d65d8456cd5c5ac6c0d23a1e30dc66b722ab")) + (2901000, uint256S("0x0000015029fd0001c47219ba9eb923e8c19dc3b6bbb8986797fdcb796ba5e7d6")) + (2902000, uint256S("0x000001a9deda168129dcfd63eb8cc147e3278a4364dfcc7a55461598622bb2b9")) + (2903000, uint256S("0x0000010c3e62bbe5e083e0412f13d01c17aadfaa3bf45744732737e123779b41")) + (2904000, uint256S("0x00000108897e8acc0f3d0ef3e4f083768a8831669ff41505c6a9bb2e34bae7a5")) + (2905000, uint256S("0x0000025de11376fdc16dbfd98b7cecbf07f9b134b7baba9bb5aa65f4ea88baf1")) + (2906000, uint256S("0x000001685929800faea4c5d443eca9d8a45b441098a797f30360ad5fb6a40afc")) + (2907000, uint256S("0x00000181dbf2036c3d3e13d251b3398ff7b4b36073babffd207b7bd7234224be")) + (2908000, uint256S("0x0000014d9f26e7993aae49141cf86940844dfcd3315469d4a89fa925695651fa")) + (2909000, uint256S("0x000000e1f57f68602d01fa47f8c61f02d3448158429e064dce3b693298645360")) + (2910000, uint256S("0x0000000bff875f2e5dee11daff9357824dab610b81a38f8d24bebf4590bf7925")) + (2911000, uint256S("0x0000016d24cfc8b99f25e66324ed9c221fe5ba67ab64d410796dc52b510f18b3")) + (2912000, uint256S("0x00000153cd34e3d692ff6913b74bf604735b20e5b8fcf4b30fe2be08b4d9a9ad")) + (2913000, uint256S("0x00000001b5fa024c9e0af58b0e6fe95fbd80c9ed1f0905d1db9c511f64d8de4a")) + (2914000, uint256S("0x000000edf84a56f70de7e9ab444e9940bc1c5607dff2e5efcf6b7259bcd5bd53")) + (2915000, uint256S("0x0000023faea5d5354dd535323d7cad9ee5246b060be4e78bddbb15aece65d6af")) + (2916000, uint256S("0x000001242376ee75e582ab05898533a1dc0a48aa3a661ce3bf733b30c2d3eb8b")) + (2917000, uint256S("0x000001ee27b35fc196cba13fee45d75e3eddad3f1d40610045d0f445c579a6d3")) + (2918000, uint256S("0x000000f2749ba8b02fd0f008c7bbc084660274af778e03a17784413c84704185")) + (2919000, uint256S("0x00000189484712fa646fff55d24adc7c312f300b9b8d1c4b0b755779cb0e7794")) + (2920000, uint256S("0x0000012ac689d379a42028bfe52ca7b8ffe203202b525c1b0271ee2b35ad6402")) + (2921000, uint256S("0x00000041006270f03bbb9e37ede42e75e32e257cac45696f28d225a4b43e6777")) + (2922000, uint256S("0x0000012a7cd291103fc118e49a040c24a08ab1ee04745b2b0f2ea6b2f81e0cca")) + (2923000, uint256S("0x000002d505a407c118df5ed44edae441c0bdc228559ae5a74c5fc3069b92f446")) + (2924000, uint256S("0x000001cffdd7e67d52ba4218752fbf45dfeb9d9c595e00399517a99daecadf69")) + (2925000, uint256S("0x0000011d18edd59d9af62f5a028bc79ead29dee6f8c88e5f7dd87109588cbecb")) + (2926000, uint256S("0x0000015e595dfdb4b59126c9d867f148a7d921d38711bb1533324a52757a4272")) + (2927000, uint256S("0x00000096eb782ea4ba9e047359b02f5848cd6799877c7bc5835cb4bbb299b14f")) + (2928000, uint256S("0x000001c190235fb344abf38fc1dcaa75a3a9c55b8b5f5dd199b1f95cf8d201ed")) + (2929000, uint256S("0x000000c58a7468d05737541fa5df2c1aba6d310fc5c03303e8d72b13d3cb277f")) + (2930000, uint256S("0x00000169380c6a08fb48b379a5ca5d866f18fd9d453a13d6e0b8cf3348f75df1")) + (2931000, uint256S("0x0000019761664eb4d775090c4e24505b1add18096cf42ff856715afa0d1a3e1c")) + (2932000, uint256S("0x0000009f88c63b6c57d1a0b09ab2540eb5f09982234430d0630746991db4e2fb")) + (2933000, uint256S("0x000001829533eec65e9241b1b63742e1359cf55198f089c14f183effe76e8629")) + (2934000, uint256S("0x000000a4040f326a6b4c1fb64a6c3df87b4e9695a944bbac713fb0cfecd8d551")) + (2935000, uint256S("0x000001e58d485a5400e800643db600eb6df312d2832054bac1785207212ca059")) + (2936000, uint256S("0x000001d1bf65e88819f93e9fae9d7d682ac18708756b2f1b7d5d2c7779384942")) + (2937000, uint256S("0x000001df066b347d3037a0ff5769bce7a7832591775116847e52cfece1725c62")) + (2938000, uint256S("0x00000029d4607a9ef85fdfefd8427f6d3ba8585d3e96edf82487c6593fb4f22e")) + (2939000, uint256S("0x000001c21ec8f18b48b6a05817b133630d60c9880bc3be26610f1e3d9dd6a8b0")) + (2940000, uint256S("0x000001c936816ab5875198fe942e3a33bbb030ae8a5defb457e80d01246a5a71")) + (2941000, uint256S("0x0000016ba0563ce8e727c92689b0adf945071d95a6adcce7f65f5f4507762a94")) + (2942000, uint256S("0x00000118035772133444f5facdae2a84b939b12dae23a9658cd39684d4548908")) + (2943000, uint256S("0x000000fa1fbab3a011da37bbe13d2eda64496e69a531705720cb49d3cce8e714")) + (2944000, uint256S("0x0000018a1c728b6e2c6fa6e53ae329810134cceb1203795601cb5e196eba0525")) + (2945000, uint256S("0x000001abab339ba3e21bbc3c65d684f9da927abf65fd0a9308f116b76cf0888e")) + (2946000, uint256S("0x000000532f16a10c6e51877041ba94c946c15a607e4e4c1c654e7e810f95266b")) + (2947000, uint256S("0x00000020c9d00eb7d964325826f2d7d4313b4d24aeac9da2b96951a70ec8cd1a")) + (2948000, uint256S("0x00000189c6bd01c4d8a912dc6f68e59415033e9d99f20a6f8ab895d50af65a72")) + (2949000, uint256S("0x00000222769cdcb49042f93ba8e449292807a2d998123d30d3508c3759c82a93")) + (2950000, uint256S("0x00000116cbd21afdd1da358c3953de242eb4d2710540338e3f92fa66e768420e")) + (2951000, uint256S("0x000000bc86ed75d1e906cf75e7b471a4ffe31ee4ca13a8e69c1c7a4fef3b4069")) + (2952000, uint256S("0x0000020e923c424ae4769114c453a3e31d3215b355703daf631c0afa1dfaab20")) + (2953000, uint256S("0x00000113603a84890a04ce15deecfe025e26bf770a2ae8ec207774851ec851c6")) + (2954000, uint256S("0x000001f3ccfa445ef2603c3c3ed7d41582aeaf3e3da4fdff4587248a586e99b1")) + (2955000, uint256S("0x0000036a1b2e6ac72888d4b97002944f70b4285fe4ac10d970a96ae6e4e5e5b0")) + (2956000, uint256S("0x0000021057f0f2e55f888146dd54726b6b2e94192895a8990fa6e885cbf2cc84")) + (2957000, uint256S("0x00000029aeed195efd6421ec6906b5308a4f54fceaeb2c6066618bd170d9107a")) + (2958000, uint256S("0x000000ea5208d69660156ffacf152a63fc387e9bd92ace8f49e57fa1e6ef60e4")) + (2959000, uint256S("0x000001a8847661b4fdf27afbfb4b187ab405039a86a3d79e612d43649b311196")) + (2960000, uint256S("0x0000017cff8dc295e80f9ed4834bf017a316c2cdd0aff7f11976cc232d9fc30f")) + (2961000, uint256S("0x000001951de8a1b055c0ad7bb01a6042d8041889fba2435676161eaa96807770")) + (2962000, uint256S("0x000001955036b0967f1f30f7e34cf7b146a821b3058081354f4e6eca3263d11b")) + (2963000, uint256S("0x000000cb9e41e2a62dc2ce7678bd73d36f9094686a50ce981394c51f0a7727b6")) + (2964000, uint256S("0x000001277f60eecb3afefae842dcbc73afe966cf218637cb15bc96bfb7c248c4")) + (2965000, uint256S("0x00000024ab8135f931081c88304b55524a5786764da435a2b90109644b5d7386")) + (2966000, uint256S("0x000000dc386c492b9dec3e057c58e4890aeb3befde5b808cedde00d55bd25576")) + (2967000, uint256S("0x0000009a3b16a5fda24ddb6586b455924e604cc9253e257c022c205dc2d8840a")) + (2968000, uint256S("0x000001f135a1265c9b4db02b1e3e4f9cb0b29c97d6804d132c096ebb46330a33")) + (2969000, uint256S("0x000001bd3dc836d79d6b43f7ec6462451f507c49c2c3823cc03a3307c1e57d56")) + (2970000, uint256S("0x0000009c0b2841782b18762e4d14e69bd4c51e75de7c0f161150fc994c175823")) + (2971000, uint256S("0x0000012108b5e9367921844def97f020b0e7a1d80f222c0bf6027ca98535be5c")) + (2972000, uint256S("0x00000167122a71c4fbb63a0ebac10008a9fca86ac21bdcf4e8ae5a0dc4ac0652")) + (2973000, uint256S("0x000001379c7d87e695027690387dfd38512410872fa700b30d4c35dc46600bae")) + (2974000, uint256S("0x0000026fce7c4bb0697f528f2ea14c638e33f51ab2935bcffc7c02b310415ed4")) + (2975000, uint256S("0x0000018a159510b754e5a2ff7692f2235652130a585a13effe7da7294eedd724")) + (2976000, uint256S("0x0000015269b6e9cce2b30466d29abfc2e5d8ae0ae7e4f7cabeabeedd27ea36f2")) + (2977000, uint256S("0x000000243f50f7a27c54469830274f1ab8bf07b417c091aa43d2043d4c731b32")) + (2978000, uint256S("0x000001f171890e15e79ee12b1b3b9af9b788d13533fb09521d584d4989119246")) + (2979000, uint256S("0x000000957807cc7dc451bf5c4df3e2498aecde2927affb9c4b5cdcd824d5f6d9")) + (2980000, uint256S("0x00000207c9d63400d53e902d1ba329a3d80f2e19e412fa3642e21d8d4c642904")) + (2981000, uint256S("0x00000201716079224da4472579700f12449f6d75d34b83c4905d6ba3f3cb2ec7")) + (2982000, uint256S("0x0000002ad8bc77565cdd7e11975ba154c8d92f928bf1579d32e1934e8c44f865")) + (2983000, uint256S("0x000001b88c42d70e22f1907f7705ec09e22407525b1d015a38804a8b5663381a")) + (2984000, uint256S("0x0000006b31e10c313337ac9c8f95c7020bf5873fe9443c65fa8888bcf75cdff2")) + (2985000, uint256S("0x00000235bc5682cd7ffb0dae2255c774165f8a79e43e023db03412d80d128888")) + (2986000, uint256S("0x000000e0d64aa4eae99ce92c3daaa3c724d4771b465a3db45375f3317d77d42a")) + (2987000, uint256S("0x0000021ed226af96bd30a9d34d1944f4d0c9776eeb01bd1792c545a6bebfbf87")) + (2988000, uint256S("0x000000ab09b2d5cc61b7eacaf8c5c75d738c06eef778793569dbfe14249176c6")) + (2989000, uint256S("0x00000045fa48a265d64baa3d6fa0de317280792eb0c90a56d18a805685e1a548")) + (2990000, uint256S("0x0000019e091bf24b8c9b8c8797288dfa9a93f1fb455250b536c048ca491972bb")) + (2991000, uint256S("0x0000025c0b080404dcf8d102ca5e71fa763b3505665bf0d65d2531acea680d75")) + (2992000, uint256S("0x000003055b316b5b22ecd1717eb2190593b188c3b8c413b00cbffbdde0c34b27")) + (2993000, uint256S("0x000000a064bd95745fd853afd6ad73b731d1cd5ff9869c63ce63015fd2fb3bae")) + (2994000, uint256S("0x0000004b0af656104a4235f8aef67de14fafe2326c3141a0c789bec175f91bdc")) + (2995000, uint256S("0x000001c6c7295656d51de8c9707f4360de0d6e545067ff153fa89ec2e47e735e")) + (2996000, uint256S("0x00000125d4e3321be70bfca41bc6de3f7a13b866e1b55663f09d37c4847ed6c2")) + (2997000, uint256S("0x000001e84e1b66cdc616563f3396276635960026f26836f1c7a9baae69174039")) + (2998000, uint256S("0x000001206067731907e85eecc3e0d34b7c1b4fa3ba3dfae97c469ac32282cf9b")) + (2999000, uint256S("0x000000fb239083b6c855bf26455908662aa53c00f076e549e094a9aabbda6f89")) + (3000000, uint256S("0x00000219b1672bb1caabd27e2906287856a042529b15b15d05a2e2ff3efcfe41")) + (3001000, uint256S("0x000000d9d692717108a856946b98e588f49f7331e4f5c04f914d64104aedc61e")) + (3002000, uint256S("0x000000bee95e740ac3c6a6898958517542998189780a4992aa0b233089798653")) + (3003000, uint256S("0x00000102b4ce62b897c5c04eb20ab56a3d439f582e354e84fbb2ab5b03476569")) + (3004000, uint256S("0x00000117ad571f5197fe272be8c19a1b6ce1a72b2c58f0af82c4bb2a700055c9")) + (3005000, uint256S("0x0000018ff82a917dfb1125231f34bcf3dee4142d5b5591cbea8c52d6c97dd25c")) + (3006000, uint256S("0x0000023a1c1b99ee74b10bb4ca8271f9c94aaea42fee9155af21222f20a46f7d")) + (3007000, uint256S("0x0000027c56e38915a13965e04bae1c19968aa5302f463c3a05195fb9795736b6")) + (3008000, uint256S("0x000002dc226002e6df7aac0776a3a8691a5a8b8ac9ea8a44ec36e96c05e48ccb")) + (3009000, uint256S("0x0000018ebbed8ee0047e9797aa8ac2739a0796bfbdded7f862dbb91fbdeeb2cd")) + (3010000, uint256S("0x000002c5039f54af5d24f6b64463a6d5578c2122bc5393553557bfd991b8503d")) + (3011000, uint256S("0x0000015a1fdfc18774a9a984cead52e7fcddfbeda55981885fc0f3badc76d774")) + (3012000, uint256S("0x00000171fb194aee4f625996a18707883d2d6b381ec3696ea858170039f9c54c")) + (3013000, uint256S("0x0000015877955e47bb3a076dcc880facb15ca91a84c1173a534731f1ae327116")) + (3014000, uint256S("0x000000e6b8403f98e24474a424fb8e33c94bccaf98ab0ecbea35092b1ac41fe8")) + (3015000, uint256S("0x000001346352a5dc10c77ab0e3d201e3e1e5d40e747e0f572764267281deaa98")) + (3016000, uint256S("0x0000018e531c45772cf1b63764b899c50f3ad1d21f9f38c360e12030a90dbb8d")) + (3017000, uint256S("0x0000001794d412d1a32e065a3d3a152de00049d5bd64cd109a05d7c93a0bf8dd")) + (3018000, uint256S("0x000001eebb3aaf056a77d129564462bf989ce02693274a3356474d18c3dc9c09")) + (3019000, uint256S("0x000000a32aee249f482dba222b894d2fb42decbad676c18fd6377fd14050a3e9")) + (3020000, uint256S("0x000000e8ddbacb95d03f440ba2943b1ce76ffe4e9f26103554e5cdc7caed3c5a")) + (3021000, uint256S("0x000003b5471ea15bfc621cc24bcc900e4d4f8baa9be34eccb1cc0476180d9e87")) + (3022000, uint256S("0x0000007c7d8e8ca2d4f90bfaeb40362242771ff455b4001609cb1091e5f33870")) + (3023000, uint256S("0x0000015c222a8680576fde9424f9f694c17e3f19a62580e48986479b145cbb2f")) + (3024000, uint256S("0x000000913cdc5f2f4c35a0f342f02332f01a4b48f2a5a5a695bdedf915bc3634")) + (3025000, uint256S("0x0000026468663c251c23cb52805122582c4339d6c543b46259918ca318994b1c")) + (3026000, uint256S("0x000001198858f36489e2b27eea39cd62a8534b41dbc63b86a12d3eb99e7e22f0")) + (3027000, uint256S("0x000002008ec698cb00791d34a8e530c040d8b1aae8115afa1a26637dc645d28d")) + (3028000, uint256S("0x0000015e83194a90f0c59947d2b3f80948decf308b743bd6908ea936cd3894af")) + (3029000, uint256S("0x000000a95be914f4ad8055d9d005ab3d7ad4c8966e93cb5c44117a7bae92b2c2")) + (3030000, uint256S("0x000000368719c99a34efc081085fcefb4bd3b88dc4a720d40f8673702a9862e3")) + (3031000, uint256S("0x0000005f44110660dc10ebca6ca87673ddb85b9b61fca435e3a5d4ec04a17fc5")) + (3032000, uint256S("0x000002a6ffc3d6b4507102b9e2341f7a0a88928ffbdabf973b8e307f7e779ab6")) + (3033000, uint256S("0x0000010a99fa68c65193d4a987acc42ac3b8a5c5f30807be75ca055f93804dcb")) + (3034000, uint256S("0x00000247b6d12d8b55ab1967e27c60bce02715516cf5b87268c69759223a2eda")) + (3035000, uint256S("0x0000010cb46aac3ac625e91a3eb4a033256a2c78ff971f83c4d5a28be9491d3b")) + (3036000, uint256S("0x000000faabdf86474cc7cf1a79dfe3272a7fcf3329f3efa0111711db71dbe27d")) + (3037000, uint256S("0x00000066bf1146fc2b79f78fa0ebbee0145bfc8d378759b0945b1e8af5185f8d")) + (3038000, uint256S("0x000002200065ea197bd831f4395c52697f03b7fb010aafb930a5d4367c7948aa")) + (3039000, uint256S("0x00000351ffd7806659b7ed9ce9206e9d14c45906bc7948aed3288161bfb27c09")) + (3040000, uint256S("0x000002160907e189c825f3ae55081e0818d3a261884e18d56558441ebf95726b")) + (3041000, uint256S("0x00000124dbac3245d964761b3065fcfe998c1dcc17f0760f931531f980cad927")) + (3042000, uint256S("0x00000101ab03831aa195bae540dce8e424a35d4a81cb5c0caf9580939f1be380")) + (3043000, uint256S("0x0000004688fae99072abcf0106b12e06b5e75447cd16f022ad6eb74761249ba8")) + (3044000, uint256S("0x000000228198f80b17094a4bc4a81a4e207b0ef6f50a2ea79b27ed8e65f8a453")) + (3045000, uint256S("0x0000015986950a34fb2cc2698941d110c6eb83484ac62b77fd40931ec34ac62c")) + (3046000, uint256S("0x00000023339b1041301db20fcfd4b48141d6807105fd62f4e45d8b1b7d1f9688")) + (3047000, uint256S("0x0000002c5b54deaa41b058dcd310f82035b4336ed0d5f84e4d2f62446a63399c")) + (3048000, uint256S("0x0000001d191f3c24dcc0d6923b520b11598e7f53e17b11aab8d1de63a4f2c38d")) + (3049000, uint256S("0x000000875c9d83457911687246a5076f892bdf128f8c10bde1629f3d0f179b5d")) + (3050000, uint256S("0x0000043f02004859e2aa5f017529ac20e17bd0b79b9d87d0e7961095b2f68af8")) + (3051000, uint256S("0x000003ae5ab267c9880b89cfdc5015bdff626bd845b3fc5abec814b4ff7b0c2b")) + (3052000, uint256S("0x0000008c0e12c19e5e463a01028628bd462054fd04bc50cce15b85630eb920c3")) + (3053000, uint256S("0x000001d89eacab8a0a3932c575fbd608f418e3f608127255b3cc14ea18b40e5a")) + (3054000, uint256S("0x000003ce6e0bb1d7d4ba63ee9327125d7fd006fd4451f4f5895193cb1e2b5b3b")) + (3055000, uint256S("0x000001f78ddcf7f9240686c2a49dbdf75c0b194721e28dc879636d914ac62f9d")) + (3056000, uint256S("0x000000d8166fbb0615d907037478ea00e5901b92046918578e0257229775a682")) + (3057000, uint256S("0x00001cbe0ed4b0688520567a4bce719d14de4fc91bde5d4f4566680a681e937a")) + (3058000, uint256S("0x0000025f2460a0a968dda345c8e70f3e879176c2469c83e701f44cb38f52757c")) + (3059000, uint256S("0x00000057dcad6c12ea3c82e28f329870d6a2050c851897a0f56ee0c76e9f0162")) + (3060000, uint256S("0x000001a55a082a6b1d942b3b1b9cc28a03337aa256a1be0ac02d842acfb1466f")) + (3061000, uint256S("0x000000a9e328c963548c95730e3cda618efd9a4e59825c1d373a2608d0be7b5b")) + (3062000, uint256S("0x00000037877d10b82a5f27eb8d2e2a3ff036771b02d5822f4a7aa0d2ab6c9916")) + (3063000, uint256S("0x0000005285d80a1f134d5a93d4ab22c221ec8f81a18a39c2ce0d4b0fb141031a")) + (3064000, uint256S("0x00000025cec143c75f0a512e3764ef35d88248b98a32c2b3c70fa8eb1d4c484f")) + (3065000, uint256S("0x0000004d0a40e92fc32ee167cd1483acefc47f9c3829c3c53bdcd5071fbd8e4d")) + (3066000, uint256S("0x00000080e0be0ff49413f1c008a7c0efbee6489ba759e940b0cb0a24f3bef660")) + (3067000, uint256S("0x000000a588ec95f08994f6eb4a75b8c5c292122cbc84bd137c9256a296458c39")) + (3068000, uint256S("0x000000d3e004372c419d50838d88a884f5c9d16f2ac86f681a7b6a590ab58a81")) + (3069000, uint256S("0x000000343322d8cf3339d463ac84e0a3e824b62857ee531901e13fb690f29108")) + (3070000, uint256S("0x000000b5f47d0bedda6805322811da7960af2ba3fc44b327be7bad34b6e598c3")) + (3071000, uint256S("0x0000007812b4eb5028e26a03b46a13f72ace2881844dad9a05655fd4ac270039")) + (3072000, uint256S("0x00000040557067d6dd006fb7b9eed2092d9a61f2a623199f2f0df2222bf5f29e")) + (3073000, uint256S("0x0000004bab6defde89d8e44307cbc0fd37fc7d35d2a86ce3b89a7eaf42a4789f")) + (3074000, uint256S("0x000000234b6b83375ce7f10bb779926eab9369fc739dd0796d9eec0d90e3b2b1")) + (3075000, uint256S("0x0000009303a35d265c46875d5f2d7d60ed6717d84f682c27b7152cb18efb9def")) + (3076000, uint256S("0x000000a0e4cf5b19c37e1cc80175caa636d480b19837d76687a56faffbdac155")) + (3077000, uint256S("0x000000ce1e51298888d8632fda2a9dc0e28dd434fc49e18e290e61e82ed8034a")) + (3078000, uint256S("0x0000001a3446535b04bc8d09822fde3f28bc0d3346155c50dae160b63a4511cd")) + (3079000, uint256S("0x000000f671d6df74a89ec74cb3516fe9e438e60ece06854825372cf6f69830f2")) + (3080000, uint256S("0x000001188832e4820fe420a7ad184ea76c287073aebd09d5c2818492f8af2627")) + (3081000, uint256S("0x0000001320c27f48fa621f99379ecb84304df231bfb07b7e7c527b8a1d684dd7")) + (3082000, uint256S("0x000000b0157a3ecbc11024ba084073f52c2e0c6b65427c34cbf68c76d6ae571f")) + (3083000, uint256S("0x0000004799f50ffe153c16700d064b491f01d07a2e6a8176151fedc01991fcb0")) + (3084000, uint256S("0x00000052c88e3057099d4a2b755efd4addf29d27228093bb5f6783ec955a1706")) + (3085000, uint256S("0x000000033ea79bbc96b0ab2b75fe32b32209510b4768de003edefee17ea77826")) + (3086000, uint256S("0x000000711fcc88d264fc72b62a79e8e64d92f5ab02a3889c69cd61c0407e4474")) + (3087000, uint256S("0x0000002c333ba65cec4eadea04dbf6266af2b1adf0f135540e2d1a4613427204")) + (3088000, uint256S("0x00000098583783298ad125bd415604f068e9ce5579eb4cd9cea63a62b668c4b8")) + (3089000, uint256S("0x000000b28d476e2da580f7b6ea6375536076cd6761409ac219c283661c271681")) + (3090000, uint256S("0x000000529a6ab02121007876e3d940db0df8f66f0b6234170ed5d0cbdef6e85c")) + (3091000, uint256S("0x0000005bb23a78e9c124f2a541bd20e4891ff0ca2ec1b2bbc9a2690807000e5a")) + (3092000, uint256S("0x00000050dd2c2fd8831c30d0e83d18f2d9451e6bd9f73e753ab0b3e0f0ad8bf0")) + (3093000, uint256S("0x00000043ac606b0dce50c3e7ef9709084e99e6ee7abdecd698a0e8e8bbc764e9")) + (3094000, uint256S("0x00000002491170b68e6ba66dd0f8cd92bc6667b28675fcd8225b01870106cb8f")) + (3095000, uint256S("0x000000b05deaf78e215a7f0102065c57c31ce7d51e64980085b396ebd057a697")) + (3096000, uint256S("0x000000ac352984c4ef4a86d26bc35f7a0111c3602d243bc35dba96872d2a118e")) + (3097000, uint256S("0x000000ed65d9033eec729348ce9aa3f79d3d09a0a4c0a63f39c37240c00b9b02")) + (3098000, uint256S("0x000000391a40a7112eae5164d24add9beaf97f95bee6205afd96c1f8e106c4f9")) + (3099000, uint256S("0x000000bb66eedf2c837f6782ee6d3a409f07237e138ffd1b75fff541fc8233c9")) + (3100000, uint256S("0x0000005a501d4322cdb6d67504586362ab7902d116f5ad226b6bfd3d8df23d61")) + (3101000, uint256S("0x000001163c9fd3eec4dd127dc705286b6cf213d607d09074536be8b69188a1a0")) + (3102000, uint256S("0x000000c772df8f9650f3104e7b4db6de8b323da3057fa9a63f18fd101f4d01e9")) + (3103000, uint256S("0x000000c1383c6f337890136f3f59841e45eb0b4c5774ae200c9c01acf88c3e68")) + (3104000, uint256S("0x000000a9926b402d77d7fb5b8a0ee805a62ed40454d7b61ccede5af2af9dd0ea")) + (3105000, uint256S("0x0000006ee4adf77a7c6912fd9ea6e5b505b9d141a35d7159f8bc9a6b4910dbbe")) + (3106000, uint256S("0x0000002bbac066dc52c61e41e33c24f9db9906b5807fa4feff6e235e9f84c1a8")) + (3107000, uint256S("0x000000339fd4105912b6dff330902d45d78e5ab025e0c21b8e2f5d2bd69ecfa7")) + (3108000, uint256S("0x000000bf7d2c31afe10ce9a45aa74d01c5ffe2ab88687bbd9365f7ade64afd62")) + (3109000, uint256S("0x00000017d43890279cbbcf11c169b8c182aaa0e92ee9190ff4445a7eef3f900b")) + (3110000, uint256S("0x0000007c461b5ad5b99029d575f17c58d6ff8a4b662686f1cf7795500f785b43")) + (3111000, uint256S("0x0000008781b7a2e56d7ce5ef4ef2d82760a493dc208542a11a9d4d2b5b54c619")) + (3112000, uint256S("0x00000070c859a6d3763862b231e377bd53ea13e94e1126be02a8538f46404942")) + (3113000, uint256S("0x000000205e90cfa5844d046f76e3fafa4cdaa2f094344361d75b253294a2c679")) + (3114000, uint256S("0x00000029666033d0c6a4bee567d4b4dc124cd4f3b0cc1e9d9700cfbe10e010d3")) + (3115000, uint256S("0x000000a8460d428de6367c79ef5099da8bc2714f61b1c878af9a8ff1d0f591f2")) + (3116000, uint256S("0x0000002fdd9b54d39ea7e1e4c01c6d5e20439373d0d824e5fb8855c77e68355f")) + (3117000, uint256S("0x00000020f413e1f5b61f60bcd35b652cc2f54b8719baef556531975c6977fd47")) + (3118000, uint256S("0x000000742a686626900a16971ba608e3cfe6d892f6c9168d79b80677d3f099f2")) + (3119000, uint256S("0x00000076af5aecd68eddacf266095593ac46926dc746751b38c21498e5f0a3f3")) + (3120000, uint256S("0x000000725ba5ce0adcc752ea1eb5e9e7bf7154093ed5bf33f6895078900a98de")) + (3121000, uint256S("0x000000b8eb16919a565650daeb00701dabe2c8d44c8d9358ae5e48d3d9a7e2a2")) + (3122000, uint256S("0x0000007da03c22e99a008dc5386ed46bd06c5f77f270e08b6714bd4d14247381")) + (3123000, uint256S("0x0000015c6195b1e64f1834f13fdabf2877f5e63349a555c7c5061baebbb03273")) + (3124000, uint256S("0x000001030dd752304091361b353f028b5e2d9a514391e2c7fdb28d5d24858553")) + (3125000, uint256S("0x00000089c9ab264ae498bdb3597655a5ccf36c7139bf58c6c02c58c7162fd2f3")) + (3126000, uint256S("0x0000004d29525a897b4e6c1d70ed2f02cb38f5a27557497c23345ec69932884d")) + (3127000, uint256S("0x0000004efb4bc01160b06186a01ea40cd9ac331347dc2fb966cd52c7191d3aa6")) + (3128000, uint256S("0x000000ef678db1b12ba5fc0441d1c335b8bb630fb4d5a3d404187322861db039")) + (3129000, uint256S("0x000000527da1f996e1c87d030c4de67749ac78a1a725982237d879f314aa379f")) + (3130000, uint256S("0x000000daea0276b41195fd48ad9333ad82d2ee181af09da773eeecb0eed1c2cb")) + (3131000, uint256S("0x00000044826c11188c2bab69afe0f6b02772899bf714152fb10e03ee9b0661d0")) + (3132000, uint256S("0x00000079d9daef2612b90005ce8aaa67dab9e294ff2dd5aa9e513179610fc7ab")) + (3133000, uint256S("0x000000329fb093db7f21b7333bd70738b4c8691c878d8ffd33d0e9bf5ff6bd18")) + (3134000, uint256S("0x00000076b0392040b5c4c8cf5e790da58b639d3d775172592a91530120e5c35d")) + (3135000, uint256S("0x000000f47aae8156dba2b7366fbab94adb9e4d23a9d0ba32568bc5d633ac3924")) + (3136000, uint256S("0x00000139e9f09bd83f69ce3dc8a403e7bcabfc771c496f9a7b64b15a399ccaeb")) + (3137000, uint256S("0x000000b44c44279f8f87b8695d05c9baa32f6f988a7fafedd2e3e3a330c44735")) + (3138000, uint256S("0x000000000806995bac02073f889fce51b84b3cef43a7b874caa7047542442952")) + (3139000, uint256S("0x00000176bc78f1daefe43a2ef7554646a0238f74fe79c5fecca19f9ec65ef6c2")) + (3140000, uint256S("0x000000f1964b55b2f8ca7394fa922353c28457903de925fcb12d60e70d6442f5")) + (3141000, uint256S("0x000000ce310267356a033299a53413c09f503268aa42e730b9e18f14cd16de99")) + (3142000, uint256S("0x0000005ec5af349cadbf59506d69eac221b9802ee84a8aa4f85a380ee9206a85")) + (3143000, uint256S("0x0000000aebc3645e845eb5e7ee46e71891a31e9006fec22d16e5cf9461306783")) + (3144000, uint256S("0x000000ad31bce3cd6065c49f01dcba984c57d9cae9ee60d6b8f509ac0dbcd8bd")) + (3145000, uint256S("0x0000006aba80796f496e4449e57fe5e966ce50fb784564cb6b624e5ac51a7cc6")) + (3146000, uint256S("0x000000ea02b55f81a5baf0091fdb30001e233cbbe61e947f584d9fc0b678efca")) + (3147000, uint256S("0x00000109d8c6b002b587d710a1f573a9a917cfc5197a2bd37b9ec87f365c0535")) + (3148000, uint256S("0x000000b02ad9ce869bb38a6d778e89485f59a880bedbf515d894370e9a1c8fa4")) + (3149000, uint256S("0x00000000dfa5469377e5d5789bdec173669cf7e1421b4ce1538c54461ee62b17")) + (3150000, uint256S("0x0000002ecdc7a4fc85881fcb8c38bdc56f2c91511f9f9e6a8539d16bf2796ab3")) + (3151000, uint256S("0x000000b0ef3e86bf1c6d7cf0e7a9771125688cc034efdc4f0f81483bd6ac5188")) + (3152000, uint256S("0x000000e6b734457317240d3b4c6e1f557b87176d18066229c78c284ba56d8449")) + (3153000, uint256S("0x0000009179388d04e83e1e2ac9f841534dca48232ad5f07a8cd88ce76a9b5ed8")) + (3154000, uint256S("0x000000bf1af2c983b0d44ae46fd625e07c40bfd3e5a28c999344f7b20b373333")) + (3155000, uint256S("0x000000f04ac08ae6358b72b15c1ee77dfaa3746db6ab56da3dba3917ffc1043d")) + (3156000, uint256S("0x00000050afb4db81fd1bbd0cb815ad14af965bb7053bb4bb44720609ef04bf10")) + (3157000, uint256S("0x000001382033d8a5a5aa3f7b6e3ce071bd36c98deb149e226b07606b3a7b7e30")) + (3158000, uint256S("0x000001009ca876f6805a6cb96e6dbba41e9100e82f693e927fad6eebc05d22bc")) + (3159000, uint256S("0x00000183a3f3406d83c258608e4f3db8f4ed019a4a9899b7d297d4c83fb79223")) + (3160000, uint256S("0x000000960ba4a97484d3d16372084e139972634c7cf4d360d1c804178af88b3a")) + (3161000, uint256S("0x0000004db0a19d395855d9b7c2b4680536fdd11decf7d6867504cb67e41c8434")) + (3162000, uint256S("0x00000057f32d96d91e4af0c8c989c3fd3c3e553308b2c44f40ef3b2a548ce941")) + (3163000, uint256S("0x0000006f8bb942cdfc57f00817d4ae2dfa2e88fd1e0a9ab593c3ec34a0bb985f")) + (3164000, uint256S("0x000000f977348314bc2fd571056df11c26779f107de5523ccc05236a75f945e0")) + (3165000, uint256S("0x0000006e5e0cfeff6306b19ad8aa291e8df95bd002ce004085b1a2a5a086392e")) + (3166000, uint256S("0x0000014b28939d4a482e9db58d152328cc040b118379f9a5e104dd8fabb9505d")) + (3167000, uint256S("0x000000228c00ea599df8bb7af090901c243a3b6a9bb210420ab6a82fc0a379cd")) + (3168000, uint256S("0x000001c3f654d7d1f307060e3bd4f5102582d9f6dfc0bd826b735ef0d24eb7e3")) + (3169000, uint256S("0x00000294fbb1ba4fb799e28048eba52216b65130005f67c235129b43af2f2e6a")) + (3170000, uint256S("0x00000017d5572bcd7250eca0ee1533b30dbfd1288f53176a8c7210c3c12514a8")) + (3171000, uint256S("0x00000075a53ba47066a5c9956782166ab67694cf43a2eea255261c011baa8a1f")) + (3172000, uint256S("0x000000c6c61726ca942713df477ac245182809b3d80532e227aa1e3df01b7e9e")) + (3173000, uint256S("0x000000787fd12c128e6bdfe9701e528421cbfdf204ada723733167844fddadd8")) + (3174000, uint256S("0x00000084b72cfe91dbd1624ce8d6dd7b565c76f8b2aa52c05be49e1894580402")) + (3175000, uint256S("0x00000090df5c3a1a35ee87c4bbb67ed828211e320a783b4bd5aa504258856d9e")) + (3176000, uint256S("0x000000c9cd391b1f8baf52ffcd74114ddd6d062b1c74c0203bf16b19d8bc52f2")) + (3177000, uint256S("0x0000000e093102ffd8c978c4786ab345196000b2789325bae1777599de1a7cf4")) + (3178000, uint256S("0x000000e4d359d3b0a1730eac385d9d6bc0c9e2e4cac055492f98c7064c9a2385")) + (3179000, uint256S("0x000000c52858873d506ebf04ab7f767ac9f4144d211a791bfb4c81ac5960ae55")) + (3180000, uint256S("0x000000b70fd9ea8ccc360593d3a75f7ac6400acd40eee22364f39e40092f6772")) + (3181000, uint256S("0x0000010871b5c6ea2c3909d4e1740ecb1d0eb4f435d82f5dec3bfdeb0bf5e649")) + (3182000, uint256S("0x000000b5bf1cbe1871c3aa7bc4b280449803eae763a149ed70cbcafea7ae12ef")) + (3183000, uint256S("0x00000153bf68c72eb0975699099f292dd5987ca42fec7d9bb57dc8a594f92565")) + (3184000, uint256S("0x000000449b96d6723728a7c5e7c6cb7a0509224185f52e91fbb53132e8f993d5")) + (3185000, uint256S("0x0000015b0ec4dacd96c97d8a83cd9942ebc8ad877f9a088c6875391b3beb0523")) + (3186000, uint256S("0x0000003636a9508d1c33f5dc7132f9612eec3960e14aafff853d59d352d78756")) + (3187000, uint256S("0x0000013c19d114806898c4734dd653ebd28158de083adaec3101ddac3b28ee32")) + (3188000, uint256S("0x00000132e633d30682c9939fa914a5f1fbc6c56877a0ea131eb1e5341aa81683")) + (3189000, uint256S("0x0000001d74ce8246a098ac7d95eb5609245dcf7d9df0f0d337696efa33d5e66b")) + (3190000, uint256S("0x0000010e099fb54b916a9d5f0f4495adddad1bb9c947b33d837ed607fa257a7d")) + (3191000, uint256S("0x00000053c6beb3b7788f5ccd5877dc4162eef98a00fe13b0cdc87cd6faf10d6a")) + (3192000, uint256S("0x000000733a88ba30bee717951134f48cdef4acd021d368edaada61e8ec870d0c")) + (3193000, uint256S("0x0000002f8b6b7c7846f94e304b17888c060fb22170b944a46921b3af6461cc47")) + (3194000, uint256S("0x00000026340cc6a617885b3f0a77bd24750f89ab6d43560beb637399d1a45ce1")) + (3195000, uint256S("0x000000a21fad1caa6397cd68bef24a999bb8b61c229e1e450c09392255c93a30")) + (3196000, uint256S("0x00000008e283d3b23e7384d46896550024b1a78c21acb6969baee8a62b83448b")) + (3197000, uint256S("0x00000068f83587ceb858f477db5f0bcdd5934ca2d7e93168e5c64c0710d06f95")) + (3198000, uint256S("0x0000009d94f70e4fe72388913f110578c10aafd8466cd3f3b2f6b21c5066feba")) + (3199000, uint256S("0x00000040c554cfea75cbf9b1ce977c536173951011125c2cf37b32c3fb29e86b")) + (3200000, uint256S("0x0000004a1c3bc726562379a7d7805ba610113ba72816bcbc1f85788a9d6d4ef9")) + (3201000, uint256S("0x000000145ffdbfcfc521b2334509fe3201ad4eeb667ec7e183c4bf9e888fbbb1")) + (3202000, uint256S("0x0000001ded77185502b724ae961a8eac5734b75c0057b4d351e58aaa5962acd6")) + (3203000, uint256S("0x00000056740ca653e79406d309a91728c4825f546f3673757bde15f6ec3f468c")) + (3204000, uint256S("0x00000085da8e803be0813d5617a6a461cc6914ef533abaeaef5d824054516218")) + (3205000, uint256S("0x0000002b7f3ef3b42f132916aa94e4b3d61c0ffeb98c7aca57b51601a3ab61b9")) + (3206000, uint256S("0x00000077bb73b9f7df4c6a6d55217ebdc48ed36b44826479e320d0925eade58e")) + (3207000, uint256S("0x0000007522193a2ef39d4cd7e9512502af313e55d5d8968ccf3226bb9a94bba0")) + (3208000, uint256S("0x00000060832ff1400278327d1f2e5c1a5eb872bee8b9b383e7777ff90fcdd009")) + (3209000, uint256S("0x00000007d8dc51b7ff58651725dc01d65f5dc696d0757d6c14a26e4d8e750e43")) + (3210000, uint256S("0x000000541ff2ced91b5d31a42ec0f405b5e9aa24e95e76c38ce23e904ac10e52")) + (3211000, uint256S("0x000000727913ea052a4a00845fd19fc7024c57e8fae341f5022bfbd4a62bb774")) + (3212000, uint256S("0x0000000ea83220465ca147e8eab43d71776fdedad9cd79e33347db2c98a4c89e")) + (3213000, uint256S("0x00000013b82d62857feacb10aed6c86417c8310431037e386bdf6e352c906bc8")) + (3214000, uint256S("0x00000046e777e7e3a0a20f4e4a16bf8c881c4b32cec5ed6c46b9271c184922f8")) + (3215000, uint256S("0x0000002668dc21fe0252168397ce5b27acf6ff4c39c937c847e62ddacf0f4b36")) + (3216000, uint256S("0x00000060af593fe53d77063152de1bf341eb2e4c72c4f714b7be021a776cede0")) + (3217000, uint256S("0x000000905d46326bcfb55169956a7e73c0885b98bba32defde31c1b9c6ab723a")) + (3218000, uint256S("0x0000004c178fff51b6c7329417988724d19903f47e370ced6edd3ee2bec49c00")) + (3219000, uint256S("0x0000007819ed1bc19308a116eec0837b9826ed78e68ca41d4f718d2a164fcf6f")) + (3220000, uint256S("0x000000f29bdf2175086feb09ee2603d14685a4e58e2641be7ebb03e58402e9f3")) + (3221000, uint256S("0x000000726da4da04803e39d1ad6bc8bd4a956eee49f6629059154564cafe84eb")) + (3222000, uint256S("0x0000005c77415666ca339c55de179d1cd43f4db4de2ac105326b51de549c066c")) + (3223000, uint256S("0x000000a63e3d529ef124c34a64cf168d6db8ba8a22461a20298654bc70aded2c")) + (3224000, uint256S("0x00000068d7fdfc5bf76b1465afd89b2cc05c60f191a28dfedfedd54f68b1d632")) + (3225000, uint256S("0x00000063b8bd774216c7d438c1f07f6d7cc73a1bd03a38a0c7e2d861db679cee")) + (3226000, uint256S("0x0000005a5d9c7a9ffa27f23cad423c2c3e3fcd341be06c912819815cd64135cc")) + ,(int64_t) 1787132038, // time of last checkpointed block + (int64_t) 3415780, // total txs + (double) 3012 // txs in the last day before block 3226000 }; } else { diff --git a/src/chainparams.h b/src/chainparams.h index 962f8ece9..904c44c22 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -117,6 +117,10 @@ public: void SetNValue(uint64_t n) { nEquihashN = n; } void SetKValue(uint64_t k) { nEquihashK = k; } void SetMiningRequiresPeers(bool flag) { fMiningRequiresPeers = flag; } + //! Drop any inherited peer seeds. An assetchain gets its params by copying a + //! base network and overriding pieces; without this it silently keeps the base + //! chain's DNS and fixed seeds, which are wrong for it by definition. + void ClearSeeds() { vSeeds.clear(); vFixedSeeds.clear(); } CMessageHeader::MessageStartChars pchMessageStart; Consensus::Params consensus; diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 90f2d4182..0f3e03216 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -11,14 +11,16 @@ // Each line contains a BIP155 serialized address. // static const uint8_t chainparams_seed_main[] = { - 0x01,0x04,0xd4,0x38,0x29,0x3f,0x00,0x00, // 212.56.41.63 - 0x01,0x04,0xc2,0x8c,0xc6,0xb0,0x00,0x00, // 194.140.198.176 - 0x01,0x04,0xd4,0x38,0x29,0x2f,0x00,0x00, // 212.56.41.47 - 0x01,0x04,0x90,0x7e,0x93,0xa5,0x00,0x00, // 144.126.147.165 - 0x01,0x04,0xb0,0x7e,0x57,0xf1,0x00,0x00, // 176.126.87.241 + 0x01,0x04,0xd4,0x38,0x29,0x3f,0x55,0x08, // 212.56.41.63:21768 + 0x01,0x04,0xc2,0x8c,0xc6,0xb0,0x55,0x08, // 194.140.198.176:21768 + 0x01,0x04,0xd4,0x38,0x29,0x2f,0x55,0x08, // 212.56.41.47:21768 + 0x01,0x04,0x90,0x7e,0x93,0xa5,0x55,0x08, // 144.126.147.165:21768 + 0x01,0x04,0xb0,0x7e,0x57,0xf1,0x55,0x08, // 176.126.87.241:21768 + 0x01,0x04,0x0d,0x8c,0x3a,0xfb,0x55,0x08, // 13.140.58.251:21768 + 0x01,0x04,0x05,0x68,0x53,0x64,0x55,0x08, // 5.104.83.100:21768 }; static const uint8_t chainparams_seed_test[] = { 0x01,0x04,0x01,0x02,0x03,0x04,0x00,0x00, // 1.2.3.4 }; -#endif // HUSH_CHAINPARAMSSEEDS_H +#endif // DRAGONX_CHAINPARAMSSEEDS_H diff --git a/src/clientversion.cpp b/src/clientversion.cpp index 635121bdb..88f03fa47 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -62,11 +62,15 @@ const std::string CLIENT_NAME = GetArg("-clientname", "DragonX"); #endif //TODO: clean up this stuff -//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives. -#define GIT_ARCHIVE 1 +//! git archives get "#define GIT_ARCHIVE 1" substituted on the next line by export-subst. +//! Do NOT hardcode it: the BUILD_DESC chain below falls back to GIT_COMMIT_ID whenever +//! build.h carries no BUILD_DESC -- any build without git metadata, e.g. from a tarball -- +//! so a hardcoded id makes those binaries claim an identity that is not theirs. Until +//! 2026-08-21 this asserted Komodo commit a86845f3dc, dated Feb 2018, on every such build. +//! With it gone that case reports "-unk", which is honest and greppable. #ifdef GIT_ARCHIVE -#define GIT_COMMIT_ID "a86845f3dc" -#define GIT_COMMIT_DATE "Wed, 21 Feb 2018 16:15:11 +0200" +#define GIT_COMMIT_ID "$Format:%h$" +#define GIT_COMMIT_DATE "$Format:%cD$" #endif #define RENDER_BETA_STRING(num) "-beta" DO_STRINGIZE(num) diff --git a/src/clientversion.h b/src/clientversion.h index 9f8308415..734105201 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -29,8 +29,8 @@ //! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it // Must be kept in sync with configure.ac , ugh! #define CLIENT_VERSION_MAJOR 1 -#define CLIENT_VERSION_MINOR 0 -#define CLIENT_VERSION_REVISION 3 +#define CLIENT_VERSION_MINOR 3 +#define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 50 //! Set to true for release, false for prerelease or test build diff --git a/src/coins.cpp b/src/coins.cpp index 1e5bff4e3..40557363d 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -214,19 +214,6 @@ void CCoinsViewCache::AbstractPushAnchor( } } -//TODO: delete -/* -template<> void CCoinsViewCache::PushAnchor(const SproutMerkleTree &tree) -{ - AbstractPushAnchor( - tree, - SPROUT, - cacheSproutAnchors, - hashSproutAnchor - ); -} -*/ - template<> void CCoinsViewCache::PushAnchor(const SaplingMerkleTree &tree) { AbstractPushAnchor( @@ -406,8 +393,6 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { void BatchWriteNullifiers(CNullifiersMap &mapNullifiers, CNullifiersMap &cacheNullifiers) { - //if(fZdebug) - // LogPrintf("%s\n", __FUNCTION__); for (CNullifiersMap::iterator child_it = mapNullifiers.begin(); child_it != mapNullifiers.end();) { if (child_it->second.flags & CNullifiersCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). CNullifiersMap::iterator parent_it = cacheNullifiers.find(child_it->first); @@ -531,10 +516,7 @@ unsigned int CCoinsViewCache::GetCacheSize() const { const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const { const CCoins* coins = AccessCoins(input.prevout.hash); - //fprintf(stderr, "GetOutputFor: input=%s", input.ToString().c_str()); - //fprintf(stderr, "GetOutputFor: prevout n=%d,txid=%s\n", input.prevout.n, input.prevout.hash.ToString().c_str()); assert(coins && coins->IsAvailable(input.prevout.n)); - //fprintf(stderr, "GetOutputFor: IsAvailable\n"); return coins->vout[input.prevout.n]; } @@ -596,7 +578,6 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const const COutPoint &prevout = tx.vin[i].prevout; const CCoins* coins = AccessCoins(prevout.hash); if (!coins || !coins->IsAvailable(prevout.n)) { - //fprintf(stderr,"HaveInputs missing input %s/v%d\n",prevout.hash.ToString().c_str(),prevout.n); return false; } } diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index 5179aa461..56125f2c0 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -63,10 +63,6 @@ UpgradeState NetworkUpgradeState( const Consensus::Params& params, Consensus::UpgradeIndex idx) { - if (nHeight < 0) - { - printf("height: %d", nHeight); - } assert(nHeight >= 0); assert(idx >= Consensus::BASE_SPROUT && idx < Consensus::MAX_NETWORK_UPGRADES); auto nActivationHeight = params.vUpgrades[idx].nActivationHeight; diff --git a/src/crosschain.cpp b/src/crosschain.cpp index 4acb91f1a..2ac298e99 100644 --- a/src/crosschain.cpp +++ b/src/crosschain.cpp @@ -41,7 +41,9 @@ // XXX: There are potential crashes wherever we access chainActive without a lock, // because it might be disconnecting blocks at the same time. // TODO: this assumes a blocktime of 75 seconds for HUSH and 60 seconds for other chains -int NOTARISATION_SCAN_LIMIT_BLOCKS = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? 1152 : 1440; +// DragonX: the HUSH3 (1152) branch is dead — SMART_CHAIN_SYMBOL is always "DRAGONX", and at +// static-init time it is empty, so this already always resolved to 1440. Pinned to 1440. +int NOTARISATION_SCAN_LIMIT_BLOCKS = 1440; CBlockIndex *hush_getblockindex(uint256 hash); /* On HUSH */ diff --git a/src/gtest/test_mnemonic_compat.cpp b/src/gtest/test_mnemonic_compat.cpp index cb623c921..ce7468e57 100644 --- a/src/gtest/test_mnemonic_compat.cpp +++ b/src/gtest/test_mnemonic_compat.cpp @@ -139,3 +139,103 @@ TEST(mnemonic_compat, RawEntropyDiffersFromMnemonicSeed) const std::string entropyT = DeriveTAddrFromSeedBytes(zeros); // wrong (32-byte) EXPECT_NE(seedT, entropyT); } +// New storage form: the HD seed IS the expanded 64-byte BIP39 seed, the chain is +// NOT flagged mnemonic, and the phrase comes from the separate entropy record. +TEST(mnemonic_compat, ExpandedSeedIsStoredDirectly) +{ + SelectParams(CBaseChainParams::MAIN); + + CWallet wallet; + ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART)); + + // Stored bytes == the 64-byte BIP39 seed, fed to derivation unchanged. + HDSeed stored; + ASSERT_TRUE(wallet.GetHDSeed(stored)); + auto raw = stored.RawSeed(); + EXPECT_EQ(raw.size(), (size_t)64); + EXPECT_EQ(HexStr(raw.begin(), raw.end()), std::string(SEED64_HEX)); + + // No CHDChain version bump / no mnemonic flag: an older binary reads this + // wallet and derives the same tree. + EXPECT_FALSE(wallet.GetHDChain().fMnemonicSeed); + EXPECT_LT(wallet.GetHDChain().nVersion, CHDChain::VERSION_HD_MNEMONIC); + + HDSeed forDerivation; + ASSERT_TRUE(wallet.GetHDSeedForDerivation(forDerivation)); + EXPECT_EQ(forDerivation.RawSeed(), raw); + + // The phrase is still exportable, and IsMnemonicSeed() (which gates + // z_exportmnemonic) still says yes. + EXPECT_TRUE(wallet.IsMnemonicSeed()); + EXPECT_TRUE(wallet.HaveMnemonicEntropy()); + std::string exported; + ASSERT_TRUE(wallet.GetMnemonicPhrase(exported)); + EXPECT_EQ(exported, std::string(ABANDON_ART)); +} + +// Backwards compatibility: a wallet in the OLD form (stored HD seed == 32-byte +// entropy, fMnemonicSeed = true) must still derive and still export its phrase. +TEST(mnemonic_compat, LegacyEntropySeedStillWorks) +{ + SelectParams(CBaseChainParams::MAIN); + + RawHDSeed zeros(32, 0), seed64; + ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64)); + + CWallet wallet; + { + LOCK(wallet.cs_wallet); + RawHDSeed entropy(32, 0); + HDSeed legacy(entropy); + ASSERT_TRUE(wallet.InstallHDSeed(legacy, true, 1)); + } + EXPECT_TRUE(wallet.GetHDChain().fMnemonicSeed); + EXPECT_FALSE(wallet.HaveMnemonicEntropy()); + EXPECT_TRUE(wallet.IsMnemonicSeed()); + + // Still expanded on read -> same key tree as the new form. + HDSeed forDerivation; + ASSERT_TRUE(wallet.GetHDSeedForDerivation(forDerivation)); + EXPECT_EQ(forDerivation.RawSeed(), seed64); + { + LOCK(wallet.cs_wallet); + EXPECT_EQ(EncodePaymentAddress(wallet.GenerateNewSaplingZKey()), + DeriveZAddrFromSeed64(seed64)); + } + + std::string exported; + ASSERT_TRUE(wallet.GetMnemonicPhrase(exported)); + EXPECT_EQ(exported, std::string(ABANDON_ART)); +} + +// The entropy record refuses replacement, and a phrase that does not restore the +// installed seed is never printed. +TEST(mnemonic_compat, MnemonicEntropyGuards) +{ + SelectParams(CBaseChainParams::MAIN); + + RawHDSeed zeros(32, 0), ones(32, 1); + + // Refuse-to-replace. + CWallet wallet; + ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART)); + EXPECT_FALSE(wallet.SetMnemonicEntropy(ones)); + EXPECT_FALSE(wallet.SetMnemonicEntropy(RawHDSeed())); // empty is not "installed" + + // Mismatched entropy -> no phrase. Install a 64-byte seed that is NOT the + // expansion of `zeros`, then attach `zeros` as entropy. + RawHDSeed otherSeed64; + ASSERT_TRUE(Bip39SeedFromEntropy(ones, otherSeed64)); + CWallet mismatched; + ASSERT_TRUE(mismatched.SetHDSeedFromHex(HexStr(otherSeed64.begin(), otherSeed64.end()))); + ASSERT_TRUE(mismatched.SetMnemonicEntropy(zeros)); + std::string phrase; + EXPECT_FALSE(mismatched.GetMnemonicPhrase(phrase)); + + // Matching entropy attached to a hex-restored wallet -> phrase available. + CWallet matched; + ASSERT_TRUE(matched.SetHDSeedFromHex(std::string(SEED64_HEX))); + ASSERT_TRUE(matched.SetMnemonicEntropy(zeros)); + ASSERT_TRUE(matched.GetMnemonicPhrase(phrase)); + EXPECT_EQ(phrase, std::string(ABANDON_ART)); +} diff --git a/src/gtest/test_stratum_jobid.cpp b/src/gtest/test_stratum_jobid.cpp new file mode 100644 index 000000000..7c248a6a9 --- /dev/null +++ b/src/gtest/test_stratum_jobid.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2016-2026 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html +// +// Regression coverage for the b3e81f1ed fix: +// "stratum: do not abort the daemon on a malformed 63-character job_id" +// +// The EWBF "31 bytes job_id" path in stratum_mining_submit() completes a +// 63-character job_id with each hex digit in turn and feeds the result to +// uint256(). ParseHex() stops at the first non-hex character and returns a +// SHORT vector without signalling an error, and base_blob's vector ctor +// asserts vch.size() == 32. asserts are live in release builds here, and the +// job_id arrives from an unauthenticated client -- so a single mining.submit +// whose 63-character job_id contains any non-hex byte (63 spaces will do) +// used to abort the node. +// +// The fix size-checks each candidate before constructing uint256. These tests +// pin the exact invariant that guard relies on, using the real ParseHex and +// uint256 primitives, without ever constructing a uint256 from a short vector +// (which would still abort under the guard we are protecting). + +#include +#include +#include + +#include "uint256.h" +#include "utilstrencodings.h" + +namespace { + +const std::string HEXDIGITS = "0123456789abcdef"; + +// Mirrors the guarded completion loop in stratum_mining_submit(): try every +// single-hex-digit completion and report whether ANY of them parses to a +// whole 32-byte value. Only then is uint256() construction reached. +bool AnyCompletionParsesTo32(const std::string& jobid) { + for (char d : HEXDIGITS) { + std::vector vch = ParseHex(jobid + d); + if (vch.size() == 32) return true; + } + return false; +} + +} // namespace + +// The exact example from the fix commit: a 63-character job_id of spaces. +// No completion may reach a 32-byte vector, so the daemon never constructs +// uint256() and never aborts. +TEST(StratumJobId, AllSpacesNeverYields32Bytes) { + std::string spaces(63, ' '); + ASSERT_EQ(spaces.size(), 63u); + for (char d : HEXDIGITS) { + EXPECT_NE(ParseHex(spaces + d).size(), 32u) + << "completion '" << d << "' unexpectedly produced 32 bytes"; + } + EXPECT_FALSE(AnyCompletionParsesTo32(spaces)); +} + +// A single non-hex byte embedded in an otherwise-hex 63-char job_id is enough: +// ParseHex stops at it, so every completion is short. +TEST(StratumJobId, SingleNonHexByteInMiddleIsRejected) { + std::string jobid(63, 'a'); + jobid[30] = 'g'; // 'g' is not a hex digit + ASSERT_EQ(jobid.size(), 63u); + EXPECT_FALSE(AnyCompletionParsesTo32(jobid)); +} + +// A non-hex byte at the very end (position 62) is likewise rejected: the last +// hex pair can never complete to a whole byte. +TEST(StratumJobId, NonHexByteAtEndIsRejected) { + std::string jobid(62, 'a'); + jobid.push_back('z'); // length 63, last char non-hex + ASSERT_EQ(jobid.size(), 63u); + EXPECT_FALSE(AnyCompletionParsesTo32(jobid)); +} + +// A genuine truncated EWBF job_id -- 63 real hex characters -- must complete +// to exactly 32 bytes for every digit, so uint256() construction is safe. +TEST(StratumJobId, ValidSixtyThreeHexCompletesToExactly32Bytes) { + std::string jobid(63, 'a'); + ASSERT_EQ(jobid.size(), 63u); + for (char d : HEXDIGITS) { + std::vector vch = ParseHex(jobid + d); + ASSERT_EQ(vch.size(), 32u) << "completion '" << d << "'"; + // 32-byte vector: construction must not trip the size assertion. + uint256 h(vch); + EXPECT_EQ(h.size(), 32u); + } + EXPECT_TRUE(AnyCompletionParsesTo32(jobid)); +} + +// Sanity anchors for ParseHex's odd/even handling that the loop depends on: +// an odd hex length drops the trailing nibble (63 hex -> 31 bytes), and one +// more hex char fills the 32nd byte (64 hex -> 32 bytes). +TEST(StratumJobId, ParseHexOddLengthDropsTrailingNibble) { + EXPECT_EQ(ParseHex(std::string(63, 'a')).size(), 31u); + EXPECT_EQ(ParseHex(std::string(64, 'a')).size(), 32u); +} diff --git a/src/hush.h b/src/hush.h index fad5be098..24daa7a7c 100644 --- a/src/hush.h +++ b/src/hush.h @@ -95,7 +95,6 @@ int32_t hush_parsestatefile(struct hush_state *sp,FILE *fp,char *symbol,char *de errs++; else { - //printf("updated %d pubkeys at %s ht.%d\n",num,symbol,ht); if ( (HUSH_EXTERNAL_NOTARIES != 0 && matched != 0) ) hush_eventadd_pubkeys(sp,symbol,ht,num,pubkeys); } @@ -131,7 +130,6 @@ int32_t hush_parsestatefile(struct hush_state *sp,FILE *fp,char *symbol,char *de uint8_t n,nid; uint256 hash; uint64_t mask; n = fgetc(fp); nid = fgetc(fp); - //printf("U %d %d\n",n,nid); if ( fread(&mask,1,sizeof(mask),fp) != sizeof(mask) ) errs++; if ( fread(&hash,1,sizeof(hash),fp) != sizeof(hash) ) @@ -145,7 +143,6 @@ int32_t hush_parsestatefile(struct hush_state *sp,FILE *fp,char *symbol,char *de if ( fread(&kheight,1,sizeof(kheight),fp) != sizeof(kheight) ) errs++; //if ( matched != 0 ) global independent states -> inside *sp - //printf("%s.%d load[%s] ht.%d\n",SMART_CHAIN_SYMBOL,ht,symbol,kheight); hush_eventadd_hushheight(sp,symbol,ht,kheight,0); } else if ( func == 'T' ) @@ -156,7 +153,6 @@ int32_t hush_parsestatefile(struct hush_state *sp,FILE *fp,char *symbol,char *de if ( fread(&ktimestamp,1,sizeof(ktimestamp),fp) != sizeof(ktimestamp) ) errs++; //if ( matched != 0 ) global independent states -> inside *sp - //printf("%s.%d load[%s] ht.%d t.%u\n",SMART_CHAIN_SYMBOL,ht,symbol,kheight,ktimestamp); hush_eventadd_hushheight(sp,symbol,ht,kheight,ktimestamp); } else if ( func == 'R' ) @@ -186,7 +182,6 @@ int32_t hush_parsestatefile(struct hush_state *sp,FILE *fp,char *symbol,char *de int32_t i; for (i=0; i global PVALS - //printf("%s load[%s] prices %d\n",SMART_CHAIN_SYMBOL,symbol,ht); hush_eventadd_pricefeed(sp,symbol,ht,pvals,numpvals); - //printf("load pvals ht.%d numpvals.%d\n",ht,numpvals); } else printf("error loading pvals[%d]\n",numpvals); } // else printf("[%s] %s illegal func.(%d %c)\n",SMART_CHAIN_SYMBOL,symbol,func,func); return(func); @@ -239,7 +232,6 @@ int32_t hush_parsestatefiledata(struct hush_state *sp,uint8_t *filedata,long *fp errs++; else { - //printf("updated %d pubkeys at %s ht.%d\n",num,symbol,ht); if ( (HUSH_EXTERNAL_NOTARIES != 0 && matched != 0) ) hush_eventadd_pubkeys(sp,symbol,ht,num,pubkeys); } @@ -274,7 +266,6 @@ int32_t hush_parsestatefiledata(struct hush_state *sp,uint8_t *filedata,long *fp uint8_t n,nid; uint256 hash; uint64_t mask; n = filedata[fpos++]; nid = filedata[fpos++]; - //printf("U %d %d\n",n,nid); if ( memread(&mask,sizeof(mask),filedata,&fpos,datalen) != sizeof(mask) ) errs++; if ( memread(&hash,sizeof(hash),filedata,&fpos,datalen) != sizeof(hash) ) @@ -295,7 +286,6 @@ int32_t hush_parsestatefiledata(struct hush_state *sp,uint8_t *filedata,long *fp if ( memread(&ktimestamp,sizeof(ktimestamp),filedata,&fpos,datalen) != sizeof(ktimestamp) ) errs++; //if ( matched != 0 ) global independent states -> inside *sp - //printf("%s.%d load[%s] ht.%d t.%u\n",SMART_CHAIN_SYMBOL,ht,symbol,kheight,ktimestamp); hush_eventadd_hushheight(sp,symbol,ht,kheight,ktimestamp); } else if ( func == 'R' ) @@ -325,7 +315,6 @@ int32_t hush_parsestatefiledata(struct hush_state *sp,uint8_t *filedata,long *fp int32_t i; for (i=0; i global PVALS - //printf("%s load[%s] prices %d\n",SMART_CHAIN_SYMBOL,symbol,ht); hush_eventadd_pricefeed(sp,symbol,ht,pvals,numpvals); - //printf("load pvals ht.%d numpvals.%d\n",ht,numpvals); } else printf("error loading pvals[%d]\n",numpvals); } // else printf("[%s] %s illegal func.(%d %c)\n",SMART_CHAIN_SYMBOL,symbol,func,func); *fposp = fpos; @@ -366,7 +353,6 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie printf("[%s] no hush_stateptr\n",SMART_CHAIN_SYMBOL); return; } - //printf("[%s] (%s) -> (%s)\n",SMART_CHAIN_SYMBOL,symbol,dest); if ( fp == 0 ) { hush_statefname(fname,SMART_CHAIN_SYMBOL,(char *)"hushstate"); @@ -385,12 +371,10 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie } if ( height <= 0 ) { - //printf("early return: stateupdate height.%d\n",height); return; } if ( fp != 0 ) // write out funcid, height, other fields, call side effect function { - //printf("fpos.%ld ",ftell(fp)); if ( HUSHheight != 0 ) { if ( HUSHtimestamp != 0 ) @@ -425,7 +409,6 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie errs++; if ( fwrite(opretbuf,1,olen,fp) != olen ) errs++; - //printf("create ht.%d R opret[%d] sp.%p\n",height,olen,sp); hush_eventadd_opreturn(sp,symbol,height,txhash,opretvalue,vout,opretbuf,olen); } else if ( notarypubs != 0 && numnotaries > 0 ) @@ -441,7 +424,6 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie } else if ( voutmask != 0 && numvouts > 0 ) { - //printf("ht.%d func U %d %d errs.%d hashsize.%ld\n",height,numvouts,notaryid,errs,sizeof(txhash)); fputc('U',fp); if ( fwrite(&height,1,sizeof(height),fp) != sizeof(height) ) errs++; @@ -468,13 +450,10 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie if ( fwrite(pvals,sizeof(uint32_t),numpvals,fp) != numpvals ) errs++; hush_eventadd_pricefeed(sp,symbol,height,pvals,numpvals); - //printf("ht.%d V numpvals[%d]\n",height,numpvals); } - //printf("save pvals height.%d numpvals.%d\n",height,numpvals); } else if ( height != 0 ) { - //printf("ht.%d func N ht.%d errs.%d\n",height,NOTARIZED_HEIGHT,errs); if ( sp != 0 ) { if ( sp->MoMdepth != 0 && sp->MoM != zero ) @@ -504,7 +483,6 @@ void hush_stateupdate(int32_t height,uint8_t notarypubs[][33],uint8_t numnotarie int32_t hush_validate_chain(uint256 srchash,int32_t notarized_height) { - //fprintf(stderr,"%s\n", __func__); static int32_t last_rewind; int32_t rewindtarget; CBlockIndex *pindex; struct hush_state *sp; char symbol[HUSH_SMART_CHAIN_MAXLEN],dest[HUSH_SMART_CHAIN_MAXLEN]; if ( (sp= hush_stateptr(symbol,dest)) == 0 ) return(0); @@ -555,11 +533,9 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi if ( memcmp(crypto555,scriptbuf+1,33) == 0 ) { *specialtxp = 1; - //printf(">>>>>>>> "); } else if ( hush_chosennotary(&nid,height,scriptbuf + 1,timestamp) >= 0 ) { - //printf("found notary.k%d\n",k); if ( notaryid < 64 ) { if ( notaryid < 0 ) @@ -569,9 +545,6 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi } else if ( notaryid != nid ) { - //for (i=0; i<33; i++) - // printf("%02x",scriptbuf[i+1]); - //printf(" %s mismatch notaryid.%d k.%d\n",SMART_CHAIN_SYMBOL,notaryid,nid); notaryid = 64; *voutmaskp = 0; } @@ -605,7 +578,6 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi } else { if ( scriptbuf[len] == 'K' ) { - //fprintf(stderr,"i.%d j.%d KV OPRET len.%d %.8f\n",i,j,opretlen,dstr(value)); hush_stateupdate(height,0,0,0,txhash,0,0,0,0,0,0,value,&scriptbuf[len],opretlen,j,zero,0); return(-1); } @@ -727,9 +699,6 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi } else if ( matched != 0 ) { - //int32_t k; for (k=0; k= 32*2+4 && strcmp(SMART_CHAIN_SYMBOL,(char *)&scriptbuf[len+32*2+4]) == 0 ) { for (k=0; k<32; k++) @@ -793,7 +762,6 @@ int32_t hush_connectblock(bool fJustCheck, CBlockIndex *pindex,CBlock& block) fprintf(stderr,"unexpected null stateptr.[%s]\n",SMART_CHAIN_SYMBOL); return(0); } - //fprintf(stderr,"%s connect.%d\n",SMART_CHAIN_SYMBOL,pindex->nHeight); // Wallet Filter. Disabled here. Cant be activated by notaries or pools with some changes. numnotaries = hush_notaries(pubkeys,pindex->GetHeight(),pindex->GetBlockTime()); calc_rmd160_sha256(rmd160,pubkeys[0],33); @@ -970,7 +938,6 @@ int32_t hush_connectblock(bool fJustCheck, CBlockIndex *pindex,CBlock& block) else { fprintf(stderr,"hush_connectblock: unexpected null pindex\n"); return(0); } //HUSH_INITDONE = (uint32_t)time(NULL); - //fprintf(stderr,"%s end connect.%d\n",SMART_CHAIN_SYMBOL,pindex->GetHeight()); if (fJustCheck) { if ( notarizations.size() == 0 ) diff --git a/src/hush_bitcoind.h b/src/hush_bitcoind.h index 1def979bc..e29b4f5bd 100644 --- a/src/hush_bitcoind.h +++ b/src/hush_bitcoind.h @@ -975,8 +975,9 @@ uint64_t hush_commission(int height) INTERVAL = GetArg("-ac_halving1",840000), TRANSITION = 129; uint64_t commission = 0; - //TODO: Likely a bug hiding here or at the next halving :) - //if( height >= HALVING1) { + // NB: INTERVAL is consumed only by the debug fprintf at the end of this function; + // the commission schedule below uses hardcoded height thresholds, not INTERVAL. So + // the > vs >= boundary at HALVING1 has no consensus effect. Left as > for stability. if( height > HALVING1) { // Block time going from 150s to 75s (half) means the interval between halvings // must be twice as often, i.e. 840000*2=1680000 @@ -1019,14 +1020,14 @@ uint64_t hush_commission(int height) commission = 61035; } else if (height < 23860000) { commission = 30517; - } else if (height < 23860000) { - commission = 15258; + // removed unreachable duplicate `height < 23860000` (=> 15258); the schedule + // intentionally drops straight to 7629 next — this is the deployed behavior. } else if (height < 25540000) { commission = 7629; } else if (height < 27220000) { commission = 3814; - } else if (height < 27220000) { - commission = 1907; + // removed unreachable duplicate `height < 27220000` (=> 1907); the schedule + // intentionally drops straight to 953 next — this is the deployed behavior. } else if (height < 28900000) { commission = 953; } else if (height < 30580000) { diff --git a/src/hush_defs.h b/src/hush_defs.h index 6fd33a8f9..2264a7160 100644 --- a/src/hush_defs.h +++ b/src/hush_defs.h @@ -597,7 +597,7 @@ void hush_netevent(std::vector payload); int32_t getacseason(uint32_t timestamp); int32_t gethushseason(int32_t height); -#define DRAGON_MAXSCRIPTSIZE 10001 +// DRAGON_MAXSCRIPTSIZE is defined once near the top of this header; the duplicate here was removed. #define HUSH_KVDURATION 1440 #define HUSH_KVBINARY 2 #define PRICES_SMOOTHWIDTH 1 diff --git a/src/hush_nSPV.h b/src/hush_nSPV.h index bf559e8ce..4d7dc4555 100644 --- a/src/hush_nSPV.h +++ b/src/hush_nSPV.h @@ -568,8 +568,6 @@ uint256 NSPV_opretextract(int32_t *heightp,uint256 *blockhashp,char *symbol,std: ((uint8_t *)blockhashp)[i] = opret[i]; for (i=0; i<32; i++) ((uint8_t *)&desttxid)[i] = opret[4 + 32 + i]; - if ( 0 && *heightp != 2690 ) - fprintf(stderr," ntzht.%d %s <- txid.%s size.%d\n",*heightp,(*blockhashp).GetHex().c_str(),(txid).GetHex().c_str(),(int32_t)opret.size()); return(desttxid); } diff --git a/src/hush_nSPV_fullnode.h b/src/hush_nSPV_fullnode.h index 53817725c..e7efd144d 100644 --- a/src/hush_nSPV_fullnode.h +++ b/src/hush_nSPV_fullnode.h @@ -38,7 +38,8 @@ struct NSPV_ntzargs int32_t NSPV_notarization_find(struct NSPV_ntzargs *args,int32_t height,int32_t dir) { int32_t ntzheight = 0; uint256 hashBlock; CTransaction tx; Notarization nota; char *symbol; std::vector opret; - symbol = (SMART_CHAIN_SYMBOL[0] == 0) ? (char *)"HUSH3" : SMART_CHAIN_SYMBOL; + // Notarization symbol; the empty-symbol fallback is dead on DragonX (SMART_CHAIN_SYMBOL is always "DRAGONX", never empty) + symbol = (SMART_CHAIN_SYMBOL[0] == 0) ? (char *)"DRAGONX" : SMART_CHAIN_SYMBOL; memset(args,0,sizeof(*args)); if ( dir > 0 ) height += 10; @@ -659,7 +660,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector request) // received a reque struct NSPV_utxosresp U; if ( len >= 2 && len < 64+5 && request[1] < 64 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) ) { - int32_t skipcount = 0; char coinaddr[64]; uint8_t filter; uint8_t isCC = 0; + int32_t skipcount = 0; char coinaddr[64]; uint32_t filter = 0; uint8_t isCC = 0; // only assigned on the len-11 form; the other two passed it on uninitialized memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write coinaddr[request[1]] = 0; if ( request[1] == len-3 ) @@ -675,8 +676,6 @@ void hush_nSPVreq(CNode *pfrom,std::vector request) // received a reque dragon_rwnum(0,&request[len-8],sizeof(skipcount),&skipcount); dragon_rwnum(0,&request[len-4],sizeof(filter),&filter); } - if ( 0 && isCC != 0 ) - fprintf(stderr,"utxos %s isCC.%d skipcount.%d filter.%x\n",coinaddr,isCC,skipcount,filter); memset(&U,0,sizeof(U)); if ( (slen= NSPV_getaddressutxos(&U,coinaddr,isCC,skipcount,filter)) > 0 ) { @@ -699,7 +698,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector request) // received a reque struct NSPV_txidsresp T; if ( len >= 2 && len < 64+5 && request[1] < 64 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) ) { - int32_t skipcount = 0; char coinaddr[64]; uint32_t filter; uint8_t isCC = 0; + int32_t skipcount = 0; char coinaddr[64]; uint32_t filter = 0; uint8_t isCC = 0; // only assigned on the len-11 form; the other two passed it on uninitialized memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write coinaddr[request[1]] = 0; if ( request[1] == len-3 ) @@ -715,8 +714,6 @@ void hush_nSPVreq(CNode *pfrom,std::vector request) // received a reque dragon_rwnum(0,&request[len-8],sizeof(skipcount),&skipcount); dragon_rwnum(0,&request[len-4],sizeof(filter),&filter); } - if ( 0 && isCC != 0 ) - fprintf(stderr,"txids %s isCC.%d skipcount.%d filter.%d\n",coinaddr,isCC,skipcount,filter); memset(&T,0,sizeof(T)); if ( (slen= NSPV_getaddresstxids(&T,coinaddr,isCC,skipcount,filter)) > 0 ) { diff --git a/src/hush_nSPV_superlite.h b/src/hush_nSPV_superlite.h index ec7c844d1..895ae3b13 100644 --- a/src/hush_nSPV_superlite.h +++ b/src/hush_nSPV_superlite.h @@ -62,7 +62,7 @@ struct NSPV_ntzsresp *NSPV_ntzsresp_add(struct NSPV_ntzsresp *ptr) i = (rand() % (sizeof(NSPV_ntzsresp_cache)/sizeof(*NSPV_ntzsresp_cache))); NSPV_ntzsresp_purge(&NSPV_ntzsresp_cache[i]); NSPV_ntzsresp_copy(&NSPV_ntzsresp_cache[i],ptr); - fprintf(stderr,"ADD CACHE ntzsresp req.%d\n",ptr->reqheight); + LogPrint("nspv","ADD CACHE ntzsresp req.%d\n",ptr->reqheight); return(&NSPV_ntzsresp_cache[i]); } @@ -101,7 +101,7 @@ struct NSPV_txproof *NSPV_txproof_add(struct NSPV_txproof *ptr) i = (rand() % (sizeof(NSPV_txproof_cache)/sizeof(*NSPV_txproof_cache))); NSPV_txproof_purge(&NSPV_txproof_cache[i]); NSPV_txproof_copy(&NSPV_txproof_cache[i],ptr); - fprintf(stderr,"ADD CACHE txproof %s\n",ptr->txid.GetHex().c_str()); + LogPrint("nspv","ADD CACHE txproof %s\n",ptr->txid.GetHex().c_str()); return(&NSPV_txproof_cache[i]); } @@ -124,7 +124,7 @@ struct NSPV_ntzsproofresp *NSPV_ntzsproof_add(struct NSPV_ntzsproofresp *ptr) i = (rand() % (sizeof(NSPV_ntzsproofresp_cache)/sizeof(*NSPV_ntzsproofresp_cache))); NSPV_ntzsproofresp_purge(&NSPV_ntzsproofresp_cache[i]); NSPV_ntzsproofresp_copy(&NSPV_ntzsproofresp_cache[i],ptr); - fprintf(stderr,"ADD CACHE ntzsproof %s %s\n",ptr->prevtxid.GetHex().c_str(),ptr->nexttxid.GetHex().c_str()); + LogPrint("nspv","ADD CACHE ntzsproof %s %s\n",ptr->prevtxid.GetHex().c_str(),ptr->nexttxid.GetHex().c_str()); return(&NSPV_ntzsproofresp_cache[i]); } @@ -139,13 +139,13 @@ void hush_nSPVresp(CNode *pfrom,std::vector response) // received a res switch ( response[0] ) { case NSPV_INFORESP: - fprintf(stderr,"got version.%d info response %u size.%d height.%d\n",NSPV_inforesult.version,timestamp,(int32_t)response.size(),NSPV_inforesult.height); // update current height and ntrz status + LogPrint("nspv","got version.%d info response %u size.%d height.%d\n",NSPV_inforesult.version,timestamp,(int32_t)response.size(),NSPV_inforesult.height); // update current height and ntrz status I = NSPV_inforesult; NSPV_inforesp_purge(&NSPV_inforesult); NSPV_rwinforesp(0,&response[1],&NSPV_inforesult); if ( NSPV_inforesult.height < I.height ) { - fprintf(stderr,"got old info response %u size.%d height.%d\n",timestamp,(int32_t)response.size(),NSPV_inforesult.height); // update current height and ntrz status + LogPrint("nspv","got old info response %u size.%d height.%d\n",timestamp,(int32_t)response.size(),NSPV_inforesult.height); // update current height and ntrz status NSPV_inforesp_purge(&NSPV_inforesult); NSPV_inforesult = I; } @@ -160,56 +160,56 @@ void hush_nSPVresp(CNode *pfrom,std::vector response) // received a res case NSPV_UTXOSRESP: NSPV_utxosresp_purge(&NSPV_utxosresult); NSPV_rwutxosresp(0,&response[1],&NSPV_utxosresult); - fprintf(stderr,"got utxos response %u size.%d\n",timestamp,(int32_t)response.size()); + LogPrint("nspv","got utxos response %u size.%d\n",timestamp,(int32_t)response.size()); break; case NSPV_TXIDSRESP: NSPV_txidsresp_purge(&NSPV_txidsresult); NSPV_rwtxidsresp(0,&response[1],&NSPV_txidsresult); - fprintf(stderr,"got txids response %u size.%d %s CC.%d num.%d\n",timestamp,(int32_t)response.size(),NSPV_txidsresult.coinaddr,NSPV_txidsresult.CCflag,NSPV_txidsresult.numtxids); + LogPrint("nspv","got txids response %u size.%d %s CC.%d num.%d\n",timestamp,(int32_t)response.size(),NSPV_txidsresult.coinaddr,NSPV_txidsresult.CCflag,NSPV_txidsresult.numtxids); break; case NSPV_MEMPOOLRESP: NSPV_mempoolresp_purge(&NSPV_mempoolresult); NSPV_rwmempoolresp(0,&response[1],&NSPV_mempoolresult); - fprintf(stderr,"got mempool response %u size.%d %s CC.%d num.%d funcid.%d %s/v%d\n",timestamp,(int32_t)response.size(),NSPV_mempoolresult.coinaddr,NSPV_mempoolresult.CCflag,NSPV_mempoolresult.numtxids,NSPV_mempoolresult.funcid,NSPV_mempoolresult.txid.GetHex().c_str(),NSPV_mempoolresult.vout); + LogPrint("nspv","got mempool response %u size.%d %s CC.%d num.%d funcid.%d %s/v%d\n",timestamp,(int32_t)response.size(),NSPV_mempoolresult.coinaddr,NSPV_mempoolresult.CCflag,NSPV_mempoolresult.numtxids,NSPV_mempoolresult.funcid,NSPV_mempoolresult.txid.GetHex().c_str(),NSPV_mempoolresult.vout); break; case NSPV_NTZSRESP: NSPV_ntzsresp_purge(&NSPV_ntzsresult); NSPV_rwntzsresp(0,&response[1],&NSPV_ntzsresult); if ( NSPV_ntzsresp_find(NSPV_ntzsresult.reqheight) == 0 ) NSPV_ntzsresp_add(&NSPV_ntzsresult); - fprintf(stderr,"got ntzs response %u size.%d %s prev.%d, %s next.%d\n",timestamp,(int32_t)response.size(),NSPV_ntzsresult.prevntz.txid.GetHex().c_str(),NSPV_ntzsresult.prevntz.height,NSPV_ntzsresult.nextntz.txid.GetHex().c_str(),NSPV_ntzsresult.nextntz.height); + LogPrint("nspv","got ntzs response %u size.%d %s prev.%d, %s next.%d\n",timestamp,(int32_t)response.size(),NSPV_ntzsresult.prevntz.txid.GetHex().c_str(),NSPV_ntzsresult.prevntz.height,NSPV_ntzsresult.nextntz.txid.GetHex().c_str(),NSPV_ntzsresult.nextntz.height); break; case NSPV_NTZSPROOFRESP: NSPV_ntzsproofresp_purge(&NSPV_ntzsproofresult); NSPV_rwntzsproofresp(0,&response[1],&NSPV_ntzsproofresult); if ( NSPV_ntzsproof_find(NSPV_ntzsproofresult.prevtxid,NSPV_ntzsproofresult.nexttxid) == 0 ) NSPV_ntzsproof_add(&NSPV_ntzsproofresult); - fprintf(stderr,"got ntzproof response %u size.%d prev.%d next.%d\n",timestamp,(int32_t)response.size(),NSPV_ntzsproofresult.common.prevht,NSPV_ntzsproofresult.common.nextht); + LogPrint("nspv","got ntzproof response %u size.%d prev.%d next.%d\n",timestamp,(int32_t)response.size(),NSPV_ntzsproofresult.common.prevht,NSPV_ntzsproofresult.common.nextht); break; case NSPV_TXPROOFRESP: NSPV_txproof_purge(&NSPV_txproofresult); NSPV_rwtxproof(0,&response[1],&NSPV_txproofresult); if ( NSPV_txproof_find(NSPV_txproofresult.txid) == 0 ) NSPV_txproof_add(&NSPV_txproofresult); - fprintf(stderr,"got txproof response %u size.%d %s ht.%d\n",timestamp,(int32_t)response.size(),NSPV_txproofresult.txid.GetHex().c_str(),NSPV_txproofresult.height); + LogPrint("nspv","got txproof response %u size.%d %s ht.%d\n",timestamp,(int32_t)response.size(),NSPV_txproofresult.txid.GetHex().c_str(),NSPV_txproofresult.height); break; case NSPV_SPENTINFORESP: NSPV_spentinfo_purge(&NSPV_spentresult); NSPV_rwspentinfo(0,&response[1],&NSPV_spentresult); - fprintf(stderr,"got spentinfo response %u size.%d\n",timestamp,(int32_t)response.size()); + LogPrint("nspv","got spentinfo response %u size.%d\n",timestamp,(int32_t)response.size()); break; case NSPV_BROADCASTRESP: NSPV_broadcast_purge(&NSPV_broadcastresult); NSPV_rwbroadcastresp(0,&response[1],&NSPV_broadcastresult); - fprintf(stderr,"got broadcast response %u size.%d %s retcode.%d\n",timestamp,(int32_t)response.size(),NSPV_broadcastresult.txid.GetHex().c_str(),NSPV_broadcastresult.retcode); + LogPrint("nspv","got broadcast response %u size.%d %s retcode.%d\n",timestamp,(int32_t)response.size(),NSPV_broadcastresult.txid.GetHex().c_str(),NSPV_broadcastresult.retcode); break; case NSPV_CCMODULEUTXOSRESP: NSPV_utxosresp_purge(&NSPV_utxosresult); NSPV_rwutxosresp(0, &response[1], &NSPV_utxosresult); - fprintf(stderr, "got cc module utxos response %u size.%d\n", timestamp, (int32_t)response.size()); + LogPrint("nspv", "got cc module utxos response %u size.%d\n", timestamp, (int32_t)response.size()); break; - default: fprintf(stderr,"unexpected response %02x size.%d at %u\n",response[0],(int32_t)response.size(),timestamp); + default: LogPrint("nspv","unexpected response %02x size.%d at %u\n",response[0],(int32_t)response.size(),timestamp); break; } } @@ -254,7 +254,7 @@ CNode *NSPV_req(CNode *pnode,uint8_t *msg,int32_t len,uint64_t mask,int32_t ind) pnode->PushMessage("getnSPV",request); pnode->prevtimes[ind] = timestamp; return(pnode); - } else fprintf(stderr,"no pnodes\n"); + } else LogPrint("nspv","no pnodes\n"); return(0); } @@ -263,7 +263,7 @@ UniValue NSPV_logout() UniValue result(UniValue::VOBJ); result.push_back(Pair("result","success")); if ( NSPV_logintime != 0 ) - fprintf(stderr,"scrub wif and privkey from NSPV memory\n"); + LogPrint("nspv","scrub wif and privkey from NSPV memory\n"); else result.push_back(Pair("status","wasnt logged in")); memset(NSPV_ntzsproofresp_cache,0,sizeof(NSPV_ntzsproofresp_cache)); memset(NSPV_txproof_cache,0,sizeof(NSPV_txproof_cache)); @@ -294,7 +294,6 @@ void hush_nSPV(CNode *pto) // polling loop from SendMessages len = 0; msg[len++] = NSPV_INFO; len += dragon_rwnum(1,&msg[len],sizeof(reqht),&reqht); - //fprintf(stderr,"issue getinfo\n"); NSPV_req(pto,msg,len,NODE_NSPV,NSPV_INFO>>1); } } @@ -485,7 +484,6 @@ UniValue NSPV_ntzsproof_json(struct NSPV_ntzsproofresp *ptr) result.push_back(Pair("numhdrs",(int64_t)ptr->common.numhdrs)); result.push_back(Pair("headers",NSPV_headers_json(ptr->common.hdrs,ptr->common.numhdrs,ptr->common.prevht))); result.push_back(Pair("lastpeer",NSPV_lastpeer)); - //fprintf(stderr,"ntzs_proof %s %d, %s %d\n",ptr->prevtxid.GetHex().c_str(),ptr->common.prevht,ptr->nexttxid.GetHex().c_str(),ptr->common.nextht); return(result); } @@ -577,7 +575,7 @@ uint32_t NSPV_blocktime(int32_t hdrheight) { timestamp = NSPV_inforesult.H.nTime; NSPV_inforesult = old; - fprintf(stderr,"NSPV_blocktime ht.%d -> t%u\n",hdrheight,timestamp); + LogPrint("nspv","NSPV_blocktime ht.%d -> t%u\n",hdrheight,timestamp); return(timestamp); } } @@ -588,7 +586,6 @@ uint32_t NSPV_blocktime(int32_t hdrheight) UniValue NSPV_addressutxos(char *coinaddr,int32_t CCflag,int32_t skipcount,int32_t filter) { UniValue result(UniValue::VOBJ); uint8_t msg[512]; int32_t i,iter,slen,len = 0; - //fprintf(stderr,"utxos %s NSPV addr %s\n",coinaddr,NSPV_address.c_str()); //if ( NSPV_utxosresult.nodeheight >= NSPV_inforesult.height && strcmp(coinaddr,NSPV_utxosresult.coinaddr) == 0 && CCflag == NSPV_utxosresult.CCflag && skipcount == NSPV_utxosresult.skipcount && filter == NSPV_utxosresult.filter ) // return(NSPV_utxosresp_json(&NSPV_utxosresult)); if ( skipcount < 0 ) @@ -644,7 +641,6 @@ UniValue NSPV_addresstxids(char *coinaddr,int32_t CCflag,int32_t skipcount,int32 msg[len++] = (CCflag != 0); len += dragon_rwnum(1,&msg[len],sizeof(skipcount),&skipcount); len += dragon_rwnum(1,&msg[len],sizeof(filter),&filter); - //fprintf(stderr,"skipcount.%d\n",skipcount); for (iter=0; iter<3; iter++) if ( NSPV_req(0,msg,len,NODE_ADDRINDEX,msg[0]>>1) != 0 ) { @@ -683,7 +679,7 @@ UniValue NSPV_ccaddresstxids(char *coinaddr,int32_t CCflag,int32_t skipcount,uin slen = (int32_t)strlen(coinaddr); msg[len++] = slen; memcpy(&msg[len],coinaddr,slen), len += slen; - fprintf(stderr,"(%s) func.%d CC.%d %s skipcount.%d len.%d\n",coinaddr,NSPV_CC_TXIDS,CCflag,filtertxid.GetHex().c_str(),skipcount,len); + LogPrint("nspv","(%s) func.%d CC.%d %s skipcount.%d len.%d\n",coinaddr,NSPV_CC_TXIDS,CCflag,filtertxid.GetHex().c_str(),skipcount,len); for (iter=0; iter<3; iter++) if ( NSPV_req(0,msg,len,NODE_NSPV,msg[0]>>1) != 0 ) { @@ -721,7 +717,7 @@ UniValue NSPV_mempooltxids(char *coinaddr,int32_t CCflag,uint8_t funcid,uint256 slen = (int32_t)strlen(coinaddr); msg[len++] = slen; memcpy(&msg[len],coinaddr,slen), len += slen; - fprintf(stderr,"(%s) func.%d CC.%d %s/v%d len.%d\n",coinaddr,funcid,CCflag,txid.GetHex().c_str(),vout,len); + LogPrint("nspv","(%s) func.%d CC.%d %s/v%d len.%d\n",coinaddr,funcid,CCflag,txid.GetHex().c_str(),vout,len); for (iter=0; iter<3; iter++) if ( NSPV_req(0,msg,len,NODE_NSPV,msg[0]>>1) != 0 ) { @@ -782,7 +778,7 @@ UniValue NSPV_notarizations(int32_t reqheight) uint8_t msg[512]; int32_t i,iter,len = 0; struct NSPV_ntzsresp N,*ptr; if ( (ptr= NSPV_ntzsresp_find(reqheight)) != 0 ) { - fprintf(stderr,"FROM CACHE NSPV_notarizations.%d\n",reqheight); + LogPrint("nspv","FROM CACHE NSPV_notarizations.%d\n",reqheight); NSPV_ntzsresp_purge(&NSPV_ntzsresult); NSPV_ntzsresp_copy(&NSPV_ntzsresult,ptr); return(NSPV_ntzsresp_json(ptr)); @@ -808,7 +804,7 @@ UniValue NSPV_txidhdrsproof(uint256 prevtxid,uint256 nexttxid) uint8_t msg[512]; int32_t i,iter,len = 0; struct NSPV_ntzsproofresp P,*ptr; if ( (ptr= NSPV_ntzsproof_find(prevtxid,nexttxid)) != 0 ) { - fprintf(stderr,"FROM CACHE NSPV_txidhdrsproof %s %s\n",ptr->prevtxid.GetHex().c_str(),ptr->nexttxid.GetHex().c_str()); + LogPrint("nspv","FROM CACHE NSPV_txidhdrsproof %s %s\n",ptr->prevtxid.GetHex().c_str(),ptr->nexttxid.GetHex().c_str()); NSPV_ntzsproofresp_purge(&NSPV_ntzsproofresult); NSPV_ntzsproofresp_copy(&NSPV_ntzsproofresult,ptr); return(NSPV_ntzsproof_json(ptr)); @@ -846,7 +842,7 @@ UniValue NSPV_txproof(int32_t vout,uint256 txid,int32_t height) uint8_t msg[512]; int32_t i,iter,len = 0; struct NSPV_txproof P,*ptr; if ( (ptr= NSPV_txproof_find(txid)) != 0 ) { - fprintf(stderr,"FROM CACHE NSPV_txproof %s\n",txid.GetHex().c_str()); + LogPrint("nspv","FROM CACHE NSPV_txproof %s\n",txid.GetHex().c_str()); NSPV_txproof_purge(&NSPV_txproofresult); NSPV_txproof_copy(&NSPV_txproofresult,ptr); return(NSPV_txproof_json(ptr)); @@ -856,7 +852,7 @@ UniValue NSPV_txproof(int32_t vout,uint256 txid,int32_t height) len += dragon_rwnum(1,&msg[len],sizeof(height),&height); len += dragon_rwnum(1,&msg[len],sizeof(vout),&vout); len += dragon_rwbignum(1,&msg[len],sizeof(txid),(uint8_t *)&txid); - fprintf(stderr,"req txproof %s/v%d at height.%d\n",txid.GetHex().c_str(),vout,height); + LogPrint("nspv","req txproof %s/v%d at height.%d\n",txid.GetHex().c_str(),vout,height); for (iter=0; iter<3; iter++) if ( NSPV_req(0,msg,len,NODE_NSPV,msg[0]>>1) != 0 ) { @@ -867,7 +863,7 @@ UniValue NSPV_txproof(int32_t vout,uint256 txid,int32_t height) return(NSPV_txproof_json(&NSPV_txproofresult)); } } else sleep(1); - fprintf(stderr,"txproof timeout\n"); + LogPrint("nspv","txproof timeout\n"); memset(&P,0,sizeof(P)); return(NSPV_txproof_json(&P)); } @@ -907,7 +903,6 @@ UniValue NSPV_broadcast(char *hex) len += dragon_rwnum(1,&msg[len],sizeof(n),&n); memcpy(&msg[len],data,n), len += n; free(data); - //fprintf(stderr,"send txid.%s\n",txid.GetHex().c_str()); for (iter=0; iter<3; iter++) if ( NSPV_req(0,msg,len,NODE_NSPV,msg[0]>>1) != 0 ) { diff --git a/src/hush_nSPV_wallet.h b/src/hush_nSPV_wallet.h index 85075e29f..3edabb7b8 100644 --- a/src/hush_nSPV_wallet.h +++ b/src/hush_nSPV_wallet.h @@ -26,7 +26,7 @@ int32_t NSPV_validatehdrs(struct NSPV_ntzsproofresp *ptr) int32_t i,height,txidht; CTransaction tx; uint256 blockhash,txid,desttxid; if ( (ptr->common.nextht-ptr->common.prevht+1) != ptr->common.numhdrs ) { - fprintf(stderr,"next.%d prev.%d -> %d vs %d\n",ptr->common.nextht,ptr->common.prevht,ptr->common.nextht-ptr->common.prevht+1,ptr->common.numhdrs); + LogPrintf("next.%d prev.%d -> %d vs %d\n",ptr->common.nextht,ptr->common.prevht,ptr->common.nextht-ptr->common.prevht+1,ptr->common.numhdrs); return(-2); } else if ( NSPV_txextract(tx,ptr->nextntz,ptr->nexttxlen) < 0 ) @@ -64,7 +64,6 @@ int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int struct NSPV_txproof *ptr; int32_t i,offset,retval; int64_t rewards = 0; uint32_t nLockTime; std::vector proof; retval = skipvalidation != 0 ? 0 : -1; - //fprintf(stderr,"NSPV_gettx %s/v%d ht.%d\n",txid.GetHex().c_str(),vout,height); if ( (ptr= NSPV_txproof_find(txid)) == 0 ) { NSPV_txproof(vout,txid,height); @@ -75,7 +74,7 @@ int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int currentheight=NSPV_inforesult.height; if ( ptr->txid != txid ) { - fprintf(stderr,"txproof error %s != %s\n",ptr->txid.GetHex().c_str(),txid.GetHex().c_str()); + LogPrintf("txproof error %s != %s\n",ptr->txid.GetHex().c_str(),txid.GetHex().c_str()); return(-1); } else if ( NSPV_txextract(tx,ptr->tx,ptr->txlen) < 0 || ptr->txlen <= 0 ) @@ -87,8 +86,7 @@ int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int //char coinaddr[64]; //Getscriptaddress(coinaddr,tx.vout[0].scriptPubKey); causes crash?? - //fprintf(stderr,"%s txid.%s vs hash.%s\n",coinaddr,txid.GetHex().c_str(),tx.GetHash().GetHex().c_str()); - + if ( skipvalidation == 0 ) { if ( ptr->txprooflen > 0 ) @@ -99,18 +97,17 @@ int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int NSPV_notarizations(height); // gets the prev and next notarizations if ( NSPV_inforesult.notarization.height >= height && (NSPV_ntzsresult.prevntz.height == 0 || NSPV_ntzsresult.prevntz.height >= NSPV_ntzsresult.nextntz.height) ) { - fprintf(stderr,"issue manual bracket\n"); + LogPrintf("issue manual bracket\n"); NSPV_notarizations(height-1); NSPV_notarizations(height+1); NSPV_notarizations(height); // gets the prev and next notarizations } if ( NSPV_ntzsresult.prevntz.height != 0 && NSPV_ntzsresult.prevntz.height <= NSPV_ntzsresult.nextntz.height ) { - fprintf(stderr,">>>>> gettx ht.%d prev.%d next.%d\n",height,NSPV_ntzsresult.prevntz.height, NSPV_ntzsresult.nextntz.height); + LogPrintf(">>>>> gettx ht.%d prev.%d next.%d\n",height,NSPV_ntzsresult.prevntz.height, NSPV_ntzsresult.nextntz.height); offset = (height - NSPV_ntzsresult.prevntz.height); if ( offset >= 0 && height <= NSPV_ntzsresult.nextntz.height ) { - //fprintf(stderr,"call NSPV_txidhdrsproof %s %s\n",NSPV_ntzsresult.prevntz.txid.GetHex().c_str(),NSPV_ntzsresult.nextntz.txid.GetHex().c_str()); NSPV_txidhdrsproof(NSPV_ntzsresult.prevntz.txid,NSPV_ntzsresult.nextntz.txid); usleep(10000); if ( (retval= NSPV_validatehdrs(&NSPV_ntzsproofresult)) == 0 ) @@ -119,8 +116,8 @@ int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int proofroot = BitcoinGetProofMerkleRoot(proof,txids); if ( proofroot != NSPV_ntzsproofresult.common.hdrs[offset].hashMerkleRoot || txids[0] != txid ) { - fprintf(stderr,"txid.%s vs txids[0] %s\n",txid.GetHex().c_str(),txids[0].GetHex().c_str()); - fprintf(stderr,"prooflen.%d proofroot.%s vs %s\n",(int32_t)proof.size(),proofroot.GetHex().c_str(),NSPV_ntzsproofresult.common.hdrs[offset].hashMerkleRoot.GetHex().c_str()); + LogPrintf("txid.%s vs txids[0] %s\n",txid.GetHex().c_str(),txids[0].GetHex().c_str()); + LogPrintf("prooflen.%d proofroot.%s vs %s\n",(int32_t)proof.size(),proofroot.GetHex().c_str(),NSPV_ntzsproofresult.common.hdrs[offset].hashMerkleRoot.GetHex().c_str()); retval = -2003; } else retval = 0; } @@ -162,13 +159,11 @@ int32_t NSPV_vinselect(int32_t *aboveip,int64_t *abovep,int32_t *belowip,int64_t belowi = i; } } - //printf("value %.8f gap %.8f abovei.%d %.8f belowi.%d %.8f\n",dstr(value),dstr(gap),abovei,dstr(above),belowi,dstr(below)); } *aboveip = abovei; *abovep = above; *belowip = belowi; *belowp = below; - //printf("above.%d below.%d\n",abovei,belowi); if ( abovei >= 0 && belowi >= 0 ) { if ( above < (below >> 1) ) @@ -195,14 +190,13 @@ int64_t NSPV_addinputs(struct NSPV_utxoresp *used,CMutableTransaction &mtx,int64 utxos[n++] = ptr[i]; } remains = total; - //fprintf(stderr,"threshold %.8f n.%d for total %.8f\n",(double)threshold/COIN,n,(double)total/COIN); for (i=0; i0; i++) { below = above = 0; abovei = belowi = -1; if ( NSPV_vinselect(&abovei,&above,&belowi,&below,utxos,n,remains) < 0 ) { - fprintf(stderr,"error finding unspent i.%d of %d, %.8f vs %.8f\n",i,n,(double)remains/COIN,(double)total/COIN); + LogPrintf("error finding unspent i.%d of %d, %.8f vs %.8f\n",i,n,(double)remains/COIN,(double)total/COIN); return(0); } if ( belowi < 0 || abovei >= 0 ) @@ -210,10 +204,9 @@ int64_t NSPV_addinputs(struct NSPV_utxoresp *used,CMutableTransaction &mtx,int64 else ind = belowi; if ( ind < 0 ) { - fprintf(stderr,"error finding unspent i.%d of %d, %.8f vs %.8f, abovei.%d belowi.%d ind.%d\n",i,n,(double)remains/COIN,(double)total/COIN,abovei,belowi,ind); + LogPrintf("error finding unspent i.%d of %d, %.8f vs %.8f, abovei.%d belowi.%d ind.%d\n",i,n,(double)remains/COIN,(double)total/COIN,abovei,belowi,ind); return(0); } - //fprintf(stderr,"i.%d ind.%d abovei.%d belowi.%d n.%d\n",i,ind,abovei,belowi,n); up = &utxos[ind]; mtx.vin.push_back(CTxIn(up->txid,up->vout,CScript())); used[i] = *up; @@ -221,11 +214,9 @@ int64_t NSPV_addinputs(struct NSPV_utxoresp *used,CMutableTransaction &mtx,int64 remains -= up->satoshis; utxos[ind] = utxos[--n]; memset(&utxos[n],0,sizeof(utxos[n])); - //fprintf(stderr,"totalinputs %.8f vs total %.8f i.%d vs max.%d\n",(double)totalinputs/COIN,(double)total/COIN,i,maxinputs); if ( totalinputs >= total || (i+1) >= maxinputs ) break; } - //fprintf(stderr,"totalinputs %.8f vs total %.8f\n",(double)totalinputs/COIN,(double)total/COIN); if ( totalinputs >= total ) return(totalinputs); return(0); @@ -236,21 +227,20 @@ bool NSPV_SignTx(CMutableTransaction &mtx,int32_t vini,int64_t utxovalue,const C CTransaction txNewConst(mtx); SignatureData sigdata; CBasicKeyStore keystore; int64_t branchid = NSPV_BRANCHID; if ( NSPV_logintime == 0 || time(NULL) > NSPV_logintime+NSPV_AUTOLOGOUT ) { - fprintf(stderr,"need to be logged in to get myprivkey\n"); + LogPrintf("need to be logged in to get myprivkey\n"); return false; } keystore.AddKey(NSPV_key); if ( nTime != 0 && nTime < HUSH_SAPING_ACTIVATION ) { - fprintf(stderr,"use legacy sig validation\n"); + LogPrintf("use legacy sig validation\n"); branchid = 0; } if ( ProduceSignature(TransactionSignatureCreator(&keystore,&txNewConst,vini,utxovalue,SIGHASH_ALL),scriptPubKey,sigdata,branchid) != 0 ) { UpdateTransaction(mtx,vini,sigdata); - fprintf(stderr,"SIG_TXHASH %s vini.%d %.8f\n",SIG_TXHASH.GetHex().c_str(),vini,(double)utxovalue/COIN); return(true); - } //else fprintf(stderr,"sigerr SIG_TXHASH %s vini.%d %.8f\n",SIG_TXHASH.GetHex().c_str(),vini,(double)utxovalue/COIN); + } return(false); } @@ -285,22 +275,21 @@ std::string NSPV_signtx(int64_t &rewardsum,int64_t &interestsum,UniValue &retcod { if ( vintx.vout[utxovout].nValue != used[i].satoshis ) { - fprintf(stderr,"vintx mismatch %.8f != %.8f\n",(double)vintx.vout[utxovout].nValue/COIN,(double)used[i].satoshis/COIN); + LogPrintf("vintx mismatch %.8f != %.8f\n",(double)vintx.vout[utxovout].nValue/COIN,(double)used[i].satoshis/COIN); return(""); } else if ( utxovout != used[i].vout ) { - fprintf(stderr,"vintx vout mismatch %d != %d\n",utxovout,used[i].vout); + LogPrintf("vintx vout mismatch %d != %d\n",utxovout,used[i].vout); return(""); } else if ( NSPV_SignTx(mtx,i,vintx.vout[utxovout].nValue,vintx.vout[utxovout].scriptPubKey,0) == 0 ) { - fprintf(stderr,"signing error for vini.%d\n",i); + LogPrintf("signing error for vini.%d\n",i); return(""); } - } else fprintf(stderr,"couldnt find txid.%s/v%d or it was spent\n",mtx.vin[i].prevout.hash.GetHex().c_str(),utxovout); // of course much better handling is needed + } else LogPrintf("couldnt find txid.%s/v%d or it was spent\n",mtx.vin[i].prevout.hash.GetHex().c_str(),utxovout); // of course much better handling is needed } - fprintf(stderr,"sign %d inputs %.8f + interest %.8f -> %d outputs %.8f change %.8f\n",(int32_t)mtx.vin.size(),(double)totalinputs/COIN,(double)interest/COIN,(int32_t)mtx.vout.size(),(double)totaloutputs/COIN,(double)change/COIN); return(EncodeHexTx(mtx)); } @@ -360,7 +349,6 @@ UniValue NSPV_spend(char *srcaddr,char *destaddr,int64_t satoshis) // what its a result.push_back(Pair("amount",(double)satoshis/COIN)); return(result); } - printf("%s numutxos.%d balance %.8f\n",NSPV_utxosresult.coinaddr,NSPV_utxosresult.numutxos,(double)NSPV_utxosresult.total/COIN); CScript opret; std::string hex; struct NSPV_utxoresp used[NSPV_MAXVINS]; CMutableTransaction mtx; CTransaction tx; int64_t rewardsum=0,interestsum=0; mtx.fOverwintered = true; mtx.nExpiryHeight = 0; @@ -428,7 +416,7 @@ int64_t NSPV_AddNormalinputs(CMutableTransaction &mtx,CPubKey mypk,int64_t total NSPV_utxosresp_purge(&ptr->U); NSPV_utxosresp_copy(&ptr->U,&NSPV_utxosresult); // } - fprintf(stderr,"%s numutxos.%d\n",ptr->U.coinaddr,ptr->U.numutxos); + LogPrintf("%s numutxos.%d\n",ptr->U.coinaddr,ptr->U.numutxos); memset(ptr->used,0,sizeof(ptr->used)); return(NSPV_addinputs(ptr->used,mtx,total,maxinputs,ptr->U.utxos,ptr->U.numutxos)); } else return(0); @@ -442,7 +430,7 @@ void NSPV_utxos2CCunspents(struct NSPV_utxosresp *ptr,std::vectorCCflag) == 0 ) { - fprintf(stderr,"couldnt get indexkey\n"); + LogPrintf("couldnt get indexkey\n"); return; } for (i = 0; i < ptr->numutxos; i ++) @@ -466,7 +454,7 @@ void NSPV_txids2CCtxids(struct NSPV_txidsresp *ptr,std::vectorCCflag) == 0 ) { - fprintf(stderr,"couldnt get indexkey\n"); + LogPrintf("couldnt get indexkey\n"); return; } for (i = 0; i < ptr->numtxids; i ++) diff --git a/src/hush_utils.h b/src/hush_utils.h index 91a51e05a..3ff16791a 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -770,19 +770,15 @@ int32_t bitcoin_addr2rmd160(uint8_t *addrtypep,uint8_t rmd160[20],char *coinaddr memcpy(rmd160,buf+1,20); if ( (buf[21]&0xff) == hash.bytes[31] && (buf[22]&0xff) == hash.bytes[30] &&(buf[23]&0xff) == hash.bytes[29] && (buf[24]&0xff) == hash.bytes[28] ) { - //printf("coinaddr.(%s) valid checksum addrtype.%02x\n",coinaddr,*addrtypep); return(20); } else { - int32_t i; if ( len > 20 ) { hash = bits256_doublesha256(0,buf,len); } - for (i=0; i 0 ) { bytes[0] = unhex(hex[0]); - printf("decode_hex n.%d hex[0] (%c) -> %d hex.(%s) [n*2+1: %d] [n*2: %d %c] len.%ld\n",n,hex[0],bytes[0],hex,hex[n*2+1],hex[n*2],hex[n*2],(long)strlen(hex)); + LogPrintf("decode_hex n.%d hex[0] (%c) -> %d hex.(%s) [n*2+1: %d] [n*2: %d %c] len.%ld\n",n,hex[0],bytes[0],hex,hex[n*2+1],hex[n*2],hex[n*2],(long)strlen(hex)); } bytes++; hex++; @@ -918,10 +909,8 @@ int32_t init_hexbytes_noT(char *hexbytes,unsigned char *message,long len) { hexbytes[i*2] = hexbyte((message[i]>>4) & 0xf); hexbytes[i*2 + 1] = hexbyte(message[i] & 0xf); - //printf("i.%d (%02x) [%c%c]\n",i,message[i],hexbytes[i*2],hexbytes[i*2+1]); } hexbytes[len*2] = 0; - //printf("len.%ld\n",len*2+1); return((int32_t)len*2+1); } @@ -1087,7 +1076,7 @@ char *clonestr(char *str) char *clone; if ( str == 0 || str[0] == 0 ) { - printf("warning cloning nullstr.%p\n",str); + LogPrintf("warning cloning nullstr.%p\n",str); #ifdef __APPLE__ while ( 1 ) sleep(1); #endif @@ -1109,7 +1098,7 @@ int32_t safecopy(char *dest,char *src,long len) dest[i] = src[i]; if ( i == len ) { - printf("safecopy: %s too long %ld\n",src,len); + LogPrintf("safecopy: %s too long %ld\n",src,len); #ifdef __APPLE__ //getchar(); #endif @@ -1131,7 +1120,6 @@ char *parse_conf_line(char *line,char *field) line++; while ( line[strlen(line)-1] == '\r' || line[strlen(line)-1] == '\n' || line[strlen(line)-1] == ' ' ) line[strlen(line)-1] = 0; - //printf("LINE.(%s)\n",line); _stripwhite(line,0); return(clonestr(line)); } @@ -1141,7 +1129,6 @@ double OS_milliseconds() struct timeval tv; double millis; gettimeofday(&tv,NULL); millis = ((double)tv.tv_sec * 1000. + (double)tv.tv_usec / 1000.); - //printf("tv_sec.%ld usec.%d %f\n",tv.tv_sec,tv.tv_usec,millis); return(millis); } @@ -1193,7 +1180,7 @@ void queue_enqueue(char *name,queue_t *queue,struct queueitem *item) strcpy(queue->name,name); if ( item == 0 ) { - printf("FATAL type error: queueing empty value\n"); + LogPrintf("FATAL type error: queueing empty value\n"); return; } lock_queue(queue); @@ -1230,7 +1217,7 @@ void *queue_delete(queue_t *queue,struct queueitem *copy,int32_t copysize) { DL_DELETE(queue->list,item); portable_mutex_unlock(&queue->mutex); - printf("name.(%s) deleted item.%p list.%p\n",queue->name,item,queue->list); + LogPrintf("name.(%s) deleted item.%p list.%p\n",queue->name,item,queue->list); return(item); } } @@ -1250,7 +1237,6 @@ void *queue_free(queue_t *queue) DL_DELETE(queue->list,item); free(item); } - //printf("name.(%s) dequeue.%p list.%p\n",queue->name,item,queue->list); } portable_mutex_unlock(&queue->mutex); return(0); @@ -1268,7 +1254,6 @@ void *queue_clone(queue_t *clone,queue_t *queue,int32_t size) memcpy(ptr,item,size); queue_enqueue(queue->name,clone,ptr); } - //printf("name.(%s) dequeue.%p list.%p\n",queue->name,item,queue->list); } portable_mutex_unlock(&queue->mutex); return(0); @@ -1304,7 +1289,6 @@ uint16_t _hush_userpass(char *username,char *password,FILE *fp) { if ( line[0] == '#' ) continue; - //printf("line.(%s) %p %p\n",line,strstr(line,(char *)"rpcuser"),strstr(line,(char *)"rpcpassword")); if ( (str= strstr(line,(char *)"rpcuser")) != 0 ) rpcuser = parse_conf_line(str,(char *)"rpcuser"); else if ( (str= strstr(line,(char *)"rpcpassword")) != 0 ) @@ -1312,7 +1296,6 @@ uint16_t _hush_userpass(char *username,char *password,FILE *fp) else if ( (str= strstr(line,(char *)"rpcport")) != 0 ) { port = atoi(parse_conf_line(str,(char *)"rpcport")); - //fprintf(stderr,"rpcport.%u in file\n",port); } } if ( rpcuser != 0 && rpcpassword != 0 ) @@ -1320,7 +1303,6 @@ uint16_t _hush_userpass(char *username,char *password,FILE *fp) strcpy(username,rpcuser); strcpy(password,rpcpassword); } - //printf("rpcuser.(%s) rpcpassword.(%s) HUSHUSERPASS.(%s) %u\n",rpcuser,rpcpassword,HUSHUSERPASS,port); if ( rpcuser != 0 ) free(rpcuser); if ( rpcpassword != 0 ) @@ -1340,7 +1322,7 @@ void hush_statefname(char *fname,char *symbol,char *str) else { if ( strcmp(symbol,"ZZZ") != 0 ) - printf("unexpected fname.(%s) vs %s [%s] n.%d len.%d (%s)\n",fname,symbol,SMART_CHAIN_SYMBOL,n,len,&fname[len - n]); + LogPrintf("unexpected fname.(%s) vs %s [%s] n.%d len.%d (%s)\n",fname,symbol,SMART_CHAIN_SYMBOL,n,len,&fname[len - n]); return; } } else { @@ -1353,7 +1335,6 @@ void hush_statefname(char *fname,char *symbol,char *str) if ( symbol != 0 && symbol[0] != 0) { strcat(fname,symbol); - //printf("statefname.(%s) -> (%s)\n",symbol,fname); #ifdef _WIN32 strcat(fname,"\\"); #else @@ -1361,7 +1342,6 @@ void hush_statefname(char *fname,char *symbol,char *str) #endif } strcat(fname,str); - //printf("test.(%s) -> [%s] statename.(%s) %s\n",test,SMART_CHAIN_SYMBOL,symbol,fname); } void hush_configfile(char *symbol,uint16_t rpcport) @@ -1398,14 +1378,19 @@ void hush_configfile(char *symbol,uint16_t rpcport) { fprintf(fp,"rpcuser=user%u\nrpcpassword=pass%s\nrpcport=%u\nserver=1\ntxindex=1\nrpcworkqueue=4096\nrpcallowip=127.0.0.1\nrpcbind=127.0.0.1\n",crc,password,rpcport); fclose(fp); - printf("Created (%s)\n",fname); - } else printf("Couldnt create (%s)\n",fname); + LogPrintf("Created (%s)\n",fname); + } else LogPrintf("Couldnt create (%s)\n",fname); #endif } else { _hush_userpass(myusername,mypassword,fp); - mapArgs["-rpcpassword"] = mypassword; - mapArgs["-rpcusername"] = myusername; - //fprintf(stderr,"myusername.(%s)\n",myusername); + // Feed the credentials read by InitRPCAuthentication (httprpc.cpp) and the + // CLI (bitcoin-cli.cpp) -- both read "-rpcuser"/"-rpcpassword". Use SoftSetArg + // so a value passed on the command line (or an explicit -rpcuser/-rpcpassword) + // still wins: the old direct assignment silently overwrote a command-line + // -rpcpassword on every restart once this conf existed, and the username was + // written to a misspelled "-rpcusername" key that nothing ever reads. + SoftSetArg("-rpcpassword", mypassword); + SoftSetArg("-rpcuser", myusername); fclose(fp); } } @@ -1429,9 +1414,8 @@ void hush_configfile(char *symbol,uint16_t rpcport) DRAGONX_PORT = hushport; sprintf(HUSHUSERPASS,"%s:%s",username,password); fclose(fp); - //printf("HUSH.(%s) -> userpass.(%s)\n",fname,HUSHUSERPASS); } else { - printf("could not open.(%s)\n",fname); + LogPrintf("could not open.(%s)\n",fname); } } @@ -1466,13 +1450,13 @@ uint32_t hush_smartmagic(char *symbol,uint64_t supply,uint8_t *extraptr,int32_t { vcalc_sha256(0,hash.bytes,extraptr,extralen); crc0 = hash.uints[0]; - fprintf(stderr,"DragonX raw magic="); - int32_t i; for (i=0; i 137329); kept in sync + // with hush_commission() — the schedule drops straight to 68664 next. } else if (height < 25540000) { subsidy = 68664; } else if (height < 27220000) { subsidy = 34332; - } else if (height < 27220000) { - subsidy = 17166; + // removed unreachable duplicate `height < 27220000` (=> 17166); kept in sync + // with hush_commission() — the schedule drops straight to 8583 next. } else if (height < 28900000) { subsidy = 8583; } else if (height < 30580000) { @@ -1611,7 +1597,9 @@ uint64_t hush_block_subsidy(int height) return subsidy; } -// wrapper for more general supply curves of Hush Arrakis Chains +// Wrapper for the more general supply curves used by assetchains (era/halving/decay driven). +// On DragonX the reward comes from the -ac_reward/-ac_halving parameters set in hush_args(); +// the ishush3 branch below is a legacy special case that is dead on DragonX. uint64_t hush_sc_block_subsidy(int nHeight) { // Find current era, start from beginning reward, and determine current subsidy @@ -1619,12 +1607,13 @@ uint64_t hush_sc_block_subsidy(int nHeight) int64_t subsidyDifference; int32_t numhalvings = 0, curEra = 0, sign = 1; static uint64_t cached_subsidy; static int32_t cached_numhalvings; static int cached_era; + // Legacy-HUSH3 detection: dead on DragonX (SMART_CHAIN_SYMBOL is "DRAGONX"), used only + // to route HUSH3 mainnet through its bespoke hush_block_subsidy() emission curve below. const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false; - // fprintf(stderr,"%s: ht=%d ishush3=%d\n", __func__, nHeight, ishush3); // check for backwards compat, older chains with no explicit rewards had 0.0001 block reward if ( ASSETCHAINS_ENDSUBSIDY[0] == 0 && ASSETCHAINS_REWARD[0] == 0 ) { - fprintf(stderr,"%s: defaulting to 0.0001 subsidy\n",__func__); + LogPrintf("%s: defaulting to 0.0001 subsidy\n",__func__); subsidy = 10000; } else if ( (ASSETCHAINS_ENDSUBSIDY[0] == 0 && ASSETCHAINS_REWARD[0] != 0) || ASSETCHAINS_ENDSUBSIDY[0] != 0 ) { // if we have an end block in the first era, find our current era @@ -1656,10 +1645,11 @@ uint64_t hush_sc_block_subsidy(int nHeight) if(fDebug) fprintf(stderr,"%s: HUSH3 subsidy=%ld at height=%d\n",__func__,subsidy,nHeight); } else if ( (numhalvings = ((nHeight - nStart) / ASSETCHAINS_HALVING[curEra])) > 0 ) { - // The code below is not compatible with HUSH3 mainnet + // Generic halving/decay path used by DragonX and other assetchains. + // (Legacy HUSH3 mainnet did NOT use this path; it took the ishush3 + // branch above, which reproduces its bespoke emission curve.) if ( ASSETCHAINS_DECAY[curEra] == 0 ) { subsidy >>= numhalvings; - // fprintf(stderr,"%s: no decay, numhalvings.%d curEra.%d subsidy.%ld nStart.%ld\n",__func__, numhalvings, curEra, subsidy, nStart); } else if ( ASSETCHAINS_DECAY[curEra] == 100000000 && ASSETCHAINS_ENDSUBSIDY[curEra] != 0 ) { if ( curEra == ASSETCHAINS_LASTERA ) { @@ -1675,12 +1665,11 @@ uint64_t hush_sc_block_subsidy(int nHeight) } denominator = ASSETCHAINS_ENDSUBSIDY[curEra] - nStart; numerator = denominator - ((ASSETCHAINS_ENDSUBSIDY[curEra] - nHeight) + ((nHeight - nStart) % ASSETCHAINS_HALVING[curEra])); - // fprintf(stderr,"%s: numerator=%ld , denominator=%ld at height=%d\n",__func__,numerator, denominator,nHeight); if( denominator ) { subsidy = subsidy - sign * ((subsidyDifference * numerator) / denominator); } else { - fprintf(stderr,"%s: invalid denominator=%ld !\n", __func__, denominator); - fprintf(stderr,"%s: defaulting to 0.0001 subsidy\n",__func__); + LogPrintf("%s: invalid denominator=%ld !\n", __func__, denominator); + LogPrintf("%s: defaulting to 0.0001 subsidy\n",__func__); subsidy = 10000; } } else { @@ -1698,13 +1687,13 @@ uint64_t hush_sc_block_subsidy(int nHeight) } } } else { - fprintf(stderr,"%s: curEra.%d > lastEra.%lu\n", __func__, curEra, ASSETCHAINS_LASTERA); + LogPrintf("%s: curEra.%d > lastEra.%lu\n", __func__, curEra, ASSETCHAINS_LASTERA); } } uint32_t magicExtra = ASSETCHAINS_STAKED ? ASSETCHAINS_MAGIC : (ASSETCHAINS_MAGIC & 0xffffff); if ( ASSETCHAINS_SUPPLY > 10000000000 ) // over 10 billion? { - fprintf(stderr,"%s: Detected supply over 10 billion, danger zone!\n",__func__); + LogPrintf("%s: Detected supply over 10 billion, danger zone!\n",__func__); if ( nHeight <= ASSETCHAINS_SUPPLY/1000000000 ) { subsidy += (uint64_t)1000000000 * COIN; @@ -1782,7 +1771,7 @@ void hush_args(char *argv0) IS_HUSH_NOTARY = 1; HUSH_MININGTHREADS = 1; mapArgs ["-genproclimit"] = itostr(HUSH_MININGTHREADS); - fprintf(stderr,"running as notary.%d %s\n",i,notaries_list[hush_season-1][i][0]); + LogPrintf("running as notary.%d %s\n",i,notaries_list[hush_season-1][i][0]); break; } } @@ -1794,19 +1783,38 @@ void hush_args(char *argv0) fprintf(stderr,".oO Starting %s Full Node (Extreme Privacy!) with genproc=%d notary=%d\n",name.c_str(),HUSH_MININGTHREADS, IS_HUSH_NOTARY); vector DRAGONX_nodes = {}; - // Only DRAGONX connects to these by default, other chains must opt-in via -connect/-addnode + // Only DRAGONX connects to these by default, other chains must opt-in via -connect/-addnode. + // Never on regtest or testnet: regtest reuses mainnet's network magic, so injecting the mainnet + // seeds here makes a supposedly isolated node handshake production peers and pull their headers + // into its own index -- which is exactly what it did, and why multi-node rpc-tests were talking + // to the live chain. NOTE: hush_args() runs between ParseParameters() and ReadConfigFile() + // (bitcoind.cpp:115/144/158), so this sees a command-line -regtest (how qa/rpc-tests starts + // nodes) but NOT a bare "regtest=1" in the config file. const bool isdragonx = strncmp(name.c_str(), "DRAGONX",7) == 0 ? true : false; - LogPrint("net", "%s: isdragonx=%d\n", __func__, isdragonx); - if (isdragonx) { + // Seed injection only -- isdragonx itself must stay true here, because it also selects + // ac_private, ac_algo, blocktime and the reward/halving schedule below. + const bool isnotmainnet = GetBoolArg("-regtest", false) || GetBoolArg("-testnet", false); + + LogPrint("net", "%s: isdragonx=%d isnotmainnet=%d\n", __func__, isdragonx, isnotmainnet); + if (isdragonx && !isnotmainnet) { + // node8-node10 are PLACEHOLDERS with no DNS records yet. A hostname that + // does not resolve is harmless here: ThreadOpenAddedConnections just fails + // to open the connection and retries on its 2-minute cycle. Reserving the + // names in the binary means a future seed can be brought into the -addnode + // set by creating one DNS record, with no release and no waiting for users + // to upgrade. (seed.dragonx.is already provides that for the DNS-seed path; + // this extends the same property to the addnode path.) DRAGONX_nodes = {"node1.dragonx.is","node2.dragonx.is","node3.dragonx.is", - "node4.dragonx.is","node5.dragonx.is" + "node4.dragonx.is","node5.dragonx.is","node6.dragonx.is", + "node7.dragonx.is","node8.dragonx.is","node9.dragonx.is", + "node10.dragonx.is" }; } vector 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() ); + LogPrint("net", "%s: Adding %lu more nodes via custom -addnode arguments\n", __func__, more_nodes.size() ); } // Add default DRAGONX nodes after custom addnodes, if applicable if(DRAGONX_nodes.size() > 0) { @@ -1848,19 +1856,19 @@ void hush_args(char *argv0) if ( i > 1 && ccEnablesHeight[i-2] == ecode ) break; if ( ecode > 255 || ecode < 0 ) - fprintf(stderr, "ac_ccactivateht: invalid evalcode.%i must be between 0 and 256.\n", ecode); + LogPrintf("ac_ccactivateht: invalid evalcode.%i must be between 0 and 256.\n", ecode); else if ( ht > 0 ) { // update global map. mapHeightEvalActivate[ecode] = ht; - fprintf(stderr, "ac_ccactivateht: ecode.%i activates at height.%i\n", ecode, mapHeightEvalActivate[ecode]); + LogPrintf("ac_ccactivateht: ecode.%i activates at height.%i\n", ecode, mapHeightEvalActivate[ecode]); } i++; } if ( (HUSH_REWIND= GetArg("-rewind",0)) != 0 ) { - printf("HUSH_REWIND %d\n",HUSH_REWIND); + LogPrintf("HUSH_REWIND %d\n",HUSH_REWIND); } HUSH_EARLYTXID = Parseuint256(GetArg("-earlytxid","0").c_str()); ASSETCHAINS_EARLYTXIDCONTRACT = GetArg("-ac_earlytxidcontract",0); @@ -1878,7 +1886,7 @@ void hush_args(char *argv0) STAKING_MIN_DIFF = ASSETCHAINS_MINDIFF[i]; // only worth mentioning if it's not equihash if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH) - printf("ASSETCHAINS_ALGO, algorithm set to %s\n", selectedAlgo.c_str()); + LogPrintf("ASSETCHAINS_ALGO, algorithm set to %s\n", selectedAlgo.c_str()); break; } } @@ -1888,11 +1896,11 @@ void hush_args(char *argv0) { printf("equihash values N.%li and K.%li are not currently available\n", ASSETCHAINS_NK[0], ASSETCHAINS_NK[1]); exit(0); - } else printf("ASSETCHAINS_ALGO, algorithm set to equihash with N.%li and K.%li\n", ASSETCHAINS_NK[0], ASSETCHAINS_NK[1]); + } else LogPrintf("ASSETCHAINS_ALGO, algorithm set to equihash with N.%li and K.%li\n", ASSETCHAINS_NK[0], ASSETCHAINS_NK[1]); } if (i == ASSETCHAINS_NUMALGOS) { - printf("ASSETCHAINS_ALGO, %s not supported. using equihash\n", selectedAlgo.c_str()); + LogPrintf("ASSETCHAINS_ALGO, %s not supported. using equihash\n", selectedAlgo.c_str()); } // Set our symbol from -ac_name value @@ -1907,14 +1915,14 @@ void hush_args(char *argv0) } 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); + LogPrintf("ASSETCHAINS_RANDOMX_VALIDATION set to %d for %s\n", ASSETCHAINS_RANDOMX_VALIDATION, SMART_CHAIN_SYMBOL); } ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1); if ( ASSETCHAINS_LASTERA < 1 || ASSETCHAINS_LASTERA > ASSETCHAINS_MAX_ERAS ) { ASSETCHAINS_LASTERA = 1; - printf("ASSETCHAINS_LASTERA, if specified, must be between 1 and %u. ASSETCHAINS_LASTERA set to %lu\n", ASSETCHAINS_MAX_ERAS, ASSETCHAINS_LASTERA); + LogPrintf("ASSETCHAINS_LASTERA, if specified, must be between 1 and %u. ASSETCHAINS_LASTERA set to %lu\n", ASSETCHAINS_MAX_ERAS, ASSETCHAINS_LASTERA); } ASSETCHAINS_LASTERA -= 1; if(fDebug) @@ -1925,7 +1933,7 @@ void hush_args(char *argv0) ASSETCHAINS_TIMEUNLOCKTO = GetArg("-ac_timeunlockto", 0); if ( ASSETCHAINS_TIMEUNLOCKFROM > ASSETCHAINS_TIMEUNLOCKTO ) { - printf("ASSETCHAINS_TIMELOCKGTE - must specify valid ac_timeunlockfrom and ac_timeunlockto\n"); + LogPrintf("ASSETCHAINS_TIMELOCKGTE - must specify valid ac_timeunlockfrom and ac_timeunlockto\n"); ASSETCHAINS_TIMELOCKGTE = _ASSETCHAINS_TIMELOCKOFF; ASSETCHAINS_TIMEUNLOCKFROM = ASSETCHAINS_TIMEUNLOCKTO = 0; } @@ -1944,7 +1952,7 @@ void hush_args(char *argv0) ASSETCHAINS_SCRIPTPUB = GetArg("-ac_script",""); - fprintf(stderr,"%s: Setting custom %s reward isdragonx=%d reward,halving,subsidy chain values...\n",__func__, SMART_CHAIN_SYMBOL, isdragonx); + LogPrintf("%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 @@ -1960,12 +1968,12 @@ void hush_args(char *argv0) if ( ASSETCHAINS_DECAY[i] == 100000000 && ASSETCHAINS_ENDSUBSIDY == 0 ) { ASSETCHAINS_DECAY[i] = 0; - printf("ERA%u: ASSETCHAINS_DECAY of 100000000 means linear and that needs ASSETCHAINS_ENDSUBSIDY\n", i); + LogPrintf("ERA%u: ASSETCHAINS_DECAY of 100000000 means linear and that needs ASSETCHAINS_ENDSUBSIDY\n", i); } else if ( ASSETCHAINS_DECAY[i] > 100000000 ) { ASSETCHAINS_DECAY[i] = 0; - printf("ERA%u: ASSETCHAINS_DECAY cant be more than 100000000\n", i); + LogPrintf("ERA%u: ASSETCHAINS_DECAY cant be more than 100000000\n", i); } } @@ -1991,21 +1999,15 @@ void hush_args(char *argv0) SplitStr(GetArg("-ac_stocks",""), ASSETCHAINS_STOCKS); if ( ASSETCHAINS_STOCKS.size() > 0 ) ASSETCHAINS_CBOPRET |= 8; - for (i=0; i 0 ) { for (i=0; i<256; i++) @@ -2128,9 +2130,9 @@ void hush_args(char *argv0) if ( ASSETCHAINS_FOUNDERS_REWARD == 0 ) { ASSETCHAINS_COMMISSION = 53846154; // maps to 35% - printf("ASSETCHAINS_COMMISSION defaulted to 35%% when founders reward active\n"); + LogPrintf("ASSETCHAINS_COMMISSION defaulted to 35%% when founders reward active\n"); } else { - printf("ASSETCHAINS_FOUNDERS_REWARD set to %ld\n", ASSETCHAINS_FOUNDERS_REWARD); + LogPrintf("ASSETCHAINS_FOUNDERS_REWARD set to %ld\n", ASSETCHAINS_FOUNDERS_REWARD); } /*else if ( ASSETCHAINS_SELFIMPORT.size() == 0 ) { @@ -2142,12 +2144,12 @@ void hush_args(char *argv0) if ( ASSETCHAINS_COMMISSION != 0 ) { ASSETCHAINS_COMMISSION = 0; - printf("ASSETCHAINS_COMMISSION needs an ASSETCHAINS_OVERRIDE_PUBKEY and cant be more than 100000000 (100%%)\n"); + LogPrintf("ASSETCHAINS_COMMISSION needs an ASSETCHAINS_OVERRIDE_PUBKEY and cant be more than 100000000 (100%%)\n"); } if ( ASSETCHAINS_FOUNDERS != 0 ) { ASSETCHAINS_FOUNDERS = 0; - printf("ASSETCHAINS_FOUNDERS needs an ASSETCHAINS_OVERRIDE_PUBKEY or ASSETCHAINS_SCRIPTPUB\n"); + LogPrintf("ASSETCHAINS_FOUNDERS needs an ASSETCHAINS_OVERRIDE_PUBKEY or ASSETCHAINS_SCRIPTPUB\n"); } } @@ -2215,7 +2217,7 @@ void hush_args(char *argv0) // NOTE: Hush does not use this, we use -ac_script to implement our FR -- Duke if ( ASSETCHAINS_FOUNDERS_REWARD != 0 ) { - fprintf(stderr, "set founders reward.%lld\n",(long long)ASSETCHAINS_FOUNDERS_REWARD); + LogPrintf("set founders reward.%lld\n",(long long)ASSETCHAINS_FOUNDERS_REWARD); extralen += dragon_rwnum(1,&extraptr[extralen],sizeof(ASSETCHAINS_FOUNDERS_REWARD),(void *)&ASSETCHAINS_FOUNDERS_REWARD); } } @@ -2224,14 +2226,12 @@ void hush_args(char *argv0) decode_hex(&extraptr[extralen],ASSETCHAINS_SCRIPTPUB.size()/2,(char *)ASSETCHAINS_SCRIPTPUB.c_str()); extralen += ASSETCHAINS_SCRIPTPUB.size()/2; //extralen += dragon_rwnum(1,&extraptr[extralen],(int32_t)ASSETCHAINS_SCRIPTPUB.size(),(void *)ASSETCHAINS_SCRIPTPUB.c_str()); - fprintf(stderr,"append ac_script %s\n",ASSETCHAINS_SCRIPTPUB.c_str()); + LogPrintf("append ac_script %s\n",ASSETCHAINS_SCRIPTPUB.c_str()); } if ( ASSETCHAINS_SELFIMPORT.size() > 0 ) { memcpy(&extraptr[extralen],(char *)ASSETCHAINS_SELFIMPORT.c_str(),ASSETCHAINS_SELFIMPORT.size()); - for (i=0; i 0 ) { memcpy(&extraptr[extralen],disablebits,sizeof(disablebits)); @@ -2252,14 +2252,13 @@ void hush_args(char *argv0) for (i=0; i0, shutting down\n"); StartShutdown(); } - //fprintf(stderr,"ASSETCHAINS_RPCPORT (%s) %u\n",SMART_CHAIN_SYMBOL,ASSETCHAINS_RPCPORT); } if ( ASSETCHAINS_RPCPORT == 0 ) ASSETCHAINS_RPCPORT = ASSETCHAINS_P2PPORT + 1; @@ -2402,13 +2398,16 @@ void hush_args(char *argv0) if ( HUSH_CCACTIVATE != 0 ) { ASSETCHAINS_CC = 2; - fprintf(stderr,"smart utxo CC contracts will activate at height.%d\n",HUSH_CCACTIVATE); + LogPrintf("smart utxo CC contracts will activate at height.%d\n",HUSH_CCACTIVATE); } else if ( ccEnablesHeight[0] != 0 ) { ASSETCHAINS_CC = 2; - fprintf(stderr,"smart utxo CC contract %d will activate at height.%d\n",(int32_t)ccEnablesHeight[0],(int32_t)ccEnablesHeight[1]); + LogPrintf("smart utxo CC contract %d will activate at height.%d\n",(int32_t)ccEnablesHeight[0],(int32_t)ccEnablesHeight[1]); } } } else { + // Legacy fallback path taken only when no -ac_name is set (SMART_CHAIN_SYMBOL empty). + // Dead on DragonX, which always runs with -ac_name=DRAGONX. The HUSH3/Bitcoin conf + // paths and default ports below are historical and left as-is for backwards compat. char fname[512],username[512],password[4096]; int32_t iter; FILE *fp; ASSETCHAINS_P2PPORT = 7770; ASSETCHAINS_RPCPORT = 7771; @@ -2439,8 +2438,7 @@ void hush_args(char *argv0) _hush_userpass(username,password,fp); sprintf(iter == 0 ? HUSHUSERPASS : BTCUSERPASS,"%s:%s",username,password); fclose(fp); - //printf("HUSH.(%s) -> userpass.(%s)\n",fname,HUSHUSERPASS); - } //else printf("couldnt open.(%s)\n",fname); + } if ( IS_HUSH_NOTARY == 0 ) break; } @@ -2449,7 +2447,6 @@ void hush_args(char *argv0) if ( SMART_CHAIN_SYMBOL[0] != 0 ) { BITCOIND_RPCPORT = GetArg("-rpcport", ASSETCHAINS_RPCPORT); - //fprintf(stderr,"(%s) port.%u chain params initialized\n",SMART_CHAIN_SYMBOL,BITCOIND_RPCPORT); // Set custom cc rulse for chains here if ( strcmp("HUSH3",SMART_CHAIN_SYMBOL) == 0 ) { @@ -2505,7 +2502,7 @@ void hush_prefetch(FILE *fp) { rewind(fp); while ( fread(ignore,1,incr,fp) == incr ) // prefetch - fprintf(stderr,"."); + ; free(ignore); } } diff --git a/src/init.cpp b/src/init.cpp index a359c7200..ce8322dc4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -60,6 +60,7 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" #include "wallet/asyncrpcoperation_saplingconsolidation.h" +#include "wallet/asyncrpcoperation_autoshieldcoinbase.h" #include "wallet/asyncrpcoperation_sweep.h" #endif #include @@ -120,7 +121,11 @@ static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat"; static const char* DEFAULT_ASMAP_FILENAME="asmap.dat"; -CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h +// LevelDB read-cache size (bytes) for the notarizations (dPoW) DB. Non-consensus: just the +// in-memory cache the DB is opened with; the value here does not affect validation. +static const size_t NOTARIZATION_DB_CACHE_BYTES = 100 * 1024 * 1024; // 100 MiB + +CClientUIInterface uiInterface; // global UI callback dispatcher (declared extern in ui_interface.h) // Shutdown // @@ -148,7 +153,7 @@ std::atomic fRequestShutdown(false); void StartShutdown() { if(fDebug) { - fprintf(stderr,"%s: fRequestShudown=true\n", __FUNCTION__); + fprintf(stderr,"%s: fRequestShutdown=true\n", __FUNCTION__); } fRequestShutdown = true; } @@ -471,7 +476,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-hdtransparent", strprintf(_("Derive transparent addresses from the HD seed so they can be recovered from it (default: %u)"), 1)); strUsage += HelpMessageOpt("-hdseed=", _("Restore a fresh/empty wallet from a 32- or 64-byte HD seed hex (the value shown in z_exportwallet's '# HDSeed=' line). WARNING: exposes the seed to your shell history and process list.")); strUsage += HelpMessageOpt("-mnemonic=", _("Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible with SilentDragonXLite (English, no passphrase; cross-wallet restore parity is mainnet-only -- testnet/regtest derive a different HD coin_type). WARNING: exposes the phrase to your shell history and process list; prefer DRAGONX.conf with tight permissions.")); - strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite (default: %u)"), 0)); + strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite. Set to 0 for a raw random seed with no recovery phrase; existing wallets are never changed (default: %u)"), 1)); strUsage += HelpMessageOpt("-hdtransparentgaplimit=", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many HD transparent keys so a rescan can find coinbase paid to them (default: %u)"), 1000)); strUsage += HelpMessageOpt("-mnemonicsaplinggap=", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many shielded (Sapling) addresses so a rescan can find notes sent to them (default: %u)"), 100)); strUsage += HelpMessageOpt("-consolidation", _("Enable auto Sapling note consolidation (default: false)")); @@ -489,6 +494,14 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-zsweepexternal", _("Enable sweeping to an external wallet (default false)")); strUsage += HelpMessageOpt("-zsweepexclude", _("Addresses to exclude from sweeping (default none)")); + strUsage += HelpMessageOpt("-autoshield", _("Automatically shield matured coinbase (mining rewards) into a seed-derived wallet z-address (default: true for wallets created or restored by this software, false when the HD seed provenance is unknown). No-op when not mining or wallet is locked.")); + strUsage += HelpMessageOpt("-autoshieldinterval", strprintf(_("Block interval between automatic coinbase-shielding rounds (default: %i, min %i)"), DEFAULT_AUTOSHIELD_INTERVAL, MIN_AUTOSHIELD_INTERVAL)); + strUsage += HelpMessageOpt("-autoshieldaddress=", _("Destination Sapling z-address for auto-shielded coinbase (default: reuse or create a wallet z-address). Must be spendable by this wallet.")); + strUsage += HelpMessageOpt("-autoshieldfee", strprintf(_("Fee in puposhis for automatic coinbase-shielding transactions (default: %i)"), DEFAULT_AUTOSHIELD_FEE)); + + strUsage += HelpMessageOpt("-sietch-min-zouts=", strprintf(_("Minimum number of shielded (Sapling) outputs Sietch adds to each z_sendmany transaction as decoys, strengthening amount/linkability privacy. Higher values add privacy at the cost of larger transactions (default: %u, clamped to the range 3-50)"), 7)); + strUsage += HelpMessageOpt("-autoshieldminutxos", strprintf(_("Only auto-shield once at least this many matured coinbase UTXOs exist (default: %i)"), 1)); + strUsage += HelpMessageOpt("-deletetx", _("Enable Old Transaction Deletion")); strUsage += HelpMessageOpt("-deleteinterval", strprintf(_("Delete transaction every blocks during inital block download (default: %i)"), DEFAULT_TX_DELETE_INTERVAL)); strUsage += HelpMessageOpt("-keeptxnum", strprintf(_("Keep the last transactions (default: %i)"), DEFAULT_TX_RETENTION_LASTTX)); @@ -608,12 +621,13 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageGroup(_("Stratum server options:")); strUsage += HelpMessageOpt("-stratum", _("Enable stratum server (default: off)")); + strUsage += HelpMessageOpt("-stratumtarget=", _("Pool share target (64-hex, big-endian; larger = easier). Default is the diff-1 target. Useful for solo/low-difficulty mining.")); strUsage += HelpMessageOpt("-stratumaddress=
", _("Mining address to use when special address of 'x' is sent by miner (default: none)")); strUsage += HelpMessageOpt("-stratumbind=", _("Bind to given address to listen for Stratum work requests. Use [host]:port notation for IPv6. This option can be specified multiple times (default: bind to all interfaces)")); strUsage += HelpMessageOpt("-stratumport=", strprintf(_("Listen for Stratum work requests on (default: %u or testnet: %u)"), BaseParams().StratumPort(), BaseParams().StratumPort())); strUsage += HelpMessageOpt("-stratumallowip=", _("Allow Stratum work requests from specified source. Valid for 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 + // "ac" prefix is inherited from the Komodo asset-chain lineage ("asset chain"/"affects consensus") 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")); @@ -780,7 +794,7 @@ void ThreadImport(std::vector vImportFiles) } /** Sanity checks - * Ensure that Hush is running in a usable environment with all + * Ensure that DragonX is running in a usable environment with all * necessary library support. */ bool InitSanityCheck(void) @@ -901,7 +915,7 @@ static void ZC_LoadParams(const CChainParams& chainparams) if (!found) { // The traditional place Zcash params are stored, should not hit this case in normal circumstances, - // as Hush packages sapling params now + // as DragonX packages sapling params now sapling_spend = ZC_GetParamsDir() / "sapling-spend.params"; sapling_output = ZC_GetParamsDir() / "sapling-output.params"; if (files_exist(sapling_spend, sapling_output)) { @@ -920,7 +934,7 @@ static void ZC_LoadParams(const CChainParams& chainparams) boost::system::error_code ec1, ec2; boost::uintmax_t spend_size = file_size(sapling_spend, ec1); boost::uintmax_t output_size = file_size(sapling_output, ec2); - fprintf(stderr,"Sapling spend: %d bytes, output: %d bytes\n", (int)spend_size, (int)output_size); + LogPrintf("Sapling spend: %d bytes, output: %d bytes\n", (int)spend_size, (int)output_size); // We could check sha hashes, but we mostly want to detect on-disk file corruption // or people having a full harddrive. Full validation happens in librustzcash_init_zksnark_params @@ -973,11 +987,15 @@ static void ZC_LoadParams(const CChainParams& chainparams) bool AppInitServers(boost::thread_group& threadGroup) { - fprintf(stderr,"%s: start\n",__func__); + LogPrintf("%s: start\n",__func__); RPCServer::OnStopped(&OnRPCStopped); RPCServer::OnPreCommand(&OnRPCPreCommand); if (!InitHTTPServer()) return false; + // Stratum server (stratum.cpp) supports DragonX's RandomX PoW (32-byte solution + per-height + // RandomX key conveyed to the miner) as well as legacy Equihash, branched on ASSETCHAINS_ALGO. + // Off by default (DEFAULT_STRATUM_ENABLE=false); only -stratum turns it on. Needs a RandomX-aware + // stratum miner (see contrib/ reference miner) — stock Equihash/Monero miners won't work. if (GetBoolArg("-stratum", DEFAULT_STRATUM_ENABLE) && !InitStratumServer()) return false; if (!StartRPC()) @@ -991,7 +1009,7 @@ bool AppInitServers(boost::thread_group& threadGroup) return true; } -/** Initialize Hush. +/** Initialize DragonX. * @pre Parameters should be parsed and config file should be read. */ extern int32_t HUSH_REWIND; @@ -1115,7 +1133,6 @@ static void AdjustCoinCacheForMemoryPressure() bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) { - //fprintf(stderr,"%s start\n", __FUNCTION__); // ********************************************************* Step 1: setup #ifdef _MSC_VER // Turn off Microsoft heap dump noise @@ -1152,7 +1169,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) return InitError("Error: -sysperms is not allowed in combination with enabled wallet functionality"); #endif } else { - //fprintf(stderr,"%s setting umask\n", __FUNCTION__); umask(077); } @@ -1170,12 +1186,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) std::set_new_handler(new_handler_terminate); - //fprintf(stderr,"%s: set signal handlers\n", __FUNCTION__); // ********************************************************* Step 2: parameter interactions const CChainParams& chainparams = Params(); - //fprintf(stderr,"%s: got chain params\n", __FUNCTION__); // Set this early so that experimental features are correctly enabled/disabled fExperimentalMode = GetBoolArg("-experimentalfeatures", true); @@ -1186,11 +1200,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Fail early if user has set experimental options without the global flag if (!fExperimentalMode) { if (mapArgs.count("-developerencryptwallet")) { - fprintf(stderr,"%s wallet encryption error\n", __FUNCTION__); + LogPrintf("%s wallet encryption error\n", __FUNCTION__); return InitError(_("Wallet encryption requires -experimentalfeatures.")); } } - //fprintf(stderr,"%s tik2\n", __FUNCTION__); // Set this early so that parameter interactions go to console fPrintToConsole = GetBoolArg("-printtoconsole", false); @@ -1199,7 +1212,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - LogPrintf("Hush version %s (%s)\n", FormatFullVersion()); + LogPrintf("DragonX version %s (%s)\n", FormatFullVersion()); #ifdef DEBUG_LOCKORDER @@ -1219,7 +1232,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("%s: parameter interaction: -allowbind set -> setting -listen=1\n", __func__); } - //fprintf(stderr,"%s tik3\n", __FUNCTION__); if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0) { // when only connecting to trusted nodes, do not seed via DNS, or listen by default if (SoftSetBoolArg("-dnsseed", false)) @@ -1251,7 +1263,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("%s: parameter interaction: -externalip set -> setting -discover=0\n", __func__); } - // Read asmap file by default for HUSH3 and all Hush Arrakis Chains + // Read asmap file by default on DragonX if (GetArg("-asmap",1)) { fs::path asmap_path = fs::path(GetArg("-asmap", "")); @@ -1269,36 +1281,36 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (asmap_path.empty()) { // Most binaries will have it in PWD asmap_path = pwd / DEFAULT_ASMAP_FILENAME; - printf("%s: looking for asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for asmap file at %s\n", __func__, asmap_path.string().c_str() ); if(fs::exists(asmap_path)) { - printf("%s: found asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.string().c_str() ); } else { // Debian Packages asmap_path = fs::path("/usr/share/hush") / DEFAULT_ASMAP_FILENAME; - printf("%s: looking for asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for asmap file at %s\n", __func__, asmap_path.string().c_str() ); if(fs::exists(asmap_path)) { - printf("%s: found asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.string().c_str() ); } else { // Source code asmap_path = contrib / DEFAULT_ASMAP_FILENAME; - printf("%s: looking for asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for asmap file at %s\n", __func__, asmap_path.string().c_str() ); if(fs::exists(asmap_path)) { - printf("%s: found asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.string().c_str() ); } else { // Last Resort: Check the parent directory asmap_path = pwd / ".." / DEFAULT_ASMAP_FILENAME; - printf("%s: looking for asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for asmap file at %s\n", __func__, asmap_path.string().c_str() ); if(fs::exists(asmap_path)) { - printf("%s: found asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.string().c_str() ); } else { // Mac SD asmap_path = fs::path("/Applications/SilentDragon.app/Contents/MacOS/") / DEFAULT_ASMAP_FILENAME; - printf("%s: looking for asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for asmap file at %s\n", __func__, asmap_path.string().c_str() ); if(fs::exists(asmap_path)) { - printf("%s: found asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.string().c_str() ); } else { - // Shit is fucked up, die an honorable death - InitError(strprintf(_("Could not find any asmap file! Please report this bug to Hush Developers"))); + // No asmap file found in any known location; abort startup. + InitError(strprintf(_("Could not find any asmap file! Please report this bug to DragonX Developers"))); return false; } } @@ -1309,7 +1321,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!asmap_path.is_absolute()) { asmap_path = GetDataDir() / asmap_path; } - printf("%s: looking for custom asmap file at %s\n", __func__, asmap_path.c_str() ); + LogPrint("net", "%s: looking for custom asmap file at %s\n", __func__, asmap_path.string().c_str() ); } //TODO: verify asmap_path is not a directory @@ -1323,7 +1335,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) return false; } const uint256 asmap_version = SerializeHash(asmap); - printf("%s: asmap version=%s with %lu mappings\n", __func__, asmap_version.ToString().c_str(), asmap.size()); + LogPrint("net", "%s: asmap version=%s with %lu mappings\n", __func__, asmap_version.ToString().c_str(), asmap.size()); LogPrintf("Using asmap version %s for IP bucketing with %lu mappings\n", asmap_version.ToString(), asmap.size()); addrman.m_asmap = std::move(asmap); // //node.connman->SetAsmap(std::move(asmap)); @@ -1342,20 +1354,17 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (SoftSetBoolArg("-rescan", true)) LogPrintf("%s: parameter interaction: -zapwallettxes= -> setting -rescan=1\n", __func__); } - //fprintf(stderr,"%s tik4\n", __FUNCTION__); // Make sure enough file descriptors are available int nBind = std::max((int)mapArgs.count("-bind") + (int)mapArgs.count("-allowbind"), 1); nMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); - //fprintf(stderr,"nMaxConnections %d\n",nMaxConnections); nMaxConnections = std::max(std::min(nMaxConnections, (int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)), 0); int nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS); - fprintf(stderr,"nMaxConnections %d FD_SETSIZE.%d nBind.%d expr.%d \n",nMaxConnections,FD_SETSIZE,nBind,(int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)); + LogPrintf("nMaxConnections %d FD_SETSIZE.%d nBind.%d expr.%d \n",nMaxConnections,FD_SETSIZE,nBind,(int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS)); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); if (nFD - MIN_CORE_FILEDESCRIPTORS < nMaxConnections) nMaxConnections = nFD - MIN_CORE_FILEDESCRIPTORS; - //fprintf(stderr,"nMaxConnections %d\n",nMaxConnections); // if using block pruning, then disable txindex // also disable the wallet (for now, until SPV support is implemented in wallet) if (GetArg("-prune", 0)) { @@ -1401,10 +1410,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } if (find(categories.begin(), categories.end(), string("randomx")) != categories.end()) { fRandomXDebug = true; - fprintf(stderr,"%s: enabled randomx debug\n", __func__); + LogPrintf("%s: enabled randomx debug\n", __func__); } - //fprintf(stderr,"%s tik5\n", __FUNCTION__); // Check for -debugnet if (GetBoolArg("-debugnet", false)) InitWarning(_("Warning: Unsupported argument -debugnet ignored, use -debug=net.")); @@ -1424,7 +1432,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) mempool.setSanityCheck(1.0 / ratio); } fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); - fCheckpointsEnabled = GetBoolArg("-checkpoints", true); + // Regtest inherits the DRAGONX *mainnet* checkpoint set (chainparams_commandline applies it + // for every SMART_CHAIN_SYMBOL=="DRAGONX" network, regardless of -regtest), whose top height + // ~3.2M would otherwise pin an isolated regtest chain in IsInitialBlockDownload() forever -- + // disabling the ChainTip auto-ops (autoshield/sweep/consolidation) and the below-checkpoint + // script-check skip. Default checkpoints OFF on regtest so a fresh regtest node leaves IBD + // normally; still overridable with -checkpoints=1. + fCheckpointsEnabled = GetBoolArg("-checkpoints", chainparams.NetworkIDString() != "regtest"); // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency nScriptCheckThreads = GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); @@ -1463,7 +1477,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Bulk block streaming: %s\n", fBulkBlockSync ? "enabled" : "disabled"); fServer = GetBoolArg("-server", false); - //fprintf(stderr,"%s tik6\n", __FUNCTION__); // block pruning; get the amount of disk space (in MB) to allot for block & undo files int64_t nSignedPruneTarget = GetArg("-prune", 0) * 1024 * 1024; @@ -1551,7 +1564,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) expiryDelta = GetArg("-txexpirydelta", DEFAULT_TX_EXPIRY_DELTA); bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", true); fSendFreeTransactions = GetBoolArg("-sendfreetransactions", false); - //fprintf(stderr,"%s tik7\n", __FUNCTION__); std::string strWalletFile = GetArg("-wallet", "wallet.dat"); #endif // ENABLE_WALLET @@ -1568,7 +1580,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) nLocalServices |= NODE_BLOOM; } nMaxTipAge = GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE); - //fprintf(stderr,"%s tik8\n", __FUNCTION__); #ifdef ENABLE_MINING if (mapArgs.count("-mineraddress")) { @@ -1591,7 +1602,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } - //fprintf(stderr,"%s tik9\n", __FUNCTION__); if (!mapMultiArgs["-nuparams"].empty()) { // Allow overriding network upgrade parameters for testing if (Params().NetworkIDString() != "regtest") { @@ -1640,10 +1650,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); - //fprintf(stderr,"%s tik10\n", __FUNCTION__); // Sanity check if (!InitSanityCheck()) - return InitError(_("Initialization sanity check failed. Please check for insanity. Hush is shutting down!")); + return InitError(_("Initialization sanity check failed. Please check for insanity. DragonX is shutting down!")); std::string strDataDir = GetDataDir().string(); #ifdef ENABLE_WALLET @@ -1651,14 +1660,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (strWalletFile != boost::filesystem::basename(strWalletFile) + boost::filesystem::extension(strWalletFile)) return InitError(strprintf(_("Wallet %s resides outside data directory %s"), strWalletFile, strDataDir)); #endif - // Make sure only a single Hush process is using the data directory. + // Make sure only a single DragonX process is using the data directory. boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. if (file) fclose(file); - //fprintf(stderr,"%s tik11\n", __FUNCTION__); - fprintf(stderr,"Attempting to obtain lock %s\n", pathLockFile.string().c_str()); + LogPrintf("Attempting to obtain lock %s\n", pathLockFile.string().c_str()); try { static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); if (!lock.try_lock()) @@ -1673,10 +1681,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (GetBoolArg("-shrinkdebugfile", !fDebug)) ShrinkDebugFile(); - //fprintf(stderr,"%s tik12\n", __FUNCTION__); LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - LogPrintf("Hush version %s\n", FormatFullVersion()); + LogPrintf("DragonX version %s\n", FormatFullVersion()); if (fPrintToDebugLog) OpenDebugLog(); @@ -1706,7 +1713,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) threadGroup.create_thread(&ThreadRandomXVerify); } - //fprintf(stderr,"%s tik13\n", __FUNCTION__); // Start the lightweight task scheduler thread CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); @@ -1714,7 +1720,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Count uptime MarkStartTime(); - //fprintf(stderr,"%s tik14\n", __FUNCTION__); if ((chainparams.NetworkIDString() != "regtest") && GetBoolArg("-showmetrics", 0) && @@ -1724,7 +1729,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) threadGroup.create_thread(&ThreadShowMetricsScreen); } - //fprintf(stderr,"%s tik15\n", __FUNCTION__); if ( HUSH_NSPV_FULLNODE ) { @@ -1742,7 +1746,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!AppInitServers(threadGroup)) return InitError(_("Unable to start HTTP server. See debug log for details.")); } - //fprintf(stderr,"%s tik16\n", __FUNCTION__); int64_t nStart; @@ -1769,7 +1772,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) #endif // ENABLE_WALLET // ********************************************************* Step 6: network initialization - //fprintf(stderr,"%s tik17\n", __FUNCTION__); RegisterNodeSignals(GetNodeSignals()); // sanitize comments per BIP-0014, format user agent and check total size @@ -1785,7 +1787,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) 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)); } - //fprintf(stderr,"%s tik18\n", __FUNCTION__); // Disable clearnet peers if -clearnet=0 for this node or -ac_clearnet=0 for this chain if (ASSETCHAINS_CLEARNET == 0 || !GetBoolArg("-clearnet", DEFAULT_CLEARNET)) { @@ -1844,7 +1845,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) SetReachable(NET_IPV4, false); } - //fprintf(stderr,"%s tik19\n", __FUNCTION__); if (mapArgs.count("-allowlist")) { BOOST_FOREACH(const std::string& net, mapMultiArgs["-allowlist"]) { CSubNet subnet; @@ -1907,7 +1907,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fDiscover = GetBoolArg("-discover", true); fNameLookup = GetBoolArg("-dns", true); - //fprintf(stderr,"%s tik22\n", __FUNCTION__); bool fBound = false; if (fListen) { if (mapArgs.count("-bind") || mapArgs.count("-allowbind")) { @@ -1946,7 +1945,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } - //fprintf(stderr,"%s tik23\n", __FUNCTION__); BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"]) AddOneShot(strDest); @@ -1982,7 +1980,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) return !fRequestShutdown; } // ********************************************************* Step 7: load block chain - //fprintf(stderr,"%s tik24\n", __FUNCTION__); fReindex = GetBoolArg("-reindex", false); @@ -2047,7 +2044,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if ( checkval != fAddressIndex && fAddressIndex != 0 ) { pblocktree->WriteFlag("addressindex", fAddressIndex); - fprintf(stderr,"set addressindex, will reindex. could take a while.\n"); + LogPrintf("set addressindex, will reindex. could take a while.\n"); fReindex = true; } fSpentIndex = GetBoolArg("-spentindex", DEFAULT_SPENTINDEX); @@ -2055,7 +2052,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if ( checkval != fSpentIndex && fSpentIndex != 0 ) { pblocktree->WriteFlag("spentindex", fSpentIndex); - fprintf(stderr,"set spentindex, will reindex. could take a while.\n"); + LogPrintf("set spentindex, will reindex. could take a while.\n"); fReindex = true; } } @@ -2084,7 +2081,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); try { - pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex); + pnotarizations = new NotarizationDB(NOTARIZATION_DB_CACHE_BYTES, false, fReindex); } catch (const std::exception& e) { // The notarizations (dPoW) DB is non-essential and node-regenerable. It has been seen to // snapshot/flush torn (0-byte log -> leveldb "Database I/O error" on reopen), which @@ -2100,7 +2097,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) boost::filesystem::remove_all(ndir.string() + ".corrupt"); boost::filesystem::rename(ndir, ndir.string() + ".corrupt"); } - pnotarizations = new NotarizationDB(100*1024*1024, false, true); + pnotarizations = new NotarizationDB(NOTARIZATION_DB_CACHE_BYTES, false, true); } @@ -2108,14 +2105,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) boost::filesystem::remove(GetDataDir() / "hushstate"); boost::filesystem::remove(GetDataDir() / "hushsignedmasks"); pblocktree->WriteReindexing(true); - fprintf(stderr, "%s: Deleted hushstate and hushsignedmasks...\n", __FUNCTION__); + LogPrintf("%s: Deleted hushstate and hushsignedmasks...\n", __FUNCTION__); //If we're reindexing in prune mode, wipe away unusable block files and all undo data files if (fPruneMode) CleanupBlockRevFiles(); } - fprintf(stderr, "%s: Loading block index...\n", __FUNCTION__); + LogPrintf("%s: Loading block index...\n", __FUNCTION__); if (!LoadBlockIndex()) { strLoadError = _("Error loading block database"); break; @@ -2139,7 +2136,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) break; } - fprintf(stderr, "zindex=%s in block index\n", fZindex ? "enabled" : "disabled"); + LogPrintf("zindex=%s in block index\n", fZindex ? "enabled" : "disabled"); if (fZindex != GetBoolArg("-zindex", false)) { strLoadError = _("You need to rebuild the database using -reindex to change -zindex"); break; @@ -2194,7 +2191,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!fLoaded) { // first suggest a reindex if (!fReset) { - fprintf(stderr,"%s: error in hd data\n", __FUNCTION__); + LogPrintf("%s: error in hd data\n", __FUNCTION__); bool fRet = uiInterface.ThreadSafeMessageBox( strLoadError + ".\n\n" + _("error in HDD data, might just need to update to latest, if that doesnt work, then you need to resync"), "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); @@ -2229,7 +2226,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) mempool.ReadFeeEstimates(est_filein); fFeeEstimatesInitialized = true; - //fprintf(stderr,"%s tik25\n", __FUNCTION__); // ********************************************************* Step 8: load wallet #ifdef ENABLE_WALLET @@ -2264,7 +2260,22 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (nLoadWalletRet != DB_LOAD_OK) { if (nLoadWalletRet == DB_CORRUPT) - strErrors << _("Error loading wallet.dat: Wallet corrupted") << "\n"; + { + // Abort HERE, as the DB_NEED_REWRITE branch below already does. Falling through + // runs several hundred more lines of initialisation against a wallet we have just + // declared corrupt -- including SetHDSeedOrigin(), which WRITES to it, and the + // rescan and SetBestChain that follow. + // + // The old text advised restoring with -mnemonic. That is wrong twice over: + // -usemnemonic defaulted to 0 in v1.0.3 so many such wallets never had a phrase, + // and SetHDSeedFromMnemonic refuses a non-empty wallet, so "move wallet.dat aside" + // would discard every non-HD key the salvage preserved. + strErrors << _("Error loading wallet.dat: the wallet database is corrupt. Your keys may " + "still be intact -- do NOT delete or replace wallet.dat. Back it up now, " + "and see debug.log for the specific record at fault.") << "\n"; + LogPrintf("%s", strErrors.str()); + return InitError(strErrors.str()); + } else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) { string msg(_("Warning: error reading wallet.dat! All keys read correctly, but transaction data" @@ -2272,10 +2283,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) InitWarning(msg); } else if (nLoadWalletRet == DB_TOO_NEW) - strErrors << _("Error loading wallet.dat: Wallet requires newer version of Hush") << "\n"; + strErrors << _("Error loading wallet.dat: Wallet requires newer version of DragonX") << "\n"; else if (nLoadWalletRet == DB_NEED_REWRITE) { - strErrors << _("Wallet needed to be rewritten: restart Hush to complete") << "\n"; + strErrors << _("Wallet needed to be rewritten: restart DragonX to complete") << "\n"; LogPrintf("%s", strErrors.str()); return InitError(strErrors.str()); } @@ -2301,6 +2312,23 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!pwalletMain->HaveHDSeed()) { + // Does this wallet predate the seed we are about to install? If so, + // that seed cannot appear in any backup the user already holds. + // Checked BEFORE installing, and before the restore path pre-derives + // its gap of keys. + bool fWalletHadContent = false; + { + LOCK(pwalletMain->cs_wallet); + std::set setExistingKeys; + pwalletMain->GetKeys(setExistingKeys); // keystore.h:60 / crypter.h:212 + std::set setExistingZAddrs; + pwalletMain->GetSaplingPaymentAddresses(setExistingZAddrs); // keystore.h:226-238 + fWalletHadContent = !setExistingKeys.empty() || + !setExistingZAddrs.empty() || + !pwalletMain->mapWallet.empty() || // wallet.h:1041 + pwalletMain->IsCrypted(); // crypter.h:174 + } + std::string mnemonic = GetArg("-mnemonic", ""); std::string hdSeedHex = GetArg("-hdseed", ""); bool restoring = false; @@ -2332,6 +2360,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) pwalletMain->GenerateNewSeed(); } + pwalletMain->SetHDSeedOrigin(restoring + ? CWallet::HDSEED_ORIGIN_RESTORED + : (fWalletHadContent ? CWallet::HDSEED_ORIGIN_RETROFIT + : CWallet::HDSEED_ORIGIN_CREATED)); + if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_RETROFIT) + { + LogPrintf("%s: WARNING: generated a new HD seed for a wallet that already held keys or " + "transactions. This seed is in NO backup you made before now.\n", __func__); + InitWarning(_("A new HD seed was generated for this pre-existing wallet. Any backup you " + "made before now does not contain it: back the wallet up again " + "(z_exportwallet) before receiving funds to newly derived addresses.")); + } + if (restoring) { // Pre-derive keys (birthday = genesis) so the startup rescan finds @@ -2350,6 +2391,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("%s: pre-derived %d transparent and %d sapling keys for restore rescan\n", __func__, (int)tGap, (int)zGap); } } + else if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_UNRECORDED) + { + // The seed was installed by a build that predates this record, so + // we cannot tell whether it was minted onto a pre-existing wallet + // (and is therefore absent from the user's older backups). Assume + // the worst; the user can still opt in explicitly. + pwalletMain->SetHDSeedOrigin(CWallet::HDSEED_ORIGIN_UNKNOWN); + LogPrintf("%s: HD seed predates seed-provenance recording; recorded origin as unknown\n", __func__); + } //Set Sapling Consolidation pwalletMain->fSaplingConsolidationEnabled = GetBoolArg("-consolidation", false); @@ -2359,7 +2409,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) int consolidationInterval = GetArg("-consolidationinterval", 25); if (consolidationInterval < 5) { - fprintf(stderr,"%s: Invalid consolidation interval of %d < 5, setting to default of 25\n", __func__, consolidationInterval); + LogPrintf("%s: Invalid consolidation interval of %d < 5, setting to default of 25\n", __func__, consolidationInterval); consolidationInterval = 25; } @@ -2384,7 +2434,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (pwalletMain->fSweepEnabled) { int sweepInterval = GetArg("-zsweepinterval", 10); if (sweepInterval < 5) { - fprintf(stderr,"%s: Invalid sweep interval of %d, setting to default of 10\n", __func__, sweepInterval); + LogPrintf("%s: Invalid sweep interval of %d, setting to default of 10\n", __func__, sweepInterval); sweepInterval = 10; } pwalletMain->sweepInterval = sweepInterval; @@ -2401,7 +2451,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } for (int i = 0; i < vSweep.size(); i++) { - // LogPrintf("Sweep Address: %s\n", vSweep[i]); auto zSweep = DecodePaymentAddress(vSweep[i]); if (!IsValidPaymentAddress(zSweep)) { return InitError("Invalid zsweep address"); @@ -2451,6 +2500,87 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } + //Set Automatic Coinbase Shielding (default ON, conditional: self-guards + //on nodes where it cannot act - no owned coinbase, external mineraddress, + //or locked wallet). Closes the transparent-coinbase leak for miners. + // Default ON only when this wallet's HD seed provenance says the + // destination is genuinely recoverable. Autoshield sends mined coinbase to + // a seed-derived z-address (resolveDestination), so for a seed retrofitted + // onto a pre-existing wallet - or one predating provenance recording - we + // cannot assume the user holds it. Those wallets opt in with -autoshield=1 + // after backing the seed up. + const bool fAutoShieldSeedKnown = + (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_CREATED || + pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_RESTORED); + pwalletMain->fAutoShieldEnabled = GetBoolArg("-autoshield", fAutoShieldSeedKnown); + if (!fAutoShieldSeedKnown && !mapArgs.count("-autoshield")) { + LogPrintf("%s: autoshield left OFF by default: HD seed origin %d is not known-recoverable. " + "Back the seed up (z_exportwallet, or z_exportmnemonic on a mnemonic wallet) and " + "pass -autoshield=1 to enable.\n", __func__, pwalletMain->hdSeedOrigin); + } + if (pwalletMain->fAutoShieldEnabled) { + int autoShieldInterval = GetArg("-autoshieldinterval", DEFAULT_AUTOSHIELD_INTERVAL); + if (autoShieldInterval < MIN_AUTOSHIELD_INTERVAL) { + InitWarning(strprintf(_("autoshield interval %d below the minimum, clamping to %d"), + autoShieldInterval, MIN_AUTOSHIELD_INTERVAL)); + autoShieldInterval = MIN_AUTOSHIELD_INTERVAL; + } + pwalletMain->autoShieldInterval = autoShieldInterval; + pwalletMain->nextAutoShield = pwalletMain->autoShieldInterval + chainActive.Height(); + + // Validate the fee: floor it above the relay minimum and cap it to + // guard against a fat-finger (e.g. -autoshieldfee=5000000000) that + // would otherwise build an over-fee or malformed shield tx that + // fails mempool admission every round. + CAmount autoShieldFee = GetArg("-autoshieldfee", DEFAULT_AUTOSHIELD_FEE); + if (autoShieldFee < AUTOSHIELD_MIN_FEE || autoShieldFee > AUTOSHIELD_MAX_FEE) { + InitWarning(strprintf(_("-autoshieldfee=%lld out of range [%lld,%lld], using default %lld"), + (long long)autoShieldFee, (long long)AUTOSHIELD_MIN_FEE, (long long)AUTOSHIELD_MAX_FEE, (long long)DEFAULT_AUTOSHIELD_FEE)); + autoShieldFee = DEFAULT_AUTOSHIELD_FEE; + } + pwalletMain->autoShieldFee = autoShieldFee; + pwalletMain->autoShieldMinUtxos = GetArg("-autoshieldminutxos", DEFAULT_AUTOSHIELD_MIN_UTXOS); + if (pwalletMain->autoShieldMinUtxos < 1) { + pwalletMain->autoShieldMinUtxos = 1; + } + LogPrintf("%s: autoshield enabled, nextAutoShield=%d interval=%d\n", __func__, pwalletMain->nextAutoShield, pwalletMain->autoShieldInterval); + + //Optional explicit destination z-address. Must be a Sapling zaddr the + //wallet can spend, else the shielded coinbase would be unrecoverable. + std::string autoShieldAddress = GetArg("-autoshieldaddress", ""); + if (!autoShieldAddress.empty()) { + auto zdest = DecodePaymentAddress(autoShieldAddress); + if (!IsValidPaymentAddress(zdest) || + boost::get(&zdest) == nullptr) { + return InitError("Invalid -autoshieldaddress: must be a Sapling z-address"); + } + auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zdest); + if (!hasSpendingKey) { + return InitError("Wallet must hold the spending key of -autoshieldaddress (else shielded coinbase would be unrecoverable)"); + } + pwalletMain->autoShieldAddress = autoShieldAddress; + } else { + // No explicit destination. Resolve the seed-derived one now, read-only, + // so z_autoshieldstatus can say where coinbase will go BEFORE the first + // round rather than reporting an empty string until one fires. This + // never generates a key: a fresh account must not be a side effect of + // populating a status field. A brand-new wallet holds nothing in-gap + // yet, so the field stays empty and the RPC explains why. + LOCK(pwalletMain->cs_wallet); + if (!pwalletMain->IsLocked()) { + libzcash::SaplingPaymentAddress destAddr; + std::string destStr; + uint32_t destAccount = AUTOSHIELD_ACCOUNT_NONE; + if (ResolveAutoShieldDestinationReadOnly(destAddr, destStr, destAccount) + == AutoShieldDestStatus::Resolved) { + pwalletMain->autoShieldAddress = destStr; + LogPrintf("%s: autoshield destination %s (seed-derived sapling account %u)\n", + __func__, destStr, (unsigned)destAccount); + } + } + } + } + //Set Transaction Deletion Options fTxDeleteEnabled = GetBoolArg("-deletetx", false); fTxConflictDeleteEnabled = GetBoolArg("-deleteconflicttx", true); @@ -2524,8 +2654,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) nStart = GetTimeMillis(); pwalletMain->ScanForWalletTransactions(pindexRescan, true); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); - pwalletMain->SetBestChain(chainActive.GetLocator()); - nWalletDBUpdated++; + // ONLY record "scanned to the tip" if the scan actually reached it. An aborted or + // shutdown-interrupted scan that stamps a tip locator tells the next startup there is + // nothing left to scan (the chainActive.Tip() != pindexRescan guard above then skips + // the rescan entirely), so every transaction in the unscanned range stays out of + // mapWallet permanently: invisible to getbalance and unspendable. The scan writes its + // own locator at the interrupt point instead. + if (pwalletMain->fLastRescanCompleted) { + pwalletMain->SetBestChain(chainActive.GetLocator()); + nWalletDBUpdated++; + } else { + LogPrintf("Rescan did not complete; leaving the best-block locator at the scan's own " + "checkpoint so the remaining range is rescanned on the next start\n"); + } // Restore wallet transaction metadata after -zapwallettxes=1 if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2") @@ -2561,10 +2702,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) #ifdef ENABLE_MINING #ifndef ENABLE_WALLET if (GetBoolArg("-minetolocalwallet", false)) { - return InitError(_("Hush was not built with wallet support. Set -minetolocalwallet=0 to use -mineraddress, or rebuild Hush with wallet support.")); + return InitError(_("DragonX was not built with wallet support. Set -minetolocalwallet=0 to use -mineraddress, or rebuild DragonX with wallet support.")); } if (GetArg("-mineraddress", "").empty() && GetBoolArg("-gen", false)) { - return InitError(_("Hush was not built with wallet support. Set -mineraddress, or rebuild Hush with wallet support.")); + return InitError(_("DragonX was not built with wallet support. Set -mineraddress, or rebuild DragonX with wallet support.")); } #endif // !ENABLE_WALLET @@ -2623,7 +2764,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Advertise willingness to SERVE bulk block streams (full nodes only) when opted in. if ( fBulkBlockSync ) nLocalServices |= NODE_BULKBLOCKS; - fprintf(stderr,"nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)); + LogPrintf("nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)); } // ********************************************************* Step 10: import blocks @@ -2639,7 +2780,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if ( !ActivateBestChain(true,state)) strErrors << "Failed to connect best block"; } else { - fprintf(stderr,"HUSH_REWIND < 0\n"); + LogPrintf("HUSH_REWIND < 0\n"); } std::vector vImportFiles; if (mapArgs.count("-loadblock")) @@ -2668,7 +2809,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // ********************************************************* Step 11: start node - //fprintf(stderr,"Checking disk space...\n"); if (!CheckDiskSpace()) return false; @@ -2706,7 +2846,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) SetRPCWarmupFinished(); if(fDebug) - fprintf(stderr,"RPC warmump finished\n"); + fprintf(stderr,"RPC warmup finished\n"); uiInterface.InitMessage(_("Full Node Done Loading! :)")); #ifdef ENABLE_WALLET diff --git a/src/keystore.cpp b/src/keystore.cpp index 0e78cbdc9..b0d30bd56 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -68,6 +68,42 @@ bool CBasicKeyStore::GetHDSeed(HDSeed& seedOut) const } } +bool CBasicKeyStore::SetMnemonicEntropy(const RawHDSeed& entropy) +{ + LOCK(cs_SpendingKeyStore); + if (entropy.empty()) { + // Never "install" nothing: HaveMnemonicEntropy() would stay false while + // the caller was told the call succeeded. + return false; + } + if (!mnemonicEntropy.empty()) { + // Same refuse-to-replace rule as SetHDSeed above, for a sharper reason: + // this is the printable form of the seed. If it could be swapped while + // hdSeed stayed put, the wallet would print a seed phrase that does not + // restore it -- strictly worse than printing none. + return false; + } + mnemonicEntropy = entropy; + return true; +} + +bool CBasicKeyStore::HaveMnemonicEntropy() const +{ + LOCK(cs_SpendingKeyStore); + return !mnemonicEntropy.empty(); +} + +bool CBasicKeyStore::GetMnemonicEntropy(RawHDSeed& entropyOut) const +{ + LOCK(cs_SpendingKeyStore); + if (mnemonicEntropy.empty()) { + return false; + } else { + entropyOut = mnemonicEntropy; + return true; + } +} + bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); diff --git a/src/keystore.h b/src/keystore.h index 666d38984..827fc2bcd 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -117,6 +117,12 @@ class CBasicKeyStore : public CKeyStore { protected: HDSeed hdSeed; + // BIP39 entropy for a mnemonic-recoverable wallet, kept BESIDE hdSeed, never + // instead of it. hdSeed holds the bytes actually fed to derivation (the + // expanded 64-byte BIP39 seed on new wallets); this record exists only so the + // seed phrase can be reprinted. Empty on legacy and hex-restored wallets, + // which is a normal state, not an error. + RawHDSeed mnemonicEntropy; KeyMap mapKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; @@ -129,6 +135,18 @@ public: bool SetHDSeed(const HDSeed& seed); bool HaveHDSeed() const; bool GetHDSeed(HDSeed& seedOut) const; + //! Mnemonic entropy: optional, present only on phrase-recoverable wallets. + //! Unlike the three seed accessors above -- which override pure virtuals on + //! CKeyStore (keystore.h:48-51) and therefore dispatch dynamically -- these + //! are plain non-virtual members: CKeyStore declares nothing for them and + //! nothing reaches the entropy through a base pointer. Every caller holds a + //! CWallet*, whose static type resolves to the CCryptoKeyStore overloads. + //! Do NOT add them to CKeyStore: that would force all four subclasses + //! (CBasicKeyStore, CCryptoKeyStore, CWallet, gtest's TestCCryptoKeyStore) + //! to implement them for zero call sites. + bool SetMnemonicEntropy(const RawHDSeed& entropy); + bool HaveMnemonicEntropy() const; + bool GetMnemonicEntropy(RawHDSeed& entropyOut) const; bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool HaveKey(const CKeyID &address) const diff --git a/src/libcc.dylib b/src/libcc.dylib deleted file mode 100644 index dc1730032..000000000 Binary files a/src/libcc.dylib and /dev/null differ diff --git a/src/main.cpp b/src/main.cpp index 1375ab064..6bc40c659 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1259,11 +1259,10 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in // Ensure that a coinbase transaction is structured according to the consensus rules of the chain bool ContextualCheckCoinbaseTransaction(int32_t slowflag,const CBlock *block,CBlockIndex * const previndex,const CTransaction& tx, const int nHeight,int32_t validateprices) { - if ( slowflag != 0 && ASSETCHAINS_CBOPRET != 0 && validateprices != 0 && nHeight > 0 && tx.vout.size() > 0 ) - { - if ( hush_opretvalidate(block,previndex,nHeight,tx.vout[tx.vout.size()-1].scriptPubKey) < 0 ) - return(false); - } + // The only coinbase-specific contextual check here was CBOPRET price-oracle + // validation (hush_opretvalidate), gated on ASSETCHAINS_CBOPRET, which is always + // 0 on DragonX (no -ac_cbopret). With that dead path removed there is nothing left + // to validate, so a DragonX coinbase is unconditionally valid at this stage. return(true); } @@ -5155,10 +5154,15 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height); // RAII: save+restore the thread-local RandomX-skip flag around the verify-once dedup in CheckBlock, // so it can never clobber the miner's own fSkipRandomXValidation (TestBlockValidity -> ConnectBlock // re-entry) nor leak TRUE on an exception thrown out of hush_checkPOW. +// `arm` is false on paths where CheckBlockHeader did NOT reach its own RandomX check, so the +// dedup can never suppress the only verification a block gets. struct ScopedRandomXSkip { bool prev; - ScopedRandomXSkip() : prev(GetSkipRandomXValidation()) { SetSkipRandomXValidation(true); } - ~ScopedRandomXSkip() { SetSkipRandomXValidation(prev); } + bool armed; + explicit ScopedRandomXSkip(bool arm) : prev(GetSkipRandomXValidation()), armed(arm) { + if (armed) SetSkipRandomXValidation(true); + } + ~ScopedRandomXSkip() { if (armed) SetSkipRandomXValidation(prev); } }; bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const CBlock& block, CValidationState& state, libzcash::ProofVerifier& verifier, @@ -5168,7 +5172,8 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C // These are checks that are independent of context. hash = block.GetHash(); // Check that the header is valid (particularly PoW). This is mostly redundant with the call in AcceptBlockHeader. - if (!CheckBlockHeader(futureblockp,height,pindex,block,state,fCheckPOW)) + const bool fHeaderChecked = CheckBlockHeader(futureblockp,height,pindex,block,state,fCheckPOW); + if (!fHeaderChecked) { if ( *futureblockp == 0 ) { @@ -5196,7 +5201,11 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C // that dominates IBD). The scoped guard saves/restores the skip flag (never hardcodes false) // so the miner's own skip is preserved and nothing leaks on throw. Equihash + PoW-target in // hush_checkPOW still run. - ScopedRandomXSkip _rxskip; + // ARMED ONLY IF fHeaderChecked: CheckBlockHeader returns early -- BEFORE its RandomX + // check -- for a future-timestamped block (*futureblockp==1), and CheckBlock keeps + // going on that path. There hush_checkPOW is the ONLY RandomX verification, so + // arming unconditionally drops the check for that class of block. + ScopedRandomXSkip _rxskip(fHeaderChecked); if ( hush_checkPOW(1,(CBlock *)&block,height) < 0 ) return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW"); } diff --git a/src/miner.cpp b/src/miner.cpp index 65f3027fe..b22cc406a 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -160,7 +160,6 @@ bool hush_appendACscriptpub(); CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32_t gpucount, bool isStake) { - //fprintf(stderr,"%s\n", __func__); CScript scriptPubKeyIn(_scriptPubKeyIn); CPubKey pk; @@ -179,15 +178,13 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 uint32_t blocktime; const CChainParams& chainparams = Params(); bool fNotarizationBlock = false; std::vector NotarizationNotaries; - //fprintf(stderr,"%s: create new block with pubkey=%s\n", __func__, HexStr(pk).c_str()); // Create new block if ( gpucount < 0 ) gpucount = HUSH_MAXGPUCOUNT; std::unique_ptr pblocktemplate(new CBlockTemplate()); - //fprintf(stderr,"%s: created new block template\n", __func__); if(!pblocktemplate.get()) { - fprintf(stderr,"%s: pblocktemplate.get() failure\n", __func__); + LogPrintf("%s: pblocktemplate.get() failure\n", __func__); return NULL; } CBlock *pblock = &pblocktemplate->block; // pointer for convenience @@ -200,7 +197,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 pblock->vtx.push_back(CTransaction()); pblocktemplate->vTxFees.push_back(-1); // updated at end pblocktemplate->vTxSigOps.push_back(-1); // updated at end - //fprintf(stderr,"%s: added dummy coinbase\n", __func__); // Largest block you're willing to create: unsigned int nBlockMaxSize = GetArg("-blockmaxsize", MAX_BLOCK_SIZE(1)); // MAX_BLOCK_SIZE(chainActive.LastTip()->GetHeight()+1)); @@ -217,7 +213,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 // until there are no more or the block reaches this size: const unsigned int nBlockMinSize = std::min(nBlockMaxSize, (unsigned int) GetArg("-blockminsize", DEFAULT_BLOCK_MIN_SIZE)); // nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize); - //fprintf(stderr,"%s: nBlockMaxSize=%u, nBlockPrioritySize=%u, nBlockMinSize=%u\n", __func__, nBlockMaxSize, nBlockPrioritySize, nBlockMinSize); // Collect memory pool transactions into the block @@ -243,17 +238,18 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); uint32_t proposedTime = GetTime(); - //fprintf(stderr,"%s: nHeight=%d, consensusBranchId=%u, proposedTime=%u\n", __func__, nHeight, consensusBranchId, proposedTime); if (proposedTime == nMedianTimePast) { // too fast or stuck, this addresses the too fast issue, while moving // forward as quickly as possible - for (int i; i < 100; i++) + for (int i = 0; i < 100; i++) { proposedTime = GetTime(); if (proposedTime == nMedianTimePast) MilliSleep(10); + else + break; // time advanced past the median; stop waiting } } pblock->nTime = GetTime(); @@ -280,7 +276,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 vector vecPriority; vecPriority.reserve(mempool.mapTx.size() + 1); - //fprintf(stderr,"%s: going to add txs from mempool\n", __func__); // now add transactions from the mempool int32_t Notarizations = 0; uint64_t txvalue; uint32_t large_zins = 0; // number of ztxs with large number of inputs in block @@ -299,7 +294,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff) || IsExpiredTx(tx, nHeight)) { - fprintf(stderr,"%s: coinbase.%d finaltx.%d expired.%d\n",__func__, tx.IsCoinBase(),IsFinalTx(tx, nHeight, nLockTimeCutoff),IsExpiredTx(tx, nHeight)); + LogPrint("mempool", "%s: coinbase.%d finaltx.%d expired.%d\n",__func__, tx.IsCoinBase(),IsFinalTx(tx, nHeight, nLockTimeCutoff),IsExpiredTx(tx, nHeight)); continue; } txvalue = tx.GetValueOut(); @@ -378,9 +373,8 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 { // check a notary didnt sign twice (this would be an invalid notarization later on and cause problems) std::set checkdupes( TMP_NotarizationNotaries.begin(), TMP_NotarizationNotaries.end() ); - if ( checkdupes.size() != TMP_NotarizationNotaries.size() ) + if ( checkdupes.size() != TMP_NotarizationNotaries.size() ) { - fprintf(stderr, "%s: WTFBBQ! possible notarization is signed multiple times by same notary, passed as normal transaction.\n", __func__); } else fNotarization = true; } nTotalIn += tx.GetShieldedValueIn(); @@ -390,7 +384,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 // Priority is sum(valuein * age) / modified_txsize unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - // fprintf(stderr,"%s: computing priority with nTxSize=%u\n", __func__, nTxSize); dPriority = tx.ComputePriority(dPriority, nTxSize); uint256 hash = tx.GetHash(); @@ -410,7 +403,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 Notarizations++; if ( Notarizations > 1 ) { - fprintf(stderr, "%s: skipping notarization.%d\n",__func__, Notarizations); + LogPrint("mempool", "%s: skipping notarization.%d\n",__func__, Notarizations); // Any attempted notarization needs to be in its own block! continue; } @@ -421,7 +414,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 NotarizationNotaries = TMP_NotarizationNotaries; dPriority = 1e16; fNotarizationBlock = true; - //fprintf(stderr, "Notarization %s set to maximum priority\n",hash.ToString().c_str()); } } } @@ -436,7 +428,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx()))); } } - // fprintf(stderr,"%s: done adding txs from mempool\n", __func__); // Collect transactions into block int64_t interest; @@ -448,7 +439,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 TxPriorityCompare comparer(fSortedByFee); std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); - // fprintf(stderr,"%s: compared txs with fSortedByFee=%d\n", __func__, fSortedByFee); while (!vecPriority.empty()) { // Take highest priority transaction off the priority queue: @@ -456,10 +446,8 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 CFeeRate feeRate = vecPriority.front().get<1>(); const CTransaction& tx = *(vecPriority.front().get<2>()); - // fprintf(stderr,"%s: grabbed first tx from priority queue\n", __func__); std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); - // fprintf(stderr,"%s: compared first tx from priority queue\n", __func__); vecPriority.pop_back(); if(tx.vShieldedSpend.size() >= LARGE_ZINS_THRESHOLD && large_zins >= LARGE_ZINS_MAX) { @@ -476,12 +464,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 // Size limits unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - // fprintf(stderr,"%s: nTxSize = %u\n", __func__, nTxSize); if (nBlockSize + nTxSize >= nBlockMaxSize-512) // room for extra autotx { - fprintf(stderr,"%s: nBlockSize %d + %d nTxSize >= %d nBlockMaxSize\n",__func__, (int32_t)nBlockSize,(int32_t)nTxSize,(int32_t)nBlockMaxSize); + LogPrint("mempool", "%s: nBlockSize %d + %d nTxSize >= %d nBlockMaxSize\n",__func__, (int32_t)nBlockSize,(int32_t)nTxSize,(int32_t)nBlockMaxSize); continue; } @@ -489,11 +476,9 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 unsigned int nTxSigOps = GetLegacySigOpCount(tx); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1) { - //fprintf(stderr,"A nBlockSigOps %d + %d nTxSigOps >= %d MAX_BLOCK_SIGOPS-1\n",(int32_t)nBlockSigOps,(int32_t)nTxSigOps,(int32_t)MAX_BLOCK_SIGOPS); continue; } - // fprintf(stderr,"%s: looking to see if we need to skip any fee=0 txs\n", __func__); // Skip free transactions if we're past the minimum block size: const uint256& hash = tx.GetHash(); @@ -502,7 +487,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta); if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < ::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize)) { - fprintf(stderr,"%s: fee rate skip\n", __func__); + LogPrint("mempool", "%s: fee rate skip\n", __func__); continue; } // Prioritize by fee once past the priority size or we run out of high-priority transactions @@ -516,7 +501,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 if (!view.HaveInputs(tx)) { - //fprintf(stderr,"dont have inputs\n"); continue; } CAmount nTxFees = view.GetValueIn(chainActive.LastTip()->GetHeight(),&interest,tx,chainActive.LastTip()->nTime)-tx.GetValueOut(); @@ -541,7 +525,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 opcodetype op; std::vector opretData; if (txout.scriptPubKey.GetOp(it, op, opretData)) { - //std::cerr << HexStr(opretData.begin(), opretData.end()) << std::endl; nTxOpretSize += opretData.size(); } } @@ -552,13 +535,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 std::cerr << __func__ << ": " << tx.GetHash().ToString() << " nTxSize=" << nTxSize << " nTxOpretSize=" << nTxOpretSize << " feeRate=" << feeRate.ToString() << " opretMinFee=" << opretMinFee << " nTxFees=" << nTxFees <<" fSpamTx=" << fSpamTx << std::endl; continue; } - // std::cerr << tx.GetHash().ToString() << " vecPriority.size() = " << vecPriority.size() << std::endl; } nTxSigOps += GetP2SHSigOpCount(tx, view); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1) { - //fprintf(stderr,"B nBlockSigOps %d + %d nTxSigOps >= %d MAX_BLOCK_SIGOPS-1\n",(int32_t)nBlockSigOps,(int32_t)nTxSigOps,(int32_t)MAX_BLOCK_SIGOPS); continue; } // Note that flags: we don't want to set mempool/IsStandard() @@ -568,7 +549,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 PrecomputedTransactionData txdata(tx); if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId)) { - fprintf(stderr,"%s: ContextualCheckInputs failure\n",__func__); + LogPrint("mempool", "%s: ContextualCheckInputs failure\n",__func__); continue; } UpdateCoins(tx, view, nHeight); @@ -623,13 +604,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; - // fprintf(stderr,"%s: nLastBlockTx=%lu , nLastBlockSize=%lu\n", __func__, nLastBlockTx, nLastBlockSize); if ( ASSETCHAINS_ADAPTIVEPOW <= 0 ) blocktime = 1 + std::max(pindexPrev->GetMedianTimePast()+1, GetTime()); else blocktime = 1 + std::max((int64_t)(pindexPrev->nTime+1), GetTime()); //pblock->nTime = blocktime + 1; - // fprintf(stderr,"%s: calling GetNextWorkRequired\n", __func__); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); LogPrintf("CreateNewBlock(): total size %u blocktime.%u nBits.%08x\n", nBlockSize,blocktime,pblock->nBits); @@ -643,7 +622,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 txNew.vout.resize(1); txNew.vout[0].scriptPubKey = scriptPubKeyIn; txNew.vout[0].nValue = GetBlockSubsidy(nHeight,consensusParams) + nFees; - // fprintf(stderr,"%s: mine ht.%d with %.8f\n",__func__,nHeight,(double)txNew.vout[0].nValue/COIN); txNew.nExpiryHeight = 0; if ( ASSETCHAINS_ADAPTIVEPOW <= 0 ) txNew.nLockTime = std::max(pindexPrev->GetMedianTimePast()+1, GetTime()); @@ -665,10 +643,9 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 static bool didinit = false; if ( !didinit && nHeight > HUSH_EARLYTXID_HEIGHT && HUSH_EARLYTXID != zeroid && hush_appendACscriptpub() ) { - fprintf(stderr, "appended ccopreturn to assetchains_scriptpub.%s\n", assetchains_scriptpub.c_str()); + LogPrintf("appended ccopreturn to assetchains_scriptpub.%s\n", assetchains_scriptpub.c_str()); didinit = true; } - //fprintf(stderr,"mine to -ac_script\n"); //txNew.vout[1].scriptPubKey = CScript() << ParseHex(); int32_t len = strlen(assetchains_scriptpub.c_str()); len >>= 1; @@ -682,14 +659,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 for (i=0; i<33; i++) { ptr[i+1] = ASSETCHAINS_OVERRIDE_PUBKEY33[i]; - //fprintf(stderr,"%02x",ptr[i+1]); } ptr[34] = OP_CHECKSIG; - //fprintf(stderr," set ASSETCHAINS_OVERRIDE_PUBKEY33 into vout[1]\n"); } - //printf("autocreate commision vout\n"); } else if ( (uint64_t)(txNew.vout[0].nValue) >= ASSETCHAINS_TIMELOCKGTE) { - fprintf(stderr,"timelocked chains not supported in this code!\n"); + LogPrintf("timelocked chains not supported in this code!\n"); LEAVE_CRITICAL_SECTION(cs_main); LEAVE_CRITICAL_SECTION(mempool.cs); return(0); @@ -702,16 +676,15 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 uint64_t totalsats = hush_notarypay(txNew, NotarizationNotaries, pblock->nTime, nHeight, script, scriptlen); if ( totalsats == 0 ) { - fprintf(stderr, "Could not create notary payment, trying again.\n"); - if ( !isStake ) - { - LEAVE_CRITICAL_SECTION(cs_main); - LEAVE_CRITICAL_SECTION(mempool.cs); - } + LogPrintf("Could not create notary payment, trying again.\n"); + // Release unconditionally to match the unconditional ENTER above. The old + // `if(!isStake)` guard leaked cs_main/mempool.cs on the isStake path (this + // still return(0)s), while the success and timelock paths always release. + LEAVE_CRITICAL_SECTION(cs_main); + LEAVE_CRITICAL_SECTION(mempool.cs); return(0); } - //fprintf(stderr, "Created notary payment coinbase totalsat.%lu\n",totalsats); - } else fprintf(stderr, "vout 2 of notarization is not OP_RETURN scriptlen.%i\n", scriptlen); + } else LogPrintf("vout 2 of notarization is not OP_RETURN scriptlen.%i\n", scriptlen); } if ( ASSETCHAINS_CBOPRET != 0 ) { @@ -719,7 +692,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 txNew.vout.resize(numv+1); txNew.vout[numv].nValue = 0; txNew.vout[numv].scriptPubKey = hush_mineropret(nHeight); - //printf("autocreate commision/cbopret.%lld vout[%d]\n",(long long)ASSETCHAINS_CBOPRET,(int32_t)txNew.vout.size()); } pblock->vtx[0] = txNew; pblocktemplate->vTxFees[0] = -nFees; @@ -752,26 +724,23 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 if ( ASSETCHAINS_CC == 0 && pindexPrev != 0 && (IS_HUSH_NOTARY == 0 || My_notaryid < 0) ) { CValidationState state; - //fprintf(stderr,"%s: check validity\n", __func__); if ( !TestBlockValidity(state, *pblock, pindexPrev, false, false)) // invokes CC checks { - if ( !isStake ) - { - LEAVE_CRITICAL_SECTION(cs_main); - LEAVE_CRITICAL_SECTION(mempool.cs); - } - fprintf(stderr,"%s: TestBlockValidity failed!\n", __func__); + // Release unconditionally to match the unconditional ENTER above. The old + // `if(!isStake)` guard leaked cs_main/mempool.cs on the isStake path (this + // still return(0)s), while the success and timelock paths always release. + LEAVE_CRITICAL_SECTION(cs_main); + LEAVE_CRITICAL_SECTION(mempool.cs); + LogPrintf("%s: TestBlockValidity failed!\n", __func__); //throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed"); // crashes the node, moved to GetBlockTemplate and issue return. return(0); } - //fprintf(stderr,"valid\n"); } } LEAVE_CRITICAL_SECTION(cs_main); LEAVE_CRITICAL_SECTION(mempool.cs); - // fprintf(stderr,"%s: done\n", __func__); return pblocktemplate.release(); } @@ -781,7 +750,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce) { - //fprintf(stderr,"RandomXMiner: %s with nExtraNonce=%u\n", __func__, nExtraNonce); // Update nExtraNonce static uint256 hashPrevBlock; if (hashPrevBlock != pblock->hashPrevBlock) @@ -805,7 +773,6 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, int32_t gpucount, bool isStake) { CPubKey pubkey; CScript scriptPubKey; uint8_t *script,*ptr; int32_t i,len; - // fprintf(stderr,"%s: with nHeight=%d\n", __func__, nHeight); // Create a local variable instead of modifying the global assetchains_scriptpub auto assetchains_scriptpub = devtax_scriptpub_for_height(nHeight); @@ -815,7 +782,6 @@ CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, { pubkey = ParseHex(ASSETCHAINS_OVERRIDE_PUBKEY); scriptPubKey = CScript() << ParseHex(HexStr(pubkey)) << OP_CHECKSIG; - // fprintf(stderr,"%s: with pubkey=%s\n", __func__, HexStr(pubkey).c_str() ); } else { len = strlen(assetchains_scriptpub.c_str()); len >>= 1; @@ -824,7 +790,6 @@ CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, decode_hex(ptr,len,(char *)assetchains_scriptpub.c_str()); } } else if ( USE_EXTERNAL_PUBKEY != 0 ) { - //fprintf(stderr,"use notary pubkey\n"); pubkey = ParseHex(NOTARY_PUBKEY); scriptPubKey = CScript() << ParseHex(HexStr(pubkey)) << OP_CHECKSIG; } else { @@ -845,14 +810,13 @@ CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, // scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG; scriptPubKey = GetScriptForDestination(dest); Getscriptaddress(destaddr,scriptPubKey); - fprintf(stderr,"%s: wallet disabled with mineraddress=%s\n", __func__, destaddr); + LogPrintf("%s: wallet disabled with mineraddress=%s\n", __func__, destaddr); } else { return NULL; } } } } - // fprintf(stderr,"%s: calling CreateNewBlock\n", __func__); return CreateNewBlock(pubkey, scriptPubKey, gpucount, isStake); } @@ -866,7 +830,6 @@ void hush_sendmessage(int32_t minpeers,int32_t maxpeers,const char *message,std: continue; if ( numsent < minpeers || (rand() % 10) == 0 ) { - //fprintf(stderr,"pushmessage\n"); pnode->PushMessage(message,payload); if ( numsent++ > maxpeers ) break; @@ -887,16 +850,6 @@ static bool ProcessBlockFound(CBlock* pblock) LOCK(cs_main); if (pblock->hashPrevBlock != chainActive.LastTip()->GetBlockHash()) { - uint256 hash; int32_t i; - hash = pblock->hashPrevBlock; - for (i=31; i>=0; i--) - fprintf(stderr,"%02x",((uint8_t *)&hash)[i]); - fprintf(stderr," <- prev (stale)\n"); - hash = chainActive.LastTip()->GetBlockHash(); - for (i=31; i>=0; i--) - fprintf(stderr,"%02x",((uint8_t *)&hash)[i]); - fprintf(stderr," <- chainTip (stale)\n"); - return error("HushMiner: generated block is stale"); } } @@ -914,7 +867,6 @@ static bool ProcessBlockFound(CBlock* pblock) } } #endif - //fprintf(stderr,"process new block\n"); // Process this block the same as if we had received it from another node CValidationState state; @@ -989,9 +941,7 @@ CBlockIndex *get_chainactive(int32_t height) LOCK(cs_main); return(chainActive[height]); } - // else fprintf(stderr,"get_chainactive height %d > active.%d\n",height,chainActive.Tip()->GetHeight()); } - //fprintf(stderr,"get_chainactive null chainActive.Tip() height %d\n",height); return(0); } @@ -1040,7 +990,7 @@ static void LogProcessMemory(const char* label) { PMC_EX pmc = {}; pmc.cb = sizeof(pmc); if (pfn(GetCurrentProcess(), &pmc, sizeof(pmc))) { - LogPrintf("MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n", + LogPrint("randomx", "MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n", label, pmc.WorkingSetSize / (1024.0 * 1024.0), pmc.PrivateUsage / (1024.0 * 1024.0), @@ -1058,7 +1008,7 @@ static void LogProcessMemory(const char* label) { 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); + LogPrint("randomx", "MemDiag [%s]: %s\n", label, line); } } fclose(f); @@ -1087,7 +1037,7 @@ struct RandomXDatasetManager { 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", + LogPrint("randomx", "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)); @@ -1128,11 +1078,11 @@ struct RandomXDatasetManager { // 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)); + LogPrintf("RandomXDatasetManager: allocated shared dataset (%.2f GB, %lu items)\n", + datasetSize / (1024.0 * 1024.0 * 1024.0), datasetItemCount); + LogPrint("randomx", " - Dataset struct at: %p, memory at: %p\n", (void*)dataset, (void*)datasetMemory); + LogPrint("randomx", " - Item size: %d bytes; expected ~%.2f GB + ~2MB per mining thread\n", + RANDOMX_DATASET_ITEM_SIZE, datasetSize / (1024.0 * 1024.0 * 1024.0)); return true; } @@ -1190,9 +1140,9 @@ struct RandomXDatasetManager { 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", + LogPrint("randomx", "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"); + LogPrint("randomx", " Per-thread overhead: ~2MB scratchpad + ~84KB JIT (dataset NOT copied)\n"); LogProcessMemory("after CreateVM"); } return vm; @@ -1279,13 +1229,11 @@ void static RandomXMiner() randomx_vm *myVM = nullptr; try { - // fprintf(stderr,"RandomXMiner: mining %s with randomx\n",SMART_CHAIN_SYMBOL); rxdebug("%s: mining %s with randomx\n", SMART_CHAIN_SYMBOL); while (true) { - // fprintf(stderr,"RandomXMiner: beginning mining loop on %s with nExtraNonce=%u\n",SMART_CHAIN_SYMBOL, nExtraNonce); rxdebug("%s: start mining loop on %s with nExtraNonce=%u\n", SMART_CHAIN_SYMBOL, nExtraNonce); if (chainparams.MiningRequiresPeers()) { @@ -1303,10 +1251,8 @@ void static RandomXMiner() if (!fvNodesEmpty )//&& !IsInitialBlockDownload()) break; MilliSleep(15000); - //fprintf(stderr,"fvNodesEmpty %d IsInitialBlockDownload(%s) %d\n",(int32_t)fvNodesEmpty,SMART_CHAIN_SYMBOL,(int32_t)IsInitialBlockDownload()); } while (true); - //fprintf(stderr,"%s Found peers\n",SMART_CHAIN_SYMBOL); miningTimer.start(); } @@ -1320,7 +1266,7 @@ void static RandomXMiner() // If we don't have a valid chain tip to work from, wait and try again. if (pindexPrev == nullptr) { - fprintf(stderr,"%s: null pindexPrev, trying again...\n",__func__); + LogPrint("randomx", "%s: null pindexPrev, trying again...\n",__func__); MilliSleep(1000); continue; } @@ -1331,7 +1277,6 @@ void static RandomXMiner() Mining_start = (uint32_t)time(NULL); } - // fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag); rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height); // Update the shared dataset key — only one thread will actually rebuild, // others will see the key is already current and skip. @@ -1364,14 +1309,12 @@ void static RandomXMiner() // Acquire shared lock to prevent dataset rebuild while we're hashing boost::shared_lock 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); #else CBlockTemplate *ptr = CreateNewBlockWithKey(); #endif - // fprintf(stderr,"RandomXMiner: created new block with Mining_start=%u\n",Mining_start); rxdebug("%s: created new block with Mining_start=%u\n",Mining_start); if ( ptr == 0 ) { @@ -1384,11 +1327,10 @@ void static RandomXMiner() } static uint32_t counter; if ( counter++ < 10 ) - fprintf(stderr,"RandomXMiner: created illegal blockB, retry with counter=%u\n", counter); + LogPrint("randomx", "RandomXMiner: created illegal blockB, retry with counter=%u\n", counter); sleep(1); continue; } - // fprintf(stderr,"RandomXMiner: getting block template\n"); rxdebug("%s: getting block template\n"); unique_ptr pblocktemplate(ptr); @@ -1410,14 +1352,13 @@ void static RandomXMiner() { static uint32_t counter; if ( counter++ < 10 ) - fprintf(stderr,"skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL); + LogPrint("randomx", "skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL); sleep(10); continue; - } else fprintf(stderr,"%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT); + } else LogPrint("randomx", "%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT); } rxdebug("%s: incrementing extra nonce\n"); IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); - // fprintf(stderr,"RandomXMiner: %u transactions in block\n",(int32_t)pblock->vtx.size()); LogPrintf("Running HushRandomXMiner with %u transactions in block (%u bytes)\n",pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION)); // Search @@ -1433,12 +1374,11 @@ void static RandomXMiner() while (true) { if ( gotinvalid != 0 ) { - fprintf(stderr,"RandomXMiner: gotinvalid=%d\n",gotinvalid); + LogPrint("randomx", "RandomXMiner: gotinvalid=%d\n",gotinvalid); break; } hush_longestchain(); - // fprintf(stderr,"RandomXMiner: solving with nNonce = %s\n",pblock->nNonce.ToString().c_str()); rxdebug("%s: solving with nNonce = %s\n",pblock->nNonce.ToString().c_str()); arith_uint256 hashTarget; hashTarget = HASHTarget; @@ -1448,8 +1388,6 @@ void static RandomXMiner() // 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"); @@ -1478,7 +1416,6 @@ void static RandomXMiner() rxdebug("%s: Checking solution against target\n"); pblock->nSolution = soln; solutionTargetChecks.increment(); - // fprintf(stderr,"%s: solutionTargetChecks=%lu\n", __func__, solutionTargetChecks.get()); B = *pblock; h = UintToArith256(B.GetHash()); @@ -1508,17 +1445,6 @@ void static RandomXMiner() SetSkipRandomXValidation(false); if ( !fValid ) { - h = UintToArith256(B.GetHash()); - 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]); - 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; @@ -1575,13 +1501,13 @@ void static RandomXMiner() { if ( Mining_height > ASSETCHAINS_MINHEIGHT ) { - fprintf(stderr,"%s: no nodes, break\n", __func__); + LogPrint("randomx", "%s: no nodes, break\n", __func__); break; } } if ((UintToArith256(pblock->nNonce) & 0xffff) == 0xffff) { - fprintf(stderr,"%s: nonce & 0xffff == 0xffff, break\n", __func__); + LogPrint("randomx", "%s: nonce & 0xffff == 0xffff, break\n", __func__); break; } // Update nNonce and nTime @@ -1604,7 +1530,6 @@ void static RandomXMiner() 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 @@ -1613,7 +1538,7 @@ void static RandomXMiner() } catch (const std::runtime_error &e) { miningTimer.stop(); c.disconnect(); - fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what()); + LogPrintf("RandomXMiner: runtime error: %s\n", e.what()); if (myVM != nullptr) { randomx_destroy_vm(myVM); @@ -1672,7 +1597,7 @@ void static BitcoinMiner() assert(solver == "tromp" || solver == "default"); LogPrint("pow", "Using Equihash solver \"%s\" with n = %u, k = %u\n", solver, n, k); if ( SMART_CHAIN_SYMBOL[0] != 0 ) - fprintf(stderr,"notaryid.%d Mining.%s with %s\n",notaryid,SMART_CHAIN_SYMBOL,solver.c_str()); + LogPrintf("notaryid.%d Mining.%s with %s\n",notaryid,SMART_CHAIN_SYMBOL,solver.c_str()); std::mutex m_cs; bool cancelSolver = false; boost::signals2::connection c = uiInterface.NotifyBlockTip.connect( @@ -1685,7 +1610,7 @@ void static BitcoinMiner() try { if ( SMART_CHAIN_SYMBOL[0] != 0 ) - fprintf(stderr,"try %s Mining with %s\n",SMART_CHAIN_SYMBOL,solver.c_str()); + LogPrintf("try %s Mining with %s\n",SMART_CHAIN_SYMBOL,solver.c_str()); while (true) { if (chainparams.MiningRequiresPeers()) { @@ -1703,10 +1628,8 @@ void static BitcoinMiner() if (!fvNodesEmpty )//&& !IsInitialBlockDownload()) break; MilliSleep(15000); - //fprintf(stderr,"fvNodesEmpty %d IsInitialBlockDownload(%s) %d\n",(int32_t)fvNodesEmpty,SMART_CHAIN_SYMBOL,(int32_t)IsInitialBlockDownload()); } while (true); - //fprintf(stderr,"%s Found peers\n",SMART_CHAIN_SYMBOL); miningTimer.start(); } // @@ -1717,7 +1640,7 @@ void static BitcoinMiner() // If we don't have a valid chain tip to work from, wait and try again. if (pindexPrev == nullptr) { - fprintf(stderr,"%s: null pindexPrev, trying again...\n",__func__); + LogPrint("pow", "%s: null pindexPrev, trying again...\n",__func__); MilliSleep(1000); continue; } @@ -1729,7 +1652,6 @@ void static BitcoinMiner() } if ( SMART_CHAIN_SYMBOL[0] != 0 && ASSETCHAINS_STAKED == 0 ) { - //fprintf(stderr,"%s create new block ht.%d\n",SMART_CHAIN_SYMBOL,Mining_height); //sleep(3); } @@ -1750,11 +1672,10 @@ void static BitcoinMiner() } static uint32_t counter; if ( counter++ < 10 && ASSETCHAINS_STAKED == 0 ) - fprintf(stderr,"created illegal blockB, retry\n"); + LogPrint("pow", "created illegal blockB, retry\n"); sleep(1); continue; } - //fprintf(stderr,"get template\n"); unique_ptr pblocktemplate(ptr); if (!pblocktemplate.get()) { @@ -1775,14 +1696,13 @@ void static BitcoinMiner() { static uint32_t counter; if ( counter++ < 10 ) - fprintf(stderr,"skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL); + LogPrint("pow", "skip generating %s on-demand block, no tx avail\n",SMART_CHAIN_SYMBOL); sleep(10); continue; - } else fprintf(stderr,"%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT); + } else LogPrint("pow", "%s vouts.%d mining.%d vs %d\n",SMART_CHAIN_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT); } } IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); - //fprintf(stderr,"Running HushMiner.%s with %u transactions in block\n",solver.c_str(),(int32_t)pblock->vtx.size()); LogPrintf("Running HushMiner.%s with %u transactions in block (%u bytes)\n",solver.c_str(),pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION)); // Search @@ -1798,7 +1718,6 @@ void static BitcoinMiner() gotinvalid = 0; while (true) { - //fprintf(stderr,"gotinvalid.%d\n",gotinvalid); if ( gotinvalid != 0 ) break; hush_longestchain(); @@ -1823,7 +1742,6 @@ void static BitcoinMiner() if ( HUSH_MININGTHREADS > 0 && ASSETCHAINS_STAKED > 0 && ASSETCHAINS_STAKED < 100 && Mining_height > 10 ) hashTarget = HASHTarget_POW; //else if ( ASSETCHAINS_ADAPTIVEPOW > 0 ) - // hashTarget = HASHTarget_POW; else hashTarget = HASHTarget; std::function)> validBlock = #ifdef ENABLE_WALLET @@ -1837,7 +1755,6 @@ void static BitcoinMiner() LogPrint("pow", "- Checking solution against target\n"); pblock->nSolution = soln; solutionTargetChecks.increment(); - // fprintf(stderr, "%s: solutionTargetChecks=%lu\n", __func__, solutionTargetChecks.get()); B = *pblock; h = UintToArith256(B.GetHash()); /*for (z=31; z>=16; z--) @@ -1857,13 +1774,12 @@ void static BitcoinMiner() } if ( IS_HUSH_NOTARY != 0 && B.nTime > GetTime() ) { - //fprintf(stderr,"need to wait %d seconds to submit block\n",(int32_t)(B.nTime - GetTime())); while ( GetTime() < B.nTime-2 ) { sleep(1); if ( chainActive.LastTip()->GetHeight() >= Mining_height ) { - fprintf(stderr,"new block arrived\n"); + LogPrint("pow", "new block arrived\n"); return(false); } } @@ -1877,13 +1793,6 @@ void static BitcoinMiner() MilliSleep((rand() % (r * 1000)) + 1000); } } - else - { - uint256 tmp = B.GetHash(); - int32_t z; for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&tmp)[z]); - fprintf(stderr," mined %s block %d!\n",SMART_CHAIN_SYMBOL,Mining_height); - } CValidationState state; //{ LOCK(cs_main); @@ -1891,8 +1800,6 @@ void static BitcoinMiner() { h = UintToArith256(B.GetHash()); //for (z=31; z>=0; z--) - // fprintf(stderr,"%02x",((uint8_t *)&h)[z]); - //fprintf(stderr," Invalid block mined, try again\n"); gotinvalid = 1; return(false); } @@ -1967,8 +1874,6 @@ void static BitcoinMiner() if (found) { int32_t i; uint256 hash = pblock->GetHash(); //for (i=0; i<32; i++) - // fprintf(stderr,"%02x",((uint8_t *)&hash)[i]); - //fprintf(stderr," <- %s Block found %d\n",SMART_CHAIN_SYMBOL,Mining_height); //FOUND_BLOCK = 1; //HUSH_MAYBEMINED = Mining_height; break; @@ -1993,14 +1898,14 @@ void static BitcoinMiner() { if ( SMART_CHAIN_SYMBOL[0] == 0 || Mining_height > ASSETCHAINS_MINHEIGHT ) { - fprintf(stderr,"no nodes, break\n"); + LogPrint("pow", "no nodes, break\n"); break; } } if ((UintToArith256(pblock->nNonce) & 0xffff) == 0xffff) { //if ( 0 && SMART_CHAIN_SYMBOL[0] != 0 ) - fprintf(stderr,"0xffff, break\n"); + LogPrint("pow", "0xffff, break\n"); break; } if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60) @@ -2024,7 +1929,6 @@ void static BitcoinMiner() HASHTarget.SetCompact(pblock->nBits); hashTarget = HASHTarget; savebits = pblock->nBits; - //hashTarget = HASHTarget_POW = hush_adaptivepow_target(Mining_height,HASHTarget,pblock->nTime); } /*if ( NOTARY_PUBKEY33[0] == 0 ) { @@ -2104,7 +2008,6 @@ void static BitcoinMiner() 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; diff --git a/src/net.cpp b/src/net.cpp index c6ddb54a8..2aab1dfcd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -56,7 +56,7 @@ extern uint8_t ASSETCHAINS_CLEARNET; // Run asmap health check every 24hr by default #define ASMAP_HEALTHCHECK_INTERVAL 24*60*60 -// This is every 2 blocks, on avg, on HUSH3 +// Interval (seconds) between zindex stat dumps when -zindex is enabled. #define DUMP_ZINDEX_INTERVAL 150 #define CHECK_PLZ_STOP_INTERVAL 120 @@ -79,7 +79,9 @@ extern uint8_t ASSETCHAINS_CLEARNET; // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 -#define USE_TLS "encrypted as fuck" +// Marker macro that enables the TLS p2p transport. Only its definedness is +// ever tested (via defined()/#ifdef); the string value itself is never used. +#define USE_TLS "enabled" #if defined(USE_TLS) && !defined(TLS1_3_VERSION) // minimum secure protocol is 1.3 @@ -468,12 +470,20 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest) { bool connected = false; std::unique_ptr sock; - if (!addrConnect.IsValid()) { - return NULL; - } + // When connecting by name (pszDest is set, e.g. -connect / -addnode host:port + // or "addnode onetry"), addrConnect is an empty placeholder — the real + // target is resolved from pszDest by ConnectSocketByName() below. Only validate + // addrConnect when we are dialing it directly (pszDest == NULL); otherwise + // IsValid()/IsReachable() on the empty address abort the connection before it is + // ever attempted, which silently breaks -connect. + if (!pszDest) { + if (!addrConnect.IsValid()) { + return NULL; + } - if (!IsReachable(addrConnect)) { - return NULL; + if (!IsReachable(addrConnect)) { + return NULL; + } } if (addrConnect.GetNetwork() == NET_I2P && m_i2p_sam_session.get() != nullptr) { @@ -614,7 +624,7 @@ void DumpBanlist() if (bandb.Write(banmap)) { SetBannedSetDirty(false); } - fprintf(stderr,"%s: Dumping banlist with %lu items\n", __func__, banmap.size()); + LogPrint("net", "%s: Dumping banlist with %lu items\n", __func__, banmap.size()); LogPrint("net", "Flushed %d banned node ips/subnets to banlist.dat %dms\n", banmap.size(), GetTimeMillis() - nStart); @@ -642,7 +652,7 @@ bool CNode::IsBanned(CNetAddr ip) CBanEntry banEntry = (*it).second; if(subNet.Match(ip) && GetTime() < banEntry.nBanUntil) { - fprintf(stderr,"%s: found banned subnet %s\n", __func__, subNet.ToString().c_str()); + LogPrint("net", "%s: found banned subnet %s\n", __func__, subNet.ToString().c_str()); fResult = true; } } @@ -676,7 +686,7 @@ void CNode::Ban(const CSubNet& subNet, const BanReason &banReason, int64_t banti if (bantimeoffset > 0) banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset; - fprintf(stderr, "%s: banning %s until %ld with bantimeoffset=%ld sinceUnixEpoch=%d\n", __func__, subNet.ToString().c_str(), banEntry.nBanUntil, bantimeoffset, sinceUnixEpoch); + LogPrint("net", "%s: banning %s until %ld with bantimeoffset=%ld sinceUnixEpoch=%d\n", __func__, subNet.ToString().c_str(), banEntry.nBanUntil, bantimeoffset, sinceUnixEpoch); { LOCK(cs_setBanned); if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) { @@ -689,14 +699,15 @@ void CNode::Ban(const CSubNet& subNet, const BanReason &banReason, int64_t banti { LOCK(cs_vNodes); for (CNode* pnode : vNodes) { - if (subNet.Match(static_cast(pnode->addr))) - fprintf(stderr, "%s: disconnecting from banned node %s\n", __func__, pnode->addr.ToString().c_str() ); + if (subNet.Match(static_cast(pnode->addr))) { + LogPrint("net", "%s: disconnecting from banned node %s\n", __func__, pnode->addr.ToString().c_str() ); pnode->fDisconnect = true; + } } } if(banReason == BanReasonManuallyAdded) { - fprintf(stderr,"%s: dumping banlist after manual ban\n", __func__); + LogPrint("net", "%s: dumping banlist after manual ban\n", __func__); DumpBanlist(); //store banlist to disk immediately if user requested ban } } @@ -815,7 +826,7 @@ void CNode::copyStats(CNodeStats &stats, const std::vector &m_asmap) nPingUsecWait = GetTimeMicros() - nPingUsecStart; } - // Raw ping time is in microseconds, but show it to user as whole seconds (Hush users should be well used to small numbers with many decimal places by now :) + // Raw ping time is in microseconds; convert to seconds for display to the user. stats.dPingTime = (((double)nPingUsecTime) / 1e6); stats.dMinPing = (((double)nMinPingUsecTime) / 1e6); stats.dPingWait = (((double)nPingUsecWait) / 1e6); @@ -1581,6 +1592,7 @@ void ThreadDNSAddressSeed() CAddress addr = CAddress(CService(ip, ASSETCHAINS_P2PPORT)); addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old vAdd.push_back(addr); + found++; } } // TODO: The seed name resolve may fail, yielding an IP of [::], which results in @@ -1721,7 +1733,6 @@ void ThreadOpenConnections() boost::this_thread::interruption_point(); // Add seed nodes if DNS seeds are all down (an infrastructure attack?). - // if (addrman.size() == 0 && (GetTime() - nStart > 60)) { if (GetTime() - nStart > 60) { static bool done = false; if (!done) { @@ -1851,7 +1862,6 @@ void ThreadOpenConnections() int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000); MilliSleep(randsleep); LogPrint("net", "Making feeler connection to %s\n", addrConnect.ToString().c_str()); - printf("%s: Making feeler connection to %s\n", __func__, addrConnect.ToString().c_str()); } //int failures = setConnected.size() >= std::min(nMaxConnections - 1, 2); @@ -2510,22 +2520,24 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss) // If we have no nodes to relay to, there is nothing to do if(vNodes.size() == 0) { if (HUSH_TESTNODE==0) { - fprintf(stderr, "%s: No nodes to relay to!\n", __func__ ); + LogPrint("net", "%s: No nodes to relay to!\n", __func__ ); } return; } - // We always round down, except when we have only 1 connection + // Relay to half of our peers, rounding down, but never fewer than 1. + // Equivalent to max(1, vNodes.size()/2): the ternary picks 1 only when the + // integer division vNodes.size()/2 is 0 (i.e. exactly 1 connection). auto newSize = (vNodes.size() / 2) == 0 ? 1 : (vNodes.size() / 2); std::shuffle( vRelayNodes.begin(), vRelayNodes.end(), std::mt19937(GetRand(std::numeric_limits::max())) ); vRelayNodes.resize(newSize); if (HUSH_TESTNODE==1 && vNodes.size() == 0) { - fprintf(stderr, "%s: -testnode=1, no peers, not relaying\n", __func__ ); + LogPrint("net", "%s: -testnode=1, no peers, not relaying\n", __func__ ); return; } else { - fprintf(stderr, "%s: Relaying %s to %lu of %lu peers\n", __func__, tx.GetHash().GetHex().c_str(), newSize, vNodes.size() ); + LogPrint("net", "%s: Relaying %s to %lu of %lu peers\n", __func__, tx.GetHash().GetHex().c_str(), newSize, vNodes.size() ); } // Only relay to randomly chosen 50% of peers @@ -2774,7 +2786,7 @@ bool CNode::GetTlsValidate() { if (tlsValidate == eTlsOption::FALLBACK_UNSET) { - // This is useful for private Hush Arrakis Chains, that want to exist + // This is useful for private DragonX-based chains that want to exist // on a closed VPN with an internal CA or trusted cert system, or // various other use cases if ( GetBoolArg("-tlsvalidate", false)) { diff --git a/src/net.h b/src/net.h index fdc1984ce..2627e071a 100644 --- a/src/net.h +++ b/src/net.h @@ -44,9 +44,12 @@ #include #include #include -// Enable WolfSSL Support for Hush +// Enable WolfSSL support for DragonX #include -// TODO: these are not set correctly by wolfssl for some reason. Ja bless. +// Force-enable wolfSSL's constant-time (timing-resistant) ECC and TFM code paths. +// These are feature-enable macros that wolfSSL checks with #ifdef, so the numeric +// value is immaterial to behavior; the value 420 is arbitrary and must simply be +// non-empty. Redefined here because wolfssl/options.h does not reliably set them. #undef ECC_TIMING_RESISTANT #undef TFM_TIMING_RESISTANT #define ECC_TIMING_RESISTANT 420 diff --git a/src/pow.cpp b/src/pow.cpp index 1daf60e61..a7802b0f3 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -97,75 +97,13 @@ bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past); #define T ASSETCHAINS_BLOCKTIME #define K ((int64_t)1000000) -#ifdef original_algo -arith_uint256 oldRT_CST_RST(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t numerator,int32_t denominator,int32_t W,int32_t past) +// The proof-of-work limit for the active algorithm: Equihash chains use params.powLimit, +// everything else (DragonX = RandomX) uses params.powAlternate. Shared by the retarget +// functions below, where this selection was previously copy-pasted as an if/else. +static arith_uint256 PowLimitForAlgo(const Consensus::Params& params) { - //if (ts.size() < 2*W || ct.size() < 2*W ) { exit; } // error. a vector was too small - //if (ts.size() < past+W || ct.size() < past+W ) { past = min(ct.size(), ts.size()) - W; } // past was too small, adjust - int64_t altK; int32_t i,j,k,ii=0; // K is a scaling factor for integer divisions - if ( height < 64 ) - return(bnTarget); - //if ( ((ts[0]-ts[W]) * W * 100)/(W-1) < (T * numerator * 100)/denominator ) - if ( (ts[0] - ts[W]) < (T * numerator)/denominator ) - { - //bnTarget = ((ct[0]-ct[1])/K) * max(K,(K*(nTime-ts[0])*(ts[0]-ts[W])*denominator/numerator)/T/T); - bnTarget = ct[0] / arith_uint256(K); - //altK = (K * (nTime-ts[0]) * (ts[0]-ts[W]) * denominator * W) / (numerator * (W-1) * (T * T)); - altK = (K * (nTime-ts[0]) * (ts[0]-ts[W]) * denominator) / (numerator * (T * T)); - fprintf(stderr,"ht.%d initial altK.%lld %d * %d * %d / %d\n",height,(long long)altK,(nTime-ts[0]),(ts[0]-ts[W]),denominator,numerator); - if ( altK > K ) - altK = K; - bnTarget *= arith_uint256(altK); - if ( altK < K ) - return(bnTarget); - } - /* Check past 24 blocks for any sum of 3 STs < T/2 triggers. This is messy - because the blockchain does not allow us to store a variable to know - if we are currently in a triggered state that is making a sequence of - adjustments to prevTargets, so we have to look for them. - Nested loops do this: if block emission has not slowed to be back on track at - any time since most recent trigger and we are at current block, aggressively - adust prevTarget. */ - - for (j=past-1; j>=2; j--) - { - if ( ts[j]-ts[j+W] < T*numerator/denominator ) - { - ii = 0; - for (i=j-2; i>=0; i--) - { - ii++; - // Check if emission caught up. If yes, "trigger stopped at i". - // Break loop to try more recent j's to see if trigger activates again. - if ( (ts[i] - ts[j+W]) > (ii+W)*T ) - break; - - // We're here, so there was a TS[j]-TS[j-3] < T/2 trigger in the past and emission rate has not yet slowed up to be back on track so the "trigger is still active", aggressively adjusting target here at block "i" - if ( i == 0 ) - { - /* We made it all the way to current block. Emission rate since - last trigger never slowed enough to get back on track, so adjust again. - If avg last 3 STs = T, this increases target to prevTarget as ST increases to T. - This biases it towards ST=~1.75*T to get emission back on track. - If avg last 3 STs = T/2, target increases to prevTarget at 2*T. - Rarely, last 3 STs can be 1/2 speed => target = prevTarget at T/2, & 1/2 at T.*/ - - //bnTarget = ((ct[0]-ct[W])/W/K) * (K*(nTime-ts[0])*(ts[0]-ts[W]))/W/T/T; - bnTarget = ct[0]; - for (k=1; k mintarget ) bnTarget = mintarget; - { - int32_t z; - for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[z]); - } - fprintf(stderr," ht.%d initial W.%d outerK.%lld %d * %d * %d / %d\n",height,W,(long long)outerK,(nTime-ts[0]),(ts[0]-ts[W]),denominator,numerator); - } //else fprintf(stderr,"ht.%d no outer trigger %d >= %d\n",height,(ts[0] - ts[W]),(T * numerator)/denominator); + } return(bnTarget); } @@ -202,13 +134,6 @@ arith_uint256 RT_CST_RST_target(int32_t height,uint32_t nTime,arith_uint256 bnTa bnTarget /= arith_uint256(width * K); innerK = (K * (nTime-ts[0]) * (ts[0]-ts[width])) / (width * T * T); bnTarget *= arith_uint256(innerK); - if ( 0 ) - { - int32_t z; - for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[z]); - fprintf(stderr," ht.%d innerK %lld (%d * %d) %u - %u width.%d\n",height,(long long)innerK,(nTime-ts[0]),(ts[0]-ts[width]),ts[0],ts[width],width); - } return(bnTarget); } @@ -223,12 +148,6 @@ arith_uint256 RT_CST_RST_inner(int32_t height,uint32_t nTime,arith_uint256 bnTar bnTarget = RT_CST_RST_target(height,nTime,bnTarget,ts,ct,W); if ( bnTarget == origtarget ) // force zawyflag to 1 bnTarget = mintarget; - { - int32_t z; - for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[z]); - } - fprintf(stderr," height.%d O.%-2d, W.%-2d width.%-2d %4d vs %-4d, deficit %4d tip.%d\n",height,outeri,W,width,(ts[0] - ts[width]),expected,expected - (ts[0] - ts[width]),nTime-ts[0]); } return(bnTarget); } @@ -288,31 +207,19 @@ arith_uint256 zawy_TSA_EMA(int32_t height,int32_t tipdiff,arith_uint256 prevTarg B = (bnTarget / arith_uint256(360000)) * arith_uint256(tipdiff * zawy_exponential_val360000(tipdiff/2)); C = (bnTarget / arith_uint256(360000)) * arith_uint256(T * zawy_exponential_val360000(tipdiff/2)); bnTarget = ((A + B - C) / arith_uint256(tipdiff)) * arith_uint256(K*T); - { - int32_t z; - for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[z]); - } - fprintf(stderr," ht.%d TSA bnTarget tipdiff.%d\n",height,tipdiff); return(bnTarget); } unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) { - if (pindexLast->GetHeight() == 340000) { - LogPrintf("%s: Using blocktime=%d\n",__func__,ASSETCHAINS_BLOCKTIME); - } //if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH && ASSETCHAINS_STAKED == 0) if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH && ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX) { - fprintf(stderr,"%s: using lwma for next work\n",__func__); + LogPrint("pow","%s: using lwma for next work\n",__func__); return lwmaGetNextWorkRequired(pindexLast, pblock, params); } arith_uint256 bnLimit; - if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) - bnLimit = UintToArith256(params.powLimit); - else - bnLimit = UintToArith256(params.powAlternate); + bnLimit = PowLimitForAlgo(params); unsigned int nProofOfWorkLimit = bnLimit.GetCompact(); // Genesis block if (pindexLast == NULL ) @@ -386,13 +293,11 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead { blocktime = pindexFirst->nTime; diff = (pblock->nTime - blocktime); - //fprintf(stderr,"%d ",diff); if ( i < 6 ) { diff -= (8+i)*ASSETCHAINS_BLOCKTIME; if ( diff > mult ) { - //fprintf(stderr,"i.%d diff.%d (%u - %u - %dx)\n",i,(int32_t)diff,pblock->nTime,pindexFirst->nTime,(8+i)); mult = diff; } } @@ -402,7 +307,6 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead bnTot += bnTmp; pindexFirst = pindexFirst->pprev; } - //fprintf(stderr,"diffs %d\n",height); // Check we have enough blocks if (pindexFirst == NULL) return nProofOfWorkLimit; @@ -499,21 +403,9 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead if ( bnTarget < origtarget || bnTarget > easy ) { bnTarget = easy; - fprintf(stderr,"cmp.%d mult.%d ht.%d -> easy target\n",mult>1,(int32_t)mult,height); + LogPrint("pow","cmp.%d mult.%d ht.%d -> easy target\n",mult>1,(int32_t)mult,height); return(HUSH_MINDIFF_NBITS & (~3)); } - { - int32_t z; - for (z=31; z>=0; z--) - fprintf(stderr,"%02x",((uint8_t *)&bnTarget)[z]); - } - fprintf(stderr," exp() to the rescue cmp.%d mult.%d for ht.%d\n",mult>1,(int32_t)mult,height); - } - if ( 0 && zflags[0] == 0 && zawyflag == 0 && mult <= 1 ) - { - bnTarget = zawy_TSA_EMA(height,tipdiff,(bnTarget+ct[0]+ct[1])/arith_uint256(3),ts[0] - ts[1]); - if ( bnTarget < origtarget ) - zawyflag = 3; } } nbits = bnTarget.GetCompact(); @@ -527,7 +419,10 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead // Changing this requires changing many other things and // might change consensus. Have fun -- Duke -// NOTE: Ony HUSH3 mainnet should use this function, all HAC's should use params.AveragigWindowTimespan() +// NOTE: This hardcoded AWT is legacy from the original HUSH3 mainnet. On DragonX the +// CalculateNextWorkRequired strncmp(SMART_CHAIN_SYMBOL,"HUSH3",...) check is never true +// (SMART_CHAIN_SYMBOL is "DRAGONX"), so this function is dead here and the params-derived +// AveragingWindowTimespan() is used instead. Kept as-is to avoid a consensus change. int64_t AveragingWindowTimespan() { // used in const methods, beware! // This is the correct AWT for 75s blocktime, before block 340k @@ -545,8 +440,11 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg, int64_t nActualTimespan = nLastBlockTime - nFirstBlockTime; LogPrint("pow", " nActualTimespan = %d before dampening\n", nActualTimespan); + // Legacy branch: the original HUSH3 mainnet used the hardcoded AveragingWindowTimespan() + // above; every other chain uses the params-derived value. On DragonX the symbol is + // "DRAGONX", so this comparison is always false and the params value is used. The check is + // kept (rather than removed) because it is part of consensus difficulty calculation. bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false; - // If this is HUSH3, use AWT function defined above, else use the one in params int64_t AWT = ishush3 ? AveragingWindowTimespan() : params.AveragingWindowTimespan(); nActualTimespan = AWT + (nActualTimespan - AWT)/4; @@ -568,10 +466,7 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg, } // Retarget arith_uint256 bnLimit; - if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) - bnLimit = UintToArith256(params.powLimit); - else - bnLimit = UintToArith256(params.powAlternate); + bnLimit = PowLimitForAlgo(params); const arith_uint256 bnPowLimit = bnLimit; //UintToArith256(params.powLimit); arith_uint256 bnNew {bnAvg}; @@ -594,8 +489,9 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg, return bnNew.GetCompact(); } -// HUSH does not use these functions but Hush Arrakis Chains can opt-in to using more bleeding edge DAA's -// ASIC chains do not need these protections as much -- Duke Leto +// These LWMA difficulty functions are inherited from the Hush lineage and are only used when +// ASSETCHAINS_ALGO is neither Equihash nor RandomX (see the dispatch in GetNextWorkRequired). +// DragonX uses RandomX, so this LWMA path is not on DragonX's active difficulty codepath. unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) { return lwmaCalculateNextWorkRequired(pindexLast, params); @@ -604,14 +500,10 @@ unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlock unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params) { arith_uint256 nextTarget {0}, sumTarget {0}, bnTmp, bnLimit; - if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) - bnLimit = UintToArith256(params.powLimit); - else - bnLimit = UintToArith256(params.powAlternate); + bnLimit = PowLimitForAlgo(params); unsigned int nProofOfWorkLimit = bnLimit.GetCompact(); - - //printf("PoWLimit: %u\n", nProofOfWorkLimit); + // Find the first block in the averaging interval as we total the linearly weighted average const CBlockIndex* pindexFirst = pindexLast; const CBlockIndex* pindexNext; @@ -872,14 +764,6 @@ bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height) snprintf(buf, sizeof(buf), "%02x", pblock->nSolution[i]); solutionHex += buf; } - fprintf(stderr, "CheckRandomXSolution(): HASH MISMATCH at height %d\n", height); - fprintf(stderr, " computed : %s\n", computedHex.c_str()); - fprintf(stderr, " nSolution: %s\n", solutionHex.c_str()); - fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n", - rxKey.size(), ssInput.size(), pblock->nNonce.ToString().c_str()); - fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n", - pblock->nSolution.size(), RANDOMX_HASH_SIZE); - // Also log to debug.log LogPrintf("CheckRandomXSolution(): HASH MISMATCH at height %d\n", height); LogPrintf(" computed : %s\n", computedHex); LogPrintf(" nSolution: %s\n", solutionHex); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 132362a88..3daf6738e 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -42,6 +42,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getaddednodeinfo", 0 }, { "setgenerate", 0 }, { "setgenerate", 1 }, + { "stratummine", 1 }, // port + { "stratummine", 3 }, // timeout { "generate", 0 }, { "getnetworkhashps", 0 }, { "getnetworkhashps", 1 }, @@ -50,8 +52,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 1 }, { "sendtoaddress", 4 }, { "settxfee", 0 }, - { "getnotarysendmany", 0 }, - { "getnotarysendmany", 1 }, { "getreceivedbyaddress", 1 }, { "getreceivedbyaccount", 1 }, { "listreceivedbyaddress", 0 }, @@ -171,7 +171,6 @@ static const CRPCConvertParam vRPCConvertParams[] = // crosschain { "assetchainproof", 1}, - { "crosschainproof", 1}, { "getproofroot", 2}, { "getNotarizationsForBlock", 0}, { "height_MoM", 1}, diff --git a/src/rpc/crosschain.cpp b/src/rpc/crosschain.cpp index ef6d06db4..1e706130c 100644 --- a/src/rpc/crosschain.cpp +++ b/src/rpc/crosschain.cpp @@ -78,14 +78,6 @@ UniValue assetchainproof(const UniValue& params, bool fHelp, const CPubKey& mypk } -UniValue crosschainproof(const UniValue& params, bool fHelp, const CPubKey& mypk) -{ - UniValue ret(UniValue::VOBJ); - //fprintf(stderr,"crosschainproof needs to be implemented\n"); - return(ret); -} - - UniValue height_MoM(const UniValue& params, bool fHelp, const CPubKey& mypk) { int32_t height,depth,notarized_height,MoMoMdepth,MoMoMoffset,hushstarti,hushendi; uint256 MoM,MoMoM,hushtxid; uint32_t timestamp = 0; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 36fd5a473..0d9f15674 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -45,6 +45,21 @@ #include +#include "compat/byteswap.h" // bswap_32 for the stratum wire fields (version/time/bits) +#ifndef WIN32 +// stratummine (below) is a POSIX-only reference RandomX stratum miner used to exercise the pool +// path end-to-end. It reuses DragonX's own RandomX + GetRandomXInput so its hash is byte-identical +// to CheckRandomXSolution. Not built on Windows (raw POSIX sockets). +#include "RandomX/src/randomx.h" +#include +#include +#include +#include +#include +#include +#include +#endif + using namespace std; #include "hush_defs.h" @@ -363,7 +378,7 @@ UniValue setgenerate(const UniValue& params, bool fHelp, const CPubKey& mypk) } HUSH_MININGTHREADS = (int32_t)nGenProcLimit; - fprintf(stderr,"%s:HUSH_MININGTHREADS=%d\n", __FUNCTION__, HUSH_MININGTHREADS); + LogPrint("mining","%s:HUSH_MININGTHREADS=%d\n", __FUNCTION__, HUSH_MININGTHREADS); mapArgs["-gen"] = (fGenerate ? "1" : "0"); mapArgs ["-genproclimit"] = itostr(HUSH_MININGTHREADS); @@ -460,15 +475,8 @@ UniValue getmininginfo(const UniValue& params, bool fHelp, const CPubKey& mypk) obj.push_back(Pair("difficulty", (double)GetNetworkDifficulty())); obj.push_back(Pair("errors", GetWarnings("statusbar"))); obj.push_back(Pair("genproclimit", (int)GetArg("-genproclimit", -1))); - if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) - { - obj.push_back(Pair("localsolps" , getlocalsolps(params, false, mypk))); - obj.push_back(Pair("networksolps", getnetworksolps(params, false, mypk))); - } - else - { - obj.push_back(Pair("localhashps" , GetBoolArg("-gen", false) ? getlocalsolps(params, false, mypk) : (double)0.0)); - } + // DragonX is RandomX-only; the Equihash sol/s reporting path was removed. + obj.push_back(Pair("localhashps" , GetBoolArg("-gen", false) ? getlocalsolps(params, false, mypk) : (double)0.0)); obj.push_back(Pair("networkhashps", getnetworksolps(params, false, mypk))); obj.push_back(Pair("pooledtx", (uint64_t)mempool.size())); obj.push_back(Pair("testnet", Params().TestnetToBeDeprecatedFieldRPC())); @@ -854,7 +862,6 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp, const CPubKey& myp result.push_back(Pair("bits", strprintf("%08x", pblock->nBits))); result.push_back(Pair("height", (int64_t)(pindexPrev->GetHeight()+1))); - //fprintf(stderr,"return complete template\n"); return result; } @@ -905,7 +912,6 @@ UniValue submitblock(const UniValue& params, bool fHelp, const CPubKey& mypk) ); CBlock block; - //LogPrintStr("Hex block submission: " + params[0].get_str()); if (!DecodeHexBlk(block, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); @@ -931,7 +937,6 @@ UniValue submitblock(const UniValue& params, bool fHelp, const CPubKey& mypk) CValidationState state; submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); - //printf("submitblock, height=%d, coinbase sequence: %d, scriptSig: %s\n", chainActive.LastTip()->GetHeight()+1, block.vtx[0].vin[0].nSequence, block.vtx[0].vin[0].scriptSig.ToString().c_str()); bool fAccepted = ProcessNewBlock(1,chainActive.LastTip()->GetHeight()+1,state, NULL, &block, true, NULL); UnregisterValidationInterface(&sc); if (fBlockPresent) @@ -1063,9 +1068,292 @@ UniValue getblocksubsidy(const UniValue& params, bool fHelp, const CPubKey& mypk } +#ifndef WIN32 +extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_RANDOMX; // hush_defs.h — active PoW algorithm + +// Send one newline-terminated JSON line on a blocking socket. +static bool StratumMinerSend(int fd, const std::string& s) +{ + std::string line = s; + if (line.empty() || line.back() != '\n') line += '\n'; + size_t off = 0; + while (off < line.size()) { + ssize_t n = send(fd, line.data() + off, line.size() - off, 0); + if (n <= 0) return false; + off += (size_t)n; + } + return true; +} + +// Wait up to timeout_ms for data, then split all completed lines out of buf into out. +// Returns false only on socket error/close (a timeout with no data is success with out empty). +static bool StratumMinerRecvLines(int fd, std::string& buf, int timeout_ms, std::vector& out) +{ + fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds); + struct timeval tv; tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; + int r = select(fd + 1, &rfds, NULL, NULL, &tv); + if (r < 0) return false; + if (r == 0) return true; + char tmp[8192]; + ssize_t n = recv(fd, tmp, sizeof(tmp), 0); + if (n <= 0) return false; + buf.append(tmp, tmp + n); + size_t pos; + while ((pos = buf.find('\n')) != std::string::npos) { + std::string line = buf.substr(0, pos); + buf.erase(0, pos + 1); + if (!line.empty() && line.back() == '\r') line.pop_back(); + if (!line.empty()) out.push_back(line); + } + return true; +} + +// Reference RandomX stratum miner (test utility): connect to a DragonX stratum server, subscribe + +// authorize, receive work + the per-height RandomX key, then vary the block nNonce, hash with +// RandomX (byte-identical to CheckRandomXSolution via GetRandomXInput), and submit a 32-byte +// solution when the block hash meets target. Exists to validate the -stratum RandomX pool path. +//! Upper bound on how long stratummine will hold an RPC worker thread (and a 256 MB RandomX +//! cache). An unbounded caller-supplied deadline pins both indefinitely. +static const int64_t MAX_STRATUMMINE_TIMEOUT = 3600; + +//! Parse a 64-char hex field from mining.notify into a uint256. +//! These fields come from whatever host the operator pointed us at, and both primitives below are +//! unforgiving: uint256's vector constructor asserts on a wrong-size input (uint256.cpp:30, and +//! NDEBUG is never defined for this build so the assert is live in release), while ParseHex +//! silently truncates at the first non-hex character. A short or garbled field would therefore +//! abort the daemon rather than be rejected. Returns false instead. +static bool StratumHex256(const std::string& hex, uint256& out) +{ + if (hex.size() != 64 || !IsHex(hex)) + return false; + std::vector v = ParseHex(hex); + if (v.size() != 32) + return false; + out = uint256(v); + return true; +} + +UniValue stratummine(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (fHelp || params.size() < 2 || params.size() > 4) + throw runtime_error( + "stratummine \"host\" port ( \"address\" timeout )\n" + "\nReference RandomX stratum miner: connect to a DragonX stratum server, solve RandomX,\n" + "and submit until one share/block is accepted or the timeout elapses. For testing -stratum.\n" + "\nArguments:\n" + "1. \"host\" (string, required) stratum server host or IP\n" + "2. port (numeric, required) stratum server port\n" + "3. \"address\" (string, optional, default=\"x\") payout R-address, or \"x\" for the server default\n" + "4. timeout (numeric, optional, default=120) seconds to mine before giving up\n" + "\nResult: {\"found\":bool,\"accepted\":bool,\"hash\":\"..\",\"hashes\":n,\"seconds\":n}\n"); + + if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX) + throw JSONRPCError(RPC_MISC_ERROR, "stratummine only supports RandomX chains"); + + // This is a reference miner for exercising -stratum, not a production facility: it dials an + // operator-supplied host, blocks an RPC worker for the whole run, and parses whatever that host + // chooses to send back. Require an explicit opt-in, and exempt regtest so the test suite can + // still drive it. NOTE: do NOT gate this on fExperimentalMode -- that defaults to TRUE + // (init.cpp:1195), so it would leave the RPC exposed on every node and the gate would be a no-op. + if (!GetBoolArg("-stratummine", false) && Params().NetworkIDString() != "regtest") + throw JSONRPCError(RPC_MISC_ERROR, + "stratummine is a test-only reference miner and is disabled by default; " + "restart with -stratummine to enable it"); + + const std::string host = params[0].get_str(); + const int port = params[1].get_int(); + const std::string addr = params.size() > 2 ? params[2].get_str() : "x"; + int64_t timeout = params.size() > 3 ? params[3].get_int64() : 120; + if (timeout < 1) + timeout = 1; + if (timeout > MAX_STRATUMMINE_TIMEOUT) + timeout = MAX_STRATUMMINE_TIMEOUT; + const int64_t deadline = GetTime() + timeout; + + // connect (blocking TCP) + struct addrinfo hints; memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; + struct addrinfo* ai = NULL; + if (getaddrinfo(host.c_str(), strprintf("%d", port).c_str(), &hints, &ai) != 0 || !ai) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, strprintf("cannot resolve %s:%d", host, port)); + int fd = -1; + for (struct addrinfo* p = ai; p; p = p->ai_next) { + fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (fd < 0) continue; + if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break; + close(fd); fd = -1; + } + freeaddrinfo(ai); + if (fd < 0) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, strprintf("cannot connect to %s:%d", host, port)); + { int one = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)); } + + StratumMinerSend(fd, "{\"id\":1,\"method\":\"mining.subscribe\",\"params\":[\"dragonx-refminer/1.0\"]}"); + StratumMinerSend(fd, strprintf("{\"id\":2,\"method\":\"mining.authorize\",\"params\":[\"%s\",\"x\"]}", addr)); + + // state accumulated from the server + std::vector extranonce1; + std::string rxKey; + bool haveKey = false, haveTarget = false, haveJob = false; + arith_uint256 poolTarget; + std::string jobId, timeHex; + uint32_t nVersion = 4, nTime = 0, nBits = 0; + uint256 hashPrevBlock, hashMerkleRoot, hashReserved; + + auto processLine = [&](const std::string& line) { + // Every get_str()/get_int() below throws on a type mismatch, and this lambda runs inside the + // window where the RandomX cache and VM are allocated and the socket is open -- all of which + // are released only on the normal path. A malformed server message must therefore never + // escape from here, or it leaks 256 MB and the fd on its way out. + try { + UniValue v; + if (!v.read(line)) return; + const UniValue& id = find_value(v, "id"); + const UniValue& result = find_value(v, "result"); + if (id.isNum() && id.get_int() == 1 && result.isArray() && result.size() >= 2 && result[1].isStr()) + extranonce1 = ParseHex(result[1].get_str()); + const UniValue& method = find_value(v, "method"); + if (!method.isStr()) return; + const UniValue& p = find_value(v, "params"); + if (!p.isArray()) return; + const std::string m = method.get_str(); + if (m == "mining.set_randomx_key" && p.size() >= 1) { + std::vector kb = ParseHex(p[0].get_str()); + rxKey.assign(kb.begin(), kb.end()); + haveKey = true; + } else if (m == "mining.set_target" && p.size() >= 1) { + poolTarget = UintToArith256(uint256S(p[0].get_str())); + haveTarget = true; + } else if (m == "mining.notify" && p.size() >= 7) { + // Validate every fixed-width field before committing any of it, so a malformed job is + // ignored outright rather than half-applied over the previous one. + uint256 prev, merkle, reserved; + if (!StratumHex256(p[2].get_str(), prev) || + !StratumHex256(p[3].get_str(), merkle) || + !StratumHex256(p[4].get_str(), reserved)) + return; + jobId = p[0].get_str(); + nVersion = bswap_32((uint32_t)strtoul(p[1].get_str().c_str(), NULL, 16)); + hashPrevBlock = prev; + hashMerkleRoot = merkle; + hashReserved = reserved; + timeHex = p[5].get_str(); + nTime = bswap_32((uint32_t)strtoul(timeHex.c_str(), NULL, 16)); + nBits = bswap_32((uint32_t)strtoul(p[6].get_str().c_str(), NULL, 16)); + haveJob = true; + } + } catch (const std::exception&) { + // Malformed message from the server: ignore the line and keep mining. + } + }; + + std::string buf; + for (int i = 0; i < 120 && !(haveJob && haveTarget && haveKey && !extranonce1.empty()); i++) { + std::vector lines; + if (!StratumMinerRecvLines(fd, buf, 250, lines)) { close(fd); throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "stratum connection closed during handshake"); } + for (const std::string& l : lines) processLine(l); + if (GetTime() > deadline) break; + } + if (!(haveJob && haveTarget && haveKey && !extranonce1.empty())) { + close(fd); + throw JSONRPCError(RPC_MISC_ERROR, "did not receive complete RandomX work (need job + target + randomx key + extranonce)"); + } + + randomx_flags flags = randomx_get_flags(); + randomx_cache* cache = randomx_alloc_cache(flags); + if (!cache) { close(fd); throw JSONRPCError(RPC_MISC_ERROR, "randomx_alloc_cache failed"); } + randomx_init_cache(cache, rxKey.data(), rxKey.size()); + std::string vmKey = rxKey; + randomx_vm* vm = randomx_create_vm(flags, cache, NULL); + if (!vm) { randomx_release_cache(cache); close(fd); throw JSONRPCError(RPC_MISC_ERROR, "randomx_create_vm failed"); } + + UniValue res(UniValue::VOBJ); + bool found = false, accepted = false, submitted = false; + uint64_t hashes = 0, en2ctr = 0; + std::string foundHash; + const int64_t started = GetTime(); + + while (GetTime() <= deadline && !found) { + std::string prevJob = jobId; + std::vector lines; + if (!StratumMinerRecvLines(fd, buf, 0, lines)) break; + for (const std::string& l : lines) processLine(l); + if (jobId != prevJob) en2ctr = 0; // new tip -> restart the nonce search + if (rxKey != vmKey) { randomx_init_cache(cache, rxKey.data(), rxKey.size()); randomx_vm_set_cache(vm, cache); vmKey = rxKey; } + + arith_uint256 blockTarget; bool fNeg, fOver; + blockTarget.SetCompact(nBits, &fNeg, &fOver); + // Mine to the harder of (block target, pool share target) so a solution is a real block AND + // passes the server's low-diff share check. + arith_uint256 tgt = (haveTarget && poolTarget < blockTarget) ? poolTarget : blockTarget; + + CBlockHeader hdr; + hdr.nVersion = nVersion; + hdr.hashPrevBlock = hashPrevBlock; + hdr.hashMerkleRoot = hashMerkleRoot; + hdr.hashFinalSaplingRoot = hashReserved; + hdr.nTime = nTime; + hdr.nBits = nBits; + + for (int i = 0; i < 2000 && GetTime() <= deadline; i++) { + std::vector nonce = extranonce1; + nonce.resize(32, 0); + for (int b = 0; b < 8; b++) nonce[8 + b] = (unsigned char)((en2ctr >> (8 * b)) & 0xff); + en2ctr++; hashes++; + hdr.nNonce = uint256(nonce); + std::vector input = GetRandomXInput(hdr); + unsigned char h[RANDOMX_HASH_SIZE]; + randomx_calculate_hash(vm, input.data(), input.size(), h); + hdr.nSolution.assign(h, h + RANDOMX_HASH_SIZE); + if (UintToArith256(hdr.GetHash()) <= tgt) { + std::vector en2(nonce.begin() + 8, nonce.end()); + std::string submit = strprintf( + "{\"id\":4,\"method\":\"mining.submit\",\"params\":[\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"]}", + addr, jobId, timeHex, HexStr(en2), HexStr(hdr.nSolution)); + StratumMinerSend(fd, submit); + submitted = true; + foundHash = hdr.GetHash().ToString(); + bool sawResult = false; + for (int k = 0; k < 40 && !sawResult; k++) { + std::vector rl; + if (!StratumMinerRecvLines(fd, buf, 250, rl)) break; + for (const std::string& l : rl) { + processLine(l); + UniValue rv; if (!rv.read(l)) continue; + const UniValue& rid = find_value(rv, "id"); + if (rid.isNum() && rid.get_int() == 4) { + sawResult = true; + const UniValue& r = find_value(rv, "result"); + accepted = r.isBool() ? r.get_bool() : find_value(rv, "error").isNull(); + } + } + } + found = true; + break; + } + } + } + + randomx_destroy_vm(vm); + randomx_release_cache(cache); + close(fd); + + res.push_back(Pair("found", found)); + res.push_back(Pair("submitted", submitted)); + res.push_back(Pair("accepted", accepted)); + res.push_back(Pair("hashes", (uint64_t)hashes)); + res.push_back(Pair("seconds", (int64_t)(GetTime() - started))); + if (!foundHash.empty()) res.push_back(Pair("hash", foundHash)); + return res; +} +#endif // !WIN32 + static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- +#ifndef WIN32 + { "mining", "stratummine", &stratummine, false }, +#endif { "mining", "getlocalsolps", &getlocalsolps, true }, { "mining", "getnetworksolps", &getnetworksolps, true }, { "mining", "getnetworkhashps", &getnetworkhashps, true }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 9f334dffa..b3a14c699 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -78,106 +78,6 @@ extern int32_t ASSETCHAINS_SAPLING; extern uint64_t ASSETCHAINS_ENDSUBSIDY[],ASSETCHAINS_REWARD[],ASSETCHAINS_HALVING[],ASSETCHAINS_DECAY[],ASSETCHAINS_NOTARY_PAY[]; extern std::string NOTARY_PUBKEY,NOTARY_ADDRESS; extern uint8_t NOTARY_PUBKEY33[]; -//TODO: use non-staked eras -// Currently HUSH only uses block heights to define eras -int32_t getera(int timestamp) -{ - return(0); -} - -UniValue getdragonjson(const UniValue& params, bool fHelp, const CPubKey& mypk) -{ - if (fHelp || params.size() != 0) - throw runtime_error("getdragonjson\nreturns json for dragon, for the current ERA."); - - UniValue json(UniValue::VOBJ); - UniValue seeds(UniValue::VARR); - UniValue notaries(UniValue::VARR); - // get the current era, use local time for now. - // should ideally take blocktime of last known block? - int now = time(NULL); - int32_t era = getera(now); - - // loop over seeds array and push back to json array for seeds - for (int8_t i = 0; i < 8; i++) { - //seeds.push_back(dragonSeeds[i][0]); - } - - // get all current notaries - for (int8_t i = 0; i < NUM_HUSH_NOTARIES; i++) { - UniValue notary(UniValue::VOBJ); - notary.push_back(notaries_list[era][i][0]); - notaries.push_back(notary); - } - - // TODO: should be a config param - int minsigs = 13; - int BTCminsigs = 13; - - int dragonPort = 5555; - json.push_back(Pair("port",dragonPort)); - json.push_back(Pair("BTCminsigs",BTCminsigs)); - json.push_back(Pair("minsigs",minsigs)); - json.push_back(Pair("seeds",seeds)); - json.push_back(Pair("notaries",notaries)); - return json; -} - -UniValue getnotarysendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) -{ - if (fHelp || params.size() > 1) - throw runtime_error( - "getnotarysendmany\n" - "Returns a sendmany JSON array with all current notaries Raddress's.\n" - "\nExamples:\n" - + HelpExampleCli("getnotarysendmany", "10") - + HelpExampleRpc("getnotarysendmany", "10") - ); - int amount = 0; - if ( params.size() == 1 ) { - amount = params[0].get_int(); - } - - //TODO: this is broke - int era = getera(time(NULL)); - - UniValue ret(UniValue::VOBJ); - for (int i = 0; iGetHeight(); i++) - { - pindex = chainActive[i]; - era = getera(pindex->nTime)+1; - if ( era > lastera ) - { - char str[16]; - sprintf(str, "%d", era); - ret.push_back(Pair(str,(int64_t)i)); - lastera = era; - } - } - - return(ret); -} - extern int getWorkQueueDepth(); extern int getWorkQueueMaxDepth(); extern int getWorkQueueNumThreads(); @@ -202,7 +102,7 @@ UniValue rpcinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) { - uint256 notarized_hash,notarized_desttxid; int32_t prevMoMheight,notarized_height,longestchain,hushnotarized_height,txid_height; + int32_t longestchain; if (fHelp || params.size() != 0) throw runtime_error( "getinfo\n" @@ -240,28 +140,13 @@ UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) proxyType proxy; GetProxy(NET_IPV4, proxy); - notarized_height = hush_notarized_height(&prevMoMheight,¬arized_hash,¬arized_desttxid); - //fprintf(stderr,"after notarized_height %u\n",(uint32_t)time(NULL)); UniValue obj(UniValue::VOBJ); obj.push_back(Pair("version", CLIENT_VERSION)); obj.push_back(Pair("protocolversion", PROTOCOL_VERSION)); obj.push_back(Pair("synced", HUSH_INSYNC!=0)); - obj.push_back(Pair("notarized", notarized_height)); - obj.push_back(Pair("prevMoMheight", prevMoMheight)); - obj.push_back(Pair("notarizedhash", notarized_hash.ToString())); - obj.push_back(Pair("notarizedtxid", notarized_desttxid.ToString())); if ( HUSH_NSPV_FULLNODE ) { - txid_height = notarizedtxid_height( (char *)"HUSH3" ,(char *)notarized_desttxid.ToString().c_str(),&hushnotarized_height); - if ( txid_height > 0 ) - obj.push_back(Pair("notarizedtxid_height", txid_height)); - else obj.push_back(Pair("notarizedtxid_height", "mempool")); - if ( SMART_CHAIN_SYMBOL[0] != 0 ) { - obj.push_back(Pair("HUSHnotarized_height", hushnotarized_height)); - } - obj.push_back(Pair("notarized_confirms", txid_height < hushnotarized_height ? (hushnotarized_height - txid_height + 1) : 0)); - //fprintf(stderr,"after notarized_confirms %u\n",(uint32_t)time(NULL)); #ifdef ENABLE_WALLET if (pwalletMain) { obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); @@ -348,14 +233,8 @@ UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) if ( ASSETCHAINS_COMMISSION != 0 ) obj.push_back(Pair("commission", ASSETCHAINS_COMMISSION)); - if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) { - uint64_t N = ASSETCHAINS_NK[0] ? ASSETCHAINS_NK[0] : 200; - uint64_t K = ASSETCHAINS_NK[1] ? ASSETCHAINS_NK[1] : 9; - std::string equihash_algo = "equihash (" + std::to_string(N) + "," + std::to_string(K) + ")"; - obj.push_back(Pair("algo",equihash_algo)); - } else { - obj.push_back(Pair("algo", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO])); - } + // DragonX is RandomX-only; the Equihash (N,K) reporting path was removed. + obj.push_back(Pair("algo", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO])); } return obj; } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 806ab1ed3..62a04f45b 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -218,7 +218,7 @@ int32_t HUSH_LONGESTCHAIN; static int32_t hush_longest_depth = 0; int32_t hush_longestchain() { - int32_t ht,n=0,num=0,maxheight=0,height = 0; + int32_t ht,num=0,maxheight=0,height = 0; if ( hush_longest_depth < 0 ) hush_longest_depth = 0; if ( hush_longest_depth == 0 ) @@ -231,7 +231,6 @@ int32_t hush_longestchain() } BOOST_FOREACH(const CNodeStats& stats, vstats) { - //fprintf(stderr,"hush_longestchain iter.%d\n",n); CNodeStateStats statestats; bool fStateStats = GetNodeStateStats(stats.nodeid,statestats); if ( statestats.nSyncHeight < 0 ) @@ -251,10 +250,8 @@ int32_t hush_longestchain() height = ht; } hush_longest_depth--; - if ( num > (n >> 1) ) + if ( num > 0 ) { - if ( 0 && height != HUSH_LONGESTCHAIN ) - fprintf(stderr,"set %s HUSH_LONGESTCHAIN <- %d\n",SMART_CHAIN_SYMBOL,height); HUSH_LONGESTCHAIN = height; return(height); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index dd3ae5695..fd8e603a6 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1168,7 +1168,7 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& m numiters++; } if ( numiters > 0 ) - fprintf(stderr,"ASSETCHAINS_TXPOW.%d txpow.%d numiters.%d for signature\n",ASSETCHAINS_TXPOW,txpow,numiters); + LogPrintf("ASSETCHAINS_TXPOW.%d txpow.%d numiters.%d for signature\n",ASSETCHAINS_TXPOW,txpow,numiters); bool fComplete = vErrors.empty(); UniValue result(UniValue::VOBJ); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index fbfb85ac2..4a040b1a6 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -276,11 +276,7 @@ UniValue stop(const UniValue& params, bool fHelp, const CPubKey& mypk) // Shutdown will take long enough that the response should get back StartShutdown(); - if ((strncmp(SMART_CHAIN_SYMBOL, "HUSH3", 5) == 0) ) { - sprintf(buf,"Hush server stopping, for now..."); - } else { - sprintf(buf,"%s server stopping...",SMART_CHAIN_SYMBOL); - } + sprintf(buf,"%s server stopping...",SMART_CHAIN_SYMBOL); return buf; } @@ -292,9 +288,6 @@ static const CRPCCommand vRPCCommands[] = // --------------------- ------------------------ ----------------------- ---------- /* Overall control/query calls */ { "control", "help", &help, true }, - { "control", "getdragonjson", &getdragonjson, true }, - { "control", "getnotarysendmany", &getnotarysendmany, true }, - { "control", "geterablockheights", &geterablockheights, true }, { "control", "stop", &stop, true }, /* P2P networking */ @@ -342,7 +335,6 @@ static const CRPCCommand vRPCCommands[] = { "crosschain", "calc_MoM", &calc_MoM, true }, { "crosschain", "height_MoM", &height_MoM, true }, { "crosschain", "assetchainproof", &assetchainproof, true }, - { "crosschain", "crosschainproof", &crosschainproof, true }, { "crosschain", "getNotarizationsForBlock", &getNotarizationsForBlock, true }, { "crosschain", "scanNotarizationsDB", &scanNotarizationsDB, true }, @@ -666,7 +658,6 @@ UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms // while a very long wallet rescan is happening and do other read-only devopz if (pcmd->name != "stop" && pcmd->name != "help" && pcmd->name != "z_listaddresses" && pcmd->name != "z_exportkey" && pcmd->name != "getNotarizationsForBlock" && pcmd->name != "scanNotarizationsDB" && - pcmd->name != "getnotarysendmany" && pcmd->name != "geterablockheights" && pcmd->name != "getaddressesbyaccount" && pcmd->name != "listaddresses" && pcmd->name != "z_exportwallet" && pcmd->name != "notaries" && pcmd->name != "signmessage" && pcmd->name != "decoderawtransaction" && pcmd->name != "dumpprivkey" && pcmd->name != "getpeerinfo" && pcmd->name != "getnetworkinfo" && @@ -695,11 +686,7 @@ UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms std::string HelpExampleCli(const std::string& methodname, const std::string& args) { - if ((strncmp(SMART_CHAIN_SYMBOL, "HUSH3", 5) == 0) ) { - return "> hush-cli " + methodname + " " + args + "\n"; - } else { - return "> hush-cli -ac_name=" + strprintf("%s", SMART_CHAIN_SYMBOL) + " " + methodname + " " + args + "\n"; - } + return "> dragonx-cli " + methodname + " " + args + "\n"; } std::string HelpExampleRpc(const std::string& methodname, const std::string& args) diff --git a/src/rpc/server.h b/src/rpc/server.h index 8c345c20e..a2d5c17cc 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -280,9 +280,6 @@ extern UniValue encryptwallet(const UniValue& params, bool fHelp, const CPubKey& extern UniValue validateaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue txnotarizedconfirmed(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk); -extern UniValue getdragonjson(const UniValue& params, bool fHelp, const CPubKey& mypk); -extern UniValue getnotarysendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); -extern UniValue geterablockheights(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue setpubkey(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getwalletinfo(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getblockchaininfo(const UniValue& params, bool fHelp, const CPubKey& mypk); @@ -369,6 +366,7 @@ extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPub extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sweepstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp +extern UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_consolidationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp @@ -382,7 +380,6 @@ extern UniValue MoMoMdata(const UniValue& params, bool fHelp, const CPubKey& myp extern UniValue calc_MoM(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue height_MoM(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue assetchainproof(const UniValue& params, bool fHelp, const CPubKey& mypk); -extern UniValue crosschainproof(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getNotarizationsForBlock(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue scanNotarizationsDB(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getimports(const UniValue& params, bool fHelp, const CPubKey& mypk); diff --git a/src/stratum.cpp b/src/stratum.cpp index c7bed37df..d9cfffb44 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -16,6 +16,7 @@ #include "httpserver.h" #include "miner.h" #include "netbase.h" +#include "pow.h" // RandomX PoW: CheckRandomXSolution / GetRandomXKey / GetRandomXInput; and CheckEquihashSolution #include "net.h" #include "rpc/server.h" #include "serialize.h" @@ -637,9 +638,6 @@ void CustomizeWork(const StratumClient& client, const StratumWork& current_work, nonce.insert(nonce.end(), extranonce2.begin(), extranonce2.end()); // nonce = extranonce1 + extranonce2 - // if (instance_of_cstratumparams.fstdErrDebugOutput) { - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " nonce = " << HexStr(nonce) << std::endl; - // } if (cb.vin.empty()) { const std::string msg = strprintf("%s: first transaction is missing coinbase input; unable to customize work to miner", __func__); @@ -658,14 +656,32 @@ void CustomizeWork(const StratumClient& client, const StratumWork& current_work, LogPrint("stratum", "%s\n", msg); throw std::runtime_error(msg); } - if (cb.vout[0].scriptPubKey == (CScript() << OP_FALSE)) { - cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get()); + // Unconditional. This used to be guarded on the coinbase still carrying the OP_FALSE + // placeholder, which made it a no-op for every client after the first once a customized + // coinbase had been written back into the shared template -- so those miners silently + // mined the first miner's payout address. The template is now left pristine (see + // GetWorkUnit), and stamping unconditionally means a coinbase that somehow arrives + // already customized can never be inherited by a different miner. + if (!addr.IsValid()) { + const std::string msg = strprintf("%s: no valid payout address for this client; unable to customize work", __func__); + LogPrint("stratum", "%s\n", msg); + throw std::runtime_error(msg); } + cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get()); } // cb_branch = current_work.m_cb_branch; } +// DragonX PoW is RandomX (32-byte solution); Equihash is legacy (1347-byte solution). The stratum +// work and submit paths branch on this: RandomX hands the miner the per-height RandomX key (which it +// cannot derive without the chain) and validates a 32-byte solution via CheckRandomXSolution(); +// Equihash keeps the legacy path (1347-byte solution + the 3-byte prefix + CheckEquihashSolution). +extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_RANDOMX; // hush_defs.h — active PoW algorithm selector +extern int32_t HUSH_TESTNODE; // hush_globals.h — -testnode: relax IBD/sync guards for isolated test nodes +static inline bool StratumIsRandomX() { return ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX; } +static const size_t RX_STRATUM_SOLUTION_SIZE = 32; // == RANDOMX_HASH_SIZE (kept local to avoid pulling randomx.h into stratum) + std::string GetWorkUnit(StratumClient& client) { // LOCK(cs_main); @@ -675,7 +691,7 @@ std::string GetWorkUnit(StratumClient& client) } */ /* if (!Params().MineBlocksOnDemand() && g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Hush is not connected!"); + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "DragonX is not connected!"); } */ bool fvNodesEmpty; @@ -686,21 +702,21 @@ std::string GetWorkUnit(StratumClient& client) if (Params().MiningRequiresPeers() && fvNodesEmpty) { - const std::string msg = strprintf("%s: Unable to get work unit, Hush is not connected!", __func__); + const std::string msg = strprintf("%s: Unable to get work unit, DragonX is not connected!", __func__); LogPrint("stratum", "%s\n", msg); - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Hush is not connected!"); + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "DragonX is not connected!"); } - if (IsInitialBlockDownload()) { - const std::string msg = strprintf("%s: Unable to get work unit, Hush is still downloading blocks!", __func__); + if (IsInitialBlockDownload() && HUSH_TESTNODE == 0) { + const std::string msg = strprintf("%s: Unable to get work unit, DragonX is still downloading blocks!", __func__); LogPrint("stratum", "%s\n", msg); - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Hush is downloading blocks..."); + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "DragonX is downloading blocks..."); } if (!client.m_authorized && client.m_aux_addr.empty()) { const std::string msg = strprintf("%s: Unable to get work unit, client not authorized! Use address 'x' to mine to the default address", __func__); LogPrint("stratum", "%s\n", msg); - throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a Hush R.. address as the username or 'x' to mine to the default address."); + throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address."); } static CBlockIndex* tip = NULL; // pindexPrev @@ -737,18 +753,18 @@ std::string GetWorkUnit(StratumClient& client) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); } - // if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << "hashMerkleRoot = " << new_work->block.hashMerkleRoot.ToString() << std::endl; - // So that block.GetHash() is correct //new_work->block.hashMerkleRoot = BlockMerkleRoot(new_work->block); new_work->block.hashMerkleRoot = new_work->block.BuildMerkleTree(); // NB! here we have merkle with scriptDummy script in coinbase, after CustomizeWork we should recalculate it (!) - // if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << "hashMerkleRoot = " << new_work->block.hashMerkleRoot.ToString() << std::endl; job_id = new_work->block.GetHash(); //work_templates[job_id] = StratumWork(*new_work, new_work->block.vtx[0]->HasWitness()); work_templates[job_id] = StratumWork(*new_work, false); + // Height of the block being mined — used for RandomX key derivation (GetRandomXKey) and + // CheckRandomXSolution/CheckProofOfWork on submit. Previously left 0 (Equihash didn't need it). + work_templates[job_id].nHeight = tip_new->GetHeight() + 1; tip = tip_new; @@ -851,38 +867,30 @@ std::string GetWorkUnit(StratumClient& client) CMutableTransaction cb, bf; std::vector cb_branch; - // if (instance_of_cstratumparams.fstdErrDebugOutput) - // { - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [1] cb = " << CTransaction(cb).ToString() << std::endl; - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [1] current_work.GetBlock().vtx[0] = " << current_work.GetBlock().vtx[0].ToString() << std::endl; - // } - { std::vector extranonce1 = client.ExtraNonce1(job_id); static const std::vector dummy(32-extranonce1.size(), 0x00); // extranonce2 CustomizeWork(client, current_work, client.m_addr, extranonce1, dummy, cb, bf, cb_branch); - // without 2 lines below equihash solutinon on SubmitWork will be incorrect, bcz we should - // change vtx[0] in current work and re-calc hashMerkleRoot - // TODO: refactor all of these ... may be change this in current_work directly is bad idea, - // and we should do all checks and hashMerkleRoot at SubmitBlock(...) - - current_work.GetBlock().vtx[0] = cb; - current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree(); - } - // if (instance_of_cstratumparams.fstdErrDebugOutput) - // { - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [2] cb = " << CTransaction(cb).ToString() << std::endl; - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " [2] current_work.GetBlock().vtx[0] = " << current_work.GetBlock().vtx[0].ToString() << std::endl; - // } - CBlockHeader blkhdr; // Setup native proof-of-work - blkhdr = current_work.GetBlock().GetBlockHeader(); // copy entire blockheader created with CreateNewBlock to blkhdr + // The shared template MUST keep its pristine OP_FALSE coinbase. This previously did + // current_work.GetBlock().vtx[0] = cb; + // current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree(); + // which published one client's coinbase to every other client on the same job: the merkle + // root they were told to mine, and the block they eventually submitted, both committed to + // the first client's payout address. Derive this client's header from a local copy instead, + // which is what the TODO that used to sit here was asking for. + { + CBlock tmp(current_work.GetBlock()); + tmp.vtx[0] = cb; + blkhdr = tmp.GetBlockHeader(); + blkhdr.hashMerkleRoot = tmp.BuildMerkleTree(); + } // CDataStream ds(SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS); CDataStream ds(SER_GETHASH, PROTOCOL_VERSION); ds << cb; @@ -934,7 +942,25 @@ std::string GetWorkUnit(StratumClient& client) mining_notify.push_back(Pair("method", "mining.notify")); mining_notify.push_back(Pair("params", params)); + // RandomX: the miner cannot derive the per-height RandomX key on its own (it depends on a block + // hash deep in the chain), so hand it the key bytes + height explicitly. Sent as its own + // mining.set_randomx_key message so the equihash-format mining.notify above stays byte-compatible + // with legacy miners; a RandomX miner reads this before hashing. + std::string randomx_key_msg; + if (StratumIsRandomX()) { + const std::string rxKey = GetRandomXKey(current_work.nHeight); + UniValue set_rxkey(UniValue::VOBJ); + set_rxkey.push_back(Pair("id", client.m_nextid++)); + set_rxkey.push_back(Pair("method", "mining.set_randomx_key")); + UniValue rxparams(UniValue::VARR); + rxparams.push_back(HexStr(rxKey.begin(), rxKey.end())); // RandomX key bytes (hex) + rxparams.push_back(current_work.nHeight); // block height (sanity/logging) + set_rxkey.push_back(Pair("params", rxparams)); + randomx_key_msg = set_rxkey.write() + "\n"; + } + return GetExtraNonceRequest(client, job_id) + + randomx_key_msg + set_target.write() + "\n" + mining_notify.write() + "\n"; } @@ -942,8 +968,15 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork const std::vector& extranonce1, const std::vector& extranonce2, boost::optional nVersion, uint32_t nTime, const std::vector& sol) { + // Submit path handles BOTH proof-of-works, branched on StratumIsRandomX(): + // * RandomX (DragonX): `sol` is the 32-byte RandomX hash and IS nSolution verbatim; validated + // via CheckRandomXSolution(&blkhdr, height). The target check (GetHash() < target) and the + // nNonce = extranonce1||extranonce2 assembly are identical to the equihash path. + // * Equihash (legacy): `sol` is the 1347-byte solution; the 3-byte zcash prefix is stripped + // and CheckEquihashSolution() validates it. + // // called from stratum_mining_submit and uses following data, came from client: - // ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_SOLUTION"] + // ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "SOLUTION"] // all other params we have saved in other places if (extranonce1.size() + extranonce2.size() != 32) { @@ -952,9 +985,9 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork throw JSONRPCError(RPC_INVALID_PARAMETER, msg); } - // TODO: change hardcoded constants on actual determine of solution size, depends on equihash algo type: 200.9, etc. - if (sol.size() != 1347) { - std::string msg = strprintf("%s: solution is wrong length (received %d bytes; expected %d bytes", __func__, extranonce2.size(), 1347); + const size_t expected_sol_size = StratumIsRandomX() ? RX_STRATUM_SOLUTION_SIZE : 1347; + if (sol.size() != expected_sol_size) { + std::string msg = strprintf("%s: solution is wrong length (received %d bytes; expected %d bytes)", __func__, sol.size(), (int)expected_sol_size); LogPrint("stratum", "%s\n", msg); throw JSONRPCError(RPC_INVALID_PARAMETER, msg); } @@ -986,24 +1019,50 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork std::vector nonce(extranonce1); nonce.insert(nonce.end(), extranonce2.begin(), extranonce2.end()); - blkhdr.nSolution = std::vector(sol.begin() + 3, sol.end()); + // RandomX: nSolution IS the 32-byte RandomX hash (verbatim). Equihash: strip the 3-byte + // zcash solution-size prefix. + blkhdr.nSolution = StratumIsRandomX() ? sol + : std::vector(sol.begin() + 3, sol.end()); blkhdr.hashFinalSaplingRoot = current_work.GetBlock().hashFinalSaplingRoot; - blkhdr.hashMerkleRoot = current_work.GetBlock().hashMerkleRoot; + // Recompute from the coinbase CustomizeWork() just derived for THIS client. Reading the + // shared template's root would be wrong now that the template is left pristine, and was + // wrong before too -- it carried whichever client happened to request work first. + { + CBlock tmp(current_work.GetBlock()); + tmp.vtx[0] = cb; + blkhdr.hashMerkleRoot = tmp.BuildMerkleTree(); + } blkhdr.nNonce = (uint256) nonce; - // example how to display constructed block - // if (instance_of_cstratumparams.fstdErrDebugOutput) { - // CBlockIndex index {blkhdr}; - // index.SetHeight(current_work.nHeight); - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " blkhdr.hashPrevBlock = " << blkhdr.hashPrevBlock.GetHex() << std::endl; - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " blkhdr = " << blockToJSON(blkhdr, &index).write() << std::endl; - // } + // Cheap SHA256d filter first. This test used to sit *below* the RandomX verify, so 32 + // arbitrary bytes from any peer bought a full ~65ms randomx_calculate_hash before anything + // rejected them -- on the shared HTTP/RPC libevent thread, and holding the global + // cs_randomx_validator that block validation also takes. GetHash() is SerializeHash over the + // header including nSolution, so passing this costs real grinding. Semantics are unchanged: + // an empty local_diff still parses to zero and still rejects, exactly as before. + if (!instance_of_cstratumparams.fAllowLowDiffShares && + UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) { + CBlockIndex diff_index; + diff_index.nBits = UintToArith256(blkhdr.GetHash()).GetCompact(); + const double share_diff = GetDifficulty(&diff_index); + diff_index.nBits = arith_uint256(current_work.local_diff).GetCompact(); + const double target_diff = GetDifficulty(&diff_index); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", share_diff, target_diff)); + } // block is constructed, now it's time to VerifyEH - if (instance_of_cstratumparams.fCheckEquihashSolution && !CheckEquihashSolution(&blkhdr, Params())) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid equihash solution"); + if (StratumIsRandomX()) { + // Verify the submitted 32-byte solution really is the RandomX hash of this header + // (nSolution == randomx_hash(GetRandomXInput(blkhdr), GetRandomXKey(height))). This is the + // consensus authority for the solution; without it a miner could submit a low-GetHash() + // block with a bogus nSolution. Rejects fake shares before we count/relay them. + if (!CheckRandomXSolution(&blkhdr, current_work.nHeight)) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid RandomX solution"); + } else if (instance_of_cstratumparams.fCheckEquihashSolution && !CheckEquihashSolution(&blkhdr, Params())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid equihash solution"); + } arith_uint256 bnTarget; bool fNegative, fOverflow; bnTarget.SetCompact(blkhdr.nBits, &fNegative, &fOverflow); @@ -1018,7 +1077,6 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork uint8_t pubkey33[33]; int32_t height = current_work.nHeight; res = CheckProofOfWork(blkhdr, pubkey33, height, Params().GetConsensus()); } - // if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "res[1] = " << res << std::endl; uint256 hash = blkhdr.GetHash(); @@ -1050,10 +1108,8 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork std::chrono::duration elapsed; uint64_t shares_accepted_since_last; - // TODO: we need to check hash > local port diff, and if it's true -> throw an exception -> diff too low (!) - if (!instance_of_cstratumparams.fAllowLowDiffShares) - if (UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", hush_real_diff, hush_local_diff)); + // (the low-diff share check moved above the RandomX verify -- see SubmitBlock's cheap + // SHA256d filter -- so that attacker-controlled bytes cannot buy a RandomX hash) if (finish > start) { @@ -1061,27 +1117,8 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork shares_accepted_since_last = counter_TotalShares - counter_prev; start = finish; counter_prev = counter_TotalShares; - // std::cerr << strprintf("%f ms - %" PRIu64 "", elapsed.count(), shares_accepted_since_last) << std::endl; } - bool fDisplayDiffHUSH = true; // otherwise it will display ccminer diff - - std::cerr << DateTimeStrPrecise() << - strprintf("%saccepted: %" PRIu64 "/%" PRIu64 "%s ", ColorTypeNames[cl_WHT], counter_TotalBlocks, counter_TotalShares, ColorTypeNames[cl_N] ); - if (fDisplayDiffHUSH) { - /* hushd diff display */ - std::cerr << strprintf("%slocal %g%s ", "\x1B[90m", hush_local_diff, ColorTypeNames[cl_N]) << - strprintf("%s(diff %g, target %g) %s ", ColorTypeNames[cl_WHT], hush_real_diff, hush_target_diff, ColorTypeNames[cl_N]); - } else { /* ccminer diff display */ - std::cerr << strprintf("%slocal %.3f%s ", "\x1B[90m", ccminer_local_diff, ColorTypeNames[cl_N]) << - strprintf("%s(diff %.3f, target %.3f) %s", ColorTypeNames[cl_WHT], ccminer_real_diff, ccminer_target_diff, ColorTypeNames[cl_N]); // ccminer diff - } - - std::cerr << "" << - strprintf("%f ms ", elapsed.count()) << // 1 share took elapsed ms - strprintf("%s%s%s ", ColorTypeNames[cl_LGR], (res ? "yay!!!": "yes!"), ColorTypeNames[cl_N]) << - std::endl; - // (diff %g, target %g), % if (res) { @@ -1097,22 +1134,14 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork block.nVersion = version; // block.hashMerkleRoot = BlockMerkleRoot(block); block.hashMerkleRoot = block.BuildMerkleTree(); - //if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << "hashMerkleRoot = " << block.hashMerkleRoot.GetHex() << std::endl; block.nTime = nTime; // block.nNonce = nNonce; // nNonce <<= 32; nNonce >>= 16; // clear the top and bottom 16 bits (for local use as thread flags and counters) block.nNonce = (uint256) nonce; - block.nSolution = std::vector(sol.begin() + 3, sol.end()); - - // example how to pre-check the equihash solution - // if(instance_of_cstratumparams.fstdErrDebugOutput) { - // CBlockIndex index {blkhdr}; - // index.SetHeight(-1); - // std::cerr << "block = " << blockToJSON(block, &index, true).write(1) << std::endl; - // std::cerr << "CheckEquihashSolution = " << CheckEquihashSolution(&block, Params()) << std::endl; - // } + block.nSolution = StratumIsRandomX() ? sol + : std::vector(sol.begin() + 3, sol.end()); // std::shared_ptr pblock = std::make_shared(block); // res = ProcessNewBlock(Params(), pblock, true, NULL); @@ -1120,8 +1149,6 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork CValidationState state; res = ProcessNewBlock(0,0,state, NULL, &block, true /* forceProcessing */ , NULL); - //if (instance_of_cstratumparams.fstdErrDebugOutput) std::cerr << DateTimeStrPrecise() << "res[2] = " << res << std::endl; - // we haven't PreciousBlock, so we can't prioritize the block this way for now /* if (res) { @@ -1207,14 +1234,6 @@ UniValue stratum_mining_subscribe(StratumClient& client, const UniValue& params) * sExtraNonce1 for a given client based on m_secret. */ - // if (instance_of_cstratumparams.fstdErrDebugOutput && vExtraNonce1.size() > 3) { - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " " << strprintf("client.m_supports_extranonce = %d, [%d, %d, %d, %d], %s", client.m_supports_extranonce, vExtraNonce1[0], vExtraNonce1[1], vExtraNonce1[2], vExtraNonce1[3], sExtraNonce1) << std::endl; - // // recalc from client.m_secret example - // uint256 sha256; - // CSHA256().Write(client.m_secret.begin(), 32).Finalize(sha256.begin()); - // std::cerr << __func__ << ": " << __FILE__ << "," << __LINE__ << " " << HexStr(std::vector(sha256.begin(), sha256.begin() + 4)) << std::endl; - // } - ret.push_back(NullUniValue); ret.push_back(sExtraNonce1); @@ -1261,7 +1280,7 @@ UniValue stratum_mining_authorize(StratumClient& client, const UniValue& params) // This means a miner can run a private pool without TLS and not // worry about MITM attacks that change addresses, and leaks less metadata. // It also means many miners can be used and updating their mining address does not - // require any changes on each miner, just restart hushd with a new -stratumaddress + // require any changes on each miner, just restart dragonxd with a new -stratumaddress if(addr.ToString() == "x") { addr = CBitcoinAddress(GetArg("-stratumaddress", "")); const std::string msg = strprintf("%s: Authorized client with default stratum address=%s", __func__, addr.ToString()); @@ -1269,9 +1288,9 @@ UniValue stratum_mining_authorize(StratumClient& client, const UniValue& params) } if (!addr.IsValid()) { - const std::string msg = strprintf("%s: Invalid Hush address=%s", __func__, addr.ToString()); + const std::string msg = strprintf("%s: Invalid DragonX address=%s", __func__, addr.ToString()); LogPrint("stratum", "%s\n", msg); - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid Hush address: %s", username)); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid DragonX address: %s", username)); } client.m_addr = addr; @@ -1315,7 +1334,11 @@ UniValue stratum_mining_configure(StratumClient& client, const UniValue& params) UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) { - // {"id": 4, "method": "mining.submit", "params": ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_SOLUTION"]}\n + // Share submission. On RandomX (DragonX) the SOLUTION param is the 32-byte RandomX hash; on + // Equihash (legacy) it is the 1347-byte solution. The size is validated below and the branch is + // handled in SubmitBlock(). NONCE_2 is the miner-chosen tail of the 32-byte block nNonce. + // + // {"id": 4, "method": "mining.submit", "params": ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "SOLUTION"]}\n // NONCE_1 is first part of the block header nonce (in hex). // By protocol, Zcash's nonce is 32 bytes long. The miner will pick NONCE_2 such that len(NONCE_2) = 32 - len(NONCE_1). Please note that Stratum use hex encoding, so you have to convert NONCE_1 from hex to binary before. @@ -1334,20 +1357,39 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) const std::string method("mining.submit"); BoundParams(method, params, 5,5); + + // Parity with every other handler (GetWorkUnit, mining.aux.*, mining.extranonce.*), which all + // refuse an unauthorized client. NOTE this is not authentication: mining.authorize validates no + // credential, so it only costs an attacker one extra line. It is here so the submit path cannot + // be reached without at least completing the handshake; the cheap-target check below is what + // actually bounds the work an unknown peer can force. + if (!client.m_authorized && client.m_aux_addr.empty()) { + const std::string msg = strprintf("%s: share submitted by an unauthorized client", __func__); + LogPrint("stratum", "%s\n", msg); + throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address."); + } + // First parameter is the client username, which is ignored. /* EWBF 31 bytes job_id fix */ bool fEWBFJobIDFixNeeded = false; uint256 ret; if (params[1].isStr()) { - //std::cerr << "\"" << params[1].get_str() << "\"" << std::endl; const std::string job_id_str = params[1].get_str(); const std::string hexDigits = "0123456789abcdef"; - // std::cerr << strprintf("\"%s\" (%d)", job_id_str, job_id_str.length()) << std::endl; if (job_id_str.length() == 63) { fEWBFJobIDFixNeeded = true; for(const auto& hexDigit : hexDigits) { - ret = uint256(ParseHex(job_id_str + hexDigit)); + // ParseHex() stops at the first non-hex character and returns a SHORT vector + // without signalling an error, and base_blob(const std::vector&) + // asserts vch.size() == 32. Constructing without checking therefore lets any + // 63-character job_id containing a non-hex byte abort the daemon -- from an + // unauthenticated client, before any other validation. Skip bad candidates + // instead; if none of the 16 completions parse, ret stays null, misses + // work_templates below, and the handler returns false cleanly. + std::vector vch = ParseHex(job_id_str + hexDigit); + if (vch.size() != 32) continue; + ret = uint256(vch); if (work_templates.count(ret)) break; } } @@ -1370,8 +1412,9 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) uint32_t nTime = bswap_32(ParseHexInt4(params[2], "nTime")); std::vector sol = ParseHexV(params[4], "solution"); - if (sol.size() != 1347) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("solution is wrong length (received %d bytes; expected %d bytes", sol.size(), 1347)); + const size_t expected_sol_size = StratumIsRandomX() ? RX_STRATUM_SOLUTION_SIZE : 1347; + if (sol.size() != expected_sol_size) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("solution is wrong length (received %d bytes; expected %d bytes)", sol.size(), (int)expected_sol_size)); } std::vector extranonce1 = client.ExtraNonce1(job_id); @@ -1816,7 +1859,7 @@ void SendKeepAlivePackets() if ( (client.m_last_tip && client.m_last_tip->GetHeight() == chainActive.Tip()->GetHeight()) || (!client.m_last_tip) ) { LOCK(cs_stratum); - std::cerr << DateTimeStrPrecise() << "\033[31m" << client.m_from.ToString() << "\033[0m seems stucked (ccminer issue), need to emulate new block incoming to unstuck!" << std::endl; + LogPrint("stratum", "%s seems stucked (ccminer issue), need to emulate new block incoming to unstuck!\n", client.m_from.ToString()); mempool.AddTransactionsUpdated(1); client.m_last_tip = (client.m_last_tip ? nullptr : chainActive.Tip()); client.m_nextid++; @@ -1829,15 +1872,25 @@ void SendKeepAlivePackets() } -/** Configure the Hush stratum server */ +/** Configure the DragonX stratum server */ bool InitStratumServer() { LOCK(cs_stratum); int stratumPort = BaseParams().StratumPort(); int defaultPort = GetArg("-stratumport", stratumPort); - fprintf(stderr,"%s: Starting built-in stratum server on port %d\n",__func__, defaultPort ); + LogPrintf("%s: Starting built-in stratum server on port %d\n",__func__, defaultPort ); + // Optional pool share-target override (64-hex, big-endian like getblocktemplate's "target"). + // Loosens/tightens the accepted share difficulty; also lets a solo/test miner accept easy shares + // on a low-difficulty chain (default is the diff-1 target 00ffff00..). Larger value = easier. + if (mapArgs.count("-stratumtarget")) { + const std::string t = GetArg("-stratumtarget", ""); + if (!t.empty()) { + instance_of_cstratumparams.setTarget(arith_uint256(t)); + LogPrintf("%s: stratum pool share target overridden to %s\n", __func__, t); + } + } if (!InitStratumAllowList(stratum_allow_subnets)) { LogPrint("stratum", "Unable to bind stratum server to an endpoint.\n"); @@ -1959,7 +2012,7 @@ UniValue rpc_stratum_updatework(const UniValue& params, bool fHelp, const CPubKe // Ignore clients that aren't authorized yet. if (!client.m_authorized && client.m_aux_addr.empty()) { - fprintf(stderr,"%s: Ignoring unauthorized client\n", __func__); + LogPrint("stratum", "%s: Ignoring unauthorized client\n", __func__); continue; } diff --git a/src/txdb.cpp b/src/txdb.cpp index dc73617ce..743bba841 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -285,8 +285,9 @@ bool CBlockTreeDB::WriteBatchSync(const std::vector key = make_pair(DB_BLOCK_INDEX, it->GetBlockHash()); try { CDiskBlockIndex dbindex {it, [this, &key]() { - // It can happen that the index entry is written, then the Equihash solution is cleared from memory, + // It can happen that the index entry is written, then the solution is cleared from memory, // then the index entry is rewritten. In that case we must read the solution from the old entry. + // (GetSolution() returns DragonX's RandomX solution.) CDiskBlockIndex dbindex_old; if (!Read(key, dbindex_old)) { LogPrintf("%s: Failed to read index entry", __func__); @@ -472,7 +473,6 @@ bool CBlockTreeDB::Snapshot2(std::map &addressAmounts, Un iter->GetKey(keyObj); char chType = keyObj.first; CAddressIndexIteratorKey indexKey = keyObj.second; - //fprintf(stderr, "chType=%d\n", chType); if (chType == DB_ADDRESSUNSPENTINDEX) { try { @@ -485,7 +485,7 @@ bool CBlockTreeDB::Snapshot2(std::map &addressAmounts, Un std::map ::iterator ignored = ignoredMap.find(address); if (ignored != ignoredMap.end()) { - fprintf(stderr,"ignoring %s\n", address.c_str()); + LogPrint("coindb", "ignoring %s\n", address.c_str()); ignoredAddresses++; continue; } @@ -493,17 +493,14 @@ bool CBlockTreeDB::Snapshot2(std::map &addressAmounts, Un if ( pos == addressAmounts.end() ) { // insert new address + utxo amount - //fprintf(stderr, "inserting new address %s with amount %li\n", address.c_str(), nValue); addressAmounts[address] = nValue; totalAddresses++; } else { // update unspent tally for this address - //fprintf(stderr, "updating address %s with new utxo amount %li\n", address.c_str(), nValue); addressAmounts[address] += nValue; } - //fprintf(stderr,"{\"%s\", %.8f},\n",address.c_str(),(double)nValue/COIN); // total += nValue; utxos++; total += nValue; @@ -517,12 +514,17 @@ bool CBlockTreeDB::Snapshot2(std::map &addressAmounts, Un } catch (const std::exception& e) { - fprintf(stderr, "DONE reading index entries\n"); - break; + // A genuine deserialization/LevelDB error here is NOT normal completion: + // the for-loop's iter->Valid() already handles end-of-iteration, and + // non-address key types are skipped by the chType check above. Swallowing + // the exception and building a snapshot from partial data is wrong. Fail + // like the inner catch, which the author marked consensus-relevant + // ("we need to exit here if so for consensus code!"). + fprintf(stderr, "%s: LevelDB index iteration exception! - %s\n", __func__, e.what()); + return false; } } - //fprintf(stderr, "total=%f, totalAddresses=%li, utxos=%li, ignored=%li\n", (double) total / COIN, totalAddresses, utxos, ignoredAddresses); - + // this is for the snapshot RPC, you can skip this by passing a 0 as the last argument. if (ret) { @@ -675,23 +677,18 @@ bool CBlockTreeDB::LoadBlockIndexGuts() boost::scoped_ptr pcursor(NewIterator()); pcursor->Seek(make_pair(DB_BLOCK_INDEX, uint256())); - //fprintf(stderr,"%s: Seeked cursor to block index\n",__FUNCTION__); // Load mapBlockIndex while (pcursor->Valid()) { - //fprintf(stderr,"%s: Valid cursor\n",__FUNCTION__); boost::this_thread::interruption_point(); std::pair key; if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) { - //fprintf(stderr,"%s: Found DB_BLOCK_INDEX\n",__FUNCTION__); CDiskBlockIndex diskindex; if (pcursor->GetValue(diskindex)) { // Construct block index object - //fprintf(stderr,"%s: Creating CBlockIndex...\n",__FUNCTION__); CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); pindexNew->SetHeight(diskindex.GetHeight()); - //fprintf(stderr,"%s: Setting CBlockIndex height...\n",__FUNCTION__); pindexNew->nFile = diskindex.nFile; pindexNew->nDataPos = diskindex.nDataPos; pindexNew->nUndoPos = diskindex.nUndoPos; @@ -702,14 +699,13 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nTime = diskindex.nTime; pindexNew->nBits = diskindex.nBits; pindexNew->nNonce = diskindex.nNonce; - // the Equihash solution will be loaded lazily from the dbindex entry + // the solution (DragonX RandomX solution) will be loaded lazily from the dbindex entry // pindexNew->nSolution = diskindex.nSolution; pindexNew->nStatus = diskindex.nStatus; pindexNew->nCachedBranchId = diskindex.nCachedBranchId; pindexNew->nTx = diskindex.nTx; pindexNew->nSproutValue = diskindex.nSproutValue; pindexNew->nSaplingValue = diskindex.nSaplingValue; - //fprintf(stderr,"%s: Setting CBlockIndex details...\n",__FUNCTION__); pindexNew->segid = diskindex.segid; pindexNew->nNotaryPay = diskindex.nNotaryPay; pindexNew->nPayments = diskindex.nPayments; @@ -725,7 +721,6 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nFullyShieldedPayments = diskindex.nFullyShieldedPayments; pindexNew->nNotarizations = diskindex.nNotarizations; - //fprintf(stderr,"loadguts ht.%d\n",pindexNew->GetHeight()); // Consistency checks /* CBlockHeader header; diff --git a/src/util.cpp b/src/util.cpp index 29e67f9ce..b9d661260 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -499,27 +499,35 @@ boost::filesystem::path GetDefaultDataDir() if ( SMART_CHAIN_SYMBOL[0] != 0 ) strcpy(symbol,SMART_CHAIN_SYMBOL); else symbol[0] = 0; - // OLD NAMES: - // Windows < Vista: C:\Documents and Settings\Username\Application Data\Komodo - // Windows >= Vista: C:\Users\Username\AppData\Roaming\Komodo - // Mac: ~/Library/Application Support/Komodo - // Unix: ~/.komodo + // DragonX stores its data under a per-chain subdirectory named after + // SMART_CHAIN_SYMBOL (which is "DRAGONX"), so the default datadir resolves + // to (Unix) ~/.hush/DRAGONX, (Mac) ~/Library/Application Support/Hush/DRAGONX, + // or (Windows) %APPDATA%\Hush\DRAGONX. + // + // The "Hush" / "Komodo" parent-directory names below are retained from the + // Hush/Komodo lineage: the ".hush"/"Hush" path is the current location, and + // the ".komodo"/"Komodo" path is only probed as a backward-compatible + // fallback for pre-existing legacy data directories. Do not change these + // string literals -- they determine where node data is read from and written. - // NEW NAMES: + // Current (per-symbol subdirectory lives under these parents): // Windows < Vista: C:\Documents and Settings\Username\Application Data\Hush // Windows >= Vista: C:\Users\Username\AppData\Roaming\Hush // Mac: ~/Library/Application Support/Hush // Unix: ~/.hush - // ~/.hush was actually used by the original 1.x version of Hush, but we will - // only make subdirectories inside of it, so we won't be able to overwrite - // an old wallet.dat from the Ice Ages :) + // Legacy fallback (only used if such a directory already exists): + // Windows < Vista: C:\Documents and Settings\Username\Application Data\Komodo + // Windows >= Vista: C:\Users\Username\AppData\Roaming\Komodo + // Mac: ~/Library/Application Support/Komodo + // Unix: ~/.komodo fs::path pathRet; #ifdef _WIN32 // Windows pathRet = GetSpecialFolderPath(CSIDL_APPDATA) / "Hush" / symbol; - // Always use .hush/HUSH3, if it exists (even if .komodo/HUSH3 exists) + // Always use Hush\ (Hush\DRAGONX) if it exists, even if the legacy + // Komodo\ directory also exists. if(fs::is_directory(pathRet)) { return pathRet; } else { @@ -528,7 +536,7 @@ boost::filesystem::path GetDefaultDataDir() // existing legacy directory, use that for backward compat return pathRet; } else { - // For new clones, use Hush/ACNAME + // For new nodes, use Hush\ pathRet = GetSpecialFolderPath(CSIDL_APPDATA) / "Hush" / symbol; return pathRet; } @@ -551,7 +559,7 @@ boost::filesystem::path GetDefaultDataDir() // create Library/Application Support/Hush if it doesn't exist TryCreateDirectory(tmppath); - // Always use Hush/HUSH3 if it exists + // Always use Hush/ (Hush/DRAGONX) if it exists if(fs::is_directory(tmppath / symbol)) { return tmppath / symbol; } else { @@ -563,16 +571,16 @@ boost::filesystem::path GetDefaultDataDir() // Found legacy dir, use that return tmppath / symbol; } else { - // For new clones, use Hush/ACNAME + // For new nodes, use Hush/ tmppath = pathRet / "Hush" / symbol; } return tmppath; } #else - // Unix - // New directory :) + // Unix: current default datadir is ~/.hush/ (i.e. ~/.hush/DRAGONX) fs::path tmppath = pathRet / ".hush" / symbol; - // Always use .hush/HUSH3, if it exists (even if .komodo/HUSH3 exists) + // Always use ~/.hush/ (~/.hush/DRAGONX) if it exists, even if the + // legacy ~/.komodo/ directory also exists. if(fs::is_directory(tmppath)) { return tmppath; } else { @@ -582,7 +590,7 @@ boost::filesystem::path GetDefaultDataDir() // existing legacy directory, use that for backward compat return tmppath; } else { - // For new clones, use .hush/ACNAME + // For new nodes, use ~/.hush/ tmppath = pathRet / ".hush" / symbol; } return tmppath; @@ -598,13 +606,17 @@ static CCriticalSection csPathCached; static boost::filesystem::path ZC_GetBaseParamsDir() { - // Copied from GetDefaultDataDir and adapted for zcash params. + // Copied from GetDefaultDataDir and adapted for the zk-SNARK parameter files. + // DragonX reuses the upstream Sapling parameter directory layout, so these + // locations retain the historical "ZcashParams" / ".zcash-params" names. Do + // not change these string literals -- they determine where the proving and + // verifying keys are loaded from. namespace fs = boost::filesystem; // Windows < Vista: C:\Documents and Settings\Username\Application Data\ZcashParams // Windows >= Vista: C:\Users\Username\AppData\Roaming\ZcashParams // Mac: ~/Library/Application Support/ZcashParams // Unix: ~/.zcash-params - // Debian packages: /usr/share/hush + // System-wide install (Debian packages): /usr/share/hush fs::path pathRet; #ifdef _WIN32 return GetSpecialFolderPath(CSIDL_APPDATA) / "ZcashParams"; diff --git a/src/version.h b/src/version.h index 89b3fcc6a..68aaccd11 100644 --- a/src/version.h +++ b/src/version.h @@ -22,7 +22,13 @@ // network protocol versioning // DragonX 1.0.0 - bumped to separate from old HUSH/DragonX nodes with RandomX bug -static const int PROTOCOL_VERSION = 2000000; +// DragonX 1.3.0 - bumped for the consensus changes in this release (BLOCK_VALID_CONTEXT +// no longer overwrites the validity level, the dead CBOPRET coinbase +// price check is gone, and pow.cpp lost 157 lines). MIN_PEER_PROTO_VERSION +// is deliberately NOT raised: it currently equals this value, and raising +// it would disconnect every node still on v1.0.3 -- which is all users, +// since no release has ever been installable (unsigned archives). +static const int PROTOCOL_VERSION = 2000001; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; //! In this version, 'getheaders' was introduced. diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp new file mode 100644 index 000000000..4300dc176 --- /dev/null +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -0,0 +1,494 @@ +// 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 +#include "asyncrpcoperation_autoshieldcoinbase.h" +#include "asyncrpcoperation_shieldcoinbase.h" // for ShieldCoinbaseUTXO +#include "consensus/upgrades.h" +#include "hush_defs.h" // ASSETCHAINS_TIMELOCKGTE +#include "init.h" +#include "key_io.h" +#include "main.h" +#include "rpc/protocol.h" +#include "sync.h" +#include "tinyformat.h" +#include "transaction_builder.h" +#include "util.h" +#include "utilmoneystr.h" +#include "wallet.h" + +// Sietch dummy zaddr generator (defined in wallet/rpcwallet.cpp) +extern std::string randomSietchZaddr(); + +// Serialized-size estimates for one spent input (kept in sync with rpcwallet.cpp) +static const size_t AUTOSHIELD_CTXIN_DUST_SIZE = 148; +// Every autoshield tx carries THREE Sapling OutputDescriptions -- the change +// note to destZaddr plus the two Sietch dummies -- at ~948 bytes each. Reserving +// 2000 for "header + sietch outputs" was ~900 bytes short before a single input +// was counted, so a large enough round could build a tx over MAX_TX_SIZE. +static const size_t AUTOSHIELD_SAPLING_OUTPUT_SIZE = 948; +static const size_t AUTOSHIELD_TX_OVERHEAD = (3 * AUTOSHIELD_SAPLING_OUTPUT_SIZE) + 256; +// Hard cap on inputs per round, mirroring z_shieldcoinbase's +// SHIELD_COINBASE_DEFAULT_LIMIT. The byte estimate alone is not a safe bound: +// with a P2PKH coinbase (-mineraddress) the 148-byte figure is exact rather than +// conservative, so an under-estimate translates directly into an oversize tx. +// The remainder is simply shielded on the next round. +static const size_t AUTOSHIELD_MAX_INPUTS = 400; +// Unrelated to the cap above despite sharing the value: this is a SIZE IN BYTES for +// one spent P2SH input, mirroring CTXIN_SPEND_P2SH_SIZE in rpcwallet.cpp. +static const size_t AUTOSHIELD_CTXIN_P2SH_SIZE = 400; + +AsyncRPCOperation_autoshieldcoinbase::AsyncRPCOperation_autoshieldcoinbase(int targetHeight) + : targetHeight_(targetHeight) {} + +AsyncRPCOperation_autoshieldcoinbase::~AsyncRPCOperation_autoshieldcoinbase() {} + +void AsyncRPCOperation_autoshieldcoinbase::main() { + if (isCancelled()) { + // Only the CURRENT op owns the scheduler flag; a stale/cancelled op must + // not clear it out from under a freshly-enqueued successor. + if (pwalletMain) { + LOCK(pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingAutoShieldOperationId) { + pwalletMain->fAutoShieldRunning = false; + } + } + return; + } + + set_state(OperationStatus::EXECUTING); + start_execution_clock(); + + bool success = false; + + try { + success = main_impl(); + } catch (...) { + set_error_from_current_exception(); + } + + stop_execution_clock(); + + // ALWAYS advance the interval and clear the running flag, on success AND + // failure AND exception, so a failed/oversized/locked round still lets the + // next round fire. Only the CURRENT op does this bookkeeping: if a newer op + // has already superseded this one, leave its state untouched. + if (pwalletMain) { + LOCK2(cs_main, pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingAutoShieldOperationId) { + int tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + pwalletMain->nextAutoShield = pwalletMain->autoShieldInterval + tipHeight; + pwalletMain->fAutoShieldRunning = false; + } + } + + set_state(success ? OperationStatus::SUCCESS : OperationStatus::FAILED); + setResult(); + + LogPrintf("%s: autoshield operation finished (status=%s, txs=%d, shielded=%s)\n", + getId(), getStateAsString(), numTxCreated_, FormatMoney(amountShielded_)); +} + +// Read-only half of destination resolution, shared with init.cpp so the answer to +// "where will auto-shielding send?" is available before the first round runs rather +// than only after one has fired. Mutates nothing: no key generation, no caching. +AutoShieldDestStatus ResolveAutoShieldDestinationReadOnly( + libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut, uint32_t& accountOut) { + accountOut = AUTOSHIELD_ACCOUNT_NONE; + + // 1. Explicit -autoshieldaddress override (validated as a spendable Sapling + // zaddr in init.cpp). This doubles as the per-process cache for whatever + // the derivation below resolved on an earlier round. + if (!pwalletMain->autoShieldAddress.empty()) { + auto decoded = DecodePaymentAddress(pwalletMain->autoShieldAddress); + if (boost::get(&decoded) != nullptr) { + destOut = boost::get(decoded); + destStrOut = pwalletMain->autoShieldAddress; + return AutoShieldDestStatus::Resolved; + } + return AutoShieldDestStatus::InvalidOverride; + } + + // 2. Walk the restore window m/32'/coin'/[0, gap)' derived from the seed. + HDSeed seed; + if (!pwalletMain->GetHDSeedForDerivation(seed)) { + return AutoShieldDestStatus::NoSeed; + } + + // Mirror init.cpp's own clamp, and cap into the hardened index space so + // (i | ZIP32_HARDENED_KEY_LIMIT) below stays well formed. + int64_t gapArg = GetArg("-mnemonicsaplinggap", 100); + if (gapArg < 0) { + gapArg = 0; + } + if (gapArg > (int64_t)ZIP32_HARDENED_KEY_LIMIT) { + gapArg = (int64_t)ZIP32_HARDENED_KEY_LIMIT; + } + const uint32_t saplingGap = (uint32_t)gapArg; + + // Same derivation path as CWallet::GenerateNewSaplingZKey (wallet.cpp:139-152). + const uint32_t bip44CoinType = Params().BIP44CoinType(); + auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); + auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); + auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); + + for (uint32_t i = 0; i < saplingGap; i++) { + auto xsk = m_32h_cth.Derive(i | ZIP32_HARDENED_KEY_LIMIT); + auto addr = xsk.DefaultAddress(); + + // Spendable AND registered: GetSaplingExtendedSpendingKey resolves + // addr -> ivk -> fvk -> spending key (keystore.cpp:215-223), so a hit + // means the wallet both recognises notes sent to `addr` and can spend + // them. Exactly the pair of properties the shield needs. Lowest index + // wins: stable for the life of the wallet and reproducible from the seed + // alone, unlike std::set order over the random diversifier + // (zcash/Address.hpp:95-98). + libzcash::SaplingExtendedSpendingKey held; + if (pwalletMain->GetSaplingExtendedSpendingKey(addr, held)) { + destOut = addr; + destStrOut = EncodePaymentAddress(addr); + accountOut = i; + return AutoShieldDestStatus::Resolved; + } + } + + return AutoShieldDestStatus::NotFound; +} + +// Resolve the Sapling destination for auto-shielded coinbase. +// +// Recoverability is the hard requirement: coinbase we shield must land in an +// address that a bare -mnemonic/-hdseed restore of THIS wallet's seed re-derives +// on its own. A restore pre-derives exactly -mnemonicsaplinggap sapling accounts +// starting at index 0, with saplingAccountCounter reset to 0 (init.cpp:2349-2355), +// so the only self-recoverable destinations are the default addresses of +// m/32'/'/i' for i < gap. +// +// We therefore DERIVE those accounts from the seed and pick the lowest index the +// wallet already holds. Deriving is the only authoritative test. In particular +// CKeyMetadata is NOT evidence of provenance: z_importkey / z_importwallet copy +// both hdKeypath and seedFp verbatim out of the import source +// (wallet.cpp:5522-5529 <- rpcdump.cpp:511-516), so a foreign key can claim any +// keypath and any seed fingerprint. Filtering on metadata would let an imported +// key win as "account 0" and silently receive every shielded reward. +// +// Caller must hold cs_wallet and must already have checked the wallet is unlocked. +bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination( + libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut) { + + uint32_t account = AUTOSHIELD_ACCOUNT_NONE; + switch (ResolveAutoShieldDestinationReadOnly(destOut, destStrOut, account)) { + case AutoShieldDestStatus::Resolved: + // Cache for the life of the process; the override branch of the resolver + // short-circuits later rounds. Safe: we only cache post-validation. + pwalletMain->autoShieldAddress = destStrOut; + if (account == AUTOSHIELD_ACCOUNT_NONE) { + LogPrintf("%s: autoshield destination %s (configured)\n", getId(), destStrOut); + } else { + LogPrintf("%s: autoshield destination %s (seed-derived sapling account %u)\n", + getId(), destStrOut, (unsigned)account); + } + return true; + case AutoShieldDestStatus::InvalidOverride: + LogPrintf("%s: configured -autoshieldaddress is not a valid Sapling address\n", getId()); + return false; + case AutoShieldDestStatus::NoSeed: + LogPrintf("%s: no HD seed available; refusing to pick an autoshield destination\n", getId()); + return false; + case AutoShieldDestStatus::NotFound: + break; // nothing in the window yet: fall through and derive one + } + + // Re-establish the derivation context the resolver used, for step 3 below. + HDSeed seed; + if (!pwalletMain->GetHDSeedForDerivation(seed)) { + LogPrintf("%s: no HD seed available; refusing to pick an autoshield destination\n", getId()); + return false; + } + int64_t gapArg = GetArg("-mnemonicsaplinggap", 100); + if (gapArg < 0) { + gapArg = 0; + } + if (gapArg > (int64_t)ZIP32_HARDENED_KEY_LIMIT) { + gapArg = (int64_t)ZIP32_HARDENED_KEY_LIMIT; + } + const uint32_t saplingGap = (uint32_t)gapArg; + const uint32_t bip44CoinType = Params().BIP44CoinType(); + auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); + auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT); + auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT); + + // 3. Nothing usable in the window yet: derive the next account, but only if + // GenerateNewSaplingZKey will land INSIDE the window. It does NOT derive + // at saplingAccountCounter: its do/while skips every index whose spending + // key we already hold (wallet.cpp:150-157), so a bare "counter < gap" + // test is unsound - counter 98 with gap 100 can still land on 100. + // Predict min{ i >= counter : we do not hold i } instead. + const uint32_t counter = pwalletMain->GetHDChain().saplingAccountCounter; + uint32_t predicted = saplingGap; // sentinel: "would land outside the window" + for (uint32_t i = counter; i < saplingGap; i++) { + auto xsk = m_32h_cth.Derive(i | ZIP32_HARDENED_KEY_LIMIT); + if (!pwalletMain->HaveSaplingSpendingKey(xsk.expsk.full_viewing_key())) { + predicted = i; + break; + } + } + + if (predicted == saplingGap) { + LogPrintf("%s: no free sapling account below -mnemonicsaplinggap=%u (account counter is %u). " + "A newly derived z-address would NOT be re-derived by a seed restore, so the " + "shielded coinbase could not be recovered from the seed alone. Refusing to " + "autoshield this round. Fix: point -autoshieldaddress at an existing in-gap " + "wallet z-address, or raise -mnemonicsaplinggap here AND use the same value on " + "any future restore.\n", + getId(), (unsigned)saplingGap, (unsigned)counter); + return false; + } + + if (pwalletMain->IsLocked()) { + LogPrintf("%s: wallet is locked; cannot derive an autoshield destination z-address\n", getId()); + return false; + } + + try { + auto expectedAddr = m_32h_cth.Derive(predicted | ZIP32_HARDENED_KEY_LIMIT).DefaultAddress(); + libzcash::SaplingPaymentAddress newAddr = pwalletMain->GenerateNewSaplingZKey(); + + // Post-verify rather than trust the prediction: cheap, and it closes the + // whole class of "the counter moved further than expected" bugs. + if (!(newAddr == expectedAddr)) { + LogPrintf("%s: newly derived z-address is not sapling account %u as predicted " + "(counter %u -> %u); not using it as the autoshield destination\n", + getId(), (unsigned)predicted, (unsigned)counter, + (unsigned)pwalletMain->GetHDChain().saplingAccountCounter); + return false; + } + + destOut = newAddr; + destStrOut = EncodePaymentAddress(newAddr); + pwalletMain->autoShieldAddress = destStrOut; + LogPrintf("%s: generated new autoshield destination z-address %s (seed-derived sapling account %u)\n", + getId(), destStrOut, (unsigned)predicted); + return true; + } catch (const std::exception& e) { + LogPrintf("%s: could not generate a destination z-address: %s\n", getId(), e.what()); + return false; + } +} + +bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { + auto opid = getId(); + LogPrintf("%s: Beginning asyncrpcoperation_autoshieldcoinbase.\n", opid); + auto consensusParams = Params().GetConsensus(); + + int tipHeight; + { + LOCK(cs_main); + tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + } + + // Don't create a tx that could be mined before, but expire after, a NU + // activation. Key this off tipHeight (the height we actually set the expiry + // from below), not the stale enqueue-time targetHeight_, so a queue delay + // cannot slip a straddling expiry past this guard. + auto nextActivationHeight = NextActivationHeight(tipHeight, consensusParams); + if (nextActivationHeight && tipHeight + AUTO_OP_EXPIRY_DELTA >= nextActivationHeight.get()) { + LogPrintf("%s: autoshield tx could expire across a NU activation. Skipping this round.\n", opid); + return true; + } + + libzcash::SaplingPaymentAddress destZaddr; + std::string destStr; + std::vector inputs; + + // Proof building below runs WITHOUT cs_wallet (deliberately, so wallet RPCs + // are not stalled), which leaves a multi-second window in which a manual + // z_shieldcoinbase or z_sendmany over the same miner address would re-select + // these same coinbase outputs. AvailableCoins honours IsLockedCoin, so lock + // them for the duration exactly as z_shieldcoinbase does. RAII because there + // are several early returns between here and commit, and a leaked lock would + // silently exclude those coins from every future round. + struct ScopedCoinLocks { + std::vector locked; + ~ScopedCoinLocks() { + // A destructor is noexcept by default; letting the lock acquisition + // escape would turn a contended mutex into std::terminate. + try { + if (locked.empty()) return; + LOCK2(cs_main, pwalletMain->cs_wallet); + // UnlockCoin takes a non-const reference (upstream signature). + for (COutPoint& op : locked) pwalletMain->UnlockCoin(op); + } catch (...) {} + } + } coinLocks; + CAmount shieldedValue = 0; + unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + // Defensive: the scheduler already skips while locked, but the wallet + // could have been locked between enqueue and execution. + if (pwalletMain->IsLocked()) { + LogPrintf("%s: wallet is locked, skipping autoshield round\n", opid); + return true; + } + + if (!resolveDestination(destZaddr, destStr)) { + LogPrintf("%s: no spendable destination z-address available, skipping\n", opid); + return true; + } + + // Gather matured, spendable coinbase UTXOs, byte-capped to a single tx. + // AvailableCoins excludes immature coinbase unconditionally (wallet.cpp, + // `IsCoinBase() && GetBlocksToMaturity() > 0`) and only ever returns outputs + // we own, so external -mineraddress / pool coinbase yields zero inputs. The + // second argument here is fOnlyConfirmed, not fOnlySpendable. + size_t estimatedTxSize = AUTOSHIELD_TX_OVERHEAD; + std::vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true); + for (const COutput& out : vecOutputs) { + if (!out.fSpendable || !out.tx->IsCoinBase()) { + continue; + } + CTxDestination address; + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { + continue; + } + size_t increase = (boost::get(&address) != nullptr) + ? AUTOSHIELD_CTXIN_P2SH_SIZE : AUTOSHIELD_CTXIN_DUST_SIZE; + if (inputs.size() >= AUTOSHIELD_MAX_INPUTS) { + LogPrintf("%s: reached per-round input cap (%d); deferring remaining coinbase to next round\n", + opid, (int)AUTOSHIELD_MAX_INPUTS); + break; + } + if (estimatedTxSize + increase >= max_tx_size) { + // Size-safe batch; the remainder is shielded next round. + LogPrintf("%s: reached per-tx size cap; deferring remaining coinbase to next round\n", opid); + break; + } + estimatedTxSize += increase; + ShieldCoinbaseUTXO utxo = { out.tx->GetHash(), out.i, + out.tx->vout[out.i].scriptPubKey, + out.tx->vout[out.i].nValue }; + inputs.push_back(utxo); + shieldedValue += out.tx->vout[out.i].nValue; + } + + for (const ShieldCoinbaseUTXO& t : inputs) { + COutPoint outpt(t.txid, t.vout); + pwalletMain->LockCoin(outpt); + coinLocks.locked.push_back(outpt); + } + } + + CAmount fee = pwalletMain->autoShieldFee; + + if (inputs.size() < (size_t)pwalletMain->autoShieldMinUtxos) { + LogPrintf("%s: %d matured coinbase utxo(s) < min %d, skipping this round\n", + opid, (int)inputs.size(), pwalletMain->autoShieldMinUtxos); + return true; + } + if (shieldedValue <= fee) { + LogPrintf("%s: matured coinbase value %s <= fee %s, skipping\n", + opid, FormatMoney(shieldedValue), FormatMoney(fee)); + return true; + } + + // Common outgoing viewing key derived from the HD seed, exactly as + // z_shieldcoinbase does for t->z (keeps the note recoverable). + HDSeed seed; + if (!pwalletMain->GetHDSeedForDerivation(seed)) { + LogPrintf("%s: HD seed not available, skipping\n", opid); + return true; + } + uint256 ovk = ovkForShieldingFromTaddr(seed); + + // Build the t->z shield tx. Proof generation happens in Build() WITHOUT + // holding cs_wallet (mirrors the sweep op) so we don't stall wallet RPCs. + // tipHeight, not targetHeight_: the builder's height selects the consensus + // branch id (transaction_builder.cpp CurrentEpochBranchId), and the NU-straddle + // guard above plus SetExpiryHeight below are both keyed off tipHeight. Using the + // stale enqueue-time height here meant the guard was checking a height the + // transaction was not actually signed against. + auto builder = TransactionBuilder(consensusParams, tipHeight, pwalletMain); + builder.SetExpiryHeight(tipHeight + AUTO_OP_EXPIRY_DELTA); + builder.SetFee(fee); + + for (const auto& t : inputs) { + if (t.amount >= ASSETCHAINS_TIMELOCKGTE) { + builder.SetLockTime((uint32_t)tipHeight); + builder.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount, 0xfffffffe); + } else { + builder.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount); + } + } + + // All input value (less fee) goes back to our own z-address as change. + builder.SendChangeTo(destZaddr, ovk); + + // Sietch padding: mirror z_shieldcoinbase's two dummy zouts so autoshield + // txs are structurally indistinguishable from manual coinbase shields. + for (int i = 0; i < 2; i++) { + auto zdust = DecodePaymentAddress(randomSietchZaddr()); + if (IsValidPaymentAddress(zdust)) { + builder.AddSaplingOutput(ovk, boost::get(zdust), 0); + } + } + + auto maybe_tx = builder.Build(); + if (!maybe_tx) { + LogPrintf("%s: Failed to build autoshield transaction.\n", opid); + return false; + } + CTransaction tx = maybe_tx.get(); + + if (isCancelled()) { + LogPrintf("%s: Cancelled before commit.\n", opid); + return false; + } + + if (pwalletMain->CommitAutomatedTx(tx)) { + LogPrintf("%s: shielded %s coinbase (%d utxos) into %s via txid=%s\n", + opid, FormatMoney(shieldedValue - fee), (int)inputs.size(), + destStr, tx.GetHash().ToString()); + amountShielded_ += shieldedValue - fee; + shieldTxIds_.push_back(tx.GetHash().ToString()); + numTxCreated_++; + return true; + } + + LogPrintf("%s: autoshield tx FAILED in CommitTransaction, txid=%s\n", opid, tx.GetHash().ToString()); + return false; +} + +void AsyncRPCOperation_autoshieldcoinbase::setResult() { + UniValue res(UniValue::VOBJ); + res.push_back(Pair("num_tx_created", numTxCreated_)); + res.push_back(Pair("amount_shielded", FormatMoney(amountShielded_))); + UniValue txIds(UniValue::VARR); + for (const std::string& txId : shieldTxIds_) { + txIds.push_back(txId); + } + res.push_back(Pair("shield_txids", txIds)); + set_result(res); +} + +void AsyncRPCOperation_autoshieldcoinbase::cancel() { + // Cancelling is how the scheduler stops an in-flight round, so unlike the base + // class this must be able to move an EXECUTING operation to CANCELLED. What it + // must not do is overwrite a state that is already terminal: the scheduler + // cancels the previous operation when it enqueues the next one, and that one may + // have already SUCCEEDED, whose result would otherwise be relabelled as cancelled. + if (isSuccess() || isFailed() || isCancelled()) + return; + set_state(OperationStatus::CANCELLED); +} + +UniValue AsyncRPCOperation_autoshieldcoinbase::getStatus() const { + UniValue v = AsyncRPCOperation::getStatus(); + UniValue obj = v.get_obj(); + obj.push_back(Pair("method", "autoshieldcoinbase")); + obj.push_back(Pair("target_height", targetHeight_)); + return obj; +} diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.h b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h new file mode 100644 index 000000000..bb21f09ec --- /dev/null +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h @@ -0,0 +1,75 @@ +// 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 +#ifndef ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H +#define ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H + +#include "amount.h" +#include "asyncrpcoperation.h" +#include "univalue.h" +#include "zcash/Address.hpp" +#include "zcash/zip32.h" + +// Sentinel for "not a derived account" (i.e. the configured -autoshieldaddress). +static const uint32_t AUTOSHIELD_ACCOUNT_NONE = UINT32_MAX; + +enum class AutoShieldDestStatus { + Resolved, // destOut/destStrOut are set + NotFound, // no in-gap account held yet; the operation will derive one + InvalidOverride, // -autoshieldaddress is set but is not a Sapling address + NoSeed, // no HD seed available (e.g. locked wallet) +}; + +// Resolve the auto-shield destination WITHOUT mutating the wallet: the configured +// -autoshieldaddress if set, else the lowest in-gap seed-derived account the wallet +// already holds. It deliberately does NOT generate a key, so init can call it purely +// to answer "where will this send?" -- deriving a fresh account as a side effect of +// populating a status field would be wrong. The operation's own resolveDestination +// falls through to generation when this returns NotFound. +// Caller must hold cs_wallet. +AutoShieldDestStatus ResolveAutoShieldDestinationReadOnly( + libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut, uint32_t& accountOut); + +// A periodic, wallet-local operation that drains matured *transparent* coinbase +// UTXOs into a wallet-owned Sapling z-address in size-bounded batches. It is the +// automatic sibling of the manual z_shieldcoinbase RPC and mirrors the dispatch +// model of AsyncRPCOperation_sweep (self-gathers on the async worker thread, +// commits via CWallet::CommitAutomatedTx). It never mints a transparent output, +// so it respects the ac_private=1 transparent-output ban, and it deliberately +// does NOT toggle mining (unlike z_shieldcoinbase) so it can run every interval +// on a mining node without thrashing the miner. +class AsyncRPCOperation_autoshieldcoinbase : public AsyncRPCOperation +{ +public: + AsyncRPCOperation_autoshieldcoinbase(int targetHeight); + virtual ~AsyncRPCOperation_autoshieldcoinbase(); + + // We don't want to be copied or moved around + AsyncRPCOperation_autoshieldcoinbase(AsyncRPCOperation_autoshieldcoinbase const&) = delete; + AsyncRPCOperation_autoshieldcoinbase(AsyncRPCOperation_autoshieldcoinbase&&) = delete; + AsyncRPCOperation_autoshieldcoinbase& operator=(AsyncRPCOperation_autoshieldcoinbase const&) = delete; + AsyncRPCOperation_autoshieldcoinbase& operator=(AsyncRPCOperation_autoshieldcoinbase&&) = delete; + + virtual void main(); + virtual void cancel(); + virtual UniValue getStatus() const; + +private: + int targetHeight_; + int numTxCreated_ = 0; + CAmount amountShielded_ = 0; + std::vector shieldTxIds_; + + bool main_impl(); + + // Resolve a spendable, wallet-owned Sapling destination: the configured + // -autoshieldaddress if set, else the first spendable z-addr the wallet + // holds, else a freshly generated one (requires an unlocked wallet). + // Returns false if none is available (e.g. locked wallet with no z-addr). + bool resolveDestination(libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut); + + void setResult(); +}; + +#endif /* ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H */ diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.cpp b/src/wallet/asyncrpcoperation_mergetoaddress.cpp index 6d92a44ad..dea3b8e11 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.cpp +++ b/src/wallet/asyncrpcoperation_mergetoaddress.cpp @@ -133,23 +133,8 @@ void AsyncRPCOperation_mergetoaddress::main() try { success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); + set_error_from_current_exception(); } #ifdef ENABLE_MINING diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 755564492..6096d3d19 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -19,7 +19,12 @@ CAmount fConsolidationTxFee = DEFAULT_CONSOLIDATION_FEE; bool fConsolidationMapUsed = false; -const int CONSOLIDATION_EXPIRY_DELTA = 15; + +// Number of Sietch dummy ("zdust") shielded outputs added to every consolidation +// transaction to obscure the real output and keep the anonymity set large. This is +// a wallet privacy-tuning parameter (not a consensus rule); the sweep operation uses +// the same value under the name ZOUTS. +static const int MIN_ZOUTS = 7; extern string randomSietchZaddr(); @@ -28,8 +33,17 @@ AsyncRPCOperation_saplingconsolidation::AsyncRPCOperation_saplingconsolidation(i AsyncRPCOperation_saplingconsolidation::~AsyncRPCOperation_saplingconsolidation() {} void AsyncRPCOperation_saplingconsolidation::main() { - if (isCancelled()) + if (isCancelled()) { + // Only the current op owns the scheduler flag; a stale/cancelled op must + // not clear it out from under a freshly-enqueued successor. + if (pwalletMain) { + LOCK(pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingConsolidationOperationId) { + pwalletMain->fConsolidationRunning = false; + } + } return; + } set_state(OperationStatus::EXECUTING); start_execution_clock(); @@ -38,24 +52,8 @@ void AsyncRPCOperation_saplingconsolidation::main() { try { success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); + set_error_from_current_exception(); } stop_execution_clock(); @@ -76,6 +74,21 @@ void AsyncRPCOperation_saplingconsolidation::main() { LogPrintf("%s", s); unlock_notes(); // clean up LogPrint("zrpc", "%s: consolidation input notes unlocked\n", getId()); + + // Advance the interval and clear the running flag on EVERY terminal state + // (success, failure, exception) so consolidation runs once per interval + // instead of every block, and a failed round still lets the next one fire. + // Only the CURRENT op does this bookkeeping. This fixes the pre-existing + // wedge where nextConsolidation never advanced and fConsolidationRunning + // was never set/reset. + if (pwalletMain) { + LOCK2(cs_main, pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingConsolidationOperationId) { + int tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + pwalletMain->nextConsolidation = pwalletMain->consolidationInterval + tipHeight; + pwalletMain->fConsolidationRunning = false; + } + } } bool AsyncRPCOperation_saplingconsolidation::main_impl() { @@ -83,8 +96,18 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { auto opid=getId(); LogPrintf("%s: Beginning AsyncRPCOperation_saplingconsolidation\n", opid); auto consensusParams = Params().GetConsensus(); - auto nextActivationHeight = NextActivationHeight(targetHeight_, consensusParams); - if (nextActivationHeight && targetHeight_ + CONSOLIDATION_EXPIRY_DELTA >= nextActivationHeight.get()) { + int tipHeight; + { + LOCK(cs_main); + tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + } + + // Build and expire against tipHeight (execution-time), not the stale + // enqueue-time targetHeight_, so the builder's consensus-branch selection and + // the NU-straddle guard agree with the height the tx is signed for. Mirrors + // the autoshield op (commit 65130c312). + auto nextActivationHeight = NextActivationHeight(tipHeight, consensusParams); + if (nextActivationHeight && tipHeight + AUTO_OP_EXPIRY_DELTA >= nextActivationHeight.get()) { LogPrintf("%s: Consolidation txs would be created before a NU activation but may expire after. Skipping this round.\n",opid); setConsolidationResult(0, 0, std::vector()); return status; @@ -165,8 +188,8 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { if (fromNotes.size() < minQuantity) continue; - auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain); - builder.SetExpiryHeight(targetHeight_ + CONSOLIDATION_EXPIRY_DELTA); + auto builder = TransactionBuilder(consensusParams, tipHeight, pwalletMain); + builder.SetExpiryHeight(tipHeight + AUTO_OP_EXPIRY_DELTA); auto actualAmountToSend = amountToSend < fConsolidationTxFee ? 0 : amountToSend - fConsolidationTxFee; LogPrintf("%s: %s Beginning to create transaction with Sapling output amount=%s\n", __func__, opid, FormatMoney(actualAmountToSend)); @@ -206,10 +229,10 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { builder.AddSaplingOutput(extsk.expsk.ovk, addr, actualAmountToSend); LogPrint("zrpcunsafe", "%s: Added consolidation output %s with amount=%li\n", opid, addr.GetHash().ToString().c_str(), actualAmountToSend); - // Add sietch zouts - int MIN_ZOUTS = 7; + // Add sietch zouts: MIN_ZOUTS dummy zero-value shielded outputs to + // randomly-generated z-addresses, so the consolidation tx does not + // shrink the anonymity set. for(size_t i = 0; i < MIN_ZOUTS; i++) { - // In Privacy Zdust We Trust -- Duke string zdust = randomSietchZaddr(); auto zaddr = DecodePaymentAddress(zdust); if (IsValidPaymentAddress(zaddr)) { @@ -281,6 +304,13 @@ void AsyncRPCOperation_saplingconsolidation::setConsolidationResult(int numTxCre } void AsyncRPCOperation_saplingconsolidation::cancel() { + // Cancelling is how the scheduler stops an in-flight round, so unlike the base + // class this must be able to move an EXECUTING operation to CANCELLED. What it + // must not do is overwrite a state that is already terminal: the scheduler + // cancels the previous operation when it enqueues the next one, and that one may + // have already SUCCEEDED, whose result would otherwise be relabelled as cancelled. + if (isSuccess() || isFailed() || isCancelled()) + return; set_state(OperationStatus::CANCELLED); } diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index d7e42bcd6..adbc78f40 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -155,23 +155,8 @@ void AsyncRPCOperation_sendmany::main() { try { success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); + set_error_from_current_exception(); } unlock_notes(); @@ -215,9 +200,10 @@ bool AsyncRPCOperation_sendmany::main_impl() { bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0); CAmount minersFee = fee_; - // TODO: fix this garbage ZEC prisoner mindset bullshit - // When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere - // and if there are multiple zaddrs, we don't know where to send it. + // Coinbase-change routing constraint: + // When spending coinbase UTXOs, only a single zaddr recipient may be specified, because the + // change must be routed somewhere and with multiple zaddr recipients there is no unambiguous + // destination for it. See the isSingleZaddrOutput / isMultipleZaddrOutput handling below. if (isfromtaddr_) { if (isSingleZaddrOutput) { bool b = find_utxos(true); @@ -325,14 +311,12 @@ bool AsyncRPCOperation_sendmany::main_impl() { CScript scriptPubKey; for (auto t : t_inputs_) { scriptPubKey = GetScriptForDestination(std::get<4>(t)); - //printf("Checking new script: %s\n", scriptPubKey.ToString().c_str()); uint256 txid = std::get<0>(t); int vout = std::get<1>(t); CAmount amount = std::get<2>(t); builder_.AddTransparentInput(COutPoint(txid, vout), scriptPubKey, amount); } // for other chains, set locktime to spend time locked coinbases - //builder_.SetLockTime((uint32_t)chainActive.Tip()->GetMedianTimePast()); } else { CMutableTransaction rawTx(tx_); for (SendManyInputUTXO & t : t_inputs_) { @@ -342,7 +326,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { CTxIn in(COutPoint(txid, vout)); rawTx.vin.push_back(in); } - //rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); tx_ = CTransaction(rawTx); } } @@ -357,8 +340,8 @@ bool AsyncRPCOperation_sendmany::main_impl() { /** - * SCENARIO #0 (All HUSH and Hush Arrakis Chains) - * Sprout not involved, so we just use the TransactionBuilder and we're done. + * SCENARIO #0 (DragonX and all Sapling-only chains) + * Sprout is not involved, so we just use the TransactionBuilder and we're done. * We added the transparent inputs to the builder earlier. */ if (isUsingBuilder_) { @@ -416,7 +399,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { } // Fetch Sapling anchor and witnesses - //LogPrintf("%s: Gathering anchors and witnesses\n", __FUNCTION__); uint256 anchor; std::vector> witnesses; { @@ -510,7 +492,8 @@ bool AsyncRPCOperation_sendmany::main_impl() { return true; } // END SCENARIO #0 - // No other scenarios, because Hush developers are elite. + // No other scenarios: DragonX is Sapling-only (Sprout removed), so the builder path above + // handles every supported case. Reaching here means the builder was not used, which is unexpected. return false; } @@ -625,7 +608,6 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { continue; } - //printf("%s\n", boost::apply_visitor(AddressVisitorString(), dest).c_str()); if (!destinations.count(dest)) { continue; } @@ -673,7 +655,8 @@ void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() { rawTx.vout.push_back(out); } if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + // Pre-hardfork: set nLockTime slightly in the past so the tx is immediately spendable. + rawTx.nLockTime = (uint32_t)time(NULL) - 60; else rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); @@ -703,7 +686,8 @@ void AsyncRPCOperation_sendmany::add_taddr_change_output_to_tx(CBitcoinAddress * CMutableTransaction rawTx(tx_); rawTx.vout.push_back(out); if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) ) - rawTx.nLockTime = (uint32_t)time(NULL) - 60; // jl777 + // Pre-hardfork: set nLockTime slightly in the past so the tx is immediately spendable. + rawTx.nLockTime = (uint32_t)time(NULL) - 60; else rawTx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast(); tx_ = CTransaction(rawTx); diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp index 4e69fc23f..16e175340 100644 --- a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp @@ -68,7 +68,7 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase( throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Empty inputs"); } - if (donation < 0 || donation > 10 ) { + if (donation > 10 ) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid donation percentage, must be an integer between 0 and 10 inclusive"); } @@ -116,23 +116,8 @@ void AsyncRPCOperation_shieldcoinbase::main() { try { success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); + set_error_from_current_exception(); } #ifdef ENABLE_MINING diff --git a/src/wallet/asyncrpcoperation_sweep.cpp b/src/wallet/asyncrpcoperation_sweep.cpp index 166548a71..75a5b20c2 100644 --- a/src/wallet/asyncrpcoperation_sweep.cpp +++ b/src/wallet/asyncrpcoperation_sweep.cpp @@ -19,7 +19,6 @@ extern string randomSietchZaddr(); CAmount fSweepTxFee = DEFAULT_SWEEP_FEE; bool fSweepMapUsed = false; -const int SWEEP_EXPIRY_DELTA = 15; boost::optional rpcSweepAddress; AsyncRPCOperation_sweep::AsyncRPCOperation_sweep(int targetHeight, bool fromRpc) : targetHeight_(targetHeight), fromRPC_(fromRpc){} @@ -27,8 +26,17 @@ AsyncRPCOperation_sweep::AsyncRPCOperation_sweep(int targetHeight, bool fromRpc) AsyncRPCOperation_sweep::~AsyncRPCOperation_sweep() {} void AsyncRPCOperation_sweep::main() { - if (isCancelled()) + if (isCancelled()) { + // Only the current op owns the scheduler flag; a stale/cancelled op must + // not clear it out from under a freshly-enqueued successor. + if (pwalletMain) { + LOCK(pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingSweepOperationId) { + pwalletMain->fSweepRunning = false; + } + } return; + } set_state(OperationStatus::EXECUTING); start_execution_clock(); @@ -37,23 +45,8 @@ void AsyncRPCOperation_sweep::main() { try { success = main_impl(); - } catch (const UniValue& objError) { - int code = find_value(objError, "code").get_int(); - std::string message = find_value(objError, "message").get_str(); - set_error_code(code); - set_error_message(message); - } catch (const runtime_error& e) { - set_error_code(-1); - set_error_message("runtime error: " + string(e.what())); - } catch (const logic_error& e) { - set_error_code(-1); - set_error_message("logic error: " + string(e.what())); - } catch (const exception& e) { - set_error_code(-1); - set_error_message("general exception: " + string(e.what())); } catch (...) { - set_error_code(-2); - set_error_message("unknown error"); + set_error_from_current_exception(); } stop_execution_clock(); @@ -64,6 +57,23 @@ void AsyncRPCOperation_sweep::main() { set_state(OperationStatus::FAILED); } + // Scheduler bookkeeping, done here so it runs on success AND failure AND + // exception (main_impl's terminal code is skipped when it throws). Only the + // current op mutates scheduler state. Preserves the "keep draining every + // block until swept" model: on a successful-but-incomplete round we leave + // fSweepRunning set and nextSweep unadvanced so the next block continues. + // On completion OR on failure/exception we release fSweepRunning and back + // off one interval — critically, a persistently failing sweep no longer + // leaves fSweepRunning stuck true and wedges consolidation + autoshield. + if (pwalletMain) { + LOCK2(cs_main, pwalletMain->cs_wallet); + if (getId() == pwalletMain->saplingSweepOperationId && (!success || sweepComplete_)) { + int tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + pwalletMain->nextSweep = pwalletMain->sweepInterval + tipHeight; + pwalletMain->fSweepRunning = false; + } + } + std::string s = strprintf("%s: Sweep operation finished. (status=%s", getId(), getStateAsString()); if (success) { s += strprintf(", success)\n"); @@ -86,7 +96,7 @@ bool IsExcludedAddress(libzcash::SaplingPaymentAddress zaddr) { } } else { // This is an invalid sapling zaddr - LogPrintf("%s: Invalid zsweepexclude zaddr %s, ignoring\n", sweepExcludeAddress); + LogPrintf("%s: Invalid zsweepexclude zaddr %s, ignoring\n", __func__, sweepExcludeAddress); continue; } @@ -100,10 +110,21 @@ bool AsyncRPCOperation_sweep::main_impl() { auto opid=getId(); LogPrintf("%s: Beginning asyncrpcoperation_sweep.\n", getId()); auto consensusParams = Params().GetConsensus(); - auto nextActivationHeight = NextActivationHeight(targetHeight_, consensusParams); - if (nextActivationHeight && targetHeight_ + SWEEP_EXPIRY_DELTA >= nextActivationHeight.get()) { + int tipHeight; + { + LOCK(cs_main); + tipHeight = (chainActive.Tip() != NULL) ? chainActive.Tip()->GetHeight() : targetHeight_; + } + + // Key the NU-straddle guard and the tx builder/expiry off tipHeight (the + // height we actually build and expire against), not the stale enqueue-time + // targetHeight_, so a queue delay cannot slip a straddling expiry past this + // guard. Mirrors the autoshield op (commit 65130c312). + auto nextActivationHeight = NextActivationHeight(tipHeight, consensusParams); + if (nextActivationHeight && tipHeight + AUTO_OP_EXPIRY_DELTA >= nextActivationHeight.get()) { LogPrintf("%s: Sweep txs would be created before a NU activation but may expire after. Skipping this round.\n", getId()); setSweepResult(0, 0, std::vector()); + sweepComplete_ = true; // nothing to do this round; back nextSweep off one interval instead of re-dispatching every block return true; } @@ -232,11 +253,8 @@ bool AsyncRPCOperation_sweep::main_impl() { fee = 0; } - auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain); - { - LOCK2(cs_main, pwalletMain->cs_wallet); - builder.SetExpiryHeight(chainActive.Tip()->GetHeight()+ SWEEP_EXPIRY_DELTA); - } + auto builder = TransactionBuilder(consensusParams, tipHeight, pwalletMain); + builder.SetExpiryHeight(tipHeight + AUTO_OP_EXPIRY_DELTA); LogPrintf("%s: Beginning creating transaction with Sapling output amount=%s\n", getId(), FormatMoney(amountToSend - fee)); // Select Sapling notes @@ -314,10 +332,11 @@ bool AsyncRPCOperation_sweep::main_impl() { } } - if (sweepComplete) { - pwalletMain->nextSweep = pwalletMain->sweepInterval + chainActive.Tip()->GetHeight(); - pwalletMain->fSweepRunning = false; - } + // Record whether the wallet is fully swept; the scheduler bookkeeping + // (advancing nextSweep / clearing fSweepRunning) is done in main() so it + // also runs on the failure/exception/cancel paths and cannot wedge the + // shared fSweepRunning flag (which now also gates consolidation + autoshield). + sweepComplete_ = sweepComplete; LogPrintf("%s: Created %d transactions with total output amount=%s, status=%d\n", getId(), numTxCreated, FormatMoney(amountSwept), (int)status); setSweepResult(numTxCreated, amountSwept, sweepTxIds); @@ -337,6 +356,13 @@ void AsyncRPCOperation_sweep::setSweepResult(int numTxCreated, const CAmount& am } void AsyncRPCOperation_sweep::cancel() { + // Cancelling is how the scheduler stops an in-flight round, so unlike the base + // class this must be able to move an EXECUTING operation to CANCELLED. What it + // must not do is overwrite a state that is already terminal: the scheduler + // cancels the previous operation when it enqueues the next one, and that one may + // have already SUCCEEDED, whose result would otherwise be relabelled as cancelled. + if (isSuccess() || isFailed() || isCancelled()) + return; set_state(OperationStatus::CANCELLED); } diff --git a/src/wallet/asyncrpcoperation_sweep.h b/src/wallet/asyncrpcoperation_sweep.h index 5779e4021..254b32f3e 100644 --- a/src/wallet/asyncrpcoperation_sweep.h +++ b/src/wallet/asyncrpcoperation_sweep.h @@ -34,6 +34,10 @@ public: private: int targetHeight_; bool fromRPC_; + // Set by main_impl(): true iff there was nothing left to sweep this round. + // Read by main() to decide scheduler bookkeeping. Defaults false so an + // exception (which skips main_impl's assignment) is treated as "not done". + bool sweepComplete_ = false; bool main_impl(); diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index f3674c776..fa29dba19 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -24,7 +24,10 @@ #include #include #include -// TODO: these are not set correctly by wolfssl for some reason. Ja bless. +// Enable wolfSSL timing-resistant ECC and TFM (fastmath) code paths, which +// harden against timing side-channels. wolfSSL gates these purely with #ifdef, +// so the defined value is immaterial (any value enables the feature); we do not +// rely on 420 meaning anything. #undef ECC_TIMING_RESISTANT #undef TFM_TIMING_RESISTANT #define ECC_TIMING_RESISTANT 420 @@ -159,6 +162,34 @@ static bool DecryptHDSeed( return seed.Fingerprint() == seedFp; } +uint256 MnemonicEntropyFingerprint(const RawHDSeed& entropy) +{ + // The local copy is not gratuitous -- see the declaration in crypter.h. + // It is secure_allocator-backed, so it is memory_cleanse()d on destruction + // (support/allocators/secure.h:45-52). + RawHDSeed tmp(entropy); + return HDSeed(tmp).Fingerprint(); +} + +static bool DecryptMnemonicEntropy( + const CKeyingMaterial& vMasterKey, + const std::vector& vchCryptedSecret, + const uint256& entropyFp, + RawHDSeed& entropyOut) +{ + CKeyingMaterial vchSecret; + + // Use the entropy's fingerprint as IV, mirroring DecryptHDSeed above. + if (!DecryptSecret(vMasterKey, vchCryptedSecret, entropyFp, vchSecret)) + return false; + + // RawHDSeed and CKeyingMaterial are the SAME type (both are + // std::vector>), so this is a + // plain copy of the same bytes, not a reinterpretation. + entropyOut = vchSecret; + return MnemonicEntropyFingerprint(entropyOut) == entropyFp; +} + static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) { CKeyingMaterial vchSecret; @@ -233,6 +264,19 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) keyPass = true; } } + // Deliberately NO arm here for cryptedMnemonicEntropy. This function is + // the "some keys decrypt but not all" corruption detector and a keyFail + // ends at the assert(false) below. The mnemonic entropy is an optional, + // non-spending, display-only record: legacy wallets, hex-restored + // wallets and every wallet predating this feature legitimately have a + // seed and no entropy, and a wallet whose every key decrypts while its + // entropy does not is not corrupt in any sense that should abort the + // process -- it simply cannot print its seed phrase. It is decrypted + // lazily in GetMnemonicEntropy() instead, so that case becomes a false + // return from one RPC while derivation and spending (which read the + // seed, not the entropy) carry on. Note the arm above caches nothing + // either -- `seed` is discarded; it only votes keyPass/keyFail -- so + // nothing is lost by omitting one here. CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { @@ -265,7 +309,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) } if (keyPass && keyFail) { - LogPrintf("Oh shit! The wallet is probably corrupted: Some keys decrypt but not all.\n"); + LogPrintf("The wallet is probably corrupted: some keys decrypt but not all.\n"); assert(false); } if (keyFail || !keyPass) @@ -344,6 +388,82 @@ bool CCryptoKeyStore::GetHDSeed(HDSeed& seedOut) const return DecryptHDSeed(vMasterKey, cryptedHDSeed.second, cryptedHDSeed.first, seedOut); } +bool CCryptoKeyStore::SetMnemonicEntropy(const RawHDSeed& entropy) +{ + { + LOCK(cs_SpendingKeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::SetMnemonicEntropy(entropy); + } + + if (IsLocked()) + return false; + + if (entropy.empty()) + return false; + + std::vector vchCryptedSecret; + // Use the entropy's fingerprint as IV + // TODO: Handle this properly when we make encryption a supported feature + auto entropyFp = MnemonicEntropyFingerprint(entropy); + // RawHDSeed IS CKeyingMaterial, so `entropy` binds directly here. + if (!EncryptSecret(vMasterKey, entropy, entropyFp, vchCryptedSecret)) + return false; + + // Virtual: this calls into CWallet to store the crypted entropy to disk. + if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) + return false; + } + return true; +} + +bool CCryptoKeyStore::SetCryptedMnemonicEntropy( + const uint256& entropyFp, + const std::vector& vchCryptedSecret) +{ + { + LOCK(cs_SpendingKeyStore); + if (!IsCrypted()) { + return false; + } + + if (!cryptedMnemonicEntropy.first.IsNull()) { + // Don't allow existing entropy to be changed, mirroring + // SetCryptedHDSeed: a phrase that no longer matches the installed + // seed is worse than no phrase at all. + return false; + } + + cryptedMnemonicEntropy = std::make_pair(entropyFp, vchCryptedSecret); + } + return true; +} + +bool CCryptoKeyStore::HaveMnemonicEntropy() const +{ + LOCK(cs_SpendingKeyStore); + if (!IsCrypted()) + return CBasicKeyStore::HaveMnemonicEntropy(); + + return !cryptedMnemonicEntropy.second.empty(); +} + +bool CCryptoKeyStore::GetMnemonicEntropy(RawHDSeed& entropyOut) const +{ + LOCK(cs_SpendingKeyStore); + if (!IsCrypted()) + return CBasicKeyStore::GetMnemonicEntropy(entropyOut); + + if (cryptedMnemonicEntropy.second.empty()) + return false; + + // Decrypted lazily, on demand, and deliberately NOT in Unlock(): see the + // comment there for why the entropy must not vote in the keyPass/keyFail + // corruption detector. + return DecryptMnemonicEntropy(vMasterKey, cryptedMnemonicEntropy.second, + cryptedMnemonicEntropy.first, entropyOut); +} + bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { { @@ -505,6 +625,30 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) } hdSeed = HDSeed(); } + if (!mnemonicEntropy.empty()) { + { + std::vector vchCryptedSecret; + // Use the entropy's fingerprint as IV + // TODO: Handle this properly when we make encryption a supported feature + auto entropyFp = MnemonicEntropyFingerprint(mnemonicEntropy); + if (!EncryptSecret(vMasterKeyIn, mnemonicEntropy, entropyFp, vchCryptedSecret)) { + return false; + } + // Virtual: calls into CWallet to store the crypted entropy to disk. + if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) { + return false; + } + } + // Drop the plaintext. swap() rather than `= RawHDSeed()`: assigning a + // shorter vector destroys the elements but KEEPS the capacity, so the + // old bytes would linger in the locked buffer. swap() hands the buffer + // to a temporary whose destructor deallocates it, and + // secure_allocator::deallocate memory_cleanse()s + // (support/allocators/secure.h:45-52). The `hdSeed = HDSeed();` above + // has the same weakness but cannot be fixed here: HDSeed's raw vector + // is private with no swap accessor (zip32.h:23-33). + RawHDSeed().swap(mnemonicEntropy); + } BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys) { const CKey &key = mKey.second; diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index cb459fe07..d79a1b34b 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -138,6 +138,21 @@ public: } }; +/** Keystore which keeps the private keys encrypted. + * It derives from the basic key store, which is used if no encryption is active. + */ +//! Fingerprint of a BIP39 entropy blob, computed exactly as HDSeed::Fingerprint +//! does (BLAKE2b, ZCASH_HD_SEED_FP_PERSONAL). It is an IV / integrity tag and a +//! wallet.dat record key -- never a key-derivation input. Declared here rather +//! than duplicated because three call sites must produce identical bytes: +//! CCryptoKeyStore::SetMnemonicEntropy, CCryptoKeyStore::EncryptKeys, and +//! CWallet::SetMnemonicEntropy (which keys the plaintext record with it). +//! +//! It takes a copy internally on purpose: HDSeed's constructor takes a NON-const +//! RawHDSeed& (zip32.h:28), so HDSeed(entropy).Fingerprint() does not compile +//! against a const reference or a member read from a const method. +uint256 MnemonicEntropyFingerprint(const RawHDSeed& entropy); + /** Keystore which keeps the private keys encrypted. * It derives from the basic key store, which is used if no encryption is active. */ @@ -145,13 +160,17 @@ class CCryptoKeyStore : public CBasicKeyStore { private: std::pair> cryptedHDSeed; + // Encrypted mnemonic entropy, shaped exactly like cryptedHDSeed above: + // .first is the entropy's fingerprint (AES IV + integrity tag on decrypt), + // .second is the ciphertext. + std::pair> cryptedMnemonicEntropy; CryptedKeyMap mapCryptedKeys; - //CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys; CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys; CKeyingMaterial vMasterKey; - //! if fUseCrypto is true, mapKeys, mapSproutSpendingKeys, and mapSaplingSpendingKeys must be empty + //! if fUseCrypto is true, mapKeys and mapSaplingSpendingKeys must be empty + //! (Sprout was removed; the former mapSproutSpendingKeys no longer exists) //! if fUseCrypto is false, vMasterKey must be empty bool fUseCrypto; @@ -194,6 +213,14 @@ public: bool SetHDSeed(const HDSeed& seed); bool HaveHDSeed() const; bool GetHDSeed(HDSeed& seedOut) const; + //! Mnemonic entropy, mirroring the four HD-seed members above. + //! SetCryptedMnemonicEntropy MUST stay virtual for the same reason + //! SetCryptedHDSeed is: CWallet overrides it to persist the record, and + //! SetMnemonicEntropy() below reaches that override through the vtable. + virtual bool SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector &vchCryptedSecret); + bool SetMnemonicEntropy(const RawHDSeed& entropy); + bool HaveMnemonicEntropy() const; + bool GetMnemonicEntropy(RawHDSeed& entropyOut) const; virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 14b0dacc7..7eb29e42c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -95,7 +95,7 @@ UniValue convertpassphrase(const UniValue& params, bool fHelp, const CPubKey& my "1. \"agamapassphrase\" (string, required) Agama passphrase\n" "\nResult:\n" "\"agamapassphrase\": \"agamapassphrase\", (string) Agama passphrase you entered\n" - "\"address\": \"hushaddress\", (string) Address corresponding to your passphrase\n" + "\"address\": \"dragonxaddress\", (string) Address corresponding to your passphrase\n" "\"pubkey\": \"publickeyhex\", (string) The hex value of the raw public key\n" "\"privkey\": \"privatekeyhex\", (string) The hex value of the raw private key\n" "\"wif\": \"wif\" (string) The private key in WIF format to use with 'importprivkey'\n" @@ -196,9 +196,9 @@ UniValue getrescaninfo(const UniValue& params, bool fHelp, const CPubKey& mypk) auto startHeight = pwalletMain->rescanStartHeight; auto currentHeight = chainActive.Height(); // if current height is 0, progress=1 since there is nothing to rescan - char progress[8]; + char progress[16]; if (currentHeight != 0) { - sprintf(progress, "%.4f", (double) rescanHeight / (double) currentHeight ); + snprintf(progress, sizeof(progress), "%.4f", (double) rescanHeight / (double) currentHeight ); ret.push_back(Pair("rescan_progress", progress)); } ret.push_back(Pair("rescan_start_height", startHeight)); @@ -255,10 +255,10 @@ UniValue importprivkey(const UniValue& params, bool fHelp, const CPubKey& mypk) if (fHelp || params.size() < 1 || params.size() > 5) throw runtime_error( - "importprivkey \"hushprivkey\" ( \"label\" rescan height secret_key)\n" + "importprivkey \"dragonxprivkey\" ( \"label\" rescan height secret_key)\n" "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" "\nArguments:\n" - "1. \"hushprivkey\" (string, required) The private key (see dumpprivkey)\n" + "1. \"dragonxprivkey\" (string, required) The private key (see dumpprivkey)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "4. height (integer, optional, default=0) start at block height?\n" @@ -305,7 +305,7 @@ UniValue importprivkey(const UniValue& params, bool fHelp, const CPubKey& mypk) if (params.size() > 4) { - auto secret_key = AmountFromValue(params[4])/100000000; + secret_key = AmountFromValue(params[4])/100000000; key = DecodeCustomSecret(strSecret, secret_key); } else { key = DecodeSecret(strSecret); @@ -378,7 +378,7 @@ UniValue importaddress(const UniValue& params, bool fHelp, const CPubKey& mypk) std::vector data(ParseHex(params[0].get_str())); script = CScript(data.begin(), data.end()); } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address or script"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid DragonX address or script"); } string strLabel = ""; @@ -504,7 +504,7 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys if (vstr.size() < 2) continue; - // Let's see if the address is a valid Hush spending key + // Let's see if the address is a valid DragonX spending key if (fImportZKeys) { auto spendingkey = DecodeSpendingKey(vstr[0]); int64_t nTime = DecodeDumpTime(vstr[1]); @@ -524,7 +524,7 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys continue; } else { LogPrintf("%s: Importing detected an error: invalid spending key. Trying as a transparent key...\n",__func__); - // Not a valid spending key, so carry on and see if it's a Hush transparent address + // Not a valid spending key, so carry on and see if it's a DragonX transparent address } } @@ -585,25 +585,27 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys } } - if (fRescan) { - CBlockIndex *pindex = chainActive.LastTip(); - while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200) - pindex = pindex->pprev; + // A failed key/address add must surface on BOTH paths; previously this + // check lived inside the fRescan block, so importwallet with rescan=false + // silently reported success even when some keys failed to import. + if (!fGood) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); - LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->GetHeight() + 1); - if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) - pwalletMain->nTimeFirstKey = nTimeBegin; - pwalletMain->ScanForWalletTransactions(pindex); - pwalletMain->MarkDirty(); + if (fRescan) { + CBlockIndex *pindex = chainActive.LastTip(); + while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200) + pindex = pindex->pprev; - if (!fGood) - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); + LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->GetHeight() + 1); + if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) + pwalletMain->nTimeFirstKey = nTimeBegin; + pwalletMain->ScanForWalletTransactions(pindex); + pwalletMain->MarkDirty(); + } else { + LogPrintf("Importwallet without rescan successful\n"); + } - return NullUniValue; } - - else{ - LogPrintf("Importwallet without Rescan successfull\n"); - return NullUniValue;} + return NullUniValue; } @@ -659,7 +661,7 @@ UniValue z_exportwallet(const UniValue& params, bool fHelp, const CPubKey& mypk) "z_exportwallet \"filename\"\n" "\nExports all wallet keys, for taddr and zaddr, in a human-readable format. Overwriting an existing file is not permitted.\n" "\nArguments:\n" - "1. \"filename\" (string, required) The filename, saved in folder set by hushd -exportdir option\n" + "1. \"filename\" (string, required) The filename, saved in folder set by dragonxd -exportdir option\n" "\nResult:\n" "\"path\" (string) The full path of the destination file\n" "\nExamples:\n" @@ -680,7 +682,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp, const CPubKey& mypk) "dumpwallet \"filename\"\n" "\nDumps taddr wallet keys in a human-readable format. Overwriting an existing file is not permitted.\n" "\nArguments:\n" - "1. \"filename\" (string, required) The filename, saved in folder set by hushd -exportdir option\n" + "1. \"filename\" (string, required) The filename, saved in folder set by dragonxd -exportdir option\n" "\nResult:\n" "\"path\" (string) The full path of the destination file\n" "\nExamples:\n" @@ -736,7 +738,7 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) std::sort(vKeyBirth.begin(), vKeyBirth.end()); // produce output - file << strprintf("# Wallet dump created by Hush %s (%s)\n", CLIENT_BUILD); + file << strprintf("# Wallet dump created by DragonX %s (%s)\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); @@ -744,10 +746,17 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys) HDSeed hdSeed; // Dump the 64-byte derivation seed (for mnemonic wallets this is the // expanded BIP39 seed), so re-importing the hex reproduces the same keys. - pwalletMain->GetHDSeedForDerivation(hdSeed); - auto rawSeed = hdSeed.RawSeed(); - file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex()); - file << "\n"; + // The return MUST be checked: on failure hdSeed is default-constructed, and emitting it + // anyway writes a blank seed next to a legitimate-looking BLAKE2b-of-empty fingerprint -- + // a backup that looks valid and restores nothing. The per-key dump below is still a + // complete backup without this line. + if (pwalletMain->GetHDSeedForDerivation(hdSeed)) { + auto rawSeed = hdSeed.RawSeed(); + file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex()); + file << "\n"; + } else { + file << "# HDSeed unavailable (wallet locked, no HD seed, or hdchain unproven)\n"; + } } file << "\n"; for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { @@ -1039,8 +1048,10 @@ UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& myp "\nReveal the wallet's BIP39 seed phrase (24 words).\n" "The phrase is byte-compatible with SilentDragonXLite: the same words\n" "restore the same transparent and shielded addresses in either wallet.\n" - "Only works for wallets created or restored from a mnemonic (see the\n" - "-mnemonic and -usemnemonic options). Requires the wallet be unlocked.\n" + "New wallets get a seed phrase by default (-usemnemonic=0 opts out);\n" + "wallets restored with -mnemonic have one too. Wallets created before\n" + "this feature, or from a raw -hdseed, have no phrase -- use\n" + "z_exportwallet for those. Requires the wallet be unlocked.\n" "\nResult:\n" "{\n" " \"mnemonic\" : \"word1 ... word24\", (string) the BIP39 seed phrase\n" @@ -1229,7 +1240,6 @@ UniValue nspv_listtransactions(const UniValue& params, bool fHelp, const CPubKey CCflag = atoi((char *)params[1].get_str().c_str()); if ( params.size() == 3 ) skipcount = atoi((char *)params[2].get_str().c_str()); - //fprintf(stderr,"call txids cc.%d skip.%d\n",CCflag,skipcount); return(NSPV_addresstxids((char *)params[0].get_str().c_str(),CCflag,skipcount,0)); } else throw runtime_error("nspv_listtransactions [address [isCC [skipcount]]]\n"); @@ -1292,7 +1302,6 @@ UniValue nspv_spend(const UniValue& params, bool fHelp, const CPubKey& mypk) if ( NSPV_address.size() == 0 ) throw runtime_error("to nspv_send you need an active nspv_login\n"); satoshis = atof(params[1].get_str().c_str())*COIN + 0.0000000049; - //fprintf(stderr,"satoshis.%lld from %.8f\n",(long long)satoshis,atof(params[1].get_str().c_str())); if ( satoshis < 1000 ) throw runtime_error("amount too small\n"); return(NSPV_spend((char *)NSPV_address.c_str(),(char *)params[0].get_str().c_str(),satoshis)); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ddc40484b..0ea4fa1df 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -364,7 +364,7 @@ UniValue setaccount(const UniValue& params, bool fHelp, const CPubKey& mypk) CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid DragonX address!"); } string strAccount; @@ -411,7 +411,7 @@ UniValue getaccount(const UniValue& params, bool fHelp, const CPubKey& mypk) CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid DragonX address!"); } std::string strAccount; @@ -496,7 +496,6 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr // Check amount if (nValue <= 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); -//fprintf(stderr,"nValue %.8f vs curBalance %.8f\n",(double)nValue/COIN,(double)curBalance/COIN); if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); @@ -518,9 +517,7 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr for (i=0; i= 3 ) { flags = atoi(params[2].get_str().c_str()); - //printf("flags.%d (%s) n.%d\n",flags,params[2].get_str().c_str(),n); } else flags = 0; if ( n >= 4 ) privkey = hush_kvprivkey(&pubkey,(char *)(n >= 4 ? params[3].get_str().c_str() : "password")); @@ -704,15 +700,13 @@ UniValue kvupdate(const UniValue& params, bool fHelp, const CPubKey& mypk) if ( hush_kvsigverify(keyvalue,keylen+refvaluesize,refpubkey,sig) < 0 ) { ret.push_back(Pair("error",(char *)"error verifying sig, passphrase is probably wrong")); - printf("VERIFY ERROR\n"); + LogPrintf("VERIFY ERROR\n"); return ret; - } // else printf("verified immediately\n"); + } } } - //for (i=0; i<32; i++) - // printf("%02x",((uint8_t *)&sig)[i]); - //printf(" sig for keylen.%d + valuesize.%d\n",keylen,refvaluesize); - ret.push_back(Pair("coin",(char *)(SMART_CHAIN_SYMBOL[0] == 0 ? "HUSH3" : SMART_CHAIN_SYMBOL))); + // The SMART_CHAIN_SYMBOL[0]==0 fallback is dead on DragonX (symbol is always "DRAGONX"); kept for defensiveness. + ret.push_back(Pair("coin",(char *)(SMART_CHAIN_SYMBOL[0] == 0 ? "DRAGONX" : SMART_CHAIN_SYMBOL))); height = chainActive.LastTip()->GetHeight(); if ( memcmp(&zeroes,&refpubkey,sizeof(refpubkey)) != 0 ) ret.push_back(Pair("owner",refpubkey.GetHex())); @@ -749,9 +743,6 @@ UniValue kvupdate(const UniValue& params, bool fHelp, const CPubKey& mypk) } if ( (opretlen= hush_opreturnscript(opretbuf,'K',keyvalue,coresize)) == 40 ) opretlen++; - //for (i=0; imapAddressBook.count(r.destination)) account = pwalletMain->mapAddressBook[r.destination].name; if (fAllAccounts || (account == strAccount)) @@ -1972,9 +1962,8 @@ UniValue listtransactions(const UniValue& params, bool fHelp, const CPubKey& myp CWalletTx *const pwtx = (*it).second.first; if (pwtx != 0) { - //fprintf(stderr,"pwtx iter.%d %s\n",(int32_t)pwtx->nOrderPos,pwtx->GetHash().GetHex().c_str()); ListTransactions(*pwtx, strAccount, 0, true, ret, filter); - } //else fprintf(stderr,"null pwtx\n"); + } CAccountingEntry *const pacentry = (*it).second.second; if (pacentry != 0) AcentryToJSON(*pacentry, strAccount, ret); @@ -2558,7 +2547,7 @@ UniValue encryptwallet(const UniValue& params, bool fHelp, const CPubKey& mypk) // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: StartShutdown(); - return "wallet encrypted; Hush server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; + return "wallet encrypted; DragonX server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; } UniValue lockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) @@ -2866,7 +2855,7 @@ UniValue listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) " \"txid\" : \"txid\", (string) the transaction id \n" " \"vout\" : n, (numeric) the vout value\n" " \"generated\" : true|false (boolean) true if txout is a coinbase transaction output\n" - " \"address\" : \"address\", (string) the Hush address\n" + " \"address\" : \"address\", (string) the DragonX address\n" " \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n" " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction amount in " + CURRENCY_UNIT + "\n" @@ -2900,7 +2889,7 @@ UniValue listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) const UniValue& input = inputs[idx]; CTxDestination dest = DecodeDestination(input.get_str()); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Hush address: ") + input.get_str()); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid DragonX address: ") + input.get_str()); } if (!destinations.insert(dest).second) { throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); @@ -2959,7 +2948,6 @@ UniValue listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); CBlockIndex *tipindex,*pindex = it->second; uint32_t locktime; - //fprintf(stderr,"nValue %.8f pindex.%p tipindex.%p locktime.%u txheight.%d pindexht.%d\n",(double)nValue/COIN,pindex,chainActive.LastTip(),locktime,txheight,pindex->GetHeight()); } else if ( chainActive.LastTip() != 0 ) txheight = (chainActive.LastTip()->GetHeight() - out.nDepth - 1); @@ -3345,6 +3333,83 @@ UniValue z_sweepstatus(const UniValue& params, bool fHelp, const CPubKey& mypk) return ret; } +UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() > 0) + throw runtime_error( + "z_autoshieldstatus\n" + "\nReport the state of automatic coinbase shielding: whether it is on, where it sends,\n" + "and -- when it is off -- why.\n" + "\nResult:\n" + "{\n" + " \"autoshield\" : true|false, (boolean) whether auto-shielding is enabled\n" + " \"running\" : true|false, (boolean) whether a round is in flight\n" + " \"next_autoshield\" : n, (numeric) height of the next round\n" + " \"autoshieldinterval\" : n, (numeric) blocks between rounds\n" + " \"autoshieldaddress\" : \"zaddr\", (string) resolved destination; empty until first resolved\n" + " \"autoshieldfee\" : n, (numeric) fee in puposhis\n" + " \"autoshieldminutxos\" : n, (numeric) minimum matured coinbase utxos per round\n" + " \"hdseedorigin\" : n, (numeric) 0 unrecorded, 1 created, 2 restored, 3 retrofit, 4 unknown\n" + " \"hdseedorigin_desc\" : \"...\", (string) readable form of hdseedorigin\n" + " \"seed_recoverable\" : true|false, (boolean) whether a seed phrase can be exported\n" + " \"disabled_reason\" : \"...\" (string) why auto-shielding is not running, if it is not\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("z_autoshieldstatus", "") + + HelpExampleRpc("z_autoshieldstatus", "") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("autoshield", pwalletMain->fAutoShieldEnabled)); + ret.push_back(Pair("running", pwalletMain->fAutoShieldRunning)); + ret.push_back(Pair("next_autoshield", pwalletMain->nextAutoShield)); + ret.push_back(Pair("autoshieldinterval", pwalletMain->autoShieldInterval)); + ret.push_back(Pair("autoshieldaddress", pwalletMain->autoShieldAddress)); + ret.push_back(Pair("autoshieldfee", pwalletMain->autoShieldFee)); + ret.push_back(Pair("autoshieldminutxos", pwalletMain->autoShieldMinUtxos)); + + int origin = pwalletMain->hdSeedOrigin; + std::string desc; + switch (origin) { + case CWallet::HDSEED_ORIGIN_CREATED: desc = "created on an empty wallet"; break; + case CWallet::HDSEED_ORIGIN_RESTORED: desc = "restored from -mnemonic/-hdseed"; break; + case CWallet::HDSEED_ORIGIN_RETROFIT: desc = "retrofitted onto a pre-existing wallet"; break; + case CWallet::HDSEED_ORIGIN_UNKNOWN: desc = "predates provenance recording"; break; + default: desc = "not yet recorded"; break; + } + ret.push_back(Pair("hdseedorigin", origin)); + ret.push_back(Pair("hdseedorigin_desc", desc)); + ret.push_back(Pair("seed_recoverable", pwalletMain->IsMnemonicSeed())); + + // Say why it is off. A silent "false" is exactly what made the destination + // un-inspectable in the first place. + std::string why = ""; + if (!pwalletMain->fAutoShieldEnabled) { + if (origin != CWallet::HDSEED_ORIGIN_CREATED && origin != CWallet::HDSEED_ORIGIN_RESTORED) + why = "HD seed origin is not known-recoverable; back the seed up and pass -autoshield=1"; + else + why = "disabled by -autoshield=0"; + } else if (pwalletMain->IsLocked()) { + why = "wallet is locked; rounds are skipped until it is unlocked"; + } else if (pwalletMain->fSweepRunning || pwalletMain->fConsolidationRunning) { + // Autoshield is mutually exclusive with sweep and consolidation. Without + // this the RPC reports autoshield=true, running=false and an empty + // reason while no round can actually start. + why = strprintf("deferred while %s is running; rounds resume when it finishes", + pwalletMain->fSweepRunning ? "z_sweep" : "sapling consolidation"); + } else if (pwalletMain->autoShieldAddress.empty()) { + why = "no destination resolved yet; one will be derived from the HD seed on the first round"; + } + ret.push_back(Pair("disabled_reason", why)); + + return ret; +} + UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey&) { if (!EnsureWalletIsAvailable(fHelp)) @@ -3359,7 +3424,7 @@ UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey& "This function is slow if no filters are given, use z_listreceivedbyaddress if you do not need filters." "\n" "\nArguments:\n" - "1. \"hushaddress:\" (string, required) \n" + "1. \"dragonxaddress:\" (string, required) \n" "\n" "2. \"Minimum Confimations:\" (numeric, optional, default=0) \n" "\n" @@ -3396,13 +3461,13 @@ UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey& " \"walletconflicts\": [conflicts], An array of wallet conflicts\n" " \"recieved\": { A list of receives from the transaction\n" " \"transparentReceived\": [{ An Array of txos received for transparent addresses\n" - " \"address\": \"hushaddress\", (string) Hush transparent address (t-address)\n" + " \"address\": \"dragonxaddress\", (string) DragonX transparent address (t-address)\n" " \"scriptPubKey\": \"script\", (string) Script for the transparent address (t-address)\n" " \"amount\": x.xxxx, (numeric) Value of output being received " + CURRENCY_UNIT + ", positive for receives\n" " \"vout\": : n, (numeric) the vout value\n" " }],\n" " \"saplingReceived\": [{ An Array of utxos/notes received for sapling addresses\n" - " \"address\": \"hushaddress\", (string) Shielded address (z-address)\n" + " \"address\": \"dragonxaddress\", (string) Shielded address (z-address)\n" " \"amount\": x.xxxx, (numeric) Value of output being received " + CURRENCY_UNIT + ", positive for receives\n" " \"memo\": xxxxx, (string) hexademical string representation of memo field\n" " \"memoStr\" : \"memo\", (string) Only returned if memo contains valid UTF-8 text.\n" @@ -3535,12 +3600,12 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) if (fHelp || params.size() > 5 || params.size() == 3) throw runtime_error( "z_listsentbyaddress\n" - "\nReturns decrypted Hush outputs sent to a single address.\n" + "\nReturns decrypted DragonX outputs sent to a single address.\n" "\n" "This function only returns information on addresses sent from wallet addresses with full spending keys." "\n" "\nArguments:\n" - "1. \"hushaddress:\" (string, required) \n" + "1. \"dragonxaddress:\" (string, required) \n" "\n" "2. \"Minimum Confimations:\" (numeric, optional, default=0) \n" "\n" @@ -3578,13 +3643,13 @@ UniValue z_listsentbyaddress(const UniValue& params, bool fHelp,const CPubKey&) " \"sends\": { A list of outputs of where funds were sent to in the transaction,\n" " only available if the transaction has valid sends (inputs) belonging to the wallet\n" " \"transparentSends\": [{ An Array of spends (outputs) for transparent addresses of the receipient\n" - " \"address\": \"hushaddress\", (string) Hush transparent address (t-address)\n" - " \"scriptPubKey\": \"script\", (string) Script for the Hush transparent address (t-address)\n" + " \"address\": \"dragonxaddress\", (string) DragonX transparent address (t-address)\n" + " \"scriptPubKey\": \"script\", (string) Script for the DragonX transparent address (t-address)\n" " \"amount\": x.xxxx, (numeric) Value of output being sent " + CURRENCY_UNIT + ", negative for sends\n" " \"vout\": : n, (numeric) the vout value\n" " }],\n" " \"saplingSends\": [{ An Array of spends (outputs) for sapling addresses\n" - " \"address\": \"hushaddress\", (string) Hush sapling address (z-address) of the receipient\n" + " \"address\": \"dragonxaddress\", (string) DragonX sapling address (z-address) of the receipient\n" " \"amount\": x.xxxx, (numeric) Value of output being sent" + CURRENCY_UNIT + ", negative for sends\n" " \"memo\": xxxxx, (string) hexademical string representation of memo field\n" " \"memoStr\" : \"memo\", (string) Only returned if memo contains valid UTF-8 text.\n" @@ -4213,7 +4278,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) string address = o.get_str(); auto zaddr = DecodePaymentAddress(address); if (!IsValidPaymentAddress(zaddr)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid Hush zaddr: ") + address); + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid DragonX zaddr: ") + address); } auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr); if (!fIncludeWatchonly && !hasSpendingKey) { @@ -4598,7 +4663,6 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp, const CPubK obj.push_back(Pair("outindex", (int)entry.op.n)); obj.push_back(Pair("rawconfirmations", entry.confirmations)); auto wtx = pwalletMain->mapWallet.at(entry.op.hash); //.ToString()); - //fprintf(stderr,"%s: txid=%s not found in wallet!\n", __func__, entry.op.hash.ToString().c_str()); obj.push_back(Pair("time", wtx.GetTxTime())); obj.push_back(Pair("confirmations", dpowconfs)); @@ -4757,7 +4821,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& my // getbalance and "getbalance * 1 true" should return the same number // but they don't because wtx.GetAmounts() does not handle tx where there are no outputs // pwalletMain->GetBalance() does not accept min depth parameter - // so we use our own method to get balance of utxos, lulzwtfbbq + // so we use our own method to get balance of utxos CAmount nBalance = getBalanceTaddr("", nMinDepth, !fIncludeWatchonly); CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth, !fIncludeWatchonly); CAmount nTotalBalance = nBalance + nPrivateBalance; @@ -4793,7 +4857,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my " \"rk\" : \"rk\", (string) The rk\n" " \"zkproof\" : \"zkproof\", (string) Hexadecimal string representation of raw zksnark proof\n" " \"outputPrev\" : n, (numeric) the index of the output within the vShieldedOutput\n" - " \"address\" : \"zcashaddress\", (string) The Hush shielded address involved in the transaction\n" + " \"address\" : \"dragonxaddress\", (string) The DragonX shielded address involved in the transaction\n" " \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"valueZat\" : xxxx (numeric) The amount in puposhis\n" " }\n" @@ -4803,7 +4867,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my " {\n" " \"type\" : \"sapling\", (string) The type of address\n" " \"output\" : n, (numeric) the index of the output within the vShieldedOutput\n" - " \"address\" : \"hushaddress\", (string) The Hush address involved in the transaction\n" + " \"address\" : \"dragonxaddress\", (string) The DragonX address involved in the transaction\n" " \"outgoing\" : true|false (boolean) True if the output is not for an address in the wallet\n" " \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"valueZat\" : xxxx (numeric) The amount in puposhis\n" @@ -5043,186 +5107,96 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO #define CTXIN_SPEND_DUST_SIZE 148 #define CTXOUT_REGULAR_SIZE 34 -UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) +// Resolve the special fromaddress "z" (spend from any zaddr) to a concrete zaddr: +// gather this wallet's Sapling notes, pick a random zaddr whose confirmed balance +// covers the total outputs + fee. Extracted verbatim from z_sendmany (behavior- +// preserving); throws JSONRPCError when no single zaddr has enough funds. +static std::string SelectAnyZaddrSource(const UniValue& outputs, const UniValue& params) { - if (!EnsureWalletIsAvailable(fHelp)) - return NullUniValue; + // TODO: refactor this and z_getbalances to use common code + std::set zaddrs = {}; + std::set saplingzaddrs = {}; + pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs); - if (fHelp || params.size() < 2 || params.size() > 5) - throw runtime_error( - "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n" - "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." - "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." - "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." - + HelpRequiringPassphrase() + "\n" - "\nArguments:\n" - "1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from. Use 'z' to spend from any zaddr.\n" - "2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n" - " [{\n" - " \"address\":address (string, required) The address is a taddr or zaddr\n" - " \"amount\":amount (numeric, required) The amount to send this address\n" - " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" - " }, ... ]\n" - "3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n" - "4. fee (numeric, optional, default=" - + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" - "5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n" - "\nResult:\n" - "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" - "\nExamples:\n" - + HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'") - + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]") - + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'") - + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]") - + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") - + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") - ); + zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end()); - LOCK2(cs_main, pwalletMain->cs_wallet); + int nMinDepth = 1; + std::vector saplingEntries; + pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth); - // Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz - THROW_IF_SYNCING(HUSH_INSYNC); - - // Check that the from address is valid. - auto fromaddress = params[0].get_str(); - bool fromTaddr = false; - bool fromSapling = false; - uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); - UniValue outputs = params[1].get_array(); - - if (outputs.size()==0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); - - // TODO: implement special symbolic fromaddrs - // TODO: list of (zaddr,amount) - // "z" => spend from any zaddr - // "t" => spend from any taddr - // "*" => spend from any addr, zaddrs first - if(fromaddress == "z") { - // TODO: refactor this and z_getbalances to use common code - std::set zaddrs = {}; - std::set saplingzaddrs = {}; - pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs); - - zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end()); - - int nMinDepth = 1; - std::vector saplingEntries; - pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth); - - std::map mapBalances; - for (auto & entry : saplingEntries) { - auto zaddr = EncodePaymentAddress(entry.address); - CAmount nBalance = CAmount(entry.note.value()); - if(mapBalances.count(zaddr)) { - mapBalances[zaddr] += nBalance; - } else { - mapBalances[zaddr] = nBalance; - } - } - std::vector> vec; - std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter>>(vec)); - - std::sort(vec.begin(), vec.end(), [](const std::pair &l, const std::pair &r) - { - if (l.second != r.second) { - return l.second > r.second; - } - return l.first > r.first; - }); - - //TODO: avoid calculating nTotalOut twice - CAmount nTotalOut = 0; - for (const UniValue& o : outputs.getValues()) { - if (!o.isObject()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); - - UniValue av = find_value(o, "amount"); - CAmount nAmount = AmountFromValue( av ); - if (nAmount < 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); - - nTotalOut += nAmount; - } - - //GOAL: choose one random zaddress with enough funds - CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; // default when params.size()<=3 (was uninitialized) - if (params.size() > 3) { - if (params[3].get_real() == 0.0) { - nFee = 0; - } else { - nFee = AmountFromValue( params[3] ); - } - } - - // the total amount needed in a single zaddr to use as fromaddress - CAmount nMinBal = nTotalOut + nFee; - - std::vector vPotentialAddresses; - for (auto & entry : vec) { - if(entry.second >= nMinBal) { - vPotentialAddresses.push_back(entry.first); - } - } - - // select a random address with enough confirmed balance - auto nPotentials = vPotentialAddresses.size(); - if (nPotentials > 0) { - fprintf(stderr,"%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials); - fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ]; + std::map mapBalances; + for (auto & entry : saplingEntries) { + auto zaddr = EncodePaymentAddress(entry.address); + CAmount nBalance = CAmount(entry.note.value()); + if(mapBalances.count(zaddr)) { + mapBalances[zaddr] += nBalance; } else { - // Automagic zaddr source selection failed, exit honorably - throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations."); - } - } else { - CTxDestination taddr = DecodeDestination(fromaddress); - fromTaddr = IsValidDestination(taddr); - if (!fromTaddr) { - auto res = DecodePaymentAddress(fromaddress); - if (!IsValidPaymentAddress(res, branchId)) { - // invalid - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); - } - - // Check that we have the spending key - if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); - } - - // Remember whether this is a Sapling address - fromSapling = boost::get(&res) != nullptr; + mapBalances[zaddr] = nBalance; } } + std::vector> vec; + std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter>>(vec)); + std::sort(vec.begin(), vec.end(), [](const std::pair &l, const std::pair &r) + { + if (l.second != r.second) { + return l.second > r.second; + } + return l.first > r.first; + }); - // Recipients - std::vector taddrRecipients; - std::vector zaddrRecipients; + //TODO: avoid calculating nTotalOut twice CAmount nTotalOut = 0; - // Optional OP_RETURN data - CScript opret; - UniValue opretValue; - if(params.size() == 5) { - opretValue = params[4].get_str(); + for (const UniValue& o : outputs.getValues()) { + if (!o.isObject()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); - // Support a prefix "utf8:" which allows giving utf8 text instead of hex - if(opretValue.get_str().substr(0,5) == "utf8:") { - auto str = opretValue.get_str().substr(5); - if (utf8::is_valid(str)) { - opretValue = HexStr(str); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in opreturn"); - } + UniValue av = find_value(o, "amount"); + CAmount nAmount = AmountFromValue( av ); + if (nAmount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); + + nTotalOut += nAmount; + } + + //GOAL: choose one random zaddress with enough funds + CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; // default when params.size()<=3 (was uninitialized) + if (params.size() > 3) { + if (params[3].get_real() == 0.0) { + nFee = 0; + } else { + nFee = AmountFromValue( params[3] ); } } - bool containsSaplingOutput = false; + // the total amount needed in a single zaddr to use as fromaddress + CAmount nMinBal = nTotalOut + nFee; - // Create the CScript representation of the OP_RETURN - if (!opretValue.isNull()) { - opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); + std::vector vPotentialAddresses; + for (auto & entry : vec) { + if(entry.second >= nMinBal) { + vPotentialAddresses.push_back(entry.first); + } } + // select a random address with enough confirmed balance + auto nPotentials = vPotentialAddresses.size(); + if (nPotentials > 0) { + LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials); + return vPotentialAddresses[ GetRandInt(nPotentials) ]; + } + // Automagic zaddr source selection failed, exit honorably + throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations."); +} + +// Parse and validate the z_sendmany "outputs" array into taddr/zaddr recipient lists +// (accumulating nTotalOut). Extracted verbatim from z_sendmany; throws JSONRPCError on +// any malformed entry (unknown key, bad address, memo misuse/oversize, negative amount). +static void ParseSendManyRecipients(const UniValue& outputs, uint32_t branchId, + std::vector& taddrRecipients, + std::vector& zaddrRecipients, + CAmount& nTotalOut) +{ for (const UniValue& o : outputs.getValues()) { if (!o.isObject()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); @@ -5284,6 +5258,116 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) nTotalOut += nAmount; } +} + +UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 2 || params.size() > 5) + throw runtime_error( + "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n" + "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." + "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." + "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." + + HelpRequiringPassphrase() + "\n" + "\nArguments:\n" + "1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from. Use 'z' to spend from any zaddr.\n" + "2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n" + " [{\n" + " \"address\":address (string, required) The address is a taddr or zaddr\n" + " \"amount\":amount (numeric, required) The amount to send this address\n" + " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" + " }, ... ]\n" + "3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n" + "4. fee (numeric, optional, default=" + + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + "5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n" + "\nResult:\n" + "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" + "\nExamples:\n" + + HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'") + + HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]") + + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'") + + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]") + + HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") + + HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + // Guard against building shielded transactions before the chain is fully synced; + // sending while behind the tip can leak metadata usable for linkability analysis. + THROW_IF_SYNCING(HUSH_INSYNC); + + // Check that the from address is valid. + auto fromaddress = params[0].get_str(); + bool fromTaddr = false; + bool fromSapling = false; + uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()); + UniValue outputs = params[1].get_array(); + + if (outputs.size()==0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); + + // TODO: implement special symbolic fromaddrs + // TODO: list of (zaddr,amount) + // "z" => spend from any zaddr + // "t" => spend from any taddr + // "*" => spend from any addr, zaddrs first + if(fromaddress == "z") { + fromaddress = SelectAnyZaddrSource(outputs, params); + } else { + CTxDestination taddr = DecodeDestination(fromaddress); + fromTaddr = IsValidDestination(taddr); + if (!fromTaddr) { + auto res = DecodePaymentAddress(fromaddress); + if (!IsValidPaymentAddress(res, branchId)) { + // invalid + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); + } + + // Check that we have the spending key + if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); + } + + // Remember whether this is a Sapling address + fromSapling = boost::get(&res) != nullptr; + } + } + + + // Recipients + std::vector taddrRecipients; + std::vector zaddrRecipients; + CAmount nTotalOut = 0; + // Optional OP_RETURN data + CScript opret; + UniValue opretValue; + if(params.size() == 5) { + opretValue = params[4].get_str(); + + // Support a prefix "utf8:" which allows giving utf8 text instead of hex + if(opretValue.get_str().substr(0,5) == "utf8:") { + auto str = opretValue.get_str().substr(5); + if (utf8::is_valid(str)) { + opretValue = HexStr(str); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in opreturn"); + } + } + } + + bool containsSaplingOutput = false; + + // Create the CScript representation of the OP_RETURN + if (!opretValue.isNull()) { + opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); + } + + ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut); std::vector saplingEntries; // find all unspent and unlocked notes in this zaddr @@ -5325,12 +5409,15 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // SIETCH: Sprinkle our cave with some magic privacy zdust // End goal is to have this be as large as possible without slowing xtns down too much // A value of 7 will provide much stronger linkability privacy versus pre-Sietch operations + // DEFAULT_MIN_ZOUTS (7): default number of dummy z-outputs padded onto each z_sendmany. + // MAX_ZOUTS (50): upper bound for the operator-tunable -sietch-min-zouts arg. + // The effective floor is clamped to [3, MAX_ZOUTS] below. unsigned int DEFAULT_MIN_ZOUTS=7; unsigned int MAX_ZOUTS=50; - unsigned int MIN_ZOUTS=GetArg("--sietch-min-zouts", DEFAULT_MIN_ZOUTS); + unsigned int MIN_ZOUTS=GetArg("-sietch-min-zouts", DEFAULT_MIN_ZOUTS); if((MIN_ZOUTS<3) || (MIN_ZOUTS>MAX_ZOUTS)) { - fprintf(stderr,"%s: Sietch min zouts must be >= 3 and <= %d, setting to default value of %d\n", __FUNCTION__, MAX_ZOUTS, DEFAULT_MIN_ZOUTS ); + LogPrintf("%s: Sietch min zouts must be >= 3 and <= %d, setting to default value of %d\n", __FUNCTION__, MAX_ZOUTS, DEFAULT_MIN_ZOUTS ); MIN_ZOUTS=DEFAULT_MIN_ZOUTS; } @@ -5394,9 +5481,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion); if (fromTaddr) { txsize += CTXIN_SPEND_DUST_SIZE; - //TODO: On HUSH since block 340k there can no longer be taddr change, - // (except for notary addresses) - // so we can likely make a better estimation of max txsize + // DragonX is ac_private=1 (fully shielded from genesis); transparent outputs are + // banned, so in practice there is no taddr change and this estimate is conservative. txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change } txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size(); @@ -5466,7 +5552,10 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, saplingNoteInputs, nMinDepth, nFee, contextInfo, opret) ); - q->addOperation(operation); + if (!q->addOperation(operation)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Async RPC queue is shutting down; the operation was not queued"); + } if(fZdebug) LogPrintf("%s: Submitted to async queue\n", __FUNCTION__); @@ -5528,7 +5617,8 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp LOCK2(cs_main, pwalletMain->cs_wallet); - // Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz + // Guard against building shielded transactions before the chain is fully synced; + // sending while behind the tip can leak metadata usable for linkability analysis. THROW_IF_SYNCING(HUSH_INSYNC); // Validate the from address @@ -5694,7 +5784,13 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, donation, contextInfo) ); - q->addOperation(operation); + // The constructor has already locked the selected coins. Coin locks are + // memory-only, so a refused queue at shutdown reclaims them with the process; + // what must not happen is returning an opid for work that will never run. + if (!q->addOperation(operation)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Async RPC queue is shutting down; the operation was not queued"); + } AsyncRPCOperationId operationId = operation->getId(); // Return continuation information @@ -5768,7 +5864,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp LOCK2(cs_main, pwalletMain->cs_wallet); - // Hilarious that Komodo commented this out, opening themselves up to metadata attackz, lulz + // Guard against building shielded transactions before the chain is fully synced; + // sending while behind the tip can leak metadata usable for linkability analysis. THROW_IF_SYNCING(HUSH_INSYNC); bool useAnyUTXO = false; @@ -5939,8 +6036,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp CAmount nValue = out.tx->vout[out.i].nValue; if (maximum_utxo_size != 0) { - //fprintf(stderr, "utxo txid.%s vout.%i nValue.%li scriptpubkeylength.%i\n",out.tx->GetHash().ToString().c_str(),out.i,nValue,out.tx->vout[out.i].scriptPubKey.size()); - if (nValue > maximum_utxo_size) + if (nValue > maximum_utxo_size) continue; if (nValue == 10000 && out.tx->vout[out.i].scriptPubKey.size() == 35) continue; @@ -6001,7 +6097,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp size_t numUtxos = utxoInputs.size(); size_t numNotes = saplingNoteInputs.size(); - //fprintf(stderr, "num utxos.%li\n", numUtxos); if (numUtxos < 2 && numNotes == 0) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge."); } @@ -6048,7 +6143,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr operation( new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) ); - q->addOperation(operation); + if (!q->addOperation(operation)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + "Async RPC queue is shutting down; the operation was not queued"); + } AsyncRPCOperationId operationId = operation->getId(); // Return continuation information @@ -6159,7 +6257,6 @@ int32_t hush_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33, void *pT script = (uint8_t *)&out.tx->vout[out.i].scriptPubKey[0]; if ( out.tx->vout[out.i].scriptPubKey.size() != 35 || script[0] != 33 || script[34] != OP_CHECKSIG || memcmp(notarypub33,script+1,33) != 0 ) { - //fprintf(stderr,"scriptsize.%d [0] %02x\n",(int32_t)out.tx->vout[out.i].scriptPubKey.size(),script[0]); continue; } utxovalue = (uint64_t)nValue; @@ -6167,7 +6264,6 @@ int32_t hush_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33, void *pT utxotxid = out.tx->GetHash(); utxovout = out.i; best_scriptPubKey = out.tx->vout[out.i].scriptPubKey; - //fprintf(stderr,"check %s/v%d %llu\n",(char *)utxotxid.GetHex().c_str(),utxovout,(long long)utxovalue); txNew.vin.resize(1); txNew.vout.resize((pTr!=0)+1); @@ -6188,15 +6284,14 @@ int32_t hush_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33, void *pT CTransaction txNewConst(txNew); signSuccess = ProduceSignature(TransactionSignatureCreator(&keystore, &txNewConst, 0, utxovalue, SIGHASH_ALL), best_scriptPubKey, sigdata, consensusBranchId); if (!signSuccess) - fprintf(stderr,"notaryvin failed to create signature\n"); + LogPrintf("notaryvin failed to create signature\n"); else { UpdateTransaction(txNew,0,sigdata); ptr = (uint8_t *)&sigdata.scriptSig[0]; siglen = sigdata.scriptSig.size(); for (i=0; i #include #include @@ -555,6 +556,9 @@ void CWallet::ChainTip(const CBlockIndex *pindex, if (fSweepEnabled) { RunSaplingSweep(pindex->GetHeight()); } + if (fAutoShieldEnabled) { + RunAutoShieldCoinbase(pindex->GetHeight()); + } if (fTxDeleteEnabled) { DeleteWalletTransactions(pindex); } @@ -574,14 +578,45 @@ void CWallet::ChainTip(const CBlockIndex *pindex, } void CWallet::RunSaplingSweep(int blockHeight) { - // Sapling is always active since height=1 of HUSH+HACs + // Sapling is always active since height=1 on DragonX // if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { // return; // } if (blockHeight == 0) return; - AssertLockHeld(cs_wallet); + // Take cs_wallet ourselves: ChainTip (the notify-thread caller) does NOT + // hold it here, and we mutate fSweepRunning/nextSweep/saplingSweepOperationId + // and enqueue below. Matches RunSaplingConsolidation/RunAutoShieldCoinbase. + // (The old AssertLockHeld(cs_wallet) was a no-op in release builds and thus + // masked an unsynchronized mutation.) cs_wallet is recursive, so this is + // safe even on any path that already holds it. + LOCK(cs_wallet); + + // Stale-baton guard. A successful-but-incomplete sweep round deliberately + // returns with fSweepRunning still set and nextSweep unadvanced (see + // AsyncRPCOperation_sweep::main), as a "continue draining next block" baton. + // But every early return below leaves that baton set WITHOUT re-dispatching, + // and RunSaplingConsolidation -- which is gated on fSweepRunning -- then + // returns without advancing nextConsolidation, so the "consolidation is + // within 5 blocks" blackout at the top of this function never lifts. That + // is a self-sustaining three-way deadlock: sweep waits on consolidation, + // consolidation waits on sweep, and autoshield shares the same gate, so a + // wedged sweep silently disables coinbase shielding forever. + // Only honour the baton while a sweep operation genuinely is in flight. + if (fSweepRunning) { + std::shared_ptr sweepQueue = getAsyncRPCQueue(); + std::shared_ptr inFlightSweep = + (sweepQueue != nullptr) ? sweepQueue->getOperationForId(saplingSweepOperationId) : nullptr; + bool inFlight = (inFlightSweep != nullptr) && + (inFlightSweep->isReady() || inFlightSweep->isExecuting()); + if (!inFlight) { + LogPrintf("%s: clearing stale fSweepRunning at blockHeight=%d (no sweep operation in flight)\n", + __func__, blockHeight); + fSweepRunning = false; + } + } + if (!fSweepEnabled) { return; } @@ -604,21 +639,37 @@ void CWallet::RunSaplingSweep(int blockHeight) { return; } + //Don't Run While auto-shield is running. + if (fAutoShieldRunning) { + LogPrintf("%s: not sweeping since autoshield is currently running at height=%d\n", __func__, blockHeight); + return; + } + fSweepRunning = true; std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr lastOperation = q->getOperationForId(saplingSweepOperationId); if (lastOperation != nullptr) { lastOperation->cancel(); + // Drop it from the queue's map as well. Nothing else ever removes these: + // popOperationForId is only reached from z_getoperationresult, so on a node + // running this every interval the map grew without bound. + q->popOperationForId(saplingSweepOperationId); } pendingSaplingSweepTxs.clear(); - std::shared_ptr operation(new AsyncRPCOperation_sweep(blockHeight + 5)); + std::shared_ptr operation(new AsyncRPCOperation_sweep(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET)); saplingSweepOperationId = operation->getId(); - q->addOperation(operation); + if (!q->addOperation(operation)) { + // Queue is closing (shutdown). Release the flag we just set, or it stays + // set with no operation in flight and blocks every later round. + LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__); + fSweepRunning = false; + return; + } } void CWallet::RunSaplingConsolidation(int blockHeight) { - // Sapling is always active on HUSH+HACs + // Sapling is always active on DragonX (activated at height=1) //if (!NetworkUpgradeActive(blockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { // return; //} @@ -634,6 +685,12 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { return; } + // Self-guard: an op is already in flight (nextConsolidation only advances + // when it completes). Don't cancel + re-enqueue a fresh op every block. + if (fConsolidationRunning) { + return; + } + LogPrintf("%s: consolidation enabled at blockHeight=%d fSweepRunning=%d\n", __func__, blockHeight, fSweepRunning ); if (fSweepRunning) { @@ -641,31 +698,121 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { return; } + if (fAutoShieldRunning) { + LogPrintf("%s: not consolidating since autoshield is currently running at height=%d\n", __func__, blockHeight); + return; + } + LogPrintf("%s: creating consolidation operation at blockHeight=%d\n", __func__, blockHeight); + fConsolidationRunning = true; std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr lastOperation = q->getOperationForId(saplingConsolidationOperationId); if (lastOperation != nullptr) { lastOperation->cancel(); + // Drop it from the queue's map as well. Nothing else ever removes these: + // popOperationForId is only reached from z_getoperationresult, so on a node + // running this every interval the map grew without bound. + q->popOperationForId(saplingConsolidationOperationId); } pendingSaplingConsolidationTxs.clear(); - std::shared_ptr operation(new AsyncRPCOperation_saplingconsolidation(blockHeight + 5)); + std::shared_ptr operation(new AsyncRPCOperation_saplingconsolidation(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET)); saplingConsolidationOperationId = operation->getId(); - q->addOperation(operation); + if (!q->addOperation(operation)) { + // Queue is closing (shutdown). Release the flag we just set, or it stays + // set with no operation in flight and blocks every later round. + LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__); + fConsolidationRunning = false; + return; + } +} + +// Periodically drain matured transparent coinbase into a wallet-owned Sapling +// z-address. Default-ON but conditional: this is enqueue-only (all gathering +// happens on the async worker thread inside the op, which is why we must not +// take cs_main here — ChainTip runs from the wallet-notify context). It is a +// silent no-op wherever it cannot act (locked wallet, no owned coinbase, +// external -mineraddress), so it is safe to run on every node. +void CWallet::RunAutoShieldCoinbase(int blockHeight) { + // Sapling is always active from height 1 on DragonX+HACs. + if (blockHeight == 0) + return; + + LOCK(cs_wallet); + + if (!fAutoShieldEnabled) { + return; + } + + if (nextAutoShield > blockHeight) { + return; + } + + // Self-guard: an op is already in flight (nextAutoShield only advances when + // it completes). Don't cancel + re-enqueue a fresh op every block. + if (fAutoShieldRunning) { + return; + } + + // Mutual exclusion: sweep/consolidation share the single async worker and + // cs_wallet; don't queue an autoshield in the same connected block. + if (fSweepRunning || fConsolidationRunning) { + LogPrintf("%s: not autoshielding since sweep/consolidation is running at height=%d\n", __func__, blockHeight); + return; + } + + // Silent no-op while locked: we can neither sign the shield nor derive a + // destination z-addr. Advance the interval so we don't retry every block. + if (IsLocked()) { + LogPrintf("%s: wallet locked; matured coinbase will accumulate until unlocked (height=%d)\n", __func__, blockHeight); + nextAutoShield = autoShieldInterval + blockHeight; + return; + } + + fAutoShieldRunning = true; + + std::shared_ptr q = getAsyncRPCQueue(); + std::shared_ptr lastOperation = q->getOperationForId(saplingAutoShieldOperationId); + if (lastOperation != nullptr) { + lastOperation->cancel(); + // Drop it from the queue's map as well. Nothing else ever removes these: + // popOperationForId is only reached from z_getoperationresult, so on a node + // running this every interval the map grew without bound. + q->popOperationForId(saplingAutoShieldOperationId); + } + std::shared_ptr operation(new AsyncRPCOperation_autoshieldcoinbase(blockHeight + AUTO_OP_TARGET_HEIGHT_OFFSET)); + saplingAutoShieldOperationId = operation->getId(); + if (!q->addOperation(operation)) { + // Queue is closing (shutdown). Release the flag we just set, or it stays + // set with no operation in flight and blocks every later round. + LogPrintf("%s: async queue is not accepting operations; skipping this round\n", __func__); + fAutoShieldRunning = false; + return; + } } bool CWallet::CommitAutomatedTx(const CTransaction& tx) { CWalletTx wtx(this, tx); CReserveKey reservekey(pwalletMain); - fprintf(stderr,"%s: %s\n",__func__,tx.ToString().c_str()); + // No tx dump here: CommitTransaction already LogPrintf's the same wtx.ToString(), + // and ToString() emits a line per vin, so with the 400-input autoshield cap this + // printed tens of KB to stderr on every automated round. return CommitTransaction(wtx, reservekey); } void CWallet::SetBestChain(const CBlockLocator& loc) { + // Default ctor => fFlushOnClose=true => ~CDB runs a full BDB txn_checkpoint over the entire + // cache. Fine for the hourly/shutdown callers; ruinous inside a scan (see SetBestChainNoFlush). CWalletDB walletdb(strWalletFile); SetBestChainINTERNAL(walletdb, loc); } +bool CWallet::SetBestChainNoFlush(const CBlockLocator& loc) +{ + CWalletDB walletdb(strWalletFile, "r+", false); + return SetBestChainINTERNAL(walletdb, loc); +} + std::set> CWallet::GetNullifiersForAddresses( const std::set & addresses) { @@ -1021,9 +1168,6 @@ int64_t CWallet::NullifierCount() { LOCK(cs_wallet); if(fZdebug) { - //fprintf(stderr,"%s:mapTxSaplingNullifers.size=%d\n",__FUNCTION__,(int)mapTxSaplingNullifiers.size() ); - //fprintf(stderr,"%s:mempool.getNullifiers.size=%d\n",__FUNCTION__,(int)mempool.getNullifiers().size() ); - //fprintf(stderr,"%s:cacheSaplingNullifiers.size=%d\n",__FUNCTION__,(int)pcoinsTip->getNullifiers().size() ); } return pcoinsTip->getNullifiers().size(); } @@ -1300,8 +1444,9 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly) return; } if (pwalletMain->fAbortRescan) { + // Do NOT clear fRescanning here: a witness rebuild is not a rescan, and clearing it from + // this path desynchronises the flag from ScanForWalletTransactions, which owns it. LogPrintf("%s: rescan aborted during witness rebuild\n", __func__); - pwalletMain->fRescanning = false; return; } int h = pbi->GetHeight(); @@ -1570,7 +1715,6 @@ CWallet::TxItems CWallet::OrderedTxItems(std::list& acentries, { CWalletTx* wtx = &((*it).second); txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0))); - //fprintf(stderr,"ordered iter.%d %s\n",(int32_t)wtx->nOrderPos,wtx->GetHash().GetHex().c_str()); } acentries.clear(); walletdb.ListAccountCreditDebit(strAccount, acentries); @@ -1877,9 +2021,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl vAllowListAddress = mapMultiArgs["-allowlistaddress"]; if ( !vAllowListAddress.empty() ) { - fprintf(stderr, "Activated Wallet Filter \n Notary Address: %s \n Adding allowlist address's:\n", NotaryAddress.c_str()); + LogPrintf("Activated Wallet Filter \n Notary Address: %s \n Adding allowlist address's:\n", NotaryAddress.c_str()); for ( auto wladdr : vAllowListAddress ) - fprintf(stderr, " %s\n", wladdr.c_str()); + LogPrintf(" %s\n", wladdr.c_str()); } } if (fExisted || IsMine(tx) || IsFromMe(tx) || saplingNoteData.size() > 0) { @@ -1897,7 +2041,6 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl { if ( CBitcoinAddress(address).ToString() == wladdr ) { - //fprintf(stderr, "We received from allowlisted address.%s\n", wladdr.c_str()); numvinIsAllowList++; } } @@ -1905,7 +2048,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl } // Now we know if it was a tx sent to us, by either a allowlisted address, or ourself. if ( numvinIsOurs != 0 ) - fprintf(stderr, "We sent from address: %s vins: %d\n",NotaryAddress.c_str(),numvinIsOurs); + LogPrintf("We sent from address: %s vins: %d\n",NotaryAddress.c_str(),numvinIsOurs); if ( numvinIsOurs == 0 && numvinIsAllowList == 0 ) return false; } @@ -2246,7 +2389,7 @@ isminetype CWallet::IsMine(const CTransaction& tx, uint32_t voutNum) case TX_SCRIPTHASH: scriptID = CScriptID(uint160(vSolutions[0])); - //TODO: remove CLTV stuff not relevant to Hush + //TODO: evaluate whether this CLTV timelock handling is needed on DragonX if (this->GetCScript(scriptID, subscript)) { // if this is a CLTV, handle it differently @@ -2388,30 +2531,47 @@ void CWallet::GenerateNewSeed() // Opt-in: create the wallet from a fresh BIP39 mnemonic so its 24 words can // be exported (z_exportmnemonic) and used in SilentDragonXLite. - if (GetBoolArg("-usemnemonic", false)) { + // + // NO SILENT FALLBACK. Falling back to a random seed here produced a wallet + // that looks mnemonic-capable but whose words can never be exported + // (z_exportmnemonic refuses non-mnemonic wallets, rpcdump.cpp:1031+) and + // that no seed phrase can restore. A user who asked for -usemnemonic must + // get that or a hard failure. + if (GetBoolArg("-usemnemonic", true)) { RawHDSeed entropy; - if (GenerateMnemonicEntropy(256, entropy)) { - HDSeed seed(entropy); - if (InstallHDSeed(seed, true, nCreationTime)) - return; - } - LogPrintf("%s: -usemnemonic seed generation failed, falling back to a random seed\n", __func__); + if (!GenerateMnemonicEntropy(256, entropy)) + throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed"); + + // Store the EXPANDED 64-byte BIP39 seed as the HD seed, with + // fMnemonic = false. Every binary -- old or new -- then feeds the stored + // bytes straight into derivation, so the key tree is identical + // everywhere and no CHDChain version bump or minversion fence is needed. + // The 32-byte entropy is kept in a separate, display-only record purely + // so the phrase can be reprinted. Addresses are unchanged from the + // previous format, which expanded the stored entropy on every read. + RawHDSeed seed64; + if (!Bip39SeedFromEntropy(entropy, seed64)) + throw std::runtime_error(std::string(__func__) + ": BIP39 seed expansion failed"); + HDSeed seed(seed64); + + // ORDER IS LOAD-BEARING: seed first, entropy second, never the reverse. + // A crash between the two leaves a wallet with a seed and no phrase -- + // recoverable via z_exportwallet, merely inconvenient. The reverse order + // would leave entropy with no seed; the next start would mint a + // DIFFERENT seed while the wallet still held a phrase for the old one. + // (GetMnemonicPhrase cross-checks the two and would refuse to print it, + // but do not rely on that here.) + if (!InstallHDSeed(seed, false, nCreationTime)) + throw std::runtime_error(std::string(__func__) + ": installing the mnemonic HD seed failed"); + if (!SetMnemonicEntropy(entropy)) + throw std::runtime_error(std::string(__func__) + ": storing the mnemonic entropy failed"); + return; } - auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH); - // If the wallet is encrypted and locked, this will fail. - if (!SetHDSeed(seed)) + auto seed = HDSeed::Random(HD_WALLET_SEED_LENGTH); + if (!InstallHDSeed(seed, false, nCreationTime)) throw std::runtime_error(std::string(__func__) + ": SetHDSeed failed"); - - // store the key creation time together with - // the child index counter in the database - // as a hdchain object - CHDChain newHdChain; - newHdChain.nVersion = CHDChain::VERSION_HD_TRANSPARENT; - newHdChain.seedFp = seed.Fingerprint(); - newHdChain.nCreateTime = nCreationTime; - SetHDChain(newHdChain, false); } bool CWallet::SetHDSeed(const HDSeed& seed) @@ -2445,14 +2605,38 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vectorWriteCryptedHDSeed(seedFp, vchCryptedSecret); - else - return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret); + // Write the encrypted record, then drop the plaintext one. Both go + // through the same CWalletDB (and therefore the same transaction when + // EncryptWallet supplied pwalletdbEncryption), because CDB::Rewrite at + // the end of EncryptWallet copies every surviving record into the fresh + // file -- a leftover plaintext "hdseed" would keep the unencrypted seed + // on disk for the life of the wallet. + // + // The erase is deliberately best-effort: a hard failure here propagates + // into CCryptoKeyStore::EncryptKeys, which CWallet::EncryptWallet turns + // into assert(false) with half the keys encrypted in memory. A logged + // warning is strictly better than that. + if (pwalletdbEncryption) { + if (!pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret)) + return false; + if (!pwalletdbEncryption->EraseHDSeed(seedFp)) + LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; " + "the unencrypted HD seed may remain in wallet.dat\n", __func__); + return true; + } else { + CWalletDB walletdb(strWalletFile); + if (!walletdb.WriteCryptedHDSeed(seedFp, vchCryptedSecret)) + return false; + if (!walletdb.EraseHDSeed(seedFp)) + LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; " + "the unencrypted HD seed may remain in wallet.dat\n", __func__); + return true; + } } return false; } + void CWallet::SetHDChain(const CHDChain& chain, bool memonly) { LOCK(cs_wallet); @@ -2462,6 +2646,20 @@ void CWallet::SetHDChain(const CHDChain& chain, bool memonly) hdChain = chain; } +void CWallet::SetHDSeedOrigin(int origin) +{ + LOCK(cs_wallet); + + hdSeedOrigin = origin; + + // Deliberately non-fatal, unlike SetHDChain: losing this record must never + // stop a node from starting. The cost of a failed write is that the next + // start re-classifies the wallet, and re-classification of an already-seeded + // wallet yields HDSEED_ORIGIN_UNKNOWN, i.e. the safe answer. + if (fFileBacked && !CWalletDB(strWalletFile).WriteHDSeedOrigin((int64_t)origin)) + LogPrintf("%s: WARNING: could not record HD seed origin %d in wallet.dat\n", __func__, origin); +} + bool CWallet::LoadHDSeed(const HDSeed& seed) { return CBasicKeyStore::SetHDSeed(seed); @@ -2471,21 +2669,94 @@ bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector& vchCryptedSecret) +{ + if (!CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) { + return false; + } + + if (!fFileBacked) { + return true; + } + + { + LOCK(cs_wallet); + // Same write-then-erase discipline as SetCryptedHDSeed: CDB::Rewrite at + // the end of EncryptWallet copies every surviving record, so a leftover + // plaintext "mnementropy" would keep the seed phrase recoverable from an + // encrypted wallet.dat. The erase is best-effort for the same reason: a + // hard failure would propagate into EncryptKeys -> assert(false). + if (pwalletdbEncryption) { + if (!pwalletdbEncryption->WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) + return false; + if (!pwalletdbEncryption->EraseMnemonicEntropy(entropyFp)) + LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__); + return true; + } else { + CWalletDB walletdb(strWalletFile); + if (!walletdb.WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) + return false; + if (!walletdb.EraseMnemonicEntropy(entropyFp)) + LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__); + return true; + } + } + return false; +} + +bool CWallet::LoadMnemonicEntropy(const RawHDSeed& entropy) +{ + return CBasicKeyStore::SetMnemonicEntropy(entropy); +} + +bool CWallet::LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector& vchCryptedSecret) +{ + return CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret); +} bool CWallet::InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime) { AssertLockHeld(cs_wallet); - if (!SetHDSeed(seed)) - return false; - + // Chain BEFORE seed. A crash between the two records must never leave a + // wallet that holds a seed with no hdchain: on the next load hdChain would + // silently revert to its defaults, clearing fMnemonicSeed (which switches + // the derivation input, wallet.cpp:2615-2633) and resetting + // saplingAccountCounter. The opposite torn state — chain without seed — is + // harmless and self-healing: HaveHDSeed() is false, so init installs a seed + // again and overwrites the chain. CHDChain newHdChain; newHdChain.nVersion = fMnemonic ? CHDChain::VERSION_HD_MNEMONIC : CHDChain::VERSION_HD_TRANSPARENT; newHdChain.seedFp = seed.Fingerprint(); newHdChain.nCreateTime = nCreateTime; newHdChain.fMnemonicSeed = fMnemonic; - SetHDChain(newHdChain, false); + SetHDChain(newHdChain, false); // throws if the write fails + + if (!SetHDSeed(seed)) + return false; return true; } @@ -2524,14 +2795,42 @@ bool CWallet::SetHDSeedFromMnemonic(const std::string& phrase) if (!MnemonicToEntropy(phrase, entropy)) return false; - // Store the BIP39 entropy as the HDSeed (SilentDragonXLite's on-disk - // convention); the 64-byte seed is expanded from it on demand. - HDSeed seed(entropy); - return InstallHDSeed(seed, true, 1); // birthday = genesis for a restore + // Store the EXPANDED 64-byte BIP39 seed as the HD seed (fMnemonic = false); + // the entropy goes in its own record and is used only to reprint the phrase. + // Derivation therefore reads the stored bytes directly on any binary, and + // the resulting addresses are byte-identical to the previous format, which + // expanded the stored entropy on every derivation. SilentDragonXLite + // interop is unaffected: the same words still yield the same seed64. + RawHDSeed seed64; + if (!Bip39SeedFromEntropy(entropy, seed64)) + return false; + HDSeed seed(seed64); + + // Seed first, entropy second -- see the ordering note in GenerateNewSeed. + if (!InstallHDSeed(seed, false, 1)) // birthday = genesis for a restore + return false; + + // Non-fatal on a restore, unlike GenerateNewSeed: the user already holds the + // phrase (they just typed it), the seed is installed and the wallet is fully + // functional; only z_exportmnemonic is lost. + if (!SetMnemonicEntropy(entropy)) { + LogPrintf("%s: WARNING: HD seed installed but the mnemonic entropy record could not be " + "stored; z_exportmnemonic will be unavailable on this wallet\n", __func__); + } + return true; } bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const { + // Single choke point for every HD derivation in the wallet. When the hdchain record could not + // be trusted at load time we do not know whether fMnemonicSeed should be true, and guessing + // wrong derives into an entirely different key tree -- so refuse rather than guess. Callers + // surface this as a clean error (z_getnewaddress, sendmany, shieldcoinbase) or skip + // (autoshield). Transparent address generation falls back to the legacy random-key path, + // because hdChain.seedFp stays null and so IsHDTransparentEnabled() is false. + if (fHDChainUnproven) + return false; + HDSeed stored; if (!GetHDSeed(stored)) return false; @@ -2552,6 +2851,36 @@ bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const bool CWallet::GetMnemonicPhrase(std::string& phraseOut) const { + // Preferred form: the HD seed is the EXPANDED 64-byte BIP39 seed and the + // entropy sits in its own record. + RawHDSeed entropy; + if (GetMnemonicEntropy(entropy)) { // false on an encrypted+locked wallet + // NEVER hand out a phrase that does not restore THIS wallet. Prove the + // entropy expands to the exact bytes derivation consumes; if it does + // not (a torn install, a wallet.dat edited by hand, an entropy record + // paired with a different seed), refuse rather than print a phrase that + // silently restores someone else's key tree. Costs one PBKDF2 on a + // user-initiated RPC. + RawHDSeed seed64; + if (!Bip39SeedFromEntropy(entropy, seed64)) + return false; + + HDSeed derivationSeed; + if (!GetHDSeedForDerivation(derivationSeed)) + return false; + + if (derivationSeed.RawSeed() != seed64) { + LogPrintf("%s: refusing to export a seed phrase: the stored mnemonic entropy does not " + "expand to this wallet's HD seed\n", __func__); + return false; + } + + return EntropyToMnemonic(entropy, phraseOut); + } + + // Legacy form (earlier builds of this branch): the stored HD seed IS the + // 32-byte BIP39 entropy, expanded on every derivation. Consistent by + // construction, so no cross-check is possible or needed. if (!hdChain.fMnemonicSeed) return false; @@ -2731,14 +3060,12 @@ void CWalletTx::GetAmounts(list& listReceived, { if ( oneshot++ > 1 ) { - //fprintf(stderr,"skip change vout\n"); continue; } } } else if (!(fIsMine & filter)) { - //fprintf(stderr,"skip filtered vout %d %d\n",(int32_t)fIsMine,(int32_t)filter); continue; } // In either case, we need to get the destination address @@ -3116,6 +3443,12 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) if(fZdebug) LogPrintf("%s: fUpdate=%d now=%li\n",__func__,fUpdate,nNow); + // fAbortRescan is sticky: nothing else in the tree ever clears it, so without this a single + // `abortrescan` RPC would disable every later scan AND every later BuildWitnessCache for the + // lifetime of the process (BuildWitnessCache bails on the same flag), freezing witness heights + // while the chain advances and progressively rendering notes unspendable. + pwalletMain->fAbortRescan = false; + pwalletMain->fLastRescanCompleted = false; pwalletMain->fRescanning = true; CBlockIndex* pindex = pindexStart; pwalletMain->rescanStartHeight = pindex->GetHeight(); @@ -3130,6 +3463,51 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) pwalletMain->rescanHeight = pindex ? pindex->GetHeight() : 0; } + // --- interrupt checkpoint setup ------------------------------------------------------- + // Where does the wallet currently believe it has scanned to? A checkpoint may only ever + // ADVANCE that point, and only if this scan is contiguous with it. The RPC entry points + // (rescan / importprivkey / z_importkey / z_importviewingkey) take a caller-supplied start + // height validated only against chainActive.Height(), so a scan can legitimately begin far + // ABOVE the persisted locator -- writing a checkpoint from such a scan would mark the + // skipped range as scanned and hide any funds in it. + CBlockIndex* pindexPersisted = NULL; + { + CWalletDB walletdb(strWalletFile, "r+", false); + CBlockLocator locPersisted; + if (walletdb.ReadBestBlock(locPersisted)) + pindexPersisted = FindForkInGlobalIndex(chainActive, locPersisted); + } + const bool fMayCheckpoint = pindexPersisted != NULL && + pindexStart->GetHeight() <= pindexPersisted->GetHeight() + 1; + if (!fMayCheckpoint) { + LogPrintf("%s: scan starts at %d but the wallet is persisted at %d; progress will NOT be " + "checkpointed on interrupt (a non-contiguous scan cannot safely advance the locator)\n", + __func__, pindexStart->GetHeight(), + pindexPersisted ? pindexPersisted->GetHeight() : -1); + } + + // Persist progress when the scan is cut short. `pindexStopped` is the block we were ABOUT to + // scan, so the last fully-processed block is its parent. Resume restarts AT the locator's own + // block (CChain::GetLocator pushes it first; FindForkInGlobalIndex returns it), giving one + // block of deliberate overlap -- idempotent, because AddToWallet only merges when the tx is + // already present. No witness work is needed: witnesses are re-derived from each note's own + // witnessHeight, and witnessRootValidated is in-memory-only so every note is revalidated + // against hashFinalSaplingRoot on the next start. + auto checkpointProgress = [&](const CBlockIndex* pindexStopped) { + if (!fMayCheckpoint || !pindexStopped || !pindexStopped->pprev) + return; + const CBlockIndex* pindexDone = pindexStopped->pprev; + if (pindexDone->GetHeight() <= pindexPersisted->GetHeight()) + return; // never move the locator backwards + if (SetBestChainNoFlush(chainActive.GetLocator(pindexDone))) { + LogPrintf("%s: checkpointed scan progress at height %d\n", __func__, pindexDone->GetHeight()); + } else { + LogPrintf("%s: FAILED to checkpoint scan progress at height %d; the scan will replay " + "from height %d on the next start\n", __func__, pindexDone->GetHeight(), + pindexPersisted->GetHeight()); + } + }; + ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false); double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.LastTip(), false); @@ -3138,15 +3516,20 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) { pwalletMain->rescanHeight = pindex->GetHeight(); if(pwalletMain->fAbortRescan) { - //TODO: should we update witness caches? - LogPrintf("%s: Rescan aborted at block %d\n", pwalletMain->rescanHeight); - pwalletMain->fRescanning = false; + // The witness caches do NOT need updating here: on resume each note's witnesses are + // re-derived from its own witnessHeight, independently of the locator, and + // witnessRootValidated is in-memory-only so a full validation pass runs next boot. + // What DOES need saving is the locator -- see the checkpoint below. + pwalletMain->fRescanning = false; + pwalletMain->fAbortRescan = false; // consume it; see the note at scan entry + LogPrintf("%s: Rescan aborted at block %d\n", __func__, pwalletMain->rescanHeight); + checkpointProgress(pindex); return ret; } if (ShutdownRequested()) { - //TODO: should we update witness caches? - LogPrintf("%s: Rescan interrupted by shutdown request at block %d\n", pwalletMain->rescanHeight); pwalletMain->fRescanning = false; + LogPrintf("%s: Rescan interrupted by shutdown request at block %d\n", __func__, pwalletMain->rescanHeight); + checkpointProgress(pindex); return ret; } @@ -3207,6 +3590,9 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) // we are no longer rescanning pwalletMain->fRescanning = false; + // Reached only by running the loop to the end of the active chain. Callers use this to decide + // whether it is honest to record the wallet as scanned up to the tip. + pwalletMain->fLastRescanCompleted = true; return ret; } @@ -3259,7 +3645,6 @@ void CWallet::ReacceptWalletTransactions() bool invalid = state.IsInvalid(nDoS); // log rejection and deletion - //printf("ERROR reaccepting wallet transaction %s to mempool, reason: %s, DoS: %d\n", wtx.GetHash().ToString().c_str(), state.GetRejectReason().c_str(), nDoS); if (!wtx.IsCoinBase() && invalid && nDoS > 0 && state.GetRejectReason() != "tx-overwinter-expired") { @@ -3277,11 +3662,8 @@ void CWallet::ReacceptWalletTransactions() bool CWalletTx::RelayWalletTransaction() { int64_t nNow = GetTime(); - //if(fZdebug) - // LogPrintf("%s: now=%li\n",__func__,nNow); if ( pwallet == 0 ) { - //fprintf(stderr,"unexpected null pwallet in RelayWalletTransaction\n"); return(false); } assert(pwallet->GetBroadcastTransactions()); @@ -3519,7 +3901,7 @@ std::vector CWallet::ResendWalletTransactionsBefore(int64_t nTime) // Do not relay expired transactions, to avoid other nodes banning us // Current code will not ban nodes relaying expired txs but older nodes will if (wtx.nExpiryHeight > 0 && wtx.nExpiryHeight < chainActive.LastTip()->GetHeight()) { - fprintf(stderr,"%s: ignoring expired tx %s with expiry %d at height %d\n", __func__, wtx.GetHash().ToString().c_str(), wtx.nExpiryHeight, chainActive.LastTip()->GetHeight() ); + LogPrintf("%s: ignoring expired tx %s with expiry %d at height %d\n", __func__, wtx.GetHash().ToString().c_str(), wtx.nExpiryHeight, chainActive.LastTip()->GetHeight() ); // TODO: expired detection doesn't seem to work right // append to list of txs to delete // vwtxh.push_back(wtx.GetHash()); @@ -3840,7 +4222,6 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nTotalLower += n; if ( nTotalLower > 4*nTargetValue + CENT ) { - //fprintf(stderr,"why bother with all the utxo if we have double what is needed?\n"); break; } } else if (n < coinLowestLarger.first) @@ -4043,7 +4424,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) { - uint64_t interest2 = 0; CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0; + CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0; BOOST_FOREACH (const CRecipient& recipient, vecSend) { if (nValue < 0 || recipient.nAmount < 0) @@ -4164,7 +4545,6 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt CAmount nValueIn = 0; bool fOnlyCoinbaseCoins = false; bool fNeedCoinbaseCoins = false; - interest2 = 0; if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl)) { if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) { @@ -4183,15 +4563,13 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt //reflecting an assumption the user would accept a bit more delay for //a chance at a free transaction. //But mempool inputs might still be in the mempool, so their age stays 0 - //fprintf(stderr,"nCredit %.8f interest %.8f\n",(double)nCredit/COIN,(double)pcoin.first->vout[pcoin.second].interest/COIN); int age = pcoin.first->GetDepthInMainChain(); if (age != 0) age += 1; dPriority += (double)nCredit * age; } - CAmount nChange = (nValueIn - nValue + interest2); -//fprintf(stderr,"wallet change %.8f (%.8f - %.8f) interest2 %.8f total %.8f\n",(double)nChange/COIN,(double)nValueIn/COIN,(double)nValue/COIN,(double)interest2/COIN,(double)nTotalValue/COIN); + CAmount nChange = (nValueIn - nValue); if (nSubtractFeeFromAmount == 0) nChange -= nFeeRet; @@ -4228,7 +4606,6 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt } else { - //fprintf(stderr,"use notary pubkey\n"); scriptChange = CScript() << ParseHex(NOTARY_PUBKEY) << OP_CHECKSIG; } } @@ -4280,7 +4657,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(), std::numeric_limits::max()-1)); - // All Hush Arrakis Chains always have overwinter NU and so this option was never used + // DragonX always has the Overwinter NU active and so this option was never used // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects // const size_t limit = 0; // (size_t)GetArg("-mempooltxinputlimit", 0); //{ @@ -4403,7 +4780,8 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. - AddToWallet(wtxNew, false, pwalletdb); + if (!AddToWallet(wtxNew, false, pwalletdb)) + LogPrintf("CommitTransaction(): Error: failed to persist wallet tx %s to disk; wallet may be out of sync with the ledger\n", wtxNew.GetHash().ToString()); // Notify that old coins are spent set setCoins; @@ -4425,7 +4803,6 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) // Broadcast if (!wtxNew.AcceptToMemoryPool(false)) { - fprintf(stderr,"commit failed\n"); // This must not fail. The transaction has already been signed and recorded. LogPrintf("CommitTransaction(): Error: Transaction not valid\n"); return false; @@ -4475,7 +4852,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) fFirstRunRet = false; if ( 0 ) // doesnt help { - fprintf(stderr,"loading wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL)); + LogPrintf("loading wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL)); FILE *fp; if ( (fp= fopen(strWalletFile.c_str(),"rb")) != 0 ) { @@ -4483,9 +4860,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) fclose(fp); } } - //fprintf(stderr,"prefetched wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL)); DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); - //fprintf(stderr,"loaded wallet %s %u\n",strWalletFile.c_str(),(uint32_t)time(NULL)); if (nLoadWalletRet == DB_NEED_REWRITE) { if (CDB::Rewrite(strWalletFile, "\x04pool")) @@ -4670,7 +5045,6 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) if (!HaveKey(keypool.vchPubKey.GetID())) throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool"); assert(keypool.vchPubKey.IsValid()); - //LogPrintf("keypool reserve %d\n", nIndex); } } @@ -4692,7 +5066,6 @@ void CWallet::ReturnKey(int64_t nIndex) LOCK(cs_wallet); setKeyPool.insert(nIndex); } - //LogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey& result) @@ -4979,14 +5352,14 @@ void CWallet::LockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.insert(output); - fprintf(stderr,"%s: locking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); + LogPrintf("%s: locking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); } void CWallet::UnlockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.erase(output); - fprintf(stderr,"%s: unlocking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); + LogPrintf("%s: unlocking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); } void CWallet::UnlockAllSaplingNotes() @@ -5226,7 +5599,6 @@ int CMerkleTx::GetBlocksToMaturity() const int32_t depth = GetDepthInMainChain(); int32_t ut = UnlockTime(0); int32_t toMaturity = (ut - chainActive.Height()) < 0 ? 0 : ut - chainActive.Height(); - //printf("depth.%i, unlockTime.%i, toMaturity.%i\n", depth, ut, toMaturity); ut = (COINBASE_MATURITY - depth) < 0 ? 0 : COINBASE_MATURITY - depth; return(ut < toMaturity ? toMaturity : ut); } @@ -5433,7 +5805,7 @@ SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingE if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) { m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime; } else { - // TODO: set a better time for HUSH+HACs + // TODO: set a better time for DragonX // 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a99c0a4ea..107814971 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -100,6 +100,19 @@ static const unsigned int DEFAULT_TX_RETENTION_LASTTX = 200; //Amount of transactions to delete per run while syncing static const int MAX_DELETE_TX_SIZE = 50000; +// Shared defaults for the automated wallet operations (sweep / consolidation / +// auto-shield-coinbase). Fees are in puposhis (zats); intervals and offsets are +// in blocks. Defined here so the CWallet scheduler fields below, init.cpp's +// option parsing/help text, and the async ops reference one source of truth. +static const CAmount DEFAULT_AUTOSHIELD_FEE = 10000; +static const int DEFAULT_AUTOSHIELD_INTERVAL = 25; +static const int MIN_AUTOSHIELD_INTERVAL = 5; +static const int DEFAULT_AUTOSHIELD_MIN_UTXOS = 1; +static const CAmount AUTOSHIELD_MIN_FEE = 1000; // comfortably above minRelayTxFee for a small tx +static const CAmount AUTOSHIELD_MAX_FEE = 10000000; // 0.1 DRGX; no sane autoshield fee exceeds this +static const int AUTO_OP_TARGET_HEIGHT_OFFSET = 5; // blocks of lookahead when scheduling an async op +static const int AUTO_OP_EXPIRY_DELTA = 15; // NU-straddle expiry window, shared by all three ops + extern const char * DEFAULT_WALLET_DAT; class CBlockIndex; @@ -784,10 +797,8 @@ private: TxNullifiers mapTxSaplingNullifiers; std::vector pendingSaplingConsolidationTxs; - AsyncRPCOperationId saplingConsolidationOperationId; std::vector pendingSaplingSweepTxs; - AsyncRPCOperationId saplingSweepOperationId; void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid); void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid); @@ -802,6 +813,9 @@ public: int64_t nWitnessCacheSize; bool needsRescan = false; int nextConsolidation = 0; + // Id of the in-flight consolidation op; read by the op to confirm it is + // still the current one before mutating scheduler state. + AsyncRPCOperationId saplingConsolidationOperationId; bool fSaplingConsolidationEnabled = false; bool fConsolidationRunning = false; @@ -809,7 +823,20 @@ public: bool fSweepExternalEnabled = false; bool fSweepRunning = false; + // Automatic coinbase shielding (t->z). The real default is computed in + // init.cpp: ON only for known-recoverable seed provenance (CREATED/RESTORED), + // conditional, and a silent no-op where it cannot act (no wallet, external + // -mineraddress, non-mining, or locked wallet). See RunAutoShieldCoinbase. + // The member defaults false so a CWallet that skips that init path never + // auto-enables for provenance the gate would otherwise have rejected. + bool fAutoShieldEnabled = false; + bool fAutoShieldRunning = false; + std::atomic fAbortRescan{false}; + //! True only when the last ScanForWalletTransactions ran to completion. An aborted or + //! shutdown-interrupted scan leaves this false, so callers must not record the wallet as + //! scanned up to the chain tip -- doing so makes the unscanned range permanently invisible. + bool fLastRescanCompleted = false; // abort current rescan void AbortRescan() { fAbortRescan = true; } // Are we currently aborting a rescan? @@ -823,6 +850,9 @@ public: int rescanStartHeight = 0; int nextSweep = 0; + // Id of the in-flight sweep op; read by the op to confirm it is still the + // current one before mutating scheduler state. + AsyncRPCOperationId saplingSweepOperationId; int amountSwept = 0; int amountConsolidated = 0; int sweepInterval = 10; @@ -833,6 +863,31 @@ public: std::vector sweepExcludeAddresses; std::string consolidationAddress = ""; + int nextAutoShield = 0; + int autoShieldInterval = DEFAULT_AUTOSHIELD_INTERVAL; + CAmount autoShieldFee = DEFAULT_AUTOSHIELD_FEE; + // Minimum matured coinbase UTXOs before a round fires, to avoid per-interval + // fee churn on a single freshly-matured reward. + int autoShieldMinUtxos = DEFAULT_AUTOSHIELD_MIN_UTXOS; + // Configured destination z-addr override; also used to cache the resolved + // wallet-owned destination so we keep reusing one address. + std::string autoShieldAddress = ""; + // Id of the in-flight autoshield op; read by the op to confirm it is still + // the current one before mutating scheduler state. + AsyncRPCOperationId saplingAutoShieldOperationId; + // Provenance of this wallet's HD seed, recorded once in wallet.dat the + // first time a build that knows about it opens the wallet. Features that + // move funds into addresses only the seed can re-derive must not turn + // themselves ON by default unless the user can actually restore that seed. + enum HDSeedOrigin { + HDSEED_ORIGIN_UNRECORDED = 0, // no record in wallet.dat yet + HDSEED_ORIGIN_CREATED = 1, // minted onto a brand-new empty wallet + HDSEED_ORIGIN_RESTORED = 2, // user supplied -mnemonic / -hdseed + HDSEED_ORIGIN_RETROFIT = 3, // minted onto a pre-existing seedless wallet + HDSEED_ORIGIN_UNKNOWN = 4, // seed predates this record + }; + int hdSeedOrigin = HDSEED_ORIGIN_UNRECORDED; + void ClearNoteWitnessCache(); int64_t NullifierCount(); @@ -853,17 +908,22 @@ protected: */ void DecrementNoteWitnesses(const CBlockIndex* pindex); + //! Returns true only if the atomic write actually committed. Callers that checkpoint during a + //! long scan need to know: a silently-failing checkpoint would otherwise be retried forever at + //! full cost while never making progress. template - void SetBestChainINTERNAL(WalletDB& walletdb, const CBlockLocator& loc) { + bool SetBestChainINTERNAL(WalletDB& walletdb, const CBlockLocator& loc) { if (!walletdb.TxnBegin()) { // This needs to be done atomically, so don't do it at all LogPrintf("SetBestChain(): Couldn't start atomic write\n"); - return; + return false; } try { LOCK(cs_wallet); for (std::pair& wtxItem : mapWallet) { - auto wtx = wtxItem.second; + // By reference: a copy here deep-copies every note's witness deque + // (WITNESS_CACHE_SIZE entries) for every transaction, on every call. + const CWalletTx& wtx = wtxItem.second; // We skip transactions for which mapSaplingNoteData // is empty. This covers transactions that have no Sapling data // (i.e. are purely transparent), as well as shielding and unshielding @@ -872,32 +932,33 @@ protected: if (!walletdb.WriteTx(wtxItem.first, wtx)) { LogPrintf("SetBestChain(): Failed to write CWalletTx, aborting atomic write\n"); walletdb.TxnAbort(); - return; + return false; } } } if (!walletdb.WriteWitnessCacheSize(nWitnessCacheSize)) { LogPrintf("SetBestChain(): Failed to write nWitnessCacheSize, aborting atomic write\n"); walletdb.TxnAbort(); - return; + return false; } if (!walletdb.WriteBestBlock(loc)) { LogPrintf("SetBestChain(): Failed to write best block, aborting atomic write\n"); walletdb.TxnAbort(); - return; + return false; } } catch (const std::exception &exc) { // Unexpected failure LogPrintf("SetBestChain(): Unexpected error during atomic write:\n"); LogPrintf("%s\n", exc.what()); walletdb.TxnAbort(); - return; + return false; } if (!walletdb.TxnCommit()) { // Couldn't commit all to db, but in-memory state is fine LogPrintf("SetBestChain(): Couldn't commit atomic write\n"); - return; + return false; } + return true; } private: @@ -910,6 +971,11 @@ protected: /* the hd chain data model (chain counters) */ CHDChain hdChain; + //! Set at load time when hdChain cannot be trusted to describe this wallet's HD seed -- the + //! record was missing, or its seedFp does not match the seed actually loaded. While true the + //! wallet is fully usable for existing keys but refuses to DERIVE new ones, because it cannot + //! tell which key tree it belongs to. Never serialized; recomputed on every load. + bool fHDChainUnproven = false; public: /* @@ -1224,9 +1290,14 @@ public: const CBlock *pblock, boost::optional> added); void RunSaplingConsolidation(int blockHeight); + void RunAutoShieldCoinbase(int blockHeight); bool CommitAutomatedTx(const CTransaction& tx); - /** Saves witness caches and best block locator to disk. */ + /** Saves witness caches and best block locator to disk. Overrides CValidationInterface. */ void SetBestChain(const CBlockLocator& loc); + /** As SetBestChain, but for use INSIDE a long scan: opens the wallet DB with fFlushOnClose=false + * so the call does not trigger a full BDB txn_checkpoint over the whole cache (see the comment + * at CWallet::SetBestChain), and reports whether the write actually committed. */ + bool SetBestChainNoFlush(const CBlockLocator& loc); std::set> GetNullifiersForAddresses(const std::set & addresses); bool IsNoteSaplingChange(const std::set> & nullifierSet, const libzcash::PaymentAddress & address, const SaplingOutPoint & entry); @@ -1309,6 +1380,14 @@ public: bool SetHDSeed(const HDSeed& seed); bool SetCryptedHDSeed(const uint256& seedFp, const std::vector &vchCryptedSecret); + /* Record this wallet's BIP39 entropy so its seed phrase can be reprinted. + Display-only: derivation never reads it (the HD seed holds the bytes that + are actually derived from). Refuses to replace an existing record. + SetCryptedMnemonicEntropy overrides the CCryptoKeyStore virtual so the + record reaches disk; SetMnemonicEntropy merely hides the base version, + which is safe because no call site holds a base pointer. */ + bool SetMnemonicEntropy(const RawHDSeed& entropy); + bool SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector &vchCryptedSecret); /* Restore a wallet's HD seed from a hex string (as exported in the z_exportwallet "# HDSeed=" comment): 32 bytes for a legacy raw seed, or @@ -1327,8 +1406,14 @@ public: wallet and the seed is available (unlocked). Returns false otherwise. */ bool GetMnemonicPhrase(std::string& phraseOut) const; - /* True if the HD seed was derived from a BIP39 mnemonic (stored as entropy). */ - bool IsMnemonicSeed() const { return hdChain.fMnemonicSeed; } + /* True if this wallet has a BIP39 seed phrase available. Two storage forms + qualify: + - current: the HD seed is the EXPANDED 64-byte BIP39 seed and the + entropy lives in its own record (HaveMnemonicEntropy()); + - legacy: hdChain.fMnemonicSeed, where the stored HD seed IS the + 32-byte entropy and is expanded on every derivation. + Gates z_exportmnemonic (rpcdump.cpp). */ + bool IsMnemonicSeed() const { return hdChain.fMnemonicSeed || HaveMnemonicEntropy(); } /* Return the seed to feed into HD derivation. For mnemonic wallets this expands the stored 32-byte entropy into the 64-byte BIP39 seed; for legacy @@ -1349,11 +1434,28 @@ public: void SetHDChain(const CHDChain& chain, bool memonly); const CHDChain& GetHDChain() const { return hdChain; } + //! Mark hdChain as untrustworthy for derivation (see the member's declaration). Set only by + //! CWalletDB::LoadWallet; there is deliberately no way to clear it short of reloading, so a + //! degraded wallet cannot be talked back into deriving without a real repair. + void SetHDChainUnproven() { fHDChainUnproven = true; } + bool IsHDChainUnproven() const { return fHDChainUnproven; } + + /* Record (in memory and in wallet.dat) how this wallet's HD seed came to + exist. Best-effort: a failed write is logged, not fatal — the next start + simply re-classifies, and re-classification always errs toward + HDSEED_ORIGIN_UNKNOWN, which is the conservative answer. */ + void SetHDSeedOrigin(int origin); + /* Set the current HD seed, without saving it to disk (used by LoadWallet) */ bool LoadHDSeed(const HDSeed& key); /* Set the current encrypted HD seed, without saving it to disk (used by LoadWallet) */ bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector& seed); + /* Set the mnemonic entropy, without saving it to disk (used by LoadWallet) */ + bool LoadMnemonicEntropy(const RawHDSeed& entropy); + + /* Set the encrypted mnemonic entropy, without saving it to disk (used by LoadWallet) */ + bool LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector& vchCryptedSecret); /* Find notes filtered by payment address, min depth, ability to spend */ void GetFilteredNotes(std::vector& saplingEntries, diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 1fcb59a06..ffde7595b 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -34,11 +34,18 @@ #include #include +// Out-of-line definitions for CHDChain's in-class static constants. These are +// only initialised in the class body, so any ODR use -- binding one to a const +// reference, which is exactly what gtest's EXPECT_*/ASSERT_* macros do -- needs +// a definition or the link fails. hush-gtest hit this on VERSION_HD_MNEMONIC. +const int CHDChain::VERSION_HD_BASE; +const int CHDChain::VERSION_HD_TRANSPARENT; +const int CHDChain::VERSION_HD_MNEMONIC; +const int CHDChain::CURRENT_VERSION; + using namespace std; static uint64_t nAccountingEntryNumber = 0; -static list deadTxns; -extern CBlockIndex *hush_blockindex(uint256 hash); // // CWalletDB @@ -216,6 +223,12 @@ bool CWalletDB::WriteWitnessCacheSize(int64_t nWitnessCacheSize) return Write(std::string("witnesscachesize"), nWitnessCacheSize); } +bool CWalletDB::WriteHDSeedOrigin(int64_t nOrigin) +{ + nWalletDBUpdated++; + return Write(std::string("hdseedorigin"), nOrigin); +} + bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool) { return Read(std::make_pair(std::string("pool"), nPool), keypool); @@ -403,12 +416,30 @@ public: bool fAnyUnordered; int nFileVersion; vector vWalletUpgrade; + // True once a well-formed "hdchain" record has been loaded. + bool fHDChainRead; + // True when that record had to be repaired on read (see the "hdchain" case + // in ReadKeyValue); LoadWallet rewrites it in full form afterwards. + bool fHDChainRepaired; + // True once an "hdchain" record was ENCOUNTERED, whether or not it parsed. This is what + // separates the two failure shapes: a v1.0.3-or-earlier -salvagewallet drops the record + // entirely (its IsKeyType has no "hdchain" case), so absent == salvaged and recoverable, + // while present-but-unreadable means wider file damage. + bool fHDChainSeen; + // Fingerprint taken from the KEY of the hdseed/chdseed record. Available even for an + // encrypted wallet, where the seed itself cannot be read at load time. + bool fHDSeedSeen; + uint256 hdSeedFpSeen; CWalletScanState() { nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0; fIsEncrypted = false; fAnyUnordered = false; nFileVersion = 0; + fHDChainRead = false; + fHDChainRepaired = false; + fHDChainSeen = false; + fHDSeedSeen = false; } }; @@ -816,6 +847,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadHDSeed failed"; return false; } + wss.fHDSeedSeen = true; + wss.hdSeedFpSeen = seedFp; } else if (strType == "chdseed") { @@ -829,13 +862,106 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } wss.fIsEncrypted = true; + // The fingerprint is the plaintext KEY of the chdseed record, so this works while + // the wallet is locked and the seed itself is unreadable. + wss.fHDSeedSeen = true; + wss.hdSeedFpSeen = seedFp; } else if (strType == "hdchain") { + // Record the ENCOUNTER before any parsing can fail. + wss.fHDChainSeen = true; CHDChain chain; - ssValue >> chain; + // Keep an untouched copy: a failed >> has already consumed part of ssValue. + CDataStream ssRetry(ssValue.begin(), ssValue.end(), ssValue.GetType(), ssValue.GetVersion()); + try { + ssValue >> chain; + } catch (...) { + // Downgrade repair. A build predating VERSION_HD_TRANSPARENT writes + // this record back with only the four base fields while leaving + // nVersion at whatever it read, so the version-gated reads above run + // off the end. Without this, one address generated under such a build + // makes the wallet unopenable here ("Wallet corrupted") even though + // nothing is actually lost. + // + // Recovering is safe for a v1/v2 record: everything derivation needs + // is either in the four-field prefix or in the separate hdseed record, + // and the trailing counters are self-healing -- DeriveNewChildKey and + // GenerateNewSaplingZKey both skip indices whose key the wallet + // already holds, so restarting a counter at 0 re-walks past existing + // keys instead of reissuing them. + chain = CHDChain(); + try { + ssRetry >> chain.nVersion; + ssRetry >> chain.seedFp; + ssRetry >> chain.nCreateTime; + ssRetry >> chain.saplingAccountCounter; + } catch (...) { + // Short even in the base fields: genuinely corrupt. + strErr = "Error reading wallet database: hdchain record is corrupt"; + return false; + } + if (chain.nVersion >= CHDChain::VERSION_HD_MNEMONIC) { + // A record claiming to carry fMnemonicSeed must not have it + // guessed: that flag selects the derivation input, so defaulting + // it wrong yields a different key tree in silence. Fail loud, as + // this branch always did. + strErr = "Error reading wallet database: hdchain record is corrupt"; + return false; + } + chain.transparentChildCounter = 0; + chain.fMnemonicSeed = false; + wss.fHDChainRepaired = true; + LogPrintf("Repairing a truncated hdchain record (nVersion=%d): it was last written by " + "a wallet build that predates the transparent HD counter\n", chain.nVersion); + } + wss.fHDChainRead = true; pwallet->SetHDChain(chain, true); } + else if (strType == "hdseedorigin") + { + int64_t nOrigin = 0; + ssValue >> nOrigin; + // Clamp an out-of-range value (corrupt or hand-edited wallet.dat) to + // UNKNOWN — the conservative origin that leaves autoshield OFF — rather + // than trusting it to claim a known-recoverable (CREATED/RESTORED) seed. + if (nOrigin < CWallet::HDSEED_ORIGIN_UNRECORDED || nOrigin > CWallet::HDSEED_ORIGIN_UNKNOWN) { + nOrigin = CWallet::HDSEED_ORIGIN_UNKNOWN; + } + pwallet->hdSeedOrigin = (int)nOrigin; + } + else if (strType == "mnementropy") + { + uint256 entropyFp; + RawHDSeed entropy; + ssKey >> entropyFp; + ssValue >> entropy; + + if (MnemonicEntropyFingerprint(entropy) != entropyFp) + { + strErr = "Error reading wallet database: mnemonic entropy corrupt"; + return false; + } + + if (!pwallet->LoadMnemonicEntropy(entropy)) + { + strErr = "Error reading wallet database: LoadMnemonicEntropy failed"; + return false; + } + } + else if (strType == "cmnementropy") + { + uint256 entropyFp; + vector vchCryptedSecret; + ssKey >> entropyFp; + ssValue >> vchCryptedSecret; + if (!pwallet->LoadCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) + { + strErr = "Error reading wallet database: LoadCryptedMnemonicEntropy failed"; + return false; + } + wss.fIsEncrypted = true; + } } catch (...) { return false; @@ -847,6 +973,10 @@ static bool IsKeyType(string strType) { return (strType == "key" || strType == "wkey" || strType == "hdseed" || strType == "chdseed" || + // The mnemonic entropy must survive a keys-only salvage: without it + // a recovered wallet keeps its seed (and stays fully spendable) but + // silently loses the ability to reprint its seed phrase. + strType == "mnementropy" || strType == "cmnementropy" || strType == "zkey" || strType == "czkey" || strType == "sapzkey" || strType == "csapzkey" || strType == "vkey" || @@ -904,8 +1034,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) { // Leave other errors alone, if we try to fix them we might make things worse. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. - // set rescan for any error that is not vin-empty on staking chains. - if ( deadTxns.empty() && strType == "tx") + if ( strType == "tx") SoftSetBoolArg("-rescan", true); } } @@ -921,32 +1050,75 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) result = DB_CORRUPT; } - if ( !deadTxns.empty() ) - { - // staking chains with vin-empty error is a failed staking tx. - // we remove then re add the tx here to stop needing a full rescan, which does not actually fix the problem. - int32_t reAdded = 0; - BOOST_FOREACH (uint256& hash, deadTxns) - { - fprintf(stderr, "Removing possible orphaned staking transaction from wallet.%s\n", hash.ToString().c_str()); - if (!EraseTx(hash)) - fprintf(stderr, "could not delete tx.%s\n",hash.ToString().c_str()); - uint256 blockhash; CTransaction tx; CBlockIndex* pindex; - if ( GetTransaction(hash,tx,blockhash,false) && (pindex= hush_blockindex(blockhash)) != 0 && chainActive.Contains(pindex) ) - { - CWalletTx wtx(pwallet,tx); - pwallet->AddToWallet(wtx, true, NULL); - reAdded++; - } - } - fprintf(stderr, "Cleared %li orphaned staking transactions from wallet. Readded %i real transactions.\n",deadTxns.size(),reAdded); - fNoncriticalErrors = false; - deadTxns.clear(); - } - if (fNoncriticalErrors && result == DB_LOAD_OK) result = DB_NONCRITICAL_ERROR; + // Rewrite a repaired record in full form so the next load is clean and the + // transparent counter starts being persisted again. + if (wss.fHDChainRepaired && pwallet->HaveHDSeed()) + { + try { + pwallet->SetHDChain(pwallet->GetHDChain(), false); + LogPrintf("Rewrote the repaired hdchain record in full form\n"); + } catch (const std::exception& e) { + LogPrintf("Could not rewrite the repaired hdchain record: %s\n", e.what()); + } + } + + // A wallet that holds an HD seed but no usable hdchain record cannot safely DERIVE: hdChain + // falls back to its SetNull defaults, clearing fMnemonicSeed and so switching derivation from + // the 64-byte BIP39 seed to the raw 32-byte entropy -- an entirely different key tree. + // + // saplingAccountCounter was previously listed here as a second hazard. It is not one: + // GenerateNewSaplingZKey loops `do {...} while (HaveSaplingSpendingKey(...))` and + // DeriveNewChildKey loops `while (HaveKey(...))`, so a counter that starts low walks forward + // past accounts that already exist rather than colliding with them. + // + // Two shapes reach here and only one is real corruption: + // (a) the record is ABSENT -- the signature of a -salvagewallet run by v1.0.3 or earlier, + // whose IsKeyType has no "hdchain" case, so salvage dropped it. The keys are intact. + // Refusing strands the wallet with no way back: the node aborts before the RPC server + // exists, and -mnemonic refuses a non-empty wallet, so there is no user-executable + // recovery path at all. + // (b) the record is PRESENT but unreadable -- wider file damage. Keep refusing. + if (pwallet->HaveHDSeed() && !wss.fHDChainRead && wss.fHDChainSeen) + { + LogPrintf("Error loading wallet.dat: the hdchain record is present but unreadable. Your keys " + "are intact -- do NOT delete or replace wallet.dat. Back it up and see debug.log.\n"); + return DB_CORRUPT; + } + + { + const char* pszDegraded = NULL; + if (pwallet->HaveHDSeed() && !wss.fHDChainRead) + { + pszDegraded = "the hdchain record is missing (an older -salvagewallet drops it)"; + } + // A chain WAS read, but it does not belong to the seed we loaded. No legitimate writer can + // produce that -- InstallHDSeed always stores seedFp = seed.Fingerprint(). It is the mark + // of a wallet that lost its hdchain to an old salvage and was then USED on that old build, + // which persists a SetNull-derived chain carrying a null seedFp. Such a wallet otherwise + // starts up perfectly clean and derives into the WRONG TREE forever, silently -- strictly + // worse than failing to open, which is why it is worth detecting here. + else if (pwallet->HaveHDSeed() && wss.fHDSeedSeen && + pwallet->GetHDChain().seedFp != wss.hdSeedFpSeen) + { + pszDegraded = "the hdchain record does not belong to this wallet's HD seed"; + } + + if (pszDegraded != NULL) + { + pwallet->SetHDChainUnproven(); + LogPrintf("Wallet opened in DEGRADED mode: %s. Existing keys are intact, spendable and " + "receivable, but no NEW HD-derived key can be generated and new transparent " + "addresses will not be recoverable from a seed phrase. Back up wallet.dat now " + "and do NOT delete or replace it.\n", pszDegraded); + // An old salvage also dropped defaultkey and bestblock, which would make this look like + // a first run and skip the rescan, leaving a permanently zero balance. + SoftSetBoolArg("-rescan", true); + } + } + // Any wallet corruption at all: skip any rewriting or // upgrading, we don't want to make it worse. if (result != DB_LOAD_OK) @@ -1240,7 +1412,13 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, wss, strType, strErr); } - if (!IsKeyType(strType)) + // "hdchain" is not a key type, but it must survive a keys-only + // salvage: a recovered wallet that keeps its seed while losing its + // hdchain silently derives from a different key tree (fMnemonicSeed + // cleared -> raw entropy instead of the 64-byte BIP39 seed) and + // re-issues sapling accounts from 0. CWalletDB::LoadWallet now + // refuses such a wallet outright, so preserve the record here. + if (!IsKeyType(strType) && strType != "hdchain") continue; if (!fReadOK) { @@ -1290,6 +1468,34 @@ bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector& vchCryptedSecret) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("cmnementropy"), entropyFp), vchCryptedSecret); +} + +bool CWalletDB::EraseMnemonicEntropy(const uint256& entropyFp) +{ + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("mnementropy"), entropyFp)); +} + bool CWalletDB::WriteHDChain(const CHDChain& chain) { nWalletDBUpdated++; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d55072271..a219ec855 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -191,6 +191,9 @@ public: bool WriteWitnessCacheSize(int64_t nWitnessCacheSize); + //! Record how this wallet's HD seed came to exist (CWallet::HDSeedOrigin). + bool WriteHDSeedOrigin(int64_t nOrigin); + bool ReadPool(int64_t nPool, CKeyPool& keypool); bool WritePool(int64_t nPool, const CKeyPool& keypool); bool ErasePool(int64_t nPool); @@ -219,6 +222,17 @@ public: bool WriteHDSeed(const HDSeed& seed); bool WriteCryptedHDSeed(const uint256& seedFp, const std::vector& vchCryptedSecret); + //! Remove the PLAINTEXT hdseed record. Must be called once the seed has been + //! written in encrypted form: CDB::Rewrite (invoked at the end of + //! CWallet::EncryptWallet) copies whatever records still exist into the new + //! file, so a leftover "hdseed" leaves the unencrypted seed on disk forever. + bool EraseHDSeed(const uint256& seedFp); + //! BIP39 entropy for a phrase-recoverable wallet. Display-only: derivation + //! never reads it. Record names are deliberately distinct prefixes from + //! "hdseed"/"chdseed" so they cannot collide. + bool WriteMnemonicEntropy(const uint256& entropyFp, const RawHDSeed& entropy); + bool WriteCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector& vchCryptedSecret); + bool EraseMnemonicEntropy(const uint256& entropyFp); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); diff --git a/util/build-win.sh b/util/build-win.sh index aa644f0c6..c7a627420 100755 --- a/util/build-win.sh +++ b/util/build-win.sh @@ -35,7 +35,13 @@ then ls -la build/librandomx* else mkdir build && cd build - CC="${CC} -g " CXX="${CXX} -g " cmake -DARCH=native .. + # ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which + # tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box + # emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports + # avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU + # older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2 + # for argon2, so the portable baseline costs ~nothing. + CC="${CC} -g " CXX="${CXX} -g " cmake -DARCH=default .. make fi diff --git a/util/build.sh b/util/build.sh index 2a2a3f2d1..4ccc9ab3a 100755 --- a/util/build.sh +++ b/util/build.sh @@ -147,7 +147,13 @@ then ls -la build/librandomx* else mkdir build && cd build - cmake -DARCH=native .. + # ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which + # tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box + # emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports + # avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU + # older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2 + # for argon2, so the portable baseline costs ~nothing. + cmake -DARCH=default .. # pass along potential -jX and other args time make "$@" fi diff --git a/util/debug-build.sh b/util/debug-build.sh index a47681506..2e7dfff18 100755 --- a/util/debug-build.sh +++ b/util/debug-build.sh @@ -134,7 +134,13 @@ then ls -la build/librandomx* else mkdir build && cd build - cmake -DARCH=native .. + # ARCH=default, NOT native. RandomX's CMakeLists maps ARCH=native to -march=native, which + # tunes the binary to the BUILD machine. Measured 2026-08-21: a build on the Zen4 pool box + # emitted 746 AVX-512 (zmm) instructions into librandomx.a, and every seed reports + # avx512f=no -- that binary SIGILLs inside RandomX on the whole fleet, and on any user CPU + # older than the build host. ARCH=default still enables -maes plus per-file -mssse3/-mavx2 + # for argon2, so the portable baseline costs ~nothing. + cmake -DARCH=default .. make fi diff --git a/util/genbuild.sh b/util/genbuild.sh index 08fb91dc5..49c870287 100755 --- a/util/genbuild.sh +++ b/util/genbuild.sh @@ -18,7 +18,12 @@ fi DESC="" SUFFIX="" -if [ -e "$(which git 2>/dev/null)" -a "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then +# Allow the build system to supply the version when git metadata is unavailable: +# container builds exclude .git, and a linked worktree's .git file points outside +# the build context. Without this such builds are stamped "-unk". +if [ -n "${DRAGONX_BUILD_DESC:-}" ]; then + DESC="$DRAGONX_BUILD_DESC" +elif [ -e "$(which git 2>/dev/null)" -a "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]; then # clean 'dirty' status of touched files that haven't been modified git diff >/dev/null 2>/dev/null