From 2ebbbc777cc8e79b0007a46a10652612b5bb8d9b Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 02:06:09 -0500 Subject: [PATCH 01/68] wallet: add default-on auto-shield-coinbase; drain miner coinbase into a z-addr On this ac_private=1 chain miners accumulate one transparent coinbase UTXO per block (the only transparent output the chain permits). It had to be shielded manually via z_shieldcoinbase, and left unshielded it is the sole persistent metadata leak on the chain and the source of miner UTXO-fragmentation send failures. Add AsyncRPCOperation_autoshieldcoinbase: a periodic, default-on wallet op driven from CWallet::ChainTip alongside sweep/consolidation, draining matured coinbase into a wallet-owned Sapling z-address in size-bounded batches. - Enqueue-only driver (RunAutoShieldCoinbase): takes only cs_wallet in the notify context; all gathering runs on the async worker under LOCK2(cs_main, cs_wallet). - Dedicated op (not a reuse of z_shieldcoinbase) so it never toggles mining. - Default-ON but conditional: silent no-op on -disablewallet, external -mineraddress, non-mining, or locked wallets (explicit IsLocked() guard). - Destination is reuse-then-create; -autoshieldaddress override is spend-key-validated at init so funds cannot be stranded. - Sweep-model bookkeeping: advances nextAutoShield and clears the running flag on every terminal state; an op-id guard stops a stale op clobbering scheduler state; a self-guard stops cancel/re-enqueue churn. - Sietch-padded output shape matches manual z_shieldcoinbase txns. Config: -autoshield (default true), -autoshieldinterval, -autoshieldaddress, -autoshieldfee (range-validated), -autoshieldminutxos. Incorporates fixes from an 8-angle code review: CAmount fee type with init-time range validation, op-id-guarded flag bookkeeping, driver self-guard, and a tipHeight-consistent NU-activation guard. Note: the fConsolidationRunning / nextConsolidation consolidation-scheduler wedge is a pre-existing bug, left for a separate change. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Makefile.am | 2 + src/init.cpp | 55 +++ .../asyncrpcoperation_autoshieldcoinbase.cpp | 315 ++++++++++++++++++ .../asyncrpcoperation_autoshieldcoinbase.h | 58 ++++ src/wallet/wallet.cpp | 72 ++++ src/wallet/wallet.h | 20 ++ 6 files changed, 522 insertions(+) create mode 100644 src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp create mode 100644 src/wallet/asyncrpcoperation_autoshieldcoinbase.h diff --git a/src/Makefile.am b/src/Makefile.am index 86d199a52..1ba627e21 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 \ diff --git a/src/init.cpp b/src/init.cpp index a359c7200..89243cf1e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -489,6 +489,12 @@ 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 wallet z-address (default: true). No-op when not mining or wallet is locked.")); + strUsage += HelpMessageOpt("-autoshieldinterval", strprintf(_("Block interval between automatic coinbase-shielding rounds (default: %i, min 5)"), 25)); + 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)"), 10000)); + 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)); @@ -2451,6 +2457,55 @@ 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. + pwalletMain->fAutoShieldEnabled = GetBoolArg("-autoshield", true); + if (pwalletMain->fAutoShieldEnabled) { + int autoShieldInterval = GetArg("-autoshieldinterval", 25); + if (autoShieldInterval < 5) { + fprintf(stderr,"%s: Invalid autoshield interval of %d < 5, setting to default of 25\n", __func__, autoShieldInterval); + autoShieldInterval = 25; + } + 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", 10000); + const CAmount AUTOSHIELD_MIN_FEE = 1000; // comfortably above minRelayTxFee for a small tx + const CAmount AUTOSHIELD_MAX_FEE = 10000000; // 0.1 DRGX; no sane autoshield fee exceeds this + if (autoShieldFee < AUTOSHIELD_MIN_FEE || autoShieldFee > AUTOSHIELD_MAX_FEE) { + fprintf(stderr,"%s: -autoshieldfee=%lld out of range [%lld,%lld], using default 10000\n", + __func__, (long long)autoShieldFee, (long long)AUTOSHIELD_MIN_FEE, (long long)AUTOSHIELD_MAX_FEE); + autoShieldFee = 10000; + } + pwalletMain->autoShieldFee = autoShieldFee; + pwalletMain->autoShieldMinUtxos = GetArg("-autoshieldminutxos", 1); + 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; + } + } + //Set Transaction Deletion Options fTxDeleteEnabled = GetBoolArg("-deletetx", false); fTxConflictDeleteEnabled = GetBoolArg("-deleteconflicttx", true); diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp new file mode 100644 index 000000000..60c9eda17 --- /dev/null +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -0,0 +1,315 @@ +// 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; +static const size_t AUTOSHIELD_CTXIN_P2SH_SIZE = 400; +// Expire unmined autoshield txs after this many blocks, so a tx cannot straddle +// a network-upgrade activation. +static const int AUTOSHIELD_EXPIRY_DELTA = 15; + +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 (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"); + } + + 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_)); +} + +// Enumerate wallet-owned Sapling addresses and pick a spendable one; if none +// exists, generate a fresh one (needs an unlocked wallet, which the caller has +// already ensured). Caller must hold cs_wallet. +bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination( + libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut) { + + // 1. Explicit -autoshieldaddress override (validated + spend-key-checked at init) + if (!pwalletMain->autoShieldAddress.empty()) { + auto decoded = DecodePaymentAddress(pwalletMain->autoShieldAddress); + if (boost::get(&decoded) != nullptr) { + destOut = boost::get(decoded); + destStrOut = pwalletMain->autoShieldAddress; + return true; + } + LogPrintf("%s: configured -autoshieldaddress is not a valid Sapling address\n", getId()); + return false; + } + + // 2. Reuse the first spendable wallet-owned Sapling address (std::set order + // is deterministic, so this is stable across rounds/restarts). + std::set addrs; + pwalletMain->GetSaplingPaymentAddresses(addrs); + for (const auto& a : addrs) { + libzcash::SaplingExtendedSpendingKey extsk; + if (pwalletMain->GetSaplingExtendedSpendingKey(a, extsk)) { + destOut = a; + destStrOut = EncodePaymentAddress(a); + // Cache it so we keep reusing the same address. + pwalletMain->autoShieldAddress = destStrOut; + return true; + } + } + + // 3. No spendable z-addr yet: create one (requires unlocked wallet / HD seed). + if (pwalletMain->IsLocked()) { + return false; + } + try { + destOut = pwalletMain->GenerateNewSaplingZKey(); + destStrOut = EncodePaymentAddress(destOut); + pwalletMain->autoShieldAddress = destStrOut; + LogPrintf("%s: generated new autoshield destination z-address %s\n", getId(), destStrOut); + 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 + AUTOSHIELD_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; + 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 with fOnlySpendable already excludes immature coinbase + // (< COINBASE_MATURITY) and outputs we don't own, so external + // -mineraddress / pool coinbase naturally yields zero inputs. + size_t estimatedTxSize = 2000; // header + sietch outputs headroom + 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 (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; + } + } + + 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. + auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain); + builder.SetExpiryHeight(tipHeight + AUTOSHIELD_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() { + 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..98dd5c734 --- /dev/null +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h @@ -0,0 +1,58 @@ +// 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" + +// Default fee for automatic coinbase-shielding transactions +static const CAmount DEFAULT_AUTOSHIELD_FEE = 10000; + +// 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/wallet.cpp b/src/wallet/wallet.cpp index c762b0b97..cab7b4373 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -40,6 +40,7 @@ #include "coins.h" #include "wallet/asyncrpcoperation_saplingconsolidation.h" #include "wallet/asyncrpcoperation_sweep.h" +#include "wallet/asyncrpcoperation_autoshieldcoinbase.h" #include #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); } @@ -604,6 +608,12 @@ 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(); @@ -641,6 +651,11 @@ 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); std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr lastOperation = q->getOperationForId(saplingConsolidationOperationId); @@ -653,6 +668,63 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { q->addOperation(operation); } +// 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. + // NOTE: fConsolidationRunning is currently never set true (a pre-existing + // consolidation-scheduler bug); this half of the guard only becomes + // effective once that is fixed on the consolidation side. + 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(); + } + std::shared_ptr operation(new AsyncRPCOperation_autoshieldcoinbase(blockHeight + 5)); + saplingAutoShieldOperationId = operation->getId(); + q->addOperation(operation); +} + bool CWallet::CommitAutomatedTx(const CTransaction& tx) { CWalletTx wtx(this, tx); CReserveKey reservekey(pwalletMain); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a99c0a4ea..6018b0df1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -809,6 +809,12 @@ public: bool fSweepExternalEnabled = false; bool fSweepRunning = false; + // Automatic coinbase shielding (t->z). Default ON but conditional: it is a + // silent no-op on nodes where it cannot act (no wallet, external + // -mineraddress, non-mining, or locked wallet). See RunAutoShieldCoinbase. + bool fAutoShieldEnabled = true; + bool fAutoShieldRunning = false; + std::atomic fAbortRescan{false}; // abort current rescan void AbortRescan() { fAbortRescan = true; } @@ -833,6 +839,19 @@ public: std::vector sweepExcludeAddresses; std::string consolidationAddress = ""; + int nextAutoShield = 0; + int autoShieldInterval = 25; + CAmount autoShieldFee = 10000; + // Minimum matured coinbase UTXOs before a round fires, to avoid per-interval + // fee churn on a single freshly-matured reward. + int autoShieldMinUtxos = 1; + // 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; + void ClearNoteWitnessCache(); int64_t NullifierCount(); @@ -1224,6 +1243,7 @@ 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. */ void SetBestChain(const CBlockLocator& loc); From ca730a5d985aab84c82a7516204993fa0496dc36 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 18:28:17 -0500 Subject: [PATCH 02/68] wallet: fix consolidation-scheduler wedge (ran every block; dead mutual-exclusion) The Sapling auto-consolidation scheduler never advanced nextConsolidation after init and never set fConsolidationRunning, so once the tip passed the init threshold `-consolidation` dispatched a consolidation op every block instead of once per -consolidationinterval, and every guard that reads fConsolidationRunning (in RunSaplingSweep, and in the new autoshield driver) was dead. Mirror the intended scheduler model: - RunSaplingConsolidation sets fConsolidationRunning=true before dispatch and self-guards with `if (fConsolidationRunning) return;`. - The consolidation op advances nextConsolidation = consolidationInterval + tipHeight and clears fConsolidationRunning on every terminal state (success/failure/exception/cancel), guarded by op id so only the current op mutates scheduler state. - saplingConsolidationOperationId moved to public so the op can read it. Restores the documented once-per-interval cadence and makes the sweep/consolidation/autoshield mutual-exclusion guards effective. Note: the sweep op has the same latent wedge (fSweepRunning/nextSweep are cleared only on the sweepComplete success path, so a cancelled or throwing sweep leaves fSweepRunning stuck true) - left for a follow-up; this commit is the template to port. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...asyncrpcoperation_saplingconsolidation.cpp | 26 ++++++++++++++++++- src/wallet/wallet.cpp | 10 ++++--- src/wallet/wallet.h | 4 ++- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 755564492..1a6dfec7c 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -28,8 +28,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(); @@ -76,6 +85,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() { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cab7b4373..5a3c56fe5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -644,6 +644,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) { @@ -657,6 +663,7 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { } 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) { @@ -697,9 +704,6 @@ void CWallet::RunAutoShieldCoinbase(int blockHeight) { // Mutual exclusion: sweep/consolidation share the single async worker and // cs_wallet; don't queue an autoshield in the same connected block. - // NOTE: fConsolidationRunning is currently never set true (a pre-existing - // consolidation-scheduler bug); this half of the guard only becomes - // effective once that is fixed on the consolidation side. if (fSweepRunning || fConsolidationRunning) { LogPrintf("%s: not autoshielding since sweep/consolidation is running at height=%d\n", __func__, blockHeight); return; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6018b0df1..a259963a8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -784,7 +784,6 @@ private: TxNullifiers mapTxSaplingNullifiers; std::vector pendingSaplingConsolidationTxs; - AsyncRPCOperationId saplingConsolidationOperationId; std::vector pendingSaplingSweepTxs; AsyncRPCOperationId saplingSweepOperationId; @@ -802,6 +801,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; From 4d72e5fc30c6e657e3abdb779a9002f256fb4351 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 18:55:16 -0500 Subject: [PATCH 03/68] wallet: fix sweep-scheduler wedge and unsynchronized driver mutation The zaddr-sweep op cleared fSweepRunning/nextSweep only on the sweepComplete success path (inside main_impl), so a cancelled or throwing sweep left fSweepRunning stuck true. Since fSweepRunning now also gates consolidation and the default-on autoshield, a persistently failing sweep (e.g. a corrupt-witness note) would wedge all three background ops for the session. Move the scheduler bookkeeping into main() so it runs on every terminal state (success/failure/exception/cancel), guarded by op id. Preserve the intended "keep draining every block until swept" model: on a successful-but-incomplete round the flag stays set and nextSweep is not advanced; on completion OR on failure/exception the flag is released and nextSweep backs off one interval, so a failing sweep no longer retries every block or wedges the other ops. Also fix RunSaplingSweep to take cs_wallet itself (was AssertLockHeld, a no-op in release builds) since ChainTip does not hold it there and the driver mutates scheduler state + enqueues -- matching RunSaplingConsolidation and RunAutoShieldCoinbase. sweepComplete is recorded via a new member; saplingSweepOperationId made public. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet/asyncrpcoperation_sweep.cpp | 37 ++++++++++++++++++++++---- src/wallet/asyncrpcoperation_sweep.h | 4 +++ src/wallet/wallet.cpp | 8 +++++- src/wallet/wallet.h | 4 ++- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sweep.cpp b/src/wallet/asyncrpcoperation_sweep.cpp index 166548a71..c9b7afaf6 100644 --- a/src/wallet/asyncrpcoperation_sweep.cpp +++ b/src/wallet/asyncrpcoperation_sweep.cpp @@ -27,8 +27,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(); @@ -64,6 +73,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"); @@ -314,10 +340,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); 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/wallet.cpp b/src/wallet/wallet.cpp index 5a3c56fe5..30eabdc43 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -585,7 +585,13 @@ void CWallet::RunSaplingSweep(int blockHeight) { 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); if (!fSweepEnabled) { return; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a259963a8..8dc20618e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -786,7 +786,6 @@ private: std::vector pendingSaplingConsolidationTxs; std::vector pendingSaplingSweepTxs; - AsyncRPCOperationId saplingSweepOperationId; void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid); void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid); @@ -831,6 +830,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; From 7dc904c96f1c774b2a45f8753e2ee87579603cfd Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 16:15:33 -0500 Subject: [PATCH 04/68] perf(sync): extend DRAGONX checkpoints to 3,226,000; build portable RandomX The RandomX skip in RandomXValidationRequired() has never fired in production. It skips verification below the last in-index checkpoint, but the DRAGONX checkpoint table ended at 2,838,000 while ASSETCHAINS_RANDOMX_VALIDATION is 2,838,976 -- the window was empty by 976 blocks. Every block since the RandomX activation has been fully verified, at ~65ms per hash on the fastest x86 core available and ~180ms on a typical user machine. Extends the table by 388 entries at stride 1000, from 2,839,000 to 3,226,000 (tip - ~5,600, far beyond any reorg this chain has produced; max observed depth is 3-4). Blocks requiring a RandomX verify drop from 391,447 to ~5,500. The same extension carries the existing script/zk-proof skip (fScriptChecks, fExpensiveChecks) over the same range. Checkpoint data verification, before it went anywhere near source: - generated on a continuously-synced node - all 388 hashes identical on 4 other full nodes (388/388 on each) - reverse-verified hash -> height, all on the active chain - re-extracted from the patched file and diffed against the verified set - 2,838 pre-existing entries unchanged; all 3,226 ascending and unique Trailer fields computed from RPC, not util/checkpoints.pl, which greps a rotating debug.log and assumes 1440 blk/day (DragonX is 2400). Also switches all three build scripts from -DARCH=native to -DARCH=default. RandomX's CMakeLists maps ARCH=native to -march=native, tuning the binary to the build host: a Zen4 build emitted 746 AVX-512 instructions into librandomx.a, and every seed reports avx512f=no, so that binary would SIGILL inside RandomX fleet-wide -- and on any user CPU older than the build machine. build-win.sh had the same flag, so shipped Windows binaries inherited it. ARCH=default keeps -maes and per-file -mssse3/-mavx2, so the portable baseline costs essentially nothing. Note build.sh skips cmake entirely when src/RandomX/build/ exists, so a stale dir silently preserves the old ARCH. Validated on an isolated datadir on an EPYC seed, bootstrap -> tip: - below 3,226,000 (RandomX skipped): 91.3 blk/s (22,823 blocks / 250s) - at/above 3,226,000 (verified): 4.7 blk/s ~19x at the boundary. The 4.7 blk/s baseline matches a same-day restore on the old binary, corroborating it independently. - gettxoutsetinfo at height 3,231,951 BYTE-IDENTICAL to a live node (hash_serialized 4885c2374ef8b84c648b97d560a57cfcc99bb979142dc89c2a5ccbc90a1f1692, 222,635 txs/txouts, 15,826,352 bytes, total 667909.93689180) - synced through all 388 new checkpoints with zero rejections - verifychain 4 (500) and 3 (2000) both true Binary: v1.0.3-bf3c33c53-dirty, stripped md5 fe83d70fec5b50c38bf65ea6c733ffa9 Checkpoints decay at 2,400 blocks/day; regenerating them belongs on the release checklist. Co-Authored-By: Claude Opus 5 (1M context) --- src/chainparams.cpp | 398 +++++++++++++++++++++++++++++++++++++++++++- util/build-win.sh | 8 +- util/build.sh | 8 +- util/debug-build.sh | 8 +- 4 files changed, 416 insertions(+), 6 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 683cb2a33..cad00b9f8 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -5640,9 +5640,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/util/build-win.sh b/util/build-win.sh index 66a75c6fc..a6db10c57 100755 --- a/util/build-win.sh +++ b/util/build-win.sh @@ -32,7 +32,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 From fbcf160478a07d51c6edcd8f6e468f742ea58cc4 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 16:58:29 -0500 Subject: [PATCH 05/68] fix(pow): only dedup RandomX when CheckBlockHeader verified it; drop fake git id Brings onto dev the two fixes that until now existed only on release/1.0.4, so nothing is stranded on a branch we are not shipping from. 1. GUARDED VERIFY-ONCE (main.cpp) 4e67e687d arms the RandomX dedup unconditionally whenever fCheckPOW is set. But CheckBlockHeader returns early -- BEFORE reaching its RandomX check -- for a block whose timestamp is >60s in the future (*futureblockp==1), and CheckBlock deliberately continues on that path. There, hush_checkPOW is the ONLY RandomX verification the block gets, so suppressing it leaves the block unverified. Not a chain-acceptance hole: ConnectBlock rejects futureblock != 0, so such a block never joins the chain. But it silently weakens DoS banning -- an invalid future block gets rejected for its timestamp instead of for bad PoW, which is a regression against the un-deduped behaviour it replaced. ScopedRandomXSkip now takes an `arm` flag and CheckBlock passes fHeaderChecked, so the dedup applies only where the header check actually completed and did the verification. Strictly a tightening: it can only cause MORE verification than before, never less. 2. NO FAKE GIT IDENTITY (clientversion.cpp) A hardcoded `#define GIT_ARCHIVE 1` with GIT_COMMIT_ID "a86845f3dc", dated Feb 2018, is reached whenever build.h supplies no BUILD_DESC -- i.e. any build without git metadata, which is exactly the tarball/CI release case. Such binaries reported themselves as that Komodo commit regardless of content; a build here did precisely that before this was found. The archive substitution placeholders are kept, so a real git-archive export still works; a git-less build now reports "-unk", which is honest and greppable. Both syntax-clean. Rebuild and re-validation on EPYC follows; the earlier validated binary (md5 fe83d70fec5b50c38bf65ea6c733ffa9) predates these. release/1.0.4 is parked, not deleted -- its commit records why the v1.0.2 lineage cannot ship (block-index format incompatibility with dev-written chainstate). Co-Authored-By: Claude Opus 5 (1M context) --- src/clientversion.cpp | 12 ++++++++---- src/main.cpp | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) 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/main.cpp b/src/main.cpp index 1375ab064..c15049253 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5155,10 +5155,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 +5173,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 +5202,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"); } From 660678f9bb9ad3ab45c75b3c904fab66e23ce5f3 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 21 Aug 2026 17:09:45 -0500 Subject: [PATCH 06/68] build: bump version to 1.1.0 CLIENT_VERSION 1000350 -> 1010050. Goes to 1.1.0 rather than 1.0.4 because 1.0.3 is already burned and ambiguous: origin/dragonx's debian changelog already claims 1.0.3 and the daemon bundled in the ObsidianDragon 2.0.1 installer is labelled v1.0.3-dc45e7d90, so a 1.0.4 would sort above builds that contain less. Bumped in configure.ac (authoritative) and in the src/clientversion.h fallback used when HAVE_CONFIG_H is unset, which the header itself asks to be kept in sync. DELIBERATELY NOT BUMPED: SPROUT_VALUE_VERSION, SAPLING_VALUE_VERSION and SAPLING_VALUE_OPTIONAL_VERSION in chain.h. Those are thresholds marking the CLIENT_VERSION that INTRODUCED each block-index format, not "the current version". Raising SAPLING_VALUE_OPTIONAL_VERSION to 1010050 would push every record written by a v1.0.3 node (nVersion 1000350) into the legacy raw-CAmount branch of the deserializer and misparse it. The stale comment naming 1000350 as the current CLIENT_VERSION is updated; the constants stand. 1010050 >= 1000350, so this build still writes and reads the optional format. Co-Authored-By: Claude Opus 5 (1M context) --- configure.ac | 4 ++-- contrib/debian/changelog | 21 +++++++++++++++++++++ src/chain.h | 2 +- src/clientversion.h | 4 ++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index d0316ec49..6fc772ad3 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, 1) +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))) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index cf12082c5..82505dbd1 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,24 @@ +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 Thu, 21 Aug 2026 22:00:00 +0000 + dragonx (1.0.3) stable; urgency=medium * IBD/sync speedups: parallel RandomX pre-verification, adaptive -dbcache, P2P download fixes diff --git a/src/chain.h b/src/chain.h index 69c66c674..37a47689e 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 1010050 (v1.1.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 diff --git a/src/clientversion.h b/src/clientversion.h index 9f8308415..35bb0dbef 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 1 +#define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 50 //! Set to true for release, false for prerelease or test build From dad162a89a524a2bcc7dc72abc07ccb0970ea16b Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 02:52:34 +0200 Subject: [PATCH 07/68] wallet: pick the autoshield destination by seed re-derivation, in-gap only resolveDestination() took the first spendable address in std::set order, which orders on the raw Sapling diversifier (zcash/Address.hpp:95-98) -- uncorrelated with anything the operator can see, and unstable across restarts as addresses are added. Worse, GetSaplingPaymentAddresses() also returns z_importkey/z_importwallet addresses, and CKeyMetadata is NOT evidence of provenance: both hdKeypath and seedFp are copied verbatim out of the import source (rpcdump.cpp:511-516 -> wallet.cpp:5440-5441) with no verification. A crafted import can therefore claim this wallet's seedFp and keypath m/32'/coin'/0' and capture every shielded mining reward into a key no seed restore can reproduce. Filtering on metadata would not have caught that. Derive instead. A bare -mnemonic/-hdseed restore pre-derives exactly -mnemonicsaplinggap sapling accounts from index 0 with saplingAccountCounter reset (init.cpp:2349-2355), so the only self-recoverable destinations are the default addresses of m/32'/'/i' for i below the gap. Walk that window from the seed and take the lowest index the wallet holds a spending key for. Deriving is the only authoritative test and cannot be spoofed. When nothing in the window is held yet, derive the next account -- but only if it will land inside the window. GenerateNewSaplingZKey does not derive at saplingAccountCounter: its do/while skips indices already held (wallet.cpp:150-157), so a bare counter-below-gap test is unsound. Predict the lowest free index at or above the counter and post-verify the returned address. If no free account remains below the gap, refuse the round and leave the coinbase transparent -- transparent funds are still recoverable through the 1000-key transparent gap, an unfindable note is not. Refusing is safe: main_impl turns a false return into a clean skip, and main() always advances nextAutoShield and clears fAutoShieldRunning, so a refused round cannot latch the feature off. Co-Authored-By: Claude Opus 5 (1M context) --- .../asyncrpcoperation_autoshieldcoinbase.cpp | 130 +++++++++++++++--- 1 file changed, 111 insertions(+), 19 deletions(-) diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index 60c9eda17..aa79af438 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -93,13 +93,30 @@ void AsyncRPCOperation_autoshieldcoinbase::main() { getId(), getStateAsString(), numTxCreated_, FormatMoney(amountShielded_)); } -// Enumerate wallet-owned Sapling addresses and pick a spendable one; if none -// exists, generate a fresh one (needs an unlocked wallet, which the caller has -// already ensured). Caller must hold cs_wallet. +// 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) { - // 1. Explicit -autoshieldaddress override (validated + spend-key-checked at init) + // 1. Explicit -autoshieldaddress override (validated as a spendable Sapling + // zaddr at init.cpp:2494-2506). This also serves as the per-process cache + // for whatever step 2/3 resolved. if (!pwalletMain->autoShieldAddress.empty()) { auto decoded = DecodePaymentAddress(pwalletMain->autoShieldAddress); if (boost::get(&decoded) != nullptr) { @@ -111,30 +128,105 @@ bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination( return false; } - // 2. Reuse the first spendable wallet-owned Sapling address (std::set order - // is deterministic, so this is stable across rounds/restarts). - std::set addrs; - pwalletMain->GetSaplingPaymentAddresses(addrs); - for (const auto& a : addrs) { - libzcash::SaplingExtendedSpendingKey extsk; - if (pwalletMain->GetSaplingExtendedSpendingKey(a, extsk)) { - destOut = a; - destStrOut = EncodePaymentAddress(a); - // Cache it so we keep reusing the same address. + // 2. Walk the restore window m/32'/coin'/[0, gap)' derived from the seed. + HDSeed seed; + if (!pwalletMain->GetHDSeedForDerivation(seed)) { + LogPrintf("%s: no HD seed available; refusing to pick an autoshield destination\n", getId()); + return false; + } + + // Mirror init.cpp:2349-2350'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); + // Cache for the life of the process; step 1 short-circuits later + // rounds. Safe: we only cache post-validation. pwalletMain->autoShieldAddress = destStrOut; + LogPrintf("%s: autoshield destination %s (seed-derived sapling account %u, gap %u)\n", + getId(), destStrOut, (unsigned)i, (unsigned)saplingGap); return true; } } - // 3. No spendable z-addr yet: create one (requires unlocked wallet / HD seed). - if (pwalletMain->IsLocked()) { + // 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 { - destOut = pwalletMain->GenerateNewSaplingZKey(); - destStrOut = EncodePaymentAddress(destOut); + 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\n", getId(), 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()); From a494eabdce99e3d6c5b7661696bb733d6efd0379 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 03:18:35 +0200 Subject: [PATCH 08/68] wallet: harden HD seed/chain persistence and record seed provenance Autoshield sends mined coinbase to a seed-derived z-address, so the records that decide how the seed derives keys become fund-safety critical. Three of them were not treated that way. InstallHDSeed wrote the seed record before the chain record, non-transactionally. A crash between the two left a wallet holding a seed with no hdchain: on the next load hdChain silently reverts to defaults, clearing fMnemonicSeed -- which switches the derivation input from the expanded BIP39 seed to the raw entropy -- and resetting saplingAccountCounter. Write the chain first; the opposite torn state is harmless and self-heals, because HaveHDSeed() is then false and init installs again. The hdchain record was read as a bare deserialise inside a catch-all with strErr never set, and it is not a key type, so a corrupt record was downgraded to a non-critical error and the node booted into the wrong key tree. Report it, track whether it was read, and refuse to load a wallet that holds a seed but no readable hdchain. Also preserve hdchain through a keys-only salvage, which would otherwise drop it and produce exactly the state we now refuse. GenerateNewSeed silently fell back to a random seed when BIP39 generation failed, producing a wallet that looks mnemonic-capable but whose words can never be exported and which no seed phrase can restore. A user who asked for -usemnemonic now gets that or a hard failure. Finally, record how the seed came to exist -- created on an empty wallet, restored from -mnemonic/-hdseed, retrofitted onto a pre-existing seedless wallet, or predating this record. A retrofitted seed is in no backup the user already holds, so a feature that moves funds into addresses only that seed can re-derive must not enable itself there by default. Nothing consumes this yet. Co-Authored-By: Claude Opus 5 (1M context) --- src/init.cpp | 39 ++++++++++++++++++++++++++ src/wallet/wallet.cpp | 62 ++++++++++++++++++++++++++--------------- src/wallet/wallet.h | 18 ++++++++++++ src/wallet/walletdb.cpp | 49 ++++++++++++++++++++++++++++++-- src/wallet/walletdb.h | 3 ++ 5 files changed, 147 insertions(+), 24 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 89243cf1e..9a3e48458 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2307,6 +2307,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; @@ -2338,6 +2355,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 @@ -2356,6 +2386,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); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 30eabdc43..fd2f31fed 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2470,30 +2470,26 @@ 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. + // + // 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", false)) { RawHDSeed entropy; - if (GenerateMnemonicEntropy(256, entropy)) { - HDSeed seed(entropy); - if (InstallHDSeed(seed, true, nCreationTime)) - return; - } - LogPrintf("%s: -usemnemonic seed generation failed, falling back to a random seed\n", __func__); + if (!GenerateMnemonicEntropy(256, entropy)) + throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed"); + HDSeed seed(entropy); + if (!InstallHDSeed(seed, true, nCreationTime)) + throw std::runtime_error(std::string(__func__) + ": installing the mnemonic HD seed 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) @@ -2535,6 +2531,7 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector vWalletUpgrade; + // True once a well-formed "hdchain" record has been loaded. + bool fHDChainRead; CWalletScanState() { nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0; fIsEncrypted = false; fAnyUnordered = false; nFileVersion = 0; + fHDChainRead = false; } }; @@ -833,9 +842,24 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, else if (strType == "hdchain") { CHDChain chain; - ssValue >> chain; + try { + ssValue >> chain; + } catch (...) { + // Do not let this land in the "user can live with it" bucket: + // report it, and leave wss.fHDChainRead false so LoadWallet + // turns it into DB_CORRUPT when a seed is present. + strErr = "Error reading wallet database: hdchain record is corrupt"; + return false; + } + wss.fHDChainRead = true; pwallet->SetHDChain(chain, true); } + else if (strType == "hdseedorigin") + { + int64_t nOrigin = 0; + ssValue >> nOrigin; + pwallet->hdSeedOrigin = (int)nOrigin; + } } catch (...) { return false; @@ -947,6 +971,21 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) if (fNoncriticalErrors && result == DB_LOAD_OK) result = DB_NONCRITICAL_ERROR; + // A wallet that holds an HD seed but whose hdchain record is missing or + // unreadable is NOT safe to run. hdChain would fall back to its SetNull + // defaults (walletdb.h:105-113), which (a) clears fMnemonicSeed, switching + // HD derivation from the 64-byte BIP39 seed to the raw 32-byte entropy + // (CWallet::GetHDSeedForDerivation, wallet.cpp:2615-2633) -> an entirely + // different key tree, and (b) resets saplingAccountCounter to 0, so the + // next GenerateNewSaplingZKey walks back over accounts that already exist. + // Both are silent today (a bad hdchain read is only DB_NONCRITICAL_ERROR). + // Fail loud instead of quietly deriving into the wrong tree. + if (pwallet->HaveHDSeed() && !wss.fHDChainRead) + { + LogPrintf("Error loading wallet.dat: HD seed present but the hdchain record is missing or corrupt\n"); + return DB_CORRUPT; + } + // 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 +1279,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) { diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d55072271..b3547a003 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); From e2f88175abfb94b4d71d2caae3cb556311a91780 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 03:22:55 +0200 Subject: [PATCH 09/68] wallet: default -autoshield ON only when the HD seed is known-recoverable Autoshield moves mined coinbase into a z-address that only this wallet's HD seed can re-derive. Defaulting that ON is only defensible where the user can actually restore that seed. Two cases fail that test. A seedless legacy wallet has a seed minted onto it silently at first start, so no backup the user already holds contains it. And a wallet seeded by an earlier build predates provenance recording, so we cannot tell which case it was. Both are now classified as not-known-recoverable and autoshield stays off there until the operator backs the seed up and passes -autoshield=1. An explicit -autoshield=0/1 still wins in either direction. Wallets this software created on an empty datadir, or restored from a user-supplied -mnemonic/-hdseed, keep the ON default: in both cases the user either has the phrase or supplied the seed themselves. Verified on real wallets: a wallet carrying no origin record is classified unknown and logs "autoshield left OFF by default: HD seed origin 4"; a wallet created by the previous commit logs "autoshield enabled" with no re-classification, confirming the record persists rather than being recomputed. Co-Authored-By: Claude Opus 5 (1M context) --- src/init.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 9a3e48458..7384ebf1c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -489,7 +489,7 @@ 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 wallet z-address (default: true). No-op when not mining or wallet is locked.")); + 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 5)"), 25)); 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)"), 10000)); @@ -2499,7 +2499,21 @@ 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. - pwalletMain->fAutoShieldEnabled = GetBoolArg("-autoshield", true); + // 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", 25); if (autoShieldInterval < 5) { From 143c33de48252ae578e56fb05ca35b01510d4328 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:14:31 +0200 Subject: [PATCH 10/68] wallet: erase the plaintext HD seed record when the wallet is encrypted CWalletDB::WriteCryptedHDSeed wrote the "chdseed" record and left "hdseed" in place, unlike WriteCryptedKey which erases "key"/"wkey" after writing "ckey". No erase of "hdseed" existed anywhere in src/wallet/. CDB::Rewrite does not save us: EncryptWallet calls it with pszSkip defaulted, so it copies every surviving record verbatim into the new file. The result is that a wallet created unencrypted and later encrypted keeps its raw HD seed in cleartext on disk permanently, and reloads it into memory on every start. Add CWalletDB::EraseHDSeed and call it from CWallet::SetCryptedHDSeed after the encrypted record is written, through the same CWalletDB so it shares EncryptWallet's transaction. Erase returns true on DB_NOTFOUND, so a wallet that was never written in plaintext is unaffected. The erase is deliberately best-effort and only logs on failure. A hard failure here propagates into CCryptoKeyStore::EncryptKeys, which EncryptWallet turns into assert(false) with half the keys encrypted in memory; a warning is strictly better than that. Note this path is only reachable with -developerencryptwallet, which is experimental and off by default on this chain, so this is a latent fix rather than a live one. Co-Authored-By: Claude Opus 5 (1M context) --- src/wallet/wallet.cpp | 31 +++++++++++++++++++++++++++---- src/wallet/walletdb.cpp | 10 ++++++++++ src/wallet/walletdb.h | 5 +++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fd2f31fed..5c63a319a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2523,10 +2523,33 @@ 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; } diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 478a5ec24..4f159f9a1 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1335,6 +1335,16 @@ bool CWalletDB::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); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); From 2a7fdcc1db93407cb5e32be92be01bfe08a0fcbc Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:23:09 +0200 Subject: [PATCH 11/68] wallet: add an optional mnemonic-entropy secret to the key stores Additive plumbing for storing a BIP39 entropy alongside the HD seed, mirroring how the seed itself is handled through both key store layers: a plaintext member on CBasicKeyStore, an encrypted pair on CCryptoKeyStore, encryption during the unencrypted-to-encrypted conversion in EncryptKeys with the plaintext cleared, and decryption on unlock. RawHDSeed and CKeyingMaterial are the same secure_allocator vector type, so the entropy passes through EncryptSecret/DecryptSecret with no adaptation. Nothing calls this yet; there is no behaviour change. Co-Authored-By: Claude Opus 5 (1M context) --- src/keystore.cpp | 36 +++++++++++ src/keystore.h | 18 ++++++ src/wallet/crypter.cpp | 141 +++++++++++++++++++++++++++++++++++++++++ src/wallet/crypter.h | 27 ++++++++ 4 files changed, 222 insertions(+) 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/wallet/crypter.cpp b/src/wallet/crypter.cpp index f3674c776..fe2e9ee55 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -159,6 +159,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 +261,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) { @@ -344,6 +385,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 +622,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..d72a8a1b6 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,6 +160,10 @@ 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; @@ -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); From 3caec548ae1e4c3a173411da9dc74f038f5792ec Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:30:18 +0200 Subject: [PATCH 12/68] wallet: persist the mnemonic entropy in wallet.dat Adds the record pair for the entropy, mirroring "hdseed"/"chdseed": a plaintext form, an encrypted form that erases its plaintext counterpart the way WriteCryptedKey does, and an erase. Both new types are registered in IsKeyType so -salvagewallet preserves them. Without that, salvage would silently drop the phrase while keeping the wallet otherwise intact. The records are defined but nothing writes them yet. Co-Authored-By: Claude Opus 5 (1M context) --- src/wallet/walletdb.cpp | 22 ++++++++++++++++++++++ src/wallet/walletdb.h | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 4f159f9a1..eb144bd25 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -871,6 +871,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" || @@ -1345,6 +1349,24 @@ bool CWalletDB::EraseHDSeed(const uint256& seedFp) return Erase(std::make_pair(std::string("hdseed"), seedFp)); } +bool CWalletDB::WriteMnemonicEntropy(const uint256& entropyFp, const RawHDSeed& entropy) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("mnementropy"), entropyFp), entropy); +} + +bool CWalletDB::WriteCryptedMnemonicEntropy(const uint256& entropyFp, 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 6f08c6018..a219ec855 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -227,6 +227,12 @@ public: //! 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); From 2d7dd90c552c7598e3b1d3e4b91c8dc914d56276 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:37:43 +0200 Subject: [PATCH 13/68] wallet: plumb the mnemonic entropy through CWallet Wires the key store secret to the database records: load and store paths on CWallet, the two ReadKeyValue arms, and the export path. GetMnemonicPhrase now prefers the entropy record and verifies it before printing: a phrase is only returned if expanding it reproduces the seed derivation actually uses. It falls back to the existing fMnemonicSeed path, so wallets that store the entropy AS the seed keep working unchanged. IsMnemonicSeed() now means "a phrase is available" rather than "the seed is the entropy", which is what every caller actually wants. Still a no-op on every existing wallet: nothing creates an entropy record yet, so GetMnemonicEntropy returns false and the old code path is taken. Co-Authored-By: Claude Opus 5 (1M context) --- src/wallet/wallet.cpp | 96 +++++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 23 +++++++++- src/wallet/walletdb.cpp | 32 ++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5c63a319a..b73e5c3a9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2587,6 +2587,72 @@ 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) { @@ -2675,6 +2741,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; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 697823878..1b77ca291 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1345,6 +1345,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 @@ -1363,8 +1371,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 @@ -1396,6 +1410,11 @@ public: /* 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 eb144bd25..abac194ba 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -860,6 +860,38 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> nOrigin; 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; From 20b2cbe8301b73cd4e259c7a5ddcb974a5de0bce Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:39:38 +0200 Subject: [PATCH 14/68] wallet: store the expanded BIP39 seed for mnemonic wallets Mnemonic wallets stored the 32-byte BIP39 entropy as the HD seed and relied on CHDChain.fMnemonicSeed to tell the deriver to expand it first. That flag is version-gated in the CHDChain serialisation, so a binary that predates it reads the record, never consumes the trailing byte, and derives from the raw entropy -- a different key tree, silently, with no error. Store the expanded 64-byte BIP39 seed instead, with fMnemonic = false, and keep the entropy in its own display-only record. Derivation then reads the stored bytes directly on every binary, old or new, so key trees are identical and no CHDChain version bump or minversion fence is needed. The wallet format stays readable by earlier releases rather than becoming one-way. Addresses are unchanged: the previous format expanded the entropy on every read and fed the same 64 bytes to Master(). This is also the form the tree already round-trips through -- z_exportwallet dumps the expanded seed, and restoring that hex via -hdseed installs it with fMnemonic = false. Write order is load-bearing: seed first, entropy second. A crash between them leaves a wallet with a seed and no phrase, which is merely inconvenient. The reverse would leave an entropy record with no seed, and the next start would mint a different seed while the wallet still held a phrase for the old one. -usemnemonic still defaults to false; only explicit opt-in and -mnemonic restores take this path. Co-Authored-By: Claude Opus 5 (1M context) --- src/gtest/test_mnemonic_compat.cpp | 100 +++++++++++++++++++++++++++++ src/wallet/wallet.cpp | 52 +++++++++++++-- 2 files changed, 146 insertions(+), 6 deletions(-) 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/wallet/wallet.cpp b/src/wallet/wallet.cpp index b73e5c3a9..1b902f156 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2480,9 +2480,30 @@ void CWallet::GenerateNewSeed() RawHDSeed entropy; if (!GenerateMnemonicEntropy(256, entropy)) throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed"); - HDSeed seed(entropy); - if (!InstallHDSeed(seed, true, nCreationTime)) + + // 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; } @@ -2713,10 +2734,29 @@ 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 From 9734402d7bec2047d664f9a7b47ae572d62b790b Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 07:01:22 +0200 Subject: [PATCH 15/68] wallet: create new wallets from a BIP39 seed phrase by default New wallets now get an exportable 24-word phrase instead of a random seed that no phrase can ever reproduce. Only wallets with no seed yet are affected; GenerateNewSeed is reachable from one place, under !HaveHDSeed(), and all three key stores refuse to replace an existing seed. This is safe to default on now that the storage form is backwards compatible: the expanded 64-byte BIP39 seed is what gets stored, so a binary predating any of this reads it and derives the same keys. Verified on an isolated chain before flipping: - a new-format wallet reopened with the tagged v1.1.0 binary, which has no knowledge of the entropy record, listed identical addresses; - restoring only the 24 words into a fresh datadir recovered every address. Note this changes what a new wallet is, not what an existing one is: the same entropy yields a different key tree depending on which side of this commit created the wallet. Nothing migrates, and nothing needs to. Co-Authored-By: Claude Opus 5 (1M context) --- src/init.cpp | 2 +- src/wallet/rpcdump.cpp | 6 ++++-- src/wallet/wallet.cpp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 7384ebf1c..2aa86dc46 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -471,7 +471,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)")); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 14b0dacc7..9b72f008d 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1039,8 +1039,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" diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1b902f156..81df81e1e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2476,7 +2476,7 @@ void CWallet::GenerateNewSeed() // (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", false)) { + if (GetBoolArg("-usemnemonic", true)) { RawHDSeed entropy; if (!GenerateMnemonicEntropy(256, entropy)) throw std::runtime_error(std::string(__func__) + ": -usemnemonic entropy generation failed"); From 92e6c7008d3d6038bb5e8055d0c2100ad6772466 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 18:58:24 +0200 Subject: [PATCH 16/68] wallet: define CHDChain's static constants out of line hush-gtest failed to link with "undefined reference to CHDChain::VERSION_HD_MNEMONIC". The version constants are static const int with in-class initialisers and no definition anywhere, so any ODR use needs one -- and gtest's EXPECT_*/ASSERT_* macros take their arguments by const reference, which is exactly that. test_mnemonic_compat.cpp:161 passes VERSION_HD_MNEMONIC to EXPECT_LT. dragonxd links either way, because nothing in the daemon binds these to a reference; only the test target exposed it, and the test target was never built on the branch that introduced the test. Define all four rather than only the one that failed: VERSION_HD_BASE, VERSION_HD_TRANSPARENT and CURRENT_VERSION carry the identical latent fault, and the next EXPECT_EQ against any of them would hit the same wall. Fixing the test instead would have hidden the problem rather than removed it. Co-Authored-By: Claude Opus 5 (1M context) --- src/wallet/walletdb.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index abac194ba..31e83e4b5 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -34,6 +34,15 @@ #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; From 1ec6590fb725deb756e24cf91a4081803a114d75 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 06:04:43 +0200 Subject: [PATCH 17/68] wallet: add z_autoshieldstatus Auto-shielding could silently decline to run with no way to ask why. The destination it resolves was equally invisible: the only evidence was a LogPrintf emitted once per round, so an operator wanting to know where their mined coinbase was going had to grep debug.log. z_autoshieldstatus reports the enable state, whether a round is in flight, the next height, interval, fee, minimum utxos, and the resolved destination -- plus the HD seed provenance in both numeric and readable form, whether the seed is phrase-recoverable, and a disabled_reason explaining why it is off when it is. That last field is the point. "autoshield": false on its own does not distinguish an operator who passed -autoshield=0 from a wallet whose seed provenance is not known-recoverable, and those need different responses. Mirrors z_sweepstatus in shape and registration. Verified on all three branches: fresh wallet -> autoshield true, origin 1 "created on an empty wallet", seed_recoverable true, disabled_reason "" -autoshield=0 -> disabled_reason "disabled by -autoshield=0" upgraded wallet -> autoshield false, origin 4 "predates provenance recording", seed_recoverable false, disabled_reason "HD seed origin is not known-recoverable; back the seed up and pass -autoshield=1" Co-Authored-By: Claude Opus 5 (1M context) --- src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/rpc/server.h b/src/rpc/server.h index 8c345c20e..7b8859bcc 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -369,6 +369,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 diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ddc40484b..4a136331c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3345,6 +3345,77 @@ 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->autoShieldAddress.empty()) { + why = ""; + } + ret.push_back(Pair("disabled_reason", why)); + + return ret; +} + UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey&) { if (!EnsureWalletIsAvailable(fHelp)) @@ -6349,6 +6420,7 @@ static const CRPCCommand commands[] = { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, { "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, { "wallet", "z_sweepstatus", &z_sweepstatus, true }, + { "wallet", "z_autoshieldstatus", &z_autoshieldstatus, true }, { "wallet", "z_consolidationstatus", &z_consolidationstatus, true }, { "wallet", "z_sendmany", &z_sendmany, false }, { "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false }, From 9a8f17b2c8108d7d8e0a3755118729a2636cbb24 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 07:59:13 +0200 Subject: [PATCH 18/68] wallet: bound autoshield rounds and lock their inputs Two defects in a single autoshield round, both of the quiet kind. The size estimate reserved a flat 2000 bytes for "header + sietch outputs", but every autoshield tx carries three Sapling OutputDescriptions -- the change note plus the two Sietch dummies -- and the real fixed cost is ~2937 bytes. Measured across seven live mainnet coinbase shields: 113.5 bytes per input and 2936.6 +/- 0.8 bytes fixed, of which 3 * 948 = 2844 is the output descriptions. The estimate was therefore short by ~937 bytes before a single input was counted. Inputs are charged AUTOSHIELD_CTXIN_DUST_SIZE = 148, which is conservative for the default P2PK coinbase but exact for P2PKH, so with a P2PKH coinbase a backlog of 1332..1337 utxos passed the estimate and built a tx over MAX_TX_SIZE_AFTER_SAPLING. CommitTransaction calls AddToWallet before AcceptToMemoryPool, so a rejected oversize tx leaves its inputs reading as spent. z_shieldcoinbase caps a manual shield at SHIELD_COINBASE_DEFAULT_LIMIT = 50 utxos; autoshield dropped that cap and relied on the byte estimate alone. Restore one -- AUTOSHIELD_MAX_INPUTS = 400 -- so the byte arithmetic is no longer the only thing between a large backlog and an oversize transaction. The remainder is shielded on the next round. Second, the proof build deliberately runs without cs_wallet so wallet RPCs are not stalled, which leaves a multi-second window in which a concurrent z_shieldcoinbase or z_sendmany can re-select the same coinbase outputs. AvailableCoins already honours IsLockedCoin and z_shieldcoinbase already brackets its selection with LockCoin/UnlockCoin; autoshield made zero LockCoin calls. Take the locks under cs_wallet at selection time and release them via RAII, since several early returns sit between selection and commit and a leaked lock would exclude those coins from every future round. Verified on an isolated regtest chain with a 540-utxo backlog: round 1 logged "reached per-round input cap (400)" and committed exactly 400 inputs in a 48351-byte tx (estimate 62300, limit 200000) round 2 took the remaining 151; backlog drained 540 -> 0 listlockunspent showed 400 coins locked mid-round and 0 afterwards 48351 bytes for 400 inputs implies 2937 bytes of fixed overhead, agreeing with the mainnet measurement to 14 bytes Co-Authored-By: Claude Opus 5 (1M context) --- .../asyncrpcoperation_autoshieldcoinbase.cpp | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index aa79af438..74af78a9b 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -22,6 +22,18 @@ 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; static const size_t AUTOSHIELD_CTXIN_P2SH_SIZE = 400; // Expire unmined autoshield txs after this many blocks, so a tx cannot straddle // a network-upgrade activation. @@ -258,6 +270,27 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { 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; @@ -280,7 +313,7 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { // AvailableCoins with fOnlySpendable already excludes immature coinbase // (< COINBASE_MATURITY) and outputs we don't own, so external // -mineraddress / pool coinbase naturally yields zero inputs. - size_t estimatedTxSize = 2000; // header + sietch outputs headroom + size_t estimatedTxSize = AUTOSHIELD_TX_OVERHEAD; std::vector vecOutputs; pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true); for (const COutput& out : vecOutputs) { @@ -293,6 +326,11 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { } 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); @@ -305,6 +343,12 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { 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; From 7a62fc487751694a4499d1439956ad4faab1d86d Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 07:59:13 +0200 Subject: [PATCH 19/68] wallet: break the sweep/consolidation/autoshield deadlock A successful-but-incomplete sweep round deliberately returns with fSweepRunning still set and nextSweep unadvanced, as a "keep draining next block" baton. Every early return in RunSaplingSweep, though, 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 RunSaplingSweep never lifts: sweep waits on consolidation, consolidation waits on sweep, and neither runs again. That much is pre-existing. What is new is that autoshield now shares the gate -- RunAutoShieldCoinbase returns early on fSweepRunning || fConsolidationRunning -- so a wedged sweep silently disables coinbase shielding too, with z_autoshieldstatus reporting autoshield true, running false, and no reason. Only honour the baton while a sweep operation is genuinely in flight: if the operation for saplingSweepOperationId is absent or has reached a terminal state, drop the stale flag and let the checks below decide afresh. The drain model is unchanged -- nextSweep is still unadvanced, so the next block re-dispatches. Also report the deferral in z_autoshieldstatus, so mutual exclusion with sweep or consolidation reads as a deferral rather than an unexplained idle. Co-Authored-By: Claude Opus 5 (1M context) --- src/wallet/rpcwallet.cpp | 6 ++++++ src/wallet/wallet.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4a136331c..08f3117a7 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3408,6 +3408,12 @@ UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& m 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 = ""; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 81df81e1e..7a78591f9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -592,6 +592,31 @@ void CWallet::RunSaplingSweep(int blockHeight) { // 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; } From 358011bd5479b5e3723dd641a5d611259bca0000 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 17:41:32 +0200 Subject: [PATCH 20/68] wallet: resolve the autoshield destination at startup pwalletMain->autoShieldAddress was only ever written by a round running in the current process, so on any node without an explicit -autoshieldaddress, z_autoshieldstatus reported an empty destination from startup until the first round fired. disabled_reason was empty on that path too (rpcwallet.cpp), so the RPC showed autoshield true, running false, no address and no explanation -- the exact silent state z_autoshieldstatus was added to eliminate. On a restored wallet, which pre-derives the whole -mnemonicsaplinggap window and therefore always has an in-gap account to pick, the answer was known at startup and simply not computed. Split the read-only half of resolveDestination into a free function shared with init: the configured override if set, else the lowest in-gap account m/32'/coin'/i' the wallet already holds. init calls it once when autoshield is enabled and no explicit address was given. It deliberately does not generate a key. Deriving a fresh sapling account as a side effect of populating a status field would mutate the wallet to make an RPC prettier, so step 3 of resolveDestination -- the generation path, which must stay inside the operation where an unlocked wallet is already established -- is left where it was. A brand-new wallet holds nothing in-gap, so the field stays empty there and disabled_reason now says why instead of being blank. Behaviour is otherwise unchanged: same derivation, same lowest-index-wins rule, same refusal to trust CKeyMetadata, same caching for the life of the process. Verified on an isolated regtest chain, 12/12: fresh wallet -> address "", reason "no destination resolved yet; one will be derived from the HD seed on the first round" after one round -> address set, reason empty after RESTART -> address visible with NO block mined since (height 12 both sides), and z_listaddresses still holds exactly 1 address, so init derived nothing -autoshieldaddress-> still overrides the derived destination, and the round shields into it Co-Authored-By: Claude Opus 5 (1M context) --- src/init.cpp | 20 +++ .../asyncrpcoperation_autoshieldcoinbase.cpp | 116 ++++++++++++------ .../asyncrpcoperation_autoshieldcoinbase.h | 20 +++ src/wallet/rpcwallet.cpp | 2 +- 4 files changed, 122 insertions(+), 36 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 2aa86dc46..7e1519769 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 @@ -2556,6 +2557,25 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) 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); + } + } } } diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index 74af78a9b..d50273364 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -105,50 +105,34 @@ void AsyncRPCOperation_autoshieldcoinbase::main() { getId(), getStateAsString(), numTxCreated_, FormatMoney(amountShielded_)); } -// 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) { +// 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 at init.cpp:2494-2506). This also serves as the per-process cache - // for whatever step 2/3 resolved. + // 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 true; + return AutoShieldDestStatus::Resolved; } - LogPrintf("%s: configured -autoshieldaddress is not a valid Sapling address\n", getId()); - return false; + return AutoShieldDestStatus::InvalidOverride; } // 2. Walk the restore window m/32'/coin'/[0, gap)' derived from the seed. HDSeed seed; if (!pwalletMain->GetHDSeedForDerivation(seed)) { - LogPrintf("%s: no HD seed available; refusing to pick an autoshield destination\n", getId()); - return false; + return AutoShieldDestStatus::NoSeed; } - // Mirror init.cpp:2349-2350's own clamp, and cap into the hardened index - // space so (i | ZIP32_HARDENED_KEY_LIMIT) below stays well formed. + // 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; @@ -179,15 +163,77 @@ bool AsyncRPCOperation_autoshieldcoinbase::resolveDestination( if (pwalletMain->GetSaplingExtendedSpendingKey(addr, held)) { destOut = addr; destStrOut = EncodePaymentAddress(addr); - // Cache for the life of the process; step 1 short-circuits later - // rounds. Safe: we only cache post-validation. - pwalletMain->autoShieldAddress = destStrOut; - LogPrintf("%s: autoshield destination %s (seed-derived sapling account %u, gap %u)\n", - getId(), destStrOut, (unsigned)i, (unsigned)saplingGap); - return true; + 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 diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.h b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h index 98dd5c734..46ad01946 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.h +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h @@ -14,6 +14,26 @@ // Default fee for automatic coinbase-shielding transactions static const CAmount DEFAULT_AUTOSHIELD_FEE = 10000; +// 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 diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 08f3117a7..98fb82c29 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3415,7 +3415,7 @@ UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& m why = strprintf("deferred while %s is running; rounds resume when it finishes", pwalletMain->fSweepRunning ? "z_sweep" : "sapling consolidation"); } else if (pwalletMain->autoShieldAddress.empty()) { - why = ""; + why = "no destination resolved yet; one will be derived from the HD seed on the first round"; } ret.push_back(Pair("disabled_reason", why)); From a0ccb4be1d33f261e89f93e5d0c8328f28a46af1 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:18:38 +0200 Subject: [PATCH 21/68] wallet: repair a truncated hdchain record instead of refusing the wallet A build predating VERSION_HD_TRANSPARENT rewrites the hdchain record with only its four base fields while leaving nVersion at whatever it read. Our version-gated reads then run off the end of the stream, ReadKeyValue turns the throw into fHDChainRead=false, and LoadWallet escalates that to DB_CORRUPT. Verified against the real v1.0.2-2b011d6ee release binary: a dev wallet, opened once by v1.0.2 and given a single new sapling address, came back to Error reading wallet database: hdchain record is corrupt Error loading wallet.dat: HD seed present but the hdchain record is missing or corrupt Error loading wallet.dat: Wallet corrupted Nothing is actually lost there -- the same test showed the seed phrase restoring the full balance, the autoshield destination, and even the address v1.0.2 had generated -- but the user is shown "Wallet corrupted" with no hint of that. Recover instead, for a v1 or v2 record. Everything derivation depends on 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 rather than reissuing them. Read the prefix from an untouched copy of the stream, default the missing tail, log it, and rewrite the record in full form so the next load is clean. A record claiming nVersion >= VERSION_HD_MNEMONIC still fails loud: that flag selects the derivation input, so guessing it wrong yields a different key tree in silence. No wallet this code has written can be in that state -- every InstallHDSeed call site passes fMnemonic=false -- so the branch is defensive only. Also say what to do about it. Both the log line and the init error now name the remedy (move wallet.dat aside, restart with -mnemonic and -rescan) rather than stopping at "Wallet corrupted". Verified on regtest against the real v1.0.2 binary, 14/14: the round-trip that previously ended in "Wallet corrupted" now loads, logs the repair and the rewrite, keeps the balance, the autoshield destination and v1.0.2's own address, still issues distinct fresh t-addresses after the counter reset, needs no repair on the second load, and remains fully recoverable from the seed phrase. Co-Authored-By: Claude Opus 5 (1M context) --- src/init.cpp | 5 +++- src/wallet/walletdb.cpp | 64 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 7e1519769..0766abe03 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2271,7 +2271,10 @@ 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"; + strErrors << _("Error loading wallet.dat: Wallet corrupted. If this wallet was last opened " + "by an older version, move wallet.dat aside and restore from your seed " + "phrase with -mnemonic=\"\" -rescan (see debug.log for " + "the specific record at fault).") << "\n"; else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) { string msg(_("Warning: error reading wallet.dat! All keys read correctly, but transaction data" diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 31e83e4b5..47bc658a4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -420,6 +420,9 @@ public: 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; CWalletScanState() { nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0; @@ -427,6 +430,7 @@ public: fAnyUnordered = false; nFileVersion = 0; fHDChainRead = false; + fHDChainRepaired = false; } }; @@ -851,14 +855,48 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, else if (strType == "hdchain") { CHDChain 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 (...) { - // Do not let this land in the "user can live with it" bucket: - // report it, and leave wss.fHDChainRead false so LoadWallet - // turns it into DB_CORRUPT when a seed is present. - strErr = "Error reading wallet database: hdchain record is corrupt"; - return false; + // 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); @@ -1016,6 +1054,18 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) 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 whose hdchain record is missing or // unreadable is NOT safe to run. hdChain would fall back to its SetNull // defaults (walletdb.h:105-113), which (a) clears fMnemonicSeed, switching @@ -1027,7 +1077,9 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) // Fail loud instead of quietly deriving into the wrong tree. if (pwallet->HaveHDSeed() && !wss.fHDChainRead) { - LogPrintf("Error loading wallet.dat: HD seed present but the hdchain record is missing or corrupt\n"); + LogPrintf("Error loading wallet.dat: HD seed present but the hdchain record is missing or corrupt. " + "Recover by restoring from the seed phrase: move wallet.dat aside and start with " + "-mnemonic=\"\" -rescan\n"); return DB_CORRUPT; } From 698bcf95746b911421025df2751422846037aefa Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:56:18 +0200 Subject: [PATCH 22/68] build: derive the release version from configure.ac build.sh hardcoded VERSION="1.0.3" while configure.ac had been at 1.1.0 since 660678f9b. package_release() uses it to name the output directory, so `./build.sh --all-release` from dev would have emitted release/dragonx-1.0.3-/ containing binaries that report 1.1.0 -- mislabelled artifacts, from the one place where the label is what users see. Read the four _CLIENT_VERSION_* defines out of configure.ac instead, applying the same suffix rule its _CLIENT_VERSION_SUFFIX m4 uses (build < 25 -> beta, < 50 -> rc, == 50 -> plain, > 50 -> point release), and abort if any of them cannot be parsed rather than naming a release directory after an empty string. SCRIPT_DIR moves above the version block because the lookup needs it. Verified: derives 1.1.0 from the current tree, and a deliberately unparseable configure.ac makes it exit 1 with a message instead of guessing. Co-Authored-By: Claude Opus 5 (1M context) --- build.sh | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 9c24d4db5..d780a641b 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 From 65130c31208af2eeed6cdff1314449d370daa770 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 19:18:30 +0200 Subject: [PATCH 23/68] async/wallet: close the loose ends around automated operations Six small defects, all found by an audit of the automated-operation path and all verified present before changing anything. AsyncRPCQueue::addOperation returned void and silently dropped the operation when the queue was closed or finishing. Every caller assumed success: the three schedulers left their running flag set with nothing in flight (which then blocks every later round), and z_sendmany / z_shieldcoinbase / z_mergetoaddress returned an opid for work that would never run -- z_shieldcoinbase and z_mergetoaddress having already locked their selected coins in the constructor. It now returns bool; the schedulers release the flag and log, and the three RPCs raise an error instead of handing back an opid. Coin locks are memory-only, so a refusal at shutdown reclaims them with the process; the lie about success was the defect. Nothing ever removed finished automated operations from the queue's map. popOperationForId is reached only from z_getoperationresult, so on a node running autoshield every 25 blocks the map grew by one entry per round forever. The schedulers now pop the operation they just cancelled. The worker already handles a missing id ("cannot find operation in map, may have been removed", asyncrpcqueue.cpp), and it releases lock_ before calling main(), so popping under cs_wallet introduces no lock cycle. The autoshield operation built its transaction against targetHeight_, the enqueue-time height, while SetExpiryHeight and the network-upgrade straddle guard both used tipHeight. Since the builder's height selects the consensus branch id, the guard was checking a height the transaction was not signed against -- it could not prevent the failure it exists to prevent. Now tipHeight throughout. cancel() in the sweep, consolidation and autoshield operations set CANCELLED unconditionally, dropping the base class's guard entirely. The schedulers cancel the previous operation when they enqueue the next, so a round that had already SUCCEEDED got its result relabelled as cancelled. Restored a narrower guard: still cancellable while READY or EXECUTING (the base class refuses the latter, which would defeat cancellation here), but a terminal state is left alone. CommitAutomatedTx dumped the whole transaction to stderr on every commit, duplicating the LogPrintf that CommitTransaction does one call later. ToString() emits a line per input, so with the 400-input autoshield cap that was tens of KB of stderr per round. Removed. Also corrected a comment that credited the immature-coinbase exclusion to fOnlySpendable (the argument is fOnlyConfirmed; the exclusion is unconditional in AvailableCoins), and noted that AUTOSHIELD_CTXIN_P2SH_SIZE is a byte size that merely happens to share the value 400 with the input cap. Verified on an isolated regtest chain, 8/8: nine consecutive autoshield rounds succeed after the builder-height change; the operation map stays at 1 entry across all nine (it grew one per round before); stderr totals 1608 bytes for the whole run with zero CommitAutomatedTx dumps, while debug.log still records all nine commits via CommitTransaction; no round is relabelled cancelled; funds shield correctly and no coin locks leak. Co-Authored-By: Claude Opus 5 (1M context) --- src/asyncrpcqueue.cpp | 9 +++-- src/asyncrpcqueue.h | 7 +++- .../asyncrpcoperation_autoshieldcoinbase.cpp | 23 +++++++++-- ...asyncrpcoperation_saplingconsolidation.cpp | 7 ++++ src/wallet/asyncrpcoperation_sweep.cpp | 7 ++++ src/wallet/rpcwallet.cpp | 18 +++++++-- src/wallet/wallet.cpp | 40 +++++++++++++++++-- 7 files changed, 96 insertions(+), 15 deletions(-) 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/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index d50273364..4469adf92 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -34,6 +34,8 @@ static const size_t AUTOSHIELD_TX_OVERHEAD = (3 * AUTOSHIELD_SAPLING_OUTPUT_SIZE // 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; // Expire unmined autoshield txs after this many blocks, so a tx cannot straddle // a network-upgrade activation. @@ -356,9 +358,10 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { } // Gather matured, spendable coinbase UTXOs, byte-capped to a single tx. - // AvailableCoins with fOnlySpendable already excludes immature coinbase - // (< COINBASE_MATURITY) and outputs we don't own, so external - // -mineraddress / pool coinbase naturally yields zero inputs. + // 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); @@ -421,7 +424,12 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { // 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. - auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain); + // 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 + AUTOSHIELD_EXPIRY_DELTA); builder.SetFee(fee); @@ -485,6 +493,13 @@ void AsyncRPCOperation_autoshieldcoinbase::setResult() { } 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); } diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 1a6dfec7c..2f360dfed 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -305,6 +305,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_sweep.cpp b/src/wallet/asyncrpcoperation_sweep.cpp index c9b7afaf6..a67052414 100644 --- a/src/wallet/asyncrpcoperation_sweep.cpp +++ b/src/wallet/asyncrpcoperation_sweep.cpp @@ -364,6 +364,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/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 98fb82c29..b969994b0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5543,7 +5543,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__); @@ -5771,7 +5774,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 @@ -6125,7 +6134,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 diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a78591f9..ff80f062f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -651,11 +651,21 @@ void CWallet::RunSaplingSweep(int blockHeight) { 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)); 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) { @@ -699,11 +709,21 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { 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)); 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 @@ -754,16 +774,28 @@ void CWallet::RunAutoShieldCoinbase(int blockHeight) { 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 + 5)); saplingAutoShieldOperationId = 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__); + 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); } From 04ac7c118610c7c6517756a4931f28e620e3de98 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 21:28:12 +0200 Subject: [PATCH 24/68] doc: how to build release binaries in containers, for several glibc floors Binaries built on Ubuntu 22.04 require GLIBC_2.34 and GLIBCXX_3.4.30 and will not start on Ubuntu 20.04 -- which is four of our five seeds, and an unknown share of users. The binary the fleet actually runs today needs only GLIBC_2.29, so it was built somewhere older; seed 176 has since been upgraded to 22.04 and now produces binaries it is the only seed able to run. --linux-compat and Dockerfile.compat already solved this (6d56ad854) but were undocumented outside the build script and pinned to one base image. Parameterise the base via ARG BASE_IMAGE (default unchanged, so --linux-compat behaves exactly as before) and document the whole path. doc/build-containers.md is written to be executed by a person or an agent starting from a machine with nothing installed: why the glibc direction matters, with the measured numbers; what already exists in the repo so nobody writes a second build system; prerequisites and honest cost (~15GB, 4GB RAM, 1-2h per base because depends/ builds boost, BDB, wolfssl and rust from source); one-target and multi-target recipes; which base to pick and why 20.04 is the recommended floor while 18.04 needs verifying (GCC 7 against -std=c++17); a mandatory verification step with the exact objdump/readelf commands and the expected ceilings; and the traps. The traps are the part worth having written down: ETXTBSY when installing over a running daemon (cp fails even after the process exits -- stage and rename, then sha256-verify before starting); never touching configure.ac in a configured tree, because the mtime alone triggers a reconfigure that dies on libdb_cxx; never blind-touching a path that may not exist, which silently creates stray empty files; RandomX needing ARCH=default or it emits AVX-512 that SIGILLs the fleet; build-win.sh silently discarding every argument; and macOS being uncontainerisable because depends/ has no darwin cross path at all. Also records that full static linking is NOT the answer here: the daemon resolves node1..node5.dragonx.is via getaddrinfo, and static glibc pushes that through NSS, which dlopens libnss_dns at runtime and reintroduces the dependency it was meant to remove. Co-Authored-By: Claude Opus 5 (1M context) --- Dockerfile.compat | 6 +- doc/build-containers.md | 223 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 doc/build-containers.md diff --git a/Dockerfile.compat b/Dockerfile.compat index 27a4a3b0f..873c3d394 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 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. From 4dc57e80b1b6edbd74785cd729cfc06ae3149440 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 21:52:51 +0200 Subject: [PATCH 25/68] build: bump version to 1.2.0 dev and the v1.1.0 tag were version-indistinguishable: both reported CLIENT_VERSION 1010050, subversion "/DragonX:1.1.0/" and IS_RELEASE=true, because 660678f9b was the last commit to touch a version file and it predates the tag. The twenty commits since were therefore invisible to every channel a client can query, and the in-app updater compares exactly those. Only git-describe distinguished them, and that degrades to "-unk" on a tarball build. A minor bump rather than a patch: since v1.1.0 the tree gained auto-shield-coinbase (a new feature, on by default where the seed is known-recoverable), BIP39 seed phrases as the default for new wallets, the z_autoshieldstatus RPC, and three new wallet.dat record types. Understating that as 1.1.1 would hide an on-disk format change from the one place users look. The published v1.1.0 tag is left where it is. Re-pointing a tag that is already on the remote breaks anyone who fetched it. The wallet feature version deliberately stays at FEATURE_LATEST = 60000. The new records are additive and older binaries skip unknown types harmlessly, while bumping it would make them refuse the wallet outright with DB_TOO_NEW. The one real incompatibility, a truncated hdchain record, is self-healing as of a0ccb4be1, so refusing to load would be strictly worse for the user than what happens today. Verified: configure.ac and clientversion.h agree; CLIENT_VERSION 1010050 -> 1020050; build.sh derives 1.2.0; bitcoin-config.h carries CLIENT_VERSION_MINOR 2 after a reconfigure run with the depends CONFIG_SITE; a full rebuild of src succeeds with 0 errors and both binaries report v1.2.0. Co-Authored-By: Claude Opus 5 (1M context) --- configure.ac | 2 +- contrib/debian/changelog | 32 ++++++++++++++++++++++++++++++++ src/clientversion.h | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 6fc772ad3..edff57081 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ 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, 1) +define(_CLIENT_VERSION_MINOR, 2) 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))) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index 82505dbd1..7d8d608b2 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,35 @@ +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. + * 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 diff --git a/src/clientversion.h b/src/clientversion.h index 35bb0dbef..dd09dcc4f 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -29,7 +29,7 @@ //! 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 1 +#define CLIENT_VERSION_MINOR 2 #define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 50 From 499f02a905623dec76c3b49d5c003e7993fb9c8c Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 26 Aug 2026 00:11:55 +0200 Subject: [PATCH 26/68] net: repair DragonX peer discovery, broken three separate ways A DRAGONX node had no working peer discovery. Both mechanisms were broken, and a third bug hid the fact. 1. DNS seeds were Hush's, and all three are dead. chainparams_commandline() sets an assetchain's port, magic, blocktime, upgrade heights and checkpoints but never touches vSeeds or vFixedSeeds, so DRAGONX silently inherited CMainParams': seed1.hush.is, seed2.hush.is and dns.leto.net. None of the three has an A record any more -- verified against 1.1.1.1 and 8.8.8.8, with google.com and node1..node5.dragonx.is resolving fine from the same host as a control. Replaced with the five DragonX node hostnames, which do resolve and do listen. 2. Every fixed seed carried port 0. contrib/seeds/generate-seeds.py documents its input as :, but contrib/seeds/nodes_main.txt held bare IPs, so parse_spec() took the port as empty and emitted 0x00,0x00 for all five entries. The fixed-seed fallback -- which exists precisely for when DNS seeding yields nothing -- was therefore handing out unconnectable addresses. Added the port to nodes_main.txt and regenerated; entries now end 0x55,0x08 (21768). 3. ThreadDNSAddressSeed never incremented `found`, so "%d addresses found from DNS seeds" printed 0 unconditionally, whether seeding worked or not. That is almost certainly why nobody noticed the seeds had gone dead: the one diagnostic that would have shown it was hardcoded to say zero. Verified on a fresh datadir (empty addrman, separate ports, real node untouched): DNS seeding now reports "5 addresses found from DNS seeds" where it previously reported 0, and the fixed-seed path adds 5 entries carrying the correct port. Note on scope: the five hostnames are single-A-record hosts, so each contributes one address rather than the spread a real seeder returns. A dedicated DNS seeder, or simply a round-robin A record over the seed set, would be the proper fix and needs only a DNS change rather than a release. This restores a working discovery path; it does not make it a good one. Also corrected the generated header's #endif comment, which said HUSH_CHAINPARAMSSEEDS_H while the guard is DRAGONX_CHAINPARAMSSEEDS_H. Co-Authored-By: Claude Opus 5 (1M context) --- contrib/seeds/generate-seeds.py | 2 +- contrib/seeds/nodes_main.txt | 13 ++++++++----- src/chainparams.cpp | 24 +++++++++++++++++++----- src/chainparamsseeds.h | 12 ++++++------ src/net.cpp | 1 + 5 files changed, 35 insertions(+), 17 deletions(-) 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..6c3b6cfe5 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -1,14 +1,17 @@ +# 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 diff --git a/src/chainparams.cpp b/src/chainparams.cpp index cad00b9f8..24aaf1e2f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -181,11 +181,25 @@ 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. + // + // These are single-A-record hosts, so each contributes one address rather than + // the spread a real seeder returns. That is still strictly better than nothing; + // a dedicated seeder (or a round-robin A record over the seed set) would be the + // proper fix and needs only a DNS change, not a release. + vSeeds.push_back(CDNSSeedData("node1", "node1.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node2", "node2.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node3", "node3.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node4", "node4.dragonx.is")); + vSeeds.push_back(CDNSSeedData("node5", "node5.dragonx.is")); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,60); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,85); diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 90f2d4182..76846bd67 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -11,14 +11,14 @@ // 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 }; 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/net.cpp b/src/net.cpp index c6ddb54a8..c4d1b3e86 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1581,6 +1581,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 From dde6cd810f3a0875609476fdab855ddb87751781 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 26 Aug 2026 00:22:56 +0200 Subject: [PATCH 27/68] net: seed from a round-robin DNS record instead of hardcoded hosts seed.dragonx.is is now an A-record set over the five seed nodes (DNS-only, TTL 300, created in Cloudflare alongside this change). One lookup returns all of them, and the set can change -- a node added, a node retired -- with a DNS edit rather than a release. That is the actual point. Before this the network's entry points were hardcoded into the binary twice over: here in vSeeds, and again in the -addnode injection in hush_utils.h. Adding a sixth node meant shipping a new version and waiting for users to upgrade. node1 and node5 stay as static fallbacks against the round-robin record being mistyped or deleted. They resolve to the same hosts, so that is insurance against a DNS mistake rather than real redundancy. Verified end to end on a fresh datadir (empty addrman, real node untouched, and crucially with NO custom -port -- see below): before: 0 addresses found from DNS seeds, 0 handshakes, 1 block (genesis) after: 7 addresses found, connection attempts to all five seeds on :21768, 3 version handshakes, 3296 blocks connected and syncing The earlier run of this test appeared to fail with 0 handshakes. That was the harness, not the code: -port overrides ASSETCHAINS_P2PPORT, and net.cpp builds DNS-seeded addresses as CAddress(CService(ip, ASSETCHAINS_P2PPORT)), so a test node with a custom port dials every seeded peer on its own port and reaches nothing. That is the mechanism behind the long-standing "never use a custom -port on a test node" rule; use -listen=0 instead. Co-Authored-By: Claude Opus 5 (1M context) --- src/chainparams.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 24aaf1e2f..12ffc5402 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -191,14 +191,18 @@ public: // path that worked was the node1..node5.dragonx.is -addnode injection in // hush_utils.h, which is almost certainly why that injection exists. // - // These are single-A-record hosts, so each contributes one address rather than - // the spread a real seeder returns. That is still strictly better than nothing; - // a dedicated seeder (or a round-robin A record over the seed set) would be the - // proper fix and needs only a DNS change, not a release. + // 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("node2", "node2.dragonx.is")); - vSeeds.push_back(CDNSSeedData("node3", "node3.dragonx.is")); - vSeeds.push_back(CDNSSeedData("node4", "node4.dragonx.is")); vSeeds.push_back(CDNSSeedData("node5", "node5.dragonx.is")); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1,60); From 3dd667b127879ca1aef8a446b7dc35424053082e Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:36:03 -0500 Subject: [PATCH 28/68] net: add two seed nodes and reserve names for three more node6.dragonx.is (13.140.58.251) and node7.dragonx.is (5.104.83.100) are new full nodes in regions the existing five did not cover. Both go into the compiled-in fixed-seed list and into the DRAGONX -addnode set. node8 through node10 are reserved names with no DNS records yet. A hostname that does not resolve is harmless on this path -- ThreadOpenAddedConnections simply fails to open the connection and retries on its normal cycle -- and reserving the names in the binary means a future seed can be brought into the default addnode set by creating a single DNS record, with no release and no waiting for users to upgrade. seed.dragonx.is already gives the DNS-seed path that property; this extends it to the addnode path. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- contrib/seeds/nodes_main.txt | 6 + doc/man/hush-cli.html | 296 ---- doc/man/hushd.html | 2681 ---------------------------------- src/chainparamsseeds.h | 2 + src/hush_utils.h | 11 +- 5 files changed, 18 insertions(+), 2978 deletions(-) delete mode 100644 doc/man/hush-cli.html delete mode 100644 doc/man/hushd.html diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index 6c3b6cfe5..efa242101 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -15,3 +15,9 @@ # node5.dragonx.is 176.126.87.241:21768 + +# node6.dragonx.is +13.140.58.251:21768 + +# node7.dragonx.is +5.104.83.100:21768 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/src/chainparamsseeds.h b/src/chainparamsseeds.h index 76846bd67..0f3e03216 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -16,6 +16,8 @@ static const uint8_t chainparams_seed_main[] = { 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[] = { diff --git a/src/hush_utils.h b/src/hush_utils.h index 91a51e05a..90f50b38e 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1799,8 +1799,17 @@ void hush_args(char *argv0) LogPrint("net", "%s: isdragonx=%d\n", __func__, isdragonx); if (isdragonx) { + // 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" }; } From 092a608fd92ae79bf8f86cf0e20fa6fe71e2c2e2 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:36:03 -0500 Subject: [PATCH 29/68] chainparams: do not let other smart chains inherit DragonX's seeds A smart chain builds its params by copying a base network and overriding pieces, but nothing ever touched vSeeds/vFixedSeeds. That is how DRAGONX ran on Hush's seeds -- seed1.hush.is and friends, long since gone from DNS -- for as long as it did, and it means any other assetchain started from this binary now inherits DragonX's. Seeds are per-chain by nature: an address serving one chain is useless to another, and dialling it is at best wasted effort and at worst a peer speaking 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 an assetchain operator has to configure regardless. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/chainparams.cpp | 14 ++++++++++++++ src/chainparams.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 12ffc5402..6396faabb 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -538,10 +538,24 @@ void *chainparams_commandline() { //} 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 (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) != 0) { + pCurrentParams->ClearSeeds(); + } + if ( ASSETCHAINS_BLOCKTIME != 60 ) { pCurrentParams->consensus.nMaxFutureBlockTime = 7 * ASSETCHAINS_BLOCKTIME; // 7 blocks 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; From 5d3f7e520c44d15020798a468ac0f4271f321d2b Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:36:03 -0500 Subject: [PATCH 30/68] packaging: correct the debian changelog and the manpage file list - dragonx.manpages listed DEBIAN/manpages/*.1, a path nothing creates, so dh_installman would fail on it. Point it at doc/man/*.1, which is where util/gen-manpages.sh writes and what doc/man/Makefile.am ships. - Restore the 1.0.1 and 1.0.2 stanzas, reconstructed from the commits each tag actually contains. The file jumped 1.0.3 -> 1.0.0. - The 1.1.0 and 1.0.0 trailers named weekdays that do not match their dates ("Thu, 21 Aug 2026" is a Friday; "Mon, 03 Mar 2026" is a Tuesday). Replace both with the real v1.1.0 and v1.0.0 tag dates, which fixes the weekday and the disagreement with the tag at once. - Record the 1.2.0 peer-discovery work in its stanza. - Drop doc/man/hushd.html and doc/man/hush-cli.html: stale Hush-branded pages for binaries this tree no longer builds, referenced by nothing. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- contrib/debian/changelog | 25 +++++++++++++++++++++++-- contrib/debian/dragonx.manpages | 6 +++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index 7d8d608b2..31a3a94c9 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -24,6 +24,11 @@ dragonx (1.2.0) stable; urgency=medium * 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. @@ -49,7 +54,7 @@ dragonx (1.1.0) stable; urgency=medium pre-verification, adaptive -dbcache, Sapling witness desync fix, BIP39 seed phrases, chain-level Sapling turnstile, and the audit fixes. - -- DragonX Developers Thu, 21 Aug 2026 22:00:00 +0000 + -- DragonX Developers Sun, 23 Aug 2026 10:41:53 -0500 dragonx (1.0.3) stable; urgency=medium @@ -61,6 +66,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 @@ -68,7 +89,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 From fad05d3ab4fdb18e745acab10d2c16868a6ecf84 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 25 Aug 2026 18:42:18 -0500 Subject: [PATCH 31/68] doc: regenerate manpages for v1.2.0 They still described v1.0.3-4caf2fc68, so none of the options added since -- -autoshield and its four companions, -mnemonic, -mnemonicsaplinggap -- appeared anywhere in them. Generated from a binary built at a clean tree on an annotated v1.2.0 tag, which is what makes util/genbuild.sh emit BUILD_DESC "v1.2.0" rather than a version with a commit suffix. Note that genbuild.sh uses `git describe --abbrev=0`, which ignores lightweight tags; v1.0.0 through v1.0.3 were lightweight, which is why those releases all reported themselves as v1.0.x-. Tag releases annotated. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- doc/man/dragonx-cli.1 | 39 +++++++++--------- doc/man/dragonx-tx.1 | 25 ++++++++++-- doc/man/dragonxd.1 | 93 ++++++++++++++++++++++++++++--------------- 3 files changed, 100 insertions(+), 57 deletions(-) diff --git a/doc/man/dragonx-cli.1 b/doc/man/dragonx-cli.1 index 43b43f2cc..c6a2d4534 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.2.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.2.0 .SH DESCRIPTION -DragonX RPC client version v1.0.3\-95aeaed0c\-dirty +DragonX RPC client version v1.2.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..1bbc5b625 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.2.0" "User Commands" .SH NAME -dragonx-tx \- DragonX transaction utility +dragonx-tx \- manual page for dragonx-tx v1.2.0 .SH DESCRIPTION -hush\-tx utility version v1.0.3\-4caf2fc68 +hush\-tx utility version v1.2.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..765b2bea6 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.2.0" "User Commands" .SH NAME -DragonX \- manual page for DragonX Daemon version v1.0.3-4caf2fc68 +dragonxd \- manual page for dragonxd v1.2.0 .SH DESCRIPTION -DragonX Daemon version v1.0.3\-4caf2fc68 +DragonX Daemon version v1.2.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,33 @@ 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\-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 +476,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 +515,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 +584,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 @@ -804,25 +834,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 . From 02b4d03fc6828de05ee8356d22c669be7f48c06f Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 26 Aug 2026 22:10:35 -0500 Subject: [PATCH 32/68] wallet: low-severity polish from the dev/v1.2.0 review Follow-up nits surfaced by the multi-agent review of the dragonx..dev delta; none are correctness/consensus bugs, all are defensive/consistency tidy-ups. Builds clean; the diff was reviewed across concurrency, scheduler, and tx-building lenses. - wallet: default CWallet::fAutoShieldEnabled to false. init.cpp always recomputes it (ON only for CREATED/RESTORED seed provenance) before any ChainTip round, so this is behaviour-neutral in the normal path and stops a CWallet that skips that init from auto-enabling for provenance the gate would reject. - wallet: clamp a loaded hdSeedOrigin to UNKNOWN when out of enum range, so a corrupt/hand-edited wallet.dat cannot claim a known-recoverable seed and flip autoshield ON. - wallet: key the sweep and consolidation ops' NU-straddle guard, transaction builder height, and expiry off execution-time tipHeight instead of the stale enqueue-time targetHeight_ -- matching the autoshield op (65130c312) so the builder's consensus-branch selection agrees with the height the tx is signed for. (Sweep previously built at targetHeight_ but expired at the live tip.) - wallet: on the sweep NU-straddle skip, set sweepComplete_ so the round backs nextSweep off one interval instead of re-dispatching a fresh sweep op every block through the activation window. - init: clamp -autoshieldinterval below 5 up to the documented minimum of 5, rather than silently resetting it to the default 25. - chainparams: make the ClearSeeds guard an exact "DRAGONX" match instead of a 7-char prefix, so DRAGONX-prefixed assetchains (e.g. DRAGONX2) no longer inherit DragonX's seeds. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01UtvyqQQSqR64DNEjUEuTmb --- src/chainparams.cpp | 2 +- src/init.cpp | 4 ++-- ...asyncrpcoperation_saplingconsolidation.cpp | 18 +++++++++++---- src/wallet/asyncrpcoperation_sweep.cpp | 22 +++++++++++++------ src/wallet/wallet.h | 9 +++++--- src/wallet/walletdb.cpp | 6 +++++ 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 6396faabb..fc05e59b2 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -552,7 +552,7 @@ void *chainparams_commandline() { ASSETCHAINS_P2PPORT = 18030; } - if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) != 0) { + if (strcmp(SMART_CHAIN_SYMBOL, "DRAGONX") != 0) { pCurrentParams->ClearSeeds(); } diff --git a/src/init.cpp b/src/init.cpp index 0766abe03..1bdff45cf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2521,8 +2521,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (pwalletMain->fAutoShieldEnabled) { int autoShieldInterval = GetArg("-autoshieldinterval", 25); if (autoShieldInterval < 5) { - fprintf(stderr,"%s: Invalid autoshield interval of %d < 5, setting to default of 25\n", __func__, autoShieldInterval); - autoShieldInterval = 25; + fprintf(stderr,"%s: autoshield interval %d below the minimum, clamping to 5\n", __func__, autoShieldInterval); + autoShieldInterval = 5; } pwalletMain->autoShieldInterval = autoShieldInterval; pwalletMain->nextAutoShield = pwalletMain->autoShieldInterval + chainActive.Height(); diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 2f360dfed..085768315 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -107,8 +107,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 + CONSOLIDATION_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; @@ -189,8 +199,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 + CONSOLIDATION_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)); diff --git a/src/wallet/asyncrpcoperation_sweep.cpp b/src/wallet/asyncrpcoperation_sweep.cpp index a67052414..3abd70590 100644 --- a/src/wallet/asyncrpcoperation_sweep.cpp +++ b/src/wallet/asyncrpcoperation_sweep.cpp @@ -126,10 +126,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 + SWEEP_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; } @@ -258,11 +269,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 + SWEEP_EXPIRY_DELTA); LogPrintf("%s: Beginning creating transaction with Sapling output amount=%s\n", getId(), FormatMoney(amountToSend - fee)); // Select Sapling notes diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1b77ca291..b54805684 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -810,10 +810,13 @@ public: bool fSweepExternalEnabled = false; bool fSweepRunning = false; - // Automatic coinbase shielding (t->z). Default ON but conditional: it is a - // silent no-op on nodes where it cannot act (no wallet, external + // 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. - bool fAutoShieldEnabled = true; + // 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}; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 47bc658a4..cfe632c48 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -905,6 +905,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { 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") From ac95106abece72dd4f47689974cbfaffa6c773bb Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 03:56:23 -0500 Subject: [PATCH 33/68] build(win): make the mingw cross-compile link and find librustzcash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things broke the x86_64-w64-mingw32 build; two are real and fixed here (the third was a stale-object contamination from building Linux and Windows in the same tree, resolved by a clean rebuild — not a code fix). 1. Single-pass mingw ld could not resolve the cross-references DragonX added between the internal static archives (libbitcoin_util/common objects pulling in UniValue; util<->common mutual deps). GNU ld on Linux re-scans archives so it never surfaced; ld64 on macOS rejects the grouping flag outright. Bracket each binary's _LDADD in -Wl,--start-group/--end-group, delivered via AC_SUBST(LINK_GROUP_*) so automake does not reject the linker flag inside _LDADD, and left empty on every non-Windows target. 2. The Rust build emits the mingw archive as rustzcash.lib, but the link line asks for -lrustzcash, i.e. librustzcash.a. Normalize the staged filename to librustzcash.a for every host (a no-op on Linux/macOS, where the basename was already librustzcash.a). Co-Authored-By: Claude Opus 4.8 (1M context) --- configure.ac | 16 ++++++++++++++++ depends/packages/librustzcash.mk | 2 +- src/Makefile.am | 15 ++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index edff57081..3c4e5dbc8 100644 --- a/configure.ac +++ b/configure.ac @@ -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/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/src/Makefile.am b/src/Makefile.am index 1ba627e21..96b509435 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -489,7 +489,7 @@ if TARGET_WINDOWS dragonxd_SOURCES += bitcoind-res.rc endif -dragonxd_LDADD = \ +dragonxd_LDADD = $(LINK_GROUP_START) \ $(LIBBITCOIN_SERVER) \ $(LIBCURL) \ $(LIBBITCOIN_COMMON) \ @@ -527,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 @@ -553,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) \ @@ -566,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) \ @@ -579,6 +583,7 @@ wallet_utility_LDADD = \ $(LIBZCASH) \ $(LIBZCASH_LIBS)\ $(LIBRANDOMX) +wallet_utility_LDADD += $(LINK_GROUP_END) endif # hush-tx binary # @@ -591,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) \ @@ -602,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 = \ From 798eccc624d5805277169f2257bd8ed29703ebcf Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 11:35:38 -0500 Subject: [PATCH 34/68] hygiene: fix seven latent defects from the v1.2.0 code audit Phase 1 of the code-hygiene remediation. Each is a genuine defect, not style. The two consensus functions are touched only by provably behavior-preserving dead-code removal and a comment. - miner: initialize the anti-spin loop counter (was `int i;` -- the loop condition read an indeterminate value, UB) and break once the block time advances past the median, which is what the comment intended. - consensus/upgrades: drop a stray printf() on the NetworkUpgradeState path; the following assert already documents the invariant. - txdb: CBlockTreeDB::Snapshot2's outer catch treated ANY exception as normal end-of-iteration and built a snapshot from partial data. Fail instead, matching the inner catch the author marked consensus-relevant ("we need to exit here if so for consensus code!"). iter->Valid() already handles genuine end-of-iteration. - wallet/rpcwallet + init: -sietch-min-zouts used a "--" key that the arg parser (which normalizes --foo to -foo) can never match, so the Sietch decoy floor was silently stuck at the default. Use the single-dash key so the knob works, and document it in -help. - hush_bitcoind + hush_utils: remove four unreachable duplicate `else if` branches from hush_commission()/hush_block_subsidy() (a second `height < 23860000` and a second `height < 27220000` in each). Proven identical across 49,591 heights. NB: the dead values hint at an intended clean halving schedule that was never wired up; the DEPLOYED schedule (two double-steps) is preserved exactly. Changing it is a future consensus decision, not this cleanup. - hush_bitcoind: replace the "likely a bug" halving TODO with an accurate note -- INTERVAL is only consumed by a debug fprintf, so the > vs >= boundary at HALVING1 has no consensus effect. - git rm two committed macOS build artifacts (cc/customcc.dylib and libcc.dylib) and add the missing .dylib .gitignore rules. Built clean on Linux; an isolated node self-mined genesis->5113 exercising the miner and consensus-subsidy paths, and getsnapshot returned normally. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 2 ++ src/cc/customcc.dylib | Bin 104288 -> 0 bytes src/consensus/upgrades.cpp | 4 ---- src/hush_bitcoind.h | 13 +++++++------ src/hush_utils.h | 8 ++++---- src/init.cpp | 2 ++ src/libcc.dylib | Bin 104288 -> 0 bytes src/miner.cpp | 4 +++- src/txdb.cpp | 10 ++++++++-- src/wallet/rpcwallet.cpp | 2 +- 10 files changed, 27 insertions(+), 18 deletions(-) delete mode 100644 src/cc/customcc.dylib delete mode 100644 src/libcc.dylib diff --git a/.gitignore b/.gitignore index cdbb87517..441696a83 100644 --- a/.gitignore +++ b/.gitignore @@ -160,8 +160,10 @@ 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/src/cc/customcc.dylib b/src/cc/customcc.dylib deleted file mode 100644 index dc173003263572a3887de351cb1129a0fe562254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104288 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 YcaqT}FqIT;tJF{e= 0); assert(idx >= Consensus::BASE_SPROUT && idx < Consensus::MAX_NETWORK_UPGRADES); auto nActivationHeight = params.vUpgrades[idx].nActivationHeight; 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_utils.h b/src/hush_utils.h index 90f50b38e..8742e7f78 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1564,14 +1564,14 @@ uint64_t hush_block_subsidy(int height) subsidy = 549316; } else if (height < 23860000) { subsidy = 274658; - } else if (height < 23860000) { - subsidy = 137329; + // removed unreachable duplicate `height < 23860000` (=> 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) { diff --git a/src/init.cpp b/src/init.cpp index 1bdff45cf..9f42e43a0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -494,6 +494,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-autoshieldinterval", strprintf(_("Block interval between automatic coinbase-shielding rounds (default: %i, min 5)"), 25)); 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)"), 10000)); + + 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")); diff --git a/src/libcc.dylib b/src/libcc.dylib deleted file mode 100644 index dc173003263572a3887de351cb1129a0fe562254..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104288 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 YcaqT}FqIT;tJF{enTime = GetTime(); diff --git a/src/txdb.cpp b/src/txdb.cpp index dc73617ce..2d6019712 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -517,8 +517,14 @@ 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); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b969994b0..ef347dcf7 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5404,7 +5404,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // A value of 7 will provide much stronger linkability privacy versus pre-Sietch operations 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 ); From aff61019874ccf1a6db0375128ec49b886c905b5 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 12:24:04 -0500 Subject: [PATCH 35/68] =?UTF-8?q?hygiene:=20Phase=202=20=E2=80=94=20Dragon?= =?UTF-8?q?X-authored=20cleanup=20(13=20findings)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second phase of the code-hygiene remediation, covering the items the DragonX team introduced or the rebrand missed. No consensus behavior changes; validated on a local self-mining node. Log cruft: - chainparams: route the startup ">>>>>>>>>>" banner and the port line through LogPrintf/LogPrint("net") instead of fprintf(stderr). - pow: drop the fprintf(stderr) hash-mismatch dump that duplicated the LogPrintf copy verbatim. - miner: gate the RandomXDatasetManager per-alloc/per-VM address dumps and MemDiag /proc reads behind LogPrint("randomx"); keep a one-line "allocated shared dataset (N GB)" summary at default verbosity. Named constants (single source of truth in wallet.h): - DEFAULT_AUTOSHIELD_FEE/INTERVAL, MIN_AUTOSHIELD_INTERVAL, DEFAULT_AUTOSHIELD_MIN_UTXOS, AUTOSHIELD_MIN/MAX_FEE for the autoshield option parsing and help text (were bare 10000/25/5 literals repeated across init.cpp and wallet.h). - AUTO_OP_TARGET_HEIGHT_OFFSET replaces the three copy-pasted `blockHeight + 5` async-op scheduling offsets. - AUTO_OP_EXPIRY_DELTA replaces the three per-file *_EXPIRY_DELTA=15 constants (sweep/consolidation/autoshield) with one shared value. - Move DEFAULT_AUTOSHIELD_FEE out of the op header into wallet.h so it no longer collides when both headers are included. Error surfacing: - init: report clamped/out-of-range -autoshieldinterval/-autoshieldfee via InitWarning() (surfaces to GUI/log) instead of fprintf(stderr). Rebrand / dead foreign-chain code (approved removals): - server: drop the HUSH3 special-cases in stop() and HelpExampleCli; the cli example now shows "dragonx-cli" instead of "hush-cli". - getinfo (misc): remove the stale dPoW notarization block (notarized, prevMoMheight, notarizedhash, notarizedtxid, notarizedtxid_height, HUSHnotarized_height, notarized_confirms) — DragonX is a private chain from genesis with no active dPoW. Also fixes the hardcoded "HUSH3" that made getinfo query a foreign chain's notarization. - delete the dead Komodo notary RPCs getera/getdragonjson/ getnotarysendmany/geterablockheights (getera returned 0; getnotarysendmany was marked "this is broke") and their registrations. - remove the dead ASSETCHAINS_EQUIHASH reporting branches in getinfo and getmininginfo — DragonX is RandomX-only. chainparams: document why the upstream Equihash params and the literal Bitcoin genesis are retained under RandomX (do not "fix" them). Deferred: the hdSeedOrigin int->string switch in z_autoshieldstatus (no shared helper exists to reuse; low value). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/chainparams.cpp | 12 +- src/init.cpp | 25 ++-- src/miner.cpp | 20 +-- src/pow.cpp | 8 -- src/rpc/client.cpp | 2 - src/rpc/mining.cpp | 11 +- src/rpc/misc.cpp | 127 +----------------- src/rpc/server.cpp | 16 +-- src/rpc/server.h | 3 - .../asyncrpcoperation_autoshieldcoinbase.cpp | 7 +- .../asyncrpcoperation_autoshieldcoinbase.h | 3 - ...asyncrpcoperation_saplingconsolidation.cpp | 5 +- src/wallet/asyncrpcoperation_sweep.cpp | 5 +- src/wallet/wallet.cpp | 6 +- src/wallet/wallet.h | 19 ++- 15 files changed, 62 insertions(+), 207 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index fc05e59b2..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; @@ -533,9 +539,7 @@ 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, @@ -579,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; diff --git a/src/init.cpp b/src/init.cpp index 9f42e43a0..fa51c3963 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -491,9 +491,9 @@ std::string HelpMessage(HelpMessageMode mode) 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 5)"), 25)); + 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)"), 10000)); + 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)); @@ -2521,10 +2521,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) "pass -autoshield=1 to enable.\n", __func__, pwalletMain->hdSeedOrigin); } if (pwalletMain->fAutoShieldEnabled) { - int autoShieldInterval = GetArg("-autoshieldinterval", 25); - if (autoShieldInterval < 5) { - fprintf(stderr,"%s: autoshield interval %d below the minimum, clamping to 5\n", __func__, autoShieldInterval); - autoShieldInterval = 5; + 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(); @@ -2533,16 +2534,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // 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", 10000); - const CAmount AUTOSHIELD_MIN_FEE = 1000; // comfortably above minRelayTxFee for a small tx - const CAmount AUTOSHIELD_MAX_FEE = 10000000; // 0.1 DRGX; no sane autoshield fee exceeds this + CAmount autoShieldFee = GetArg("-autoshieldfee", DEFAULT_AUTOSHIELD_FEE); if (autoShieldFee < AUTOSHIELD_MIN_FEE || autoShieldFee > AUTOSHIELD_MAX_FEE) { - fprintf(stderr,"%s: -autoshieldfee=%lld out of range [%lld,%lld], using default 10000\n", - __func__, (long long)autoShieldFee, (long long)AUTOSHIELD_MIN_FEE, (long long)AUTOSHIELD_MAX_FEE); - autoShieldFee = 10000; + 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", 1); + pwalletMain->autoShieldMinUtxos = GetArg("-autoshieldminutxos", DEFAULT_AUTOSHIELD_MIN_UTXOS); if (pwalletMain->autoShieldMinUtxos < 1) { pwalletMain->autoShieldMinUtxos = 1; } diff --git a/src/miner.cpp b/src/miner.cpp index a9794027f..b215bd7b6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1042,7 +1042,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), @@ -1060,7 +1060,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); @@ -1089,7 +1089,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)); @@ -1130,11 +1130,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; } @@ -1192,9 +1192,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; diff --git a/src/pow.cpp b/src/pow.cpp index 1daf60e61..8ac7fc596 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -872,14 +872,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..3dbabf097 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -50,8 +50,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 1 }, { "sendtoaddress", 4 }, { "settxfee", 0 }, - { "getnotarysendmany", 0 }, - { "getnotarysendmany", 1 }, { "getreceivedbyaddress", 1 }, { "getreceivedbyaccount", 1 }, { "listreceivedbyaddress", 0 }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 36fd5a473..2cd1d472d 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -460,15 +460,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())); 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/server.cpp b/src/rpc/server.cpp index fbfb85ac2..e473ea785 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 */ @@ -666,7 +659,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 +687,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 7b8859bcc..e6971294a 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); diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index 4469adf92..94db62e97 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -37,9 +37,6 @@ 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; -// Expire unmined autoshield txs after this many blocks, so a tx cannot straddle -// a network-upgrade activation. -static const int AUTOSHIELD_EXPIRY_DELTA = 15; AsyncRPCOperation_autoshieldcoinbase::AsyncRPCOperation_autoshieldcoinbase(int targetHeight) : targetHeight_(targetHeight) {} @@ -310,7 +307,7 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { // 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 + AUTOSHIELD_EXPIRY_DELTA >= nextActivationHeight.get()) { + 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; } @@ -430,7 +427,7 @@ bool AsyncRPCOperation_autoshieldcoinbase::main_impl() { // 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 + AUTOSHIELD_EXPIRY_DELTA); + builder.SetExpiryHeight(tipHeight + AUTO_OP_EXPIRY_DELTA); builder.SetFee(fee); for (const auto& t : inputs) { diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.h b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h index 46ad01946..bb21f09ec 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.h +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.h @@ -11,9 +11,6 @@ #include "zcash/Address.hpp" #include "zcash/zip32.h" -// Default fee for automatic coinbase-shielding transactions -static const CAmount DEFAULT_AUTOSHIELD_FEE = 10000; - // Sentinel for "not a derived account" (i.e. the configured -autoshieldaddress). static const uint32_t AUTOSHIELD_ACCOUNT_NONE = UINT32_MAX; diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 085768315..93431058b 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -19,7 +19,6 @@ CAmount fConsolidationTxFee = DEFAULT_CONSOLIDATION_FEE; bool fConsolidationMapUsed = false; -const int CONSOLIDATION_EXPIRY_DELTA = 15; extern string randomSietchZaddr(); @@ -118,7 +117,7 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { // 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 + CONSOLIDATION_EXPIRY_DELTA >= nextActivationHeight.get()) { + 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; @@ -200,7 +199,7 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { continue; auto builder = TransactionBuilder(consensusParams, tipHeight, pwalletMain); - builder.SetExpiryHeight(tipHeight + CONSOLIDATION_EXPIRY_DELTA); + 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)); diff --git a/src/wallet/asyncrpcoperation_sweep.cpp b/src/wallet/asyncrpcoperation_sweep.cpp index 3abd70590..5c94bf4e5 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){} @@ -137,7 +136,7 @@ bool AsyncRPCOperation_sweep::main_impl() { // 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 + SWEEP_EXPIRY_DELTA >= nextActivationHeight.get()) { + 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 @@ -270,7 +269,7 @@ bool AsyncRPCOperation_sweep::main_impl() { } auto builder = TransactionBuilder(consensusParams, tipHeight, pwalletMain); - builder.SetExpiryHeight(tipHeight + SWEEP_EXPIRY_DELTA); + 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 diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ff80f062f..d5ec44b2a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -657,7 +657,7 @@ void CWallet::RunSaplingSweep(int blockHeight) { 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(); if (!q->addOperation(operation)) { // Queue is closing (shutdown). Release the flag we just set, or it stays @@ -715,7 +715,7 @@ void CWallet::RunSaplingConsolidation(int blockHeight) { 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(); if (!q->addOperation(operation)) { // Queue is closing (shutdown). Release the flag we just set, or it stays @@ -779,7 +779,7 @@ void CWallet::RunAutoShieldCoinbase(int blockHeight) { // running this every interval the map grew without bound. q->popOperationForId(saplingAutoShieldOperationId); } - std::shared_ptr operation(new AsyncRPCOperation_autoshieldcoinbase(blockHeight + 5)); + 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 diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index b54805684..cf77da83f 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; @@ -847,11 +860,11 @@ public: std::string consolidationAddress = ""; int nextAutoShield = 0; - int autoShieldInterval = 25; - CAmount autoShieldFee = 10000; + 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 = 1; + 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 = ""; From b7060c7de00002407f3f6fbf8b01e230e33c673f Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 13:20:51 -0500 Subject: [PATCH 36/68] =?UTF-8?q?hygiene:=20Phase=203=20=E2=80=94=20dead-c?= =?UTF-8?q?ode=20excision=20(~7,100=20lines)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third phase of the code-hygiene remediation: remove provably-dead code. Each in-file deletion was adversarially verified (a per-finding investigator plus a skeptic that greps the tree to refute deletability); the sanity grep confirms zero dangling references, and the tree builds and self-mines clean. Removed wholesale: - src/cc/dapps/ (9 files, ~6,939 lines): a git-tracked but never-built Komodo DEX / z-migration tool (hushdex.c, zmigrate.c, cJSON.c, ...). Nothing in Makefile.am/configure.ac references it. In-file dead code: - pow.cpp: the `#ifdef original_algo` oldRT_CST_RST function (the macro is never defined in source or build flags) and two `if ( 0 )` debug blocks. - walletdb.cpp: the "orphaned staking transaction" cleanup path (deadTxns) — DragonX is RandomX PoW with no staking, so the guard is always false and the block never runs; also drop the now-unused static/extern decls. - cc/eval.h: the ProcessCC / Eval::ImportCoin / ImportPayout / DisputePayout declarations that have no definition anywhere in the tree. - rpc/crosschain.cpp: the crosschainproof stub RPC (returned {} unconditionally) and its registrations in rpc/server.cpp, rpc/server.h, rpc/client.cpp. - coins.cpp: the commented-out `//TODO: delete` Sprout PushAnchor template. - wallet.cpp: the permanently-zero KMD `interest2` term in CreateTransaction. - rpc/net.cpp: hush_longestchain's always-zero `n` var (num > (n>>1) => num>0) and its `if ( 0 )` debug branch. - hush_nSPV.h / hush_nSPV_fullnode.h: three `if ( 0 && ... )` dead debug branches. - net.cpp, crypter.h, saplingconsolidation.cpp (dup set_error_code), shieldcoinbase.cpp (`donation < 0` on a uint8_t), cclib.cpp (unused FAUCET2SIZE): single-line dead-code fixes. Deliberately NOT touched (verification refuted the audit's "dead on DragonX" premise): the ~2,250-line HUSH3 checkpoint block, the miner notary/timelock paths, and hush_gateway.h — all reachable at runtime via -ac_name / -ac_* args (the inherited Komodo assetchain model), and hush_gateway's hush_opretvalidate is called from ConnectBlock (consensus). Dropping those requires a deliberate decision to remove HAC/HUSH3 mode, tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 2 - src/cc/cclib.cpp | 1 - src/cc/dapps/Makefile | 20 - src/cc/dapps/README.md | 72 - src/cc/dapps/cJSON.c | 1201 ------------- src/cc/dapps/dappinc.h | 1597 ----------------- src/cc/dapps/dappstd.c | 1114 ------------ src/cc/dapps/hushdex.c | 1385 -------------- src/cc/dapps/hushdex.json | 19 - src/cc/dapps/makedapps | 2 - src/cc/dapps/zmigrate.c | 1529 ---------------- src/cc/eval.h | 16 - src/coins.cpp | 13 - src/hush_nSPV.h | 2 - src/hush_nSPV_fullnode.h | 4 - src/net.cpp | 1 - src/pow.cpp | 83 - src/rpc/client.cpp | 1 - src/rpc/crosschain.cpp | 8 - src/rpc/net.cpp | 7 +- src/rpc/server.cpp | 1 - src/rpc/server.h | 1 - ...asyncrpcoperation_saplingconsolidation.cpp | 1 - .../asyncrpcoperation_shieldcoinbase.cpp | 2 +- src/wallet/crypter.h | 1 - src/wallet/wallet.cpp | 6 +- src/wallet/walletdb.cpp | 28 +- 27 files changed, 6 insertions(+), 7111 deletions(-) delete mode 100644 src/cc/dapps/Makefile delete mode 100644 src/cc/dapps/README.md delete mode 100644 src/cc/dapps/cJSON.c delete mode 100644 src/cc/dapps/dappinc.h delete mode 100644 src/cc/dapps/dappstd.c delete mode 100644 src/cc/dapps/hushdex.c delete mode 100644 src/cc/dapps/hushdex.json delete mode 100755 src/cc/dapps/makedapps delete mode 100644 src/cc/dapps/zmigrate.c diff --git a/.gitignore b/.gitignore index 441696a83..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,7 +153,6 @@ 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 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/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/coins.cpp b/src/coins.cpp index 1e5bff4e3..2940137ca 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( 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..e08ebdd7b 100644 --- a/src/hush_nSPV_fullnode.h +++ b/src/hush_nSPV_fullnode.h @@ -675,8 +675,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 ) { @@ -715,8 +713,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/net.cpp b/src/net.cpp index c4d1b3e86..b508122e6 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1722,7 +1722,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) { diff --git a/src/pow.cpp b/src/pow.cpp index 8ac7fc596..a4e10e2b4 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -97,76 +97,6 @@ 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) -{ - //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=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); } @@ -509,12 +432,6 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead } 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(); nbits = (nbits & 0xfffffffc) | zawyflag; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 3dbabf097..970cf704e 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -169,7 +169,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/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/server.cpp b/src/rpc/server.cpp index e473ea785..4a040b1a6 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -335,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 }, diff --git a/src/rpc/server.h b/src/rpc/server.h index e6971294a..a2d5c17cc 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -380,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/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 93431058b..147d9c4a1 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -52,7 +52,6 @@ void AsyncRPCOperation_saplingconsolidation::main() { 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) { diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp index 4e69fc23f..e179f4476 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"); } diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index d72a8a1b6..427f9e5df 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -165,7 +165,6 @@ private: // .second is the ciphertext. std::pair> cryptedMnemonicEntropy; CryptedKeyMap mapCryptedKeys; - //CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys; CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys; CKeyingMaterial vMasterKey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d5ec44b2a..d1f4232f0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4359,7 +4359,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) @@ -4480,7 +4480,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) { @@ -4506,8 +4505,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt 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; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index cfe632c48..5537972fa 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -46,8 +46,6 @@ const int CHDChain::CURRENT_VERSION; using namespace std; static uint64_t nAccountingEntryNumber = 0; -static list deadTxns; -extern CBlockIndex *hush_blockindex(uint256 hash); // // CWalletDB @@ -1017,8 +1015,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); } } @@ -1034,29 +1031,6 @@ 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; From 0de30bbbd15a6f29d86b3d6692e992a19857b647 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 13:25:17 -0500 Subject: [PATCH 37/68] =?UTF-8?q?hygiene:=20Phase=203=20follow-up=20?= =?UTF-8?q?=E2=80=94=20sweep=20commented-out=20debug=20cruft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove ~116 commented-out debug/dead-code lines the audit flagged as noise (findings F6/F7/F15/F22): the Komodo "%s tikN" step-tracer comments and other commented-out fprintf/printf/LogPrintf/std::cerr calls threaded through AppInit2 (init.cpp), CreateNewBlock and the miner loops (miner.cpp), plus a few commented-out dead-code fragments (sendmany SetLockTime/nLockTime, the CCtx CC_vinselect random-pick block and AddNormalinputsLocal remote-mypk redirect, miner adaptive-PoW assignments). Comments only — no compiled behavior changes; the tree builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cc/CCtx.cpp | 17 ------ src/init.cpp | 30 ----------- src/miner.cpp | 64 ----------------------- src/wallet/asyncrpcoperation_sendmany.cpp | 5 -- 4 files changed, 116 deletions(-) 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/init.cpp b/src/init.cpp index fa51c3963..f4f665a87 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1124,7 +1124,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 @@ -1161,7 +1160,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); } @@ -1179,12 +1177,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); @@ -1199,7 +1195,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) 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); @@ -1228,7 +1223,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)) @@ -1351,12 +1345,10 @@ 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)); @@ -1364,7 +1356,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) 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)) { @@ -1413,7 +1404,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fprintf(stderr,"%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.")); @@ -1472,7 +1462,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; @@ -1560,7 +1549,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 @@ -1577,7 +1565,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")) { @@ -1600,7 +1587,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") { @@ -1649,7 +1635,6 @@ 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!")); @@ -1666,7 +1651,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (file) fclose(file); - //fprintf(stderr,"%s tik11\n", __FUNCTION__); fprintf(stderr,"Attempting to obtain lock %s\n", pathLockFile.string().c_str()); try { static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); @@ -1682,7 +1666,6 @@ 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()); @@ -1715,7 +1698,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); @@ -1723,7 +1705,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) && @@ -1733,7 +1714,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) threadGroup.create_thread(&ThreadShowMetricsScreen); } - //fprintf(stderr,"%s tik15\n", __FUNCTION__); if ( HUSH_NSPV_FULLNODE ) { @@ -1751,7 +1731,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; @@ -1778,7 +1757,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 @@ -1794,7 +1772,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)) { @@ -1853,7 +1830,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; @@ -1916,7 +1892,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")) { @@ -1955,7 +1930,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); @@ -1991,7 +1965,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); @@ -2238,7 +2211,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 @@ -2452,7 +2424,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"); @@ -2800,7 +2771,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // ********************************************************* Step 11: start node - //fprintf(stderr,"Checking disk space...\n"); if (!CheckDiskSpace()) return false; diff --git a/src/miner.cpp b/src/miner.cpp index b215bd7b6..f87257c31 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,12 +178,10 @@ 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__); @@ -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,7 +238,6 @@ 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) { @@ -282,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 @@ -392,7 +385,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(); @@ -423,7 +415,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()); } } } @@ -438,7 +429,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; @@ -450,7 +440,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: @@ -458,10 +447,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) { @@ -478,7 +465,6 @@ 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 @@ -491,11 +477,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(); @@ -518,7 +502,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(); @@ -560,7 +543,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 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() @@ -625,13 +607,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); @@ -645,7 +625,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()); @@ -670,7 +649,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 fprintf(stderr, "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; @@ -684,12 +662,9 @@ 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"); LEAVE_CRITICAL_SECTION(cs_main); @@ -712,7 +687,6 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 } 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); } if ( ASSETCHAINS_CBOPRET != 0 ) @@ -721,7 +695,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; @@ -754,7 +727,6 @@ 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 ) @@ -766,14 +738,12 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 //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(); } @@ -783,7 +753,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) @@ -807,7 +776,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); @@ -817,7 +785,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; @@ -826,7 +793,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 { @@ -854,7 +820,6 @@ CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, } } } - // fprintf(stderr,"%s: calling CreateNewBlock\n", __func__); return CreateNewBlock(pubkey, scriptPubKey, gpucount, isStake); } @@ -868,7 +833,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; @@ -916,7 +880,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; @@ -993,7 +956,6 @@ CBlockIndex *get_chainactive(int32_t 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); } @@ -1281,13 +1243,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()) { @@ -1305,10 +1265,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(); } @@ -1333,7 +1291,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. @@ -1366,14 +1323,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 ) { @@ -1390,7 +1345,6 @@ void static RandomXMiner() sleep(1); continue; } - // fprintf(stderr,"RandomXMiner: getting block template\n"); rxdebug("%s: getting block template\n"); unique_ptr pblocktemplate(ptr); @@ -1419,7 +1373,6 @@ void static RandomXMiner() } 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 @@ -1440,7 +1393,6 @@ void static RandomXMiner() } 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; @@ -1451,7 +1403,6 @@ void static RandomXMiner() 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"); @@ -1480,7 +1431,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()); @@ -1705,10 +1655,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(); } // @@ -1731,7 +1679,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); } @@ -1756,7 +1703,6 @@ void static BitcoinMiner() sleep(1); continue; } - //fprintf(stderr,"get template\n"); unique_ptr pblocktemplate(ptr); if (!pblocktemplate.get()) { @@ -1784,7 +1730,6 @@ void static BitcoinMiner() } } 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 @@ -1800,7 +1745,6 @@ void static BitcoinMiner() gotinvalid = 0; while (true) { - //fprintf(stderr,"gotinvalid.%d\n",gotinvalid); if ( gotinvalid != 0 ) break; hush_longestchain(); @@ -1825,7 +1769,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 @@ -1839,7 +1782,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--) @@ -1859,7 +1801,6 @@ 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); @@ -1893,8 +1834,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); } @@ -1969,8 +1908,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; @@ -2026,7 +1963,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 ) { diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index d7e42bcd6..94973aa1b 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -325,14 +325,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 +340,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); } } @@ -416,7 +413,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { } // Fetch Sapling anchor and witnesses - //LogPrintf("%s: Gathering anchors and witnesses\n", __FUNCTION__); uint256 anchor; std::vector> witnesses; { @@ -625,7 +621,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; } From 267e6f7ad57cc6d829eb8b744b4c814ca2e2589b Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 14:14:41 -0500 Subject: [PATCH 38/68] consensus: drop the dead CBOPRET price-validation from the coinbase check Step 0 of the scoped HAC/HUSH3-drop work (the zero-risk, self-contained piece). ContextualCheckCoinbaseTransaction's only action was calling hush_opretvalidate() for CBOPRET price-oracle validation, gated on ASSETCHAINS_CBOPRET. On DragonX that global is always 0 (default 0, only ever set by -ac_cbopret, which DragonX never passes), so the branch is dead and the function already returns true for every DragonX coinbase. Remove the dead branch; the function is now unconditionally valid at this stage, which is behavior-identical on DragonX. Verified: a node self-mines and `verifychain` re-validates the whole chain (every coinbase re-checked through this function) = true. hush_opretvalidate (hush_gateway.h) is now unreferenced; its removal is part of the larger hush_gateway cleanup, tracked with the rest of the HAC/HUSH3-drop follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/main.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c15049253..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); } From cf15b0a399eca74775a452b18308fc778b036a28 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 15:02:01 -0500 Subject: [PATCH 39/68] =?UTF-8?q?hygiene:=20Phase=204=20=E2=80=94=20route?= =?UTF-8?q?=20unconditional=20debug=20output=20through=20LogPrint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth phase of the code-hygiene remediation, addressing the ~1,082 stray fprintf(stderr)/printf debug calls so consensus and hot paths stop spewing to stderr/stdout. Done per-file (18 files) with conservative rules; the tree builds clean, self-mines, and `verifychain` re-validates the whole chain (pow/txdb/coins/miner paths) with -debug=1 enabling every converted line — zero tinyformat/format-arg exceptions. Net across 18 files: 174 commented-out debug lines deleted, 173 unconditional live prints converted to LogPrint("",...)/LogPrintf (net/mining/pow/nspv/ zrpc categories, format strings + args preserved exactly), 40 pure-noise or sensitive prints deleted, and 194 calls DELIBERATELY LEFT (already behind fDebug/fZdebug guards, or genuine startup/fatal-error output that must reach the console before logging init). Notable: - Deleted sensitive success-path dumps (nSPV SIG_TXHASH + full tx input/ output/change amounts; kvupdate privkey/pubkey hex) that were writing key and amount material straight to stderr/stdout. - Removed the raw 32-byte target hex dumps in the zawy adaptive-PoW helpers and the legacy one-shot `if(height==340000)` HUSH artifact in pow.cpp. - Converted per-tx relay + ban/banlist (net), per-setgenerate MININGTHREADS (rpc/mining), signrawtransaction TXPOW, and per-message nSPV traces. - Left format/arg-mismatched lines untouched (flagged) to avoid introducing tinyformat runtime throws. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cc/CCutils.cpp | 20 +++-- src/coins.cpp | 6 -- src/hush.h | 33 --------- src/hush_nSPV_superlite.h | 55 +++++++------- src/hush_nSPV_wallet.h | 50 +++++-------- src/hush_utils.h | 148 +++++++++++++------------------------ src/init.cpp | 56 +++++++------- src/miner.cpp | 95 ++++++++---------------- src/net.cpp | 17 ++--- src/pow.cpp | 39 +--------- src/rpc/mining.cpp | 5 +- src/rpc/rawtransaction.cpp | 2 +- src/stratum.cpp | 73 +----------------- src/txdb.cpp | 16 +--- src/wallet/rpcdump.cpp | 2 - src/wallet/rpcwallet.cpp | 35 ++------- src/wallet/wallet.cpp | 34 ++------- 17 files changed, 197 insertions(+), 489 deletions(-) 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/coins.cpp b/src/coins.cpp index 2940137ca..40557363d 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -393,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); @@ -518,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]; } @@ -583,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/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_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 8742e7f78..96adc502a 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,13 @@ 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); fclose(fp); } } @@ -1429,9 +1408,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,10 +1444,7 @@ 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>= 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 +1647,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 +1669,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 +1753,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; } } @@ -1815,7 +1786,7 @@ void hush_args(char *argv0) 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) { @@ -1857,19 +1828,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); @@ -1887,7 +1858,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; } } @@ -1897,11 +1868,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 @@ -1916,14 +1887,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) @@ -1934,7 +1905,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; } @@ -1953,7 +1924,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 @@ -1969,12 +1940,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); } } @@ -2000,21 +1971,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++) @@ -2137,9 +2101,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 ) { @@ -2151,12 +2115,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"); } } @@ -2224,7 +2188,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); } } @@ -2233,14 +2197,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)); @@ -2261,14 +2223,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; @@ -2411,10 +2369,10 @@ 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 { @@ -2448,8 +2406,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; } @@ -2458,7 +2415,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 ) { @@ -2514,7 +2470,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 f4f665a87..07cc152bf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -929,7 +929,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 @@ -982,7 +982,7 @@ 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()) @@ -1191,7 +1191,7 @@ 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.")); } } @@ -1272,33 +1272,33 @@ 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.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.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.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.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.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.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.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.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.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.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"))); @@ -1312,7 +1312,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.c_str() ); } //TODO: verify asmap_path is not a directory @@ -1326,7 +1326,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)); @@ -1351,7 +1351,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) nMaxConnections = GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); 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) @@ -1401,7 +1401,7 @@ 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__); } // Check for -debugnet @@ -1651,7 +1651,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (file) fclose(file); - 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()) @@ -2029,7 +2029,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); @@ -2037,7 +2037,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; } } @@ -2090,14 +2090,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; @@ -2121,7 +2121,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; @@ -2176,7 +2176,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); @@ -2382,7 +2382,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; } @@ -2407,7 +2407,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; @@ -2726,7 +2726,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 @@ -2742,7 +2742,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")) diff --git a/src/miner.cpp b/src/miner.cpp index f87257c31..1a2afd327 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -184,7 +184,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 std::unique_ptr pblocktemplate(new CBlockTemplate()); 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 @@ -294,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(); @@ -373,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(); @@ -404,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; } @@ -469,7 +468,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 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; } @@ -488,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 @@ -526,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(); } } @@ -537,7 +535,6 @@ 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); @@ -552,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); @@ -646,7 +643,7 @@ 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; } //txNew.vout[1].scriptPubKey = CScript() << ParseHex(); @@ -666,7 +663,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 ptr[34] = OP_CHECKSIG; } } 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); @@ -679,7 +676,7 @@ 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"); + LogPrintf("Could not create notary payment, trying again.\n"); if ( !isStake ) { LEAVE_CRITICAL_SECTION(cs_main); @@ -687,7 +684,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 } return(0); } - } 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 ) { @@ -734,7 +731,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 LEAVE_CRITICAL_SECTION(cs_main); LEAVE_CRITICAL_SECTION(mempool.cs); } - fprintf(stderr,"%s: TestBlockValidity failed!\n", __func__); + LogPrintf("%s: TestBlockValidity failed!\n", __func__); //throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed"); // crashes the node, moved to GetBlockTemplate and issue return. return(0); } @@ -813,7 +810,7 @@ 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; } @@ -853,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"); } } @@ -954,7 +941,6 @@ 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()); } return(0); } @@ -1280,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; } @@ -1341,7 +1327,7 @@ 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; } @@ -1366,10 +1352,10 @@ 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); @@ -1388,7 +1374,7 @@ 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(); @@ -1402,7 +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"; rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str()); rxdebug("%s: calculating randomx hash\n"); @@ -1460,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; @@ -1527,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 @@ -1556,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 @@ -1565,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); @@ -1624,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( @@ -1637,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()) { @@ -1667,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; } @@ -1699,7 +1672,7 @@ 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; } @@ -1723,10 +1696,10 @@ 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); @@ -1806,7 +1779,7 @@ void static BitcoinMiner() sleep(1); if ( chainActive.LastTip()->GetHeight() >= Mining_height ) { - fprintf(stderr,"new block arrived\n"); + LogPrint("pow", "new block arrived\n"); return(false); } } @@ -1820,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); @@ -1932,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) @@ -2042,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 b508122e6..7c0240745 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -614,7 +614,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 +642,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 +676,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) { @@ -690,13 +690,13 @@ 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() ); + 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 } } @@ -1851,7 +1851,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,7 +2509,7 @@ 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; } @@ -2522,10 +2521,10 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss) 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 diff --git a/src/pow.cpp b/src/pow.cpp index a4e10e2b4..7f2d3528d 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -113,13 +113,7 @@ arith_uint256 RT_CST_RST_outer(int32_t height,uint32_t nTime,arith_uint256 bnTar } if ( bnTarget > 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); } @@ -146,12 +140,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); } @@ -211,23 +199,14 @@ 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); } @@ -309,13 +288,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; } } @@ -325,7 +302,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; @@ -422,15 +398,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); } } nbits = bnTarget.GetCompact(); @@ -527,8 +497,7 @@ unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const bnLimit = UintToArith256(params.powAlternate); 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; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 2cd1d472d..92e7adc8a 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -363,7 +363,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); @@ -847,7 +847,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; } @@ -898,7 +897,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"); @@ -924,7 +922,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) 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/stratum.cpp b/src/stratum.cpp index c7bed37df..c9f4047d3 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -637,9 +637,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__); @@ -737,14 +734,11 @@ 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()); @@ -851,12 +845,6 @@ 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); @@ -873,12 +861,6 @@ std::string GetWorkUnit(StratumClient& client) } - // 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 @@ -992,14 +974,6 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork blkhdr.hashMerkleRoot = current_work.GetBlock().hashMerkleRoot; 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; - // } - // block is constructed, now it's time to VerifyEH if (instance_of_cstratumparams.fCheckEquihashSolution && !CheckEquihashSolution(&blkhdr, Params())) @@ -1018,7 +992,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(); @@ -1061,27 +1034,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,7 +1051,6 @@ 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; @@ -1106,22 +1059,12 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork 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; - // } - // std::shared_ptr pblock = std::make_shared(block); // res = ProcessNewBlock(Params(), pblock, true, NULL); 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 +1150,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); @@ -1340,10 +1275,8 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) 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) { @@ -1816,7 +1749,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++; @@ -1836,7 +1769,7 @@ bool InitStratumServer() 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 ); if (!InitStratumAllowList(stratum_allow_subnets)) { @@ -1959,7 +1892,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 2d6019712..d0541e08a 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -472,7 +472,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 +484,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 +492,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; @@ -527,8 +523,7 @@ bool CBlockTreeDB::Snapshot2(std::map &addressAmounts, Un 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) { @@ -681,23 +676,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; @@ -715,7 +705,6 @@ bool CBlockTreeDB::LoadBlockIndexGuts() 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; @@ -731,7 +720,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/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9b72f008d..dac53a7f6 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1231,7 +1231,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"); @@ -1294,7 +1293,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 ef347dcf7..638b7d86b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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,14 +700,11 @@ 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))); height = chainActive.LastTip()->GetHeight(); if ( memcmp(&zeroes,&refpubkey,sizeof(refpubkey)) != 0 ) @@ -749,9 +742,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 +1961,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); @@ -2959,7 +2947,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); @@ -4675,7 +4662,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)); @@ -5245,7 +5231,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // 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); + LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials); fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ]; } else { // Automagic zaddr source selection failed, exit honorably @@ -5407,7 +5393,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) 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; } @@ -6025,8 +6011,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; @@ -6087,7 +6072,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."); } @@ -6248,7 +6232,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; @@ -6256,7 +6239,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); @@ -6277,15 +6259,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; igetNullifiers().size() ); } return pcoinsTip->getNullifiers().size(); } @@ -1709,7 +1706,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); @@ -2016,9 +2012,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) { @@ -2036,7 +2032,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++; } } @@ -2044,7 +2039,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; } @@ -3047,14 +3042,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 @@ -3575,7 +3568,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") { @@ -3593,11 +3585,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()); @@ -3835,7 +3824,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()); @@ -4156,7 +4145,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) @@ -4498,7 +4486,6 @@ 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; @@ -4542,7 +4529,6 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt } else { - //fprintf(stderr,"use notary pubkey\n"); scriptChange = CScript() << ParseHex(NOTARY_PUBKEY) << OP_CHECKSIG; } } @@ -4739,7 +4725,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; @@ -4789,7 +4774,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 ) { @@ -4797,9 +4782,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")) @@ -4984,7 +4967,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); } } @@ -5006,7 +4988,6 @@ void CWallet::ReturnKey(int64_t nIndex) LOCK(cs_wallet); setKeyPool.insert(nIndex); } - //LogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey& result) @@ -5293,14 +5274,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() @@ -5540,7 +5521,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); } From 817c6b2d0e61245f20bf71e604c6f31e0ff8e39f Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 15:19:22 -0500 Subject: [PATCH 40/68] =?UTF-8?q?hygiene:=20Phase=205=20=E2=80=94=20correc?= =?UTF-8?q?t=20stale=20comments=20+=20name=20safe=20magic=20numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth phase of the code-hygiene remediation, done per-file (18 files) via an 18-agent Workflow with strict guardrails. No consensus or security VALUES changed (verified: pow/hush_utils/hush_bitcoind/net.h/crypter/stratum have zero non-comment numeric changes); built clean, self-mined, verifychain=true. Totals: 57 stale comments corrected, 46 user-facing branding fixes, 6 behavior-preserving named constants, 4 stratum warnings, 77 items deliberately left (consensus/security values, copyright headers). - Stale comments -> DragonX: corrected HUSH3/HUSH/Komodo/Equihash/Arrakis leftovers that misdescribed the active chain (pow AWT/lwma notes, txdb "Equihash solution" -> RandomX, wallet CLTV/overwinter/Sapling@1 notes, util datadir provenance, crypter's stale mapSproutSpendingKeys invariant). Replaced unprofessional/editorializing comments (profane TODOs, "developers are elite") with neutral technical notes, preserving the real design info. - User-facing branding: dumpwallet header "created by Hush" -> "DragonX"; RPC help text hushd/hush-cli/hushprivkey/hushaddress/Agama -> dragonx*; rebranded provably-dead "HUSH3" symbol-fallback literals to DRAGONX. - Named constants (same values, non-consensus/non-security): addrman peer-selection factors (kChanceFactorGrowth/kChanceScale/deprioritize), sietch MIN_ZOUTS, an init notarization-DB cache size. - Left the security macros (ECC/TFM_TIMING_RESISTANT=420, comment-clarified only), the consensus subsidy/commission literals + 128/129 TRANSITION (comment only), and copyright headers untouched. - Added a prominent WARNING that the stratum server is Equihash-era and NOT updated for DragonX's RandomX PoW (flagged, not rewritten). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/addrman.cpp | 30 +++++++--- src/cc/CCinclude.h | 21 ++++--- src/hush_nSPV_fullnode.h | 3 +- src/hush_utils.h | 32 +++++++--- src/init.cpp | 44 ++++++++------ src/net.cpp | 14 +++-- src/net.h | 7 ++- src/pow.cpp | 15 +++-- src/stratum.cpp | 41 +++++++++---- src/txdb.cpp | 5 +- src/util.cpp | 50 ++++++++++------ ...asyncrpcoperation_saplingconsolidation.cpp | 12 +++- src/wallet/asyncrpcoperation_sendmany.cpp | 20 ++++--- src/wallet/crypter.cpp | 7 ++- src/wallet/crypter.h | 3 +- src/wallet/rpcdump.cpp | 18 +++--- src/wallet/rpcwallet.cpp | 60 ++++++++++--------- src/wallet/wallet.cpp | 10 ++-- 18 files changed, 251 insertions(+), 141 deletions(-) diff --git a/src/addrman.cpp b/src/addrman.cpp index ae5d5a50d..a16f0665f 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -478,6 +478,20 @@ CAddrInfo CAddrMan::Select_(bool newOnly) const int kRetriesBetweenSleep = 1000; const int kRetrySleepInterval = 100; // milliseconds + // Peer-selection tuning factors (networking heuristics, not consensus). + // On each rejected candidate the running chance factor is scaled up by this + // amount so the loop is guaranteed to eventually accept a peer. + const double kChanceFactorGrowth = 1.2; + // Candidates on unreachable networks are deprioritized to this fraction of + // their base chance. + const double kUnreachableDeprioritize = 0.25; + // Candidates that were just tried are deprioritized to this fraction of + // their base chance. + const double kJustTriedDeprioritize = 0.10; + // Fixed-point scale for the acceptance probability test: draw a random int in + // [0, kChanceScale) and accept if it falls below (factors * chance) * kChanceScale. + const int kChanceScale = 1 << 30; + if (newOnly && nNew == 0) return CAddrInfo(); @@ -513,15 +527,15 @@ CAddrInfo CAddrMan::Select_(bool newOnly) CAddrInfo& info = mapInfo[nId]; if (info.IsReachableNetwork()) { //deprioritize unreachable networks - fReachableFactor = 0.25; + fReachableFactor = kUnreachableDeprioritize; } if (info.IsJustTried()) { //deprioritize entries just tried - fJustTried = 0.10; + fJustTried = kJustTriedDeprioritize; } - if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) + if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale) return info; - fChanceFactor *= 1.2; + fChanceFactor *= kChanceFactorGrowth; } } else { // use a new node @@ -553,15 +567,15 @@ CAddrInfo CAddrMan::Select_(bool newOnly) CAddrInfo& info = mapInfo[nId]; if (info.IsReachableNetwork()) { //deprioritize unreachable networks - fReachableFactor = 0.25; + fReachableFactor = kUnreachableDeprioritize; } if (info.IsJustTried()) { //deprioritize entries just tried - fJustTried = 0.10; + fJustTried = kJustTriedDeprioritize; } - if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) + if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale) return info; - fChanceFactor *= 1.2; + fChanceFactor *= kChanceFactorGrowth; } } 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/hush_nSPV_fullnode.h b/src/hush_nSPV_fullnode.h index e08ebdd7b..54756e259 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; diff --git a/src/hush_utils.h b/src/hush_utils.h index 96adc502a..1b1cb2eeb 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1447,7 +1447,10 @@ uint32_t hush_smartmagic(char *symbol,uint64_t supply,uint8_t *extraptr,int32_t LogPrintf("DragonX raw magic extralen=%d crc0=%x\n",extralen,crc0); } - //TODO: why is this needed? + // Legacy special case: HUSH3 mainnet had a hardcoded network magic (HUSH_MAGIC) + // rather than the crc32-derived value used by every other chain. This branch is + // dead on DragonX (SMART_CHAIN_SYMBOL is "DRAGONX", never "HUSH3"); it is kept only + // so the function still reproduces HUSH3's magic if ever run with that symbol. const bool ishush3 = strncmp(symbol, "HUSH3",5) == 0 ? true : false; if(ishush3) { return HUSH_MAGIC; @@ -1493,16 +1496,19 @@ uint64_t hush_max_money() return hush_current_supply(10000000); } -// This implements the Hush Emission Curve, the miner subsidy part, -// and must be kept in sync with hush_commision() in hush_bitcoind.h! -// Changing these functions are consensus changes! -// Here Be Dragons! -- Duke Leto +// This implements the emission curve (miner subsidy part) and must be kept in +// sync with hush_commission() in hush_bitcoind.h! Changing these functions, +// including the height literals below, is a CONSENSUS change. +// NOTE: this TRANSITION boundary is 128 here, while hush_commission() uses 129. +// This off-by-one between the two curves is a historical consensus quirk and is +// deliberately left as-is: changing either value would be a consensus change. uint64_t hush_block_subsidy(int height) { uint64_t subsidy = 0; int32_t HALVING1 = GetArg("-z2zheight",340000); //TODO: support INTERVAL :( //int32_t INTERVAL = GetArg("-ac_halving1",840000); + // Consensus: TRANSITION is 128 here vs 129 in hush_commission(); do not change (see note above). int32_t TRANSITION = 128; if (height < TRANSITION) { @@ -1585,7 +1591,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 @@ -1593,6 +1601,8 @@ 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; // check for backwards compat, older chains with no explicit rewards had 0.0001 block reward @@ -1629,7 +1639,9 @@ 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; } else if ( ASSETCHAINS_DECAY[curEra] == 100000000 && ASSETCHAINS_ENDSUBSIDY[curEra] != 0 ) { @@ -1991,7 +2003,8 @@ void hush_args(char *argv0) uint8_t prevCCi = 0; ASSETCHAINS_CCLIB = GetArg("-ac_cclib","hush3"); - // these are the enabled CCs on HUSH3 mainnet + // Default CC set inherited from legacy HUSH3 mainnet; only used when a chain + // enables CryptoConditions and does not override -ac_ccenable. Split(GetArg("-ac_ccenable","228,234,235,236,241"), sizeof(ccenables)/sizeof(*ccenables), ccenables, 0); for (i=nonz=0; i<0x100; i++) { @@ -2376,6 +2389,9 @@ void hush_args(char *argv0) } } } 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; diff --git a/src/init.cpp b/src/init.cpp index 07cc152bf..e726e6623 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -121,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 // @@ -622,7 +626,7 @@ std::string HelpMessage(HelpMessageMode mode) 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")); @@ -789,7 +793,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) @@ -910,7 +914,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)) { @@ -987,6 +991,10 @@ bool AppInitServers(boost::thread_group& threadGroup) RPCServer::OnPreCommand(&OnRPCPreCommand); if (!InitHTTPServer()) return false; + // WARNING: the stratum server (stratum.cpp) is Equihash-era code: it assumes a 1347-byte + // Equihash solution and calls CheckEquihashSolution. It has NOT been updated for DragonX's + // 32-byte RandomX solution and must not be relied on for mining without a full revalidation. + // It stays off by default (DEFAULT_STRATUM_ENABLE=false); only -stratum turns it on. if (GetBoolArg("-stratum", DEFAULT_STRATUM_ENABLE) && !InitStratumServer()) return false; if (!StartRPC()) @@ -1000,7 +1008,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; @@ -1203,7 +1211,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 @@ -1254,7 +1262,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", "")); @@ -1300,8 +1308,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if(fs::exists(asmap_path)) { LogPrint("net", "%s: found asmap file at %s\n", __func__, asmap_path.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; } } @@ -1637,7 +1645,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // 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 @@ -1645,7 +1653,7 @@ 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); @@ -1668,7 +1676,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\n", FormatFullVersion()); + LogPrintf("DragonX version %s\n", FormatFullVersion()); if (fPrintToDebugLog) OpenDebugLog(); @@ -2066,7 +2074,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 @@ -2082,7 +2090,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); } @@ -2256,10 +2264,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()); } @@ -2664,10 +2672,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 diff --git a/src/net.cpp b/src/net.cpp index 7c0240745..7fc4e7669 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 @@ -815,7 +817,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); @@ -2514,7 +2516,9 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss) 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())) ); @@ -2773,7 +2777,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 7f2d3528d..d5740a9b4 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -414,7 +414,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 @@ -432,8 +435,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; @@ -481,8 +487,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); diff --git a/src/stratum.cpp b/src/stratum.cpp index c9f4047d3..9b14289bd 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -672,7 +672,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; @@ -683,21 +683,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__); + 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 @@ -924,6 +924,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) { + // ============================ WARNING (Equihash-era code) ============================ + // This entire submit path is hardcoded for the legacy Equihash proof-of-work: + // it expects a 1347-byte Equihash solution and calls CheckEquihashSolution() below. + // DragonX uses RandomX PoW (32-byte solution), NOT Equihash. This stratum path has + // NOT been updated for RandomX and must not be relied on without full revalidation. + // The 1347-byte length checks, the "sol.begin()+3" solution offset, and the equihash + // target/difficulty math are all Equihash-era and are intentionally left unchanged. + // ==================================================================================== + // // called from stratum_mining_submit and uses following data, came from client: // ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_SOLUTION"] // all other params we have saved in other places @@ -934,7 +943,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. + // WARNING (Equihash-era): 1347 is the Equihash-200,9 solution length. DragonX is RandomX + // (32-byte solution), so this length check does not match the live PoW. Left unchanged + // because this whole path is Equihash-era; do not repurpose without revalidating the miner protocol. if (sol.size() != 1347) { std::string msg = strprintf("%s: solution is wrong length (received %d bytes; expected %d bytes", __func__, extranonce2.size(), 1347); LogPrint("stratum", "%s\n", msg); @@ -1196,7 +1207,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()); @@ -1204,9 +1215,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; @@ -1250,6 +1261,14 @@ UniValue stratum_mining_configure(StratumClient& client, const UniValue& params) UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) { + // ============================ WARNING (Equihash-era code) ============================ + // This share-submission handler is hardcoded for legacy Equihash: it parses and requires + // a 1347-byte Equihash solution and hands it to SubmitBlock() (which calls + // CheckEquihashSolution). DragonX uses RandomX PoW (32-byte solution), NOT Equihash. + // This path has NOT been updated for RandomX and must not be relied on without full + // revalidation of the miner-facing stratum protocol. + // ==================================================================================== + // // {"id": 4, "method": "mining.submit", "params": ["WORKER_NAME", "JOB_ID", "TIME", "NONCE_2", "EQUIHASH_SOLUTION"]}\n // NONCE_1 is first part of the block header nonce (in hex). @@ -1762,7 +1781,7 @@ void SendKeepAlivePackets() } -/** Configure the Hush stratum server */ +/** Configure the DragonX stratum server */ bool InitStratumServer() { LOCK(cs_stratum); diff --git a/src/txdb.cpp b/src/txdb.cpp index d0541e08a..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__); @@ -698,7 +699,7 @@ 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; 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/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 147d9c4a1..5a08f8cc7 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -20,6 +20,12 @@ CAmount fConsolidationTxFee = DEFAULT_CONSOLIDATION_FEE; bool fConsolidationMapUsed = false; +// 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(); AsyncRPCOperation_saplingconsolidation::AsyncRPCOperation_saplingconsolidation(int targetHeight) : targetHeight_(targetHeight) {} @@ -238,10 +244,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)) { diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 94973aa1b..9c8fafa95 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -215,9 +215,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); @@ -354,8 +355,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_) { @@ -506,7 +507,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; } @@ -668,7 +670,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(); @@ -698,7 +701,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/crypter.cpp b/src/wallet/crypter.cpp index fe2e9ee55..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 @@ -306,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) diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 427f9e5df..d79a1b34b 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -169,7 +169,8 @@ private: 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; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index dac53a7f6..6c507780b 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" @@ -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" @@ -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 } } @@ -659,7 +659,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 +680,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 +736,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())); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 638b7d86b..33bed940b 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; @@ -571,7 +571,7 @@ UniValue sendtoaddress(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!"); } // Amount @@ -705,7 +705,8 @@ UniValue kvupdate(const UniValue& params, bool fHelp, const CPubKey& mypk) } } } - 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())); @@ -895,7 +896,7 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp, const CPubKey& // Bitcoin address 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!"); } CScript scriptPubKey = GetScriptForDestination(dest); if (!IsMine(*pwalletMain, scriptPubKey)) { @@ -1452,7 +1453,7 @@ UniValue sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) CScript tmpspk; tmpspk << ParseHex(name_) << OP_CHECKSIG; if ( !ExtractDestination(tmpspk, dest, true) ) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Hush address or pubkey: ") + name_); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid DragonX address or pubkey: ") + name_); } CScript scriptPubKey = GetScriptForDestination(dest); @@ -2546,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) @@ -2854,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" @@ -2888,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()); @@ -3423,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" @@ -3460,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" @@ -3599,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" @@ -3642,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" @@ -4277,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) { @@ -4820,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; @@ -4856,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" @@ -4866,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" @@ -5143,7 +5144,8 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) 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); // Check that the from address is valid. @@ -5388,6 +5390,9 @@ 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); @@ -5457,9 +5462,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(); @@ -5594,7 +5598,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 @@ -5840,7 +5845,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; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1fc80f4fa..1c84243a8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -578,7 +578,7 @@ 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; // } @@ -669,7 +669,7 @@ void CWallet::RunSaplingSweep(int blockHeight) { } 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; //} @@ -2380,7 +2380,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 @@ -4580,7 +4580,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); //{ @@ -5727,7 +5727,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); } From 81f803948c3c1675ef5a5d7f2da0704126e3c884 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 15:55:00 -0500 Subject: [PATCH 41/68] =?UTF-8?q?hygiene:=20Phase=206=20(partial)=20?= =?UTF-8?q?=E2=80=94=20fix=207=20verified=20bugs=20+=20quick-wins=20+=20de?= =?UTF-8?q?dup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the Phase-6 structural scoping: land the verified behavioral bugs and the safe quick-wins/dedups now; the large refactors (monolith splits, ~180 header globals) and consensus-adjacent items stay deferred. Built clean; self-mined; verifychain=true. BUGS (verified by reading the code): - net.cpp CNode::Ban: a braceless `if (subNet.Match(...))` left `pnode->fDisconnect = true;` OUTSIDE the guard, so banning any one subnet marked EVERY connected peer for disconnect (dropped the whole peer set). Wrapped the two statements in braces. (LIVE, high severity.) - rpcdump.cpp importwallet: the `!fGood -> throw "Error adding some keys"` check was trapped inside the `if (fRescan)` block, so importwallet with rescan=false silently reported success when key import failed. Hoisted the check before the rescan branch and cleaned the garbled braces/indentation. - wallet.cpp CommitTransaction: ignored AddToWallet()'s return, so a failed disk-persist of a just-signed spend was swallowed while the tx broadcast. Now logs a hard error on failure. - hush_nSPV_fullnode.h: the UTXOS branch declared `uint8_t filter` while the twin TXIDS branch uses `uint32_t filter`; dragon_rwnum switches on sizeof(filter), so the utxos path parsed only 1 of 4 wire filter bytes. Widened to uint32_t. - asyncrpcoperation_sweep.cpp: LogPrintf("%s ... %s", one-arg) read a missing vararg; added the __func__ argument. - rpcdump.cpp importprivkey: inner `auto secret_key` shadowed the outer uint8_t and changed the type into DecodeCustomSecret; dropped the shadow. - rpcdump.cpp getrescaninfo: char[8] + sprintf("%.4f") overflows when the ratio >= 10.0 (transient reorg); widened to char[16] + snprintf. QUICK WINS: removed the duplicate DRAGON_MAXSCRIPTSIZE #define; pinned the dead HUSH3-branch NOTARISATION_SCAN_LIMIT_BLOCKS to 1440; fixed init typos (fRequestShutdown, RPC warmup). DEDUP: extracted the 19-line try/catch error-mapping block — copy-pasted identically into all six async operations — into AsyncRPCOperation::set_error_from_current_exception(), so the mapping is edited in one place. Behavior-identical (verified all six blocks were byte- identical first). Deferred (endorsed by the scoping, better as their own PRs): addrman Select_ dedup, the pow.cpp powLimit helper (consensus file), the miner CreateNewBlock lock-asymmetry, the wallet monolith splits, and the ~180-global / consensus- retarget / Komodo-heritage work. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/asyncrpcoperation.cpp | 26 ++++++++++++ src/asyncrpcoperation.h | 5 +++ src/crosschain.cpp | 4 +- src/hush_defs.h | 2 +- src/hush_nSPV_fullnode.h | 2 +- src/init.cpp | 4 +- src/net.cpp | 3 +- .../asyncrpcoperation_autoshieldcoinbase.cpp | 17 +------- .../asyncrpcoperation_mergetoaddress.cpp | 17 +------- ...asyncrpcoperation_saplingconsolidation.cpp | 17 +------- src/wallet/asyncrpcoperation_sendmany.cpp | 17 +------- .../asyncrpcoperation_shieldcoinbase.cpp | 17 +------- src/wallet/asyncrpcoperation_sweep.cpp | 19 +-------- src/wallet/rpcdump.cpp | 40 ++++++++++--------- src/wallet/wallet.cpp | 3 +- 15 files changed, 70 insertions(+), 123 deletions(-) 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/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/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_fullnode.h b/src/hush_nSPV_fullnode.h index 54756e259..9287c2103 100644 --- a/src/hush_nSPV_fullnode.h +++ b/src/hush_nSPV_fullnode.h @@ -660,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; uint8_t isCC = 0; memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write coinaddr[request[1]] = 0; if ( request[1] == len-3 ) diff --git a/src/init.cpp b/src/init.cpp index e726e6623..d0c947bef 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -153,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; } @@ -2816,7 +2816,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/net.cpp b/src/net.cpp index 7fc4e7669..73e147c1a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -691,9 +691,10 @@ 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))) + 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; + } } } diff --git a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp index 94db62e97..4300dc176 100644 --- a/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_autoshieldcoinbase.cpp @@ -63,23 +63,8 @@ void AsyncRPCOperation_autoshieldcoinbase::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(); 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 5a08f8cc7..6096d3d19 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -52,23 +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_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(); diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 9c8fafa95..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(); diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp index e179f4476..16e175340 100644 --- a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp @@ -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 5c94bf4e5..75a5b20c2 100644 --- a/src/wallet/asyncrpcoperation_sweep.cpp +++ b/src/wallet/asyncrpcoperation_sweep.cpp @@ -45,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(); @@ -111,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; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6c507780b..069404ca7 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -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)); @@ -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); @@ -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; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1c84243a8..e0cd0d926 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4703,7 +4703,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; From c92e69a13fc1597b5d5261fa8c6343e2152d1b28 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 16:05:10 -0500 Subject: [PATCH 42/68] miner: fix cs_main/mempool.cs lock leak on the isStake error paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CreateNewBlock takes cs_main and mempool.cs unconditionally via ENTER_CRITICAL_SECTION, and releases them on every return path. Two of those LEAVE pairs — the notary-pay failure and the TestBlockValidity failure — were wrapped in `if (!isStake) { LEAVE; LEAVE; }` but still `return(0)` afterwards, so when isStake is true the function returned with both locks still held: a lock leak that deadlocks the next cs_main acquirer. The success and timelock return paths already release unconditionally, so the guard was simply wrong. Make both LEAVE pairs unconditional to match the ENTER. Behavior-identical on DragonX (a RandomX chain where staking/LWMAPOS is off, so isStake is always false and the LEAVE already ran), and correct for both isStake values. Verified: a node self-mined thousands of blocks across two mining threads (each block ENTER/LEAVEs the locks in CreateNewBlock) with height rising continuously and verifychain=true — a leak would have deadlocked immediately. The fuller RAII conversion (replace the manual ENTER/LEAVE with a scoped LOCK2 for exception safety) remains a worthwhile follow-up, but is a larger change to cs_main handling best done under its own review. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/miner.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/miner.cpp b/src/miner.cpp index 1a2afd327..b22cc406a 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -677,11 +677,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 if ( totalsats == 0 ) { LogPrintf("Could not create notary payment, trying again.\n"); - if ( !isStake ) - { - LEAVE_CRITICAL_SECTION(cs_main); - LEAVE_CRITICAL_SECTION(mempool.cs); - } + // 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); } } else LogPrintf("vout 2 of notarization is not OP_RETURN scriptlen.%i\n", scriptlen); @@ -726,11 +726,11 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32 CValidationState state; if ( !TestBlockValidity(state, *pblock, pindexPrev, false, false)) // invokes CC checks { - if ( !isStake ) - { - LEAVE_CRITICAL_SECTION(cs_main); - LEAVE_CRITICAL_SECTION(mempool.cs); - } + // 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); From 4a2e531cb61b3e0b30bfc71e731b0981026d4459 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 16:21:49 -0500 Subject: [PATCH 43/68] wallet: extract SelectAnyZaddrSource from z_sendmany MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First slice of the z_sendmany monolith split (Phase-6 refactor). The `fromaddress == "z"` case ("spend from any zaddr") was ~76 lines inline: it gathers the wallet's Sapling notes, sums balances per zaddr, and picks a random zaddr whose confirmed balance covers total outputs + fee. Lift it verbatim into a helper `SelectAnyZaddrSource(outputs, params)` that returns the chosen zaddr (or throws the same JSONRPCError), and call it from z_sendmany. Behavior-preserving by construction: the block was moved unchanged, the only edit being `fromaddress = vPotentialAddresses[...]` -> `return ...` with the call site doing `fromaddress = SelectAnyZaddrSource(outputs, params)`, so fromaddress receives the identical value (or the identical throw propagates). The helper takes only outputs/params + globals, which the clean compile proves (a reference to any z_sendmany local would not link). Built clean; verifychain=true. Note: the runtime happy path could not be exercised on an isolated node — z_sendmany's private-chain "still syncing" linkability guard fires before the fromaddress resolution when there are no peers — but a verbatim extract-method does not require it. z_sendmany is now ~76 lines shorter; further extraction (recipient parsing, fee/tx assembly) remains for follow-up slices. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet/rpcwallet.cpp | 158 ++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 75 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 33bed940b..90f0d45a6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5107,6 +5107,88 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO #define CTXIN_SPEND_DUST_SIZE 148 #define CTXOUT_REGULAR_SIZE 34 +// 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) +{ + // 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) { + 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."); +} + UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) @@ -5164,81 +5246,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // "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) { - LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials); - fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ]; - } 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."); - } + fromaddress = SelectAnyZaddrSource(outputs, params); } else { CTxDestination taddr = DecodeDestination(fromaddress); fromTaddr = IsValidDestination(taddr); From e55c67b0eb9c3029d82606561540801050b43086 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 16:28:46 -0500 Subject: [PATCH 44/68] wallet: extract ParseSendManyRecipients from z_sendmany MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second slice of the z_sendmany split. The ~60-line loop that parses and validates the "outputs" array — rejecting unknown keys, bad addresses, memos on taddrs, oversize memos and negative amounts, and sorting recipients into taddr/zaddr lists while accumulating nTotalOut — is lifted verbatim into ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut). Verbatim extract-method (the loop text was moved unchanged, not retyped); the helper reads only outputs + branchId and writes the three by-reference outputs the loop already produced, which the clean compile proves self-contained (a reference to any other z_sendmany local would fail to link). Built clean; verifychain=true. Together with the SelectAnyZaddrSource slice, z_sendmany is now ~135 lines shorter, with source-address resolution and recipient parsing separated out. Remaining: note selection, Sietch padding, and tx assembly (later slices, best validated against a synced node since z_sendmany's linkability guard blocks the send path on a peerless node). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet/rpcwallet.cpp | 133 +++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 90f0d45a6..0ea4fa1df 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5189,6 +5189,77 @@ static std::string SelectAnyZaddrSource(const UniValue& outputs, const UniValue& 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"); + + // sanity check, report error if unknown key-value pairs + for (const string& name_ : o.getKeys()) { + std::string s = name_; + if (s != "address" && s != "amount" && s!="memo") { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); + } + } + + string address = find_value(o, "address").get_str(); + bool isZaddr = false; + CTxDestination taddr = DecodeDestination(address); + if (!IsValidDestination(taddr)) { + auto res = DecodePaymentAddress(address); + if (IsValidPaymentAddress(res, branchId)) { + isZaddr = true; + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); + } + }// else if ( ASSETCHAINS_PRIVATE != 0 && hush_isnotaryvout((char *)address.c_str()) == 0 ) { + // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr"); + //} + + UniValue memoValue = find_value(o, "memo"); + string memo; + if (!memoValue.isNull()) { + memo = memoValue.get_str(); + if (!isZaddr) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); + } else if(memo.substr(0,5) == "utf8:") { + // Support a prefix "utf8:" which allows giving utf8 text instead of hex + auto str = memo.substr(5); + if (utf8::is_valid(str)) { + memo = HexStr(str); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo"); + } + } else if (!IsHex(memo)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix."); + } + if (memo.length() > HUSH_MEMO_SIZE*2) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE )); + } + } + + UniValue av = find_value(o, "amount"); + CAmount nAmount = AmountFromValue( av ); + if (nAmount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); + + if (isZaddr) { + zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); + } else { + taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); + } + + nTotalOut += nAmount; + } +} + UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) @@ -5296,67 +5367,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); } - for (const UniValue& o : outputs.getValues()) { - if (!o.isObject()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); - - // sanity check, report error if unknown key-value pairs - for (const string& name_ : o.getKeys()) { - std::string s = name_; - if (s != "address" && s != "amount" && s!="memo") { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); - } - } - - string address = find_value(o, "address").get_str(); - bool isZaddr = false; - CTxDestination taddr = DecodeDestination(address); - if (!IsValidDestination(taddr)) { - auto res = DecodePaymentAddress(address); - if (IsValidPaymentAddress(res, branchId)) { - isZaddr = true; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); - } - }// else if ( ASSETCHAINS_PRIVATE != 0 && hush_isnotaryvout((char *)address.c_str()) == 0 ) { - // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr"); - //} - - UniValue memoValue = find_value(o, "memo"); - string memo; - if (!memoValue.isNull()) { - memo = memoValue.get_str(); - if (!isZaddr) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); - } else if(memo.substr(0,5) == "utf8:") { - // Support a prefix "utf8:" which allows giving utf8 text instead of hex - auto str = memo.substr(5); - if (utf8::is_valid(str)) { - memo = HexStr(str); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo"); - } - } else if (!IsHex(memo)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix."); - } - if (memo.length() > HUSH_MEMO_SIZE*2) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE )); - } - } - - UniValue av = find_value(o, "amount"); - CAmount nAmount = AmountFromValue( av ); - if (nAmount < 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); - - if (isZaddr) { - zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); - } else { - taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); - } - - nTotalOut += nAmount; - } + ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut); std::vector saplingEntries; // find all unspent and unlocked notes in this zaddr From 004808403fce49169d0c7cf017e3f223ef714eea Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 16:51:16 -0500 Subject: [PATCH 45/68] cleanup: dedup addrman Select_ table walk and pow-limit-by-algo selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two behavior-preserving refactors flagged by the Phase-6 hygiene scoping: * addrman: CAddrMan::Select_ contained two ~40-line copies of the same bucket-table random walk, differing only in the table (vvTried/vvNew), its bucket count, and a log label. Extract the shared loop into SelectFromTable_(vvTable, nBucketCount, tableName); Select_ now just dispatches to it. Verbatim move — clean compile proves self-containment. * pow: the "Equihash uses powLimit, everything else uses powAlternate" selection was copy-pasted as an if/else into GetNextWorkRequired, CalculateNextWorkRequired, and lwmaCalculateNextWorkRequired. Extract into PowLimitForAlgo(params). The CheckProofOfWork site (line ~892) is left as-is: it has an extra `height <= 1` genesis special-case and is NOT the same selection. On DragonX (RandomX) this always returns powAlternate, exactly as before. Validated: full build of dragonxd/cli/tx, isolated self-mine to height 336, verifychain 4 0 -> true (exercises PowLimitForAlgo on every block). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/addrman.cpp | 154 +++++++++++++++++++----------------------------- src/addrman.h | 4 ++ src/pow.cpp | 23 ++++---- 3 files changed, 74 insertions(+), 107 deletions(-) diff --git a/src/addrman.cpp b/src/addrman.cpp index a16f0665f..3a8fc0777 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -473,25 +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 - - // Peer-selection tuning factors (networking heuristics, not consensus). - // On each rejected candidate the running chance factor is scaled up by this - // amount so the loop is guaranteed to eventually accept a peer. - const double kChanceFactorGrowth = 1.2; - // Candidates on unreachable networks are deprioritized to this fraction of - // their base chance. - const double kUnreachableDeprioritize = 0.25; - // Candidates that were just tried are deprioritized to this fraction of - // their base chance. - const double kJustTriedDeprioritize = 0.10; - // Fixed-point scale for the acceptance probability test: draw a random int in - // [0, kChanceScale) and accept if it falls below (factors * chance) * kChanceScale. - const int kChanceScale = 1 << 30; - if (newOnly && nNew == 0) return CAddrInfo(); @@ -499,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 = kUnreachableDeprioritize; - } - if (info.IsJustTried()) { - //deprioritize entries just tried - fJustTried = kJustTriedDeprioritize; - } - if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale) - return info; - fChanceFactor *= kChanceFactorGrowth; - } + 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 = kUnreachableDeprioritize; - } - if (info.IsJustTried()) { - //deprioritize entries just tried - fJustTried = kJustTriedDeprioritize; - } - if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale) - return info; - fChanceFactor *= kChanceFactorGrowth; - } + 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/pow.cpp b/src/pow.cpp index d5740a9b4..a7802b0f3 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -97,6 +97,14 @@ bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past); #define T ASSETCHAINS_BLOCKTIME #define K ((int64_t)1000000) +// 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) +{ + return UintToArith256(ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ? params.powLimit : params.powAlternate); +} + arith_uint256 RT_CST_RST_outer(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) { int64_t outerK; int32_t cmpval; arith_uint256 mintarget = bnTarget / arith_uint256(2); @@ -211,10 +219,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead } 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 ) @@ -461,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}; @@ -498,10 +500,7 @@ 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(); From 11766ec61bf03c584b84ecdf444b90454485c29c Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 17:04:36 -0500 Subject: [PATCH 46/68] =?UTF-8?q?init:=20fix=20Windows=20build=20regressio?= =?UTF-8?q?n=20=E2=80=94=20fs::path::c=5Fstr()=20into=20LogPrint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 (cf15b0a39) converted the asmap-file-location debug output from fprintf(stderr,...) to LogPrint. On Windows, boost::filesystem::path::c_str() returns const wchar_t*, which tinyformat rejects at compile time (is_wchar has no tinyformat_wchar_is_not_supported member) — breaking the mingw cross-build at init.o. The old fprintf accepted wchar_t* silently (and printed garbage on Windows), so the latent bug only surfaced once the call became type-checked. Route all 11 asmap_path.c_str() calls through .string().c_str() so the argument is a narrow std::string on every platform, matching the existing correct idiom at pathLockFile.string().c_str() (line ~1662). No behavior change on POSIX (path::c_str() is already char* there). Caught by the cross-platform build phase of the 3-box test bring-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/init.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index d0c947bef..242309d99 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1280,33 +1280,33 @@ 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; - LogPrint("net", "%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)) { - LogPrint("net", "%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; - LogPrint("net", "%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)) { - LogPrint("net", "%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; - LogPrint("net", "%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)) { - LogPrint("net", "%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; - LogPrint("net", "%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)) { - LogPrint("net", "%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; - LogPrint("net", "%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)) { - LogPrint("net", "%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 { // 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"))); @@ -1320,7 +1320,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!asmap_path.is_absolute()) { asmap_path = GetDataDir() / asmap_path; } - LogPrint("net", "%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 From 1745ee4e63401c2a8362407bc571a10474087879 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 19:08:36 -0500 Subject: [PATCH 47/68] net: fix -connect never dialing its targets (empty-addr IsValid gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ConnectNode() rejected the connection on !addrConnect.IsValid() (and !IsReachable(addrConnect)) BEFORE the pszDest branch. But -connect (and -addnode host:port / "addnode onetry") reaches ConnectNode with an empty placeholder addrConnect and the real target in pszDest, resolved later by ConnectSocketByName(). An empty CAddress is invalid, so every -connect attempt returned NULL before a socket was ever opened — the peer logs "trying connection " then "ConnectNode FAILED" and never dials. (Introduced upstream with BIP155/addrv2; -connect went unused because normal operation connects via addrman with real, valid addresses.) Guard both early-return checks with `if (!pszDest)` so they apply only when dialing addrConnect directly. Connect-by-name now falls through to ConnectSocketByName() as intended. Direct-address connections (pszDest==NULL, the addrman path) are unchanged. Validated: two nodes on one host, B started with `-connect=` exclusively (no -addnode) now connects to A over TLS, syncs A's chain, and stays isolated (0 other peers) — previously B connected to nothing. Clean build, verifychain 4 0 = true. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/net.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 73e147c1a..2aab1dfcd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -470,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) { From 2232868d9ff75ba7753f75a7913802226602fdf0 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 20:24:13 -0500 Subject: [PATCH 48/68] stratum: RandomX pool mining support + reference miner (stratummine) The stratum server was hardcoded for legacy Equihash (1347-byte solution, sol.begin()+3 offset, CheckEquihashSolution) and was off-by-default with a "do not use on RandomX" warning. DragonX is RandomX, so external pool mining was impossible. This wires RandomX end-to-end. Server (stratum.cpp), branched on ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX: * GetWorkUnit sets StratumWork.nHeight and, for RandomX, sends the per-height RandomX key via a new mining.set_randomx_key message (a miner cannot derive it without the chain). The legacy mining.notify format is unchanged. * SubmitBlock/stratum_mining_submit accept a 32-byte solution (== the RandomX hash, used as nSolution verbatim) and validate it with CheckRandomXSolution instead of CheckEquihashSolution. Target check (GetHash() < target) and the nNonce = extranonce1||extranonce2 assembly are shared with the equihash path. * -stratumtarget= overrides the pool share target (default diff-1); lets a solo/low-difficulty test miner accept easy shares. * GetWorkUnit's IsInitialBlockDownload guard is bypassed under -testnode=1 so an isolated low-work test chain can serve work. Reference miner: `stratummine "host" port ("address" timeout)` RPC (rpc/mining.cpp, POSIX-only). A minimal stratum client that subscribes/authorizes, receives work + the RandomX key, varies nNonce, hashes with RandomX via GetRandomXInput (byte- identical to CheckRandomXSolution) and submits a 32-byte solution. Off-the-shelf Equihash/Monero miners can't speak DragonX's 256-bit-nNonce Zcash header, so this is the reference implementation. Added to the rpc client arg-conversion table. Validated: loopback (chain 0->4, accepted every time, verifychain=true) AND a real 2-box LAN run (Linux miner -> Mac stratum server, 3 blocks accepted, verifychain=true). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/init.cpp | 9 +- src/rpc/client.cpp | 2 + src/rpc/mining.cpp | 248 +++++++++++++++++++++++++++++++++++++++++++++ src/stratum.cpp | 104 +++++++++++++------ 4 files changed, 330 insertions(+), 33 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 242309d99..df83709af 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -621,6 +621,7 @@ 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())); @@ -991,10 +992,10 @@ bool AppInitServers(boost::thread_group& threadGroup) RPCServer::OnPreCommand(&OnRPCPreCommand); if (!InitHTTPServer()) return false; - // WARNING: the stratum server (stratum.cpp) is Equihash-era code: it assumes a 1347-byte - // Equihash solution and calls CheckEquihashSolution. It has NOT been updated for DragonX's - // 32-byte RandomX solution and must not be relied on for mining without a full revalidation. - // It stays off by default (DEFAULT_STRATUM_ENABLE=false); only -stratum turns it on. + // 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()) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 970cf704e..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 }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 92e7adc8a..5d6fb318e 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" @@ -1053,9 +1068,242 @@ 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. +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"); + + 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"; + const int64_t timeout = params.size() > 3 ? params[3].get_int64() : 120; + 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) { + 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) { + jobId = p[0].get_str(); + nVersion = bswap_32((uint32_t)strtoul(p[1].get_str().c_str(), NULL, 16)); + hashPrevBlock = uint256(ParseHex(p[2].get_str())); + hashMerkleRoot = uint256(ParseHex(p[3].get_str())); + hashReserved = uint256(ParseHex(p[4].get_str())); + 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; + } + }; + + 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, true }, +#endif { "mining", "getlocalsolps", &getlocalsolps, true }, { "mining", "getnetworksolps", &getnetworksolps, true }, { "mining", "getnetworkhashps", &getnetworkhashps, true }, diff --git a/src/stratum.cpp b/src/stratum.cpp index 9b14289bd..42e31a10d 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" @@ -663,6 +664,15 @@ void CustomizeWork(const StratumClient& client, const StratumWork& current_work, // 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); @@ -688,7 +698,7 @@ std::string GetWorkUnit(StratumClient& client) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "DragonX is not connected!"); } - if (IsInitialBlockDownload()) { + 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, "DragonX is downloading blocks..."); @@ -743,6 +753,9 @@ std::string GetWorkUnit(StratumClient& client) 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; @@ -916,7 +929,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"; } @@ -924,17 +955,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) { - // ============================ WARNING (Equihash-era code) ============================ - // This entire submit path is hardcoded for the legacy Equihash proof-of-work: - // it expects a 1347-byte Equihash solution and calls CheckEquihashSolution() below. - // DragonX uses RandomX PoW (32-byte solution), NOT Equihash. This stratum path has - // NOT been updated for RandomX and must not be relied on without full revalidation. - // The 1347-byte length checks, the "sol.begin()+3" solution offset, and the equihash - // target/difficulty math are all Equihash-era and are intentionally left unchanged. - // ==================================================================================== + // 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) { @@ -943,11 +972,9 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork throw JSONRPCError(RPC_INVALID_PARAMETER, msg); } - // WARNING (Equihash-era): 1347 is the Equihash-200,9 solution length. DragonX is RandomX - // (32-byte solution), so this length check does not match the live PoW. Left unchanged - // because this whole path is Equihash-era; do not repurpose without revalidating the miner protocol. - 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); } @@ -979,7 +1006,10 @@ 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; @@ -987,8 +1017,16 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork // 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); @@ -1068,7 +1106,8 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork // 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()); + 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); @@ -1261,15 +1300,11 @@ UniValue stratum_mining_configure(StratumClient& client, const UniValue& params) UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) { - // ============================ WARNING (Equihash-era code) ============================ - // This share-submission handler is hardcoded for legacy Equihash: it parses and requires - // a 1347-byte Equihash solution and hands it to SubmitBlock() (which calls - // CheckEquihashSolution). DragonX uses RandomX PoW (32-byte solution), NOT Equihash. - // This path has NOT been updated for RandomX and must not be relied on without full - // revalidation of the miner-facing stratum protocol. - // ==================================================================================== + // 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", "EQUIHASH_SOLUTION"]}\n + // {"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. @@ -1322,8 +1357,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); @@ -1790,6 +1826,16 @@ bool InitStratumServer() int defaultPort = GetArg("-stratumport", stratumPort); 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"); From d05302d4505bbd5bd17f2d6527c75028c15be581 Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 29 Aug 2026 01:39:24 +0200 Subject: [PATCH 49/68] build: stamp container builds with the real version instead of "-unk" .dockerignore excludes .git, so util/genbuild.sh finds no repository inside the container and emits "// No build information available", which clientversion.cpp renders as the "-unk" suffix. Every binary produced by ./build.sh --linux-compat therefore self-reports "v1.2.0-unk" and cannot be traced to a commit -- including release artifacts, since this build path is part of the v1.2.0 tag. Pre-generating src/obj/build.h does not survive (genbuild rewrites it when the content differs), and simply un-ignoring .git does not help a linked worktree, whose .git is a file pointing outside the build context. So build.sh computes the version on the host, mirroring genbuild.sh rule for rule -- the nearest tag only when HEAD is that tag and the tree is clean, otherwise v- with a -dirty suffix -- and passes it through a BUILD_DESC build-arg that Dockerfile.compat exports as DRAGONX_BUILD_DESC. genbuild.sh honours that variable when set and is otherwise untouched; with git metadata present it emits a byte-identical build.h. Every added git call is guarded with || true because build.sh runs under set -eu -o pipefail: a source tarball, a host without git, or a branch whose only reachable tags are lightweight (v1.0.1-v1.0.3 are lightweight; v1.1.0 is the first annotated one) would otherwise abort the build with no diagnostic. Those cases now degrade to the previous "-unk" behaviour with a warning. A direct "docker build -f Dockerfile.compat" passes no BUILD_DESC and still produces -unk; the Dockerfile now says so loudly rather than silently. --- Dockerfile.compat | 11 +++++++++++ build.sh | 29 ++++++++++++++++++++++++++++- util/genbuild.sh | 7 ++++++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Dockerfile.compat b/Dockerfile.compat index 873c3d394..fc35cc26e 100644 --- a/Dockerfile.compat +++ b/Dockerfile.compat @@ -27,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 d780a641b..e39c4a400 100755 --- a/build.sh +++ b/build.sh @@ -164,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/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 From b3e81f1eda68bcd79cac0ec6e11c6e00ceb0fc9a Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 28 Aug 2026 23:30:24 -0500 Subject: [PATCH 50/68] stratum: do not abort the daemon on a malformed 63-character job_id The "EWBF 31 bytes job_id fix" in stratum_mining_submit completes a 63-character job_id with each hex digit in turn and feeds the result straight to uint256(). ParseHex() stops at the first non-hex character and returns a shorter vector without signalling an error, and base_blob(const std::vector&) asserts vch.size() == 32. So a single mining.submit whose job_id is any 63-character string containing a non-hex byte -- 63 spaces will do -- aborts the node. asserts are live in release builds here: -DNDEBUG appears only in leveldb's own makefile, never in configure.ac, and the shipped binary still carries the assertion string. The dispatch loop catches UniValue and std::exception; abort() goes through both. Size-check each candidate before constructing, as ParseUInt256() a few hundred lines up already does for the ordinary path. If none of the 16 completions parse, ret stays null, misses work_templates, and the handler returns false exactly as it does for any unknown job. Not gated on IsHex(job_id_str): 63 is odd and IsHex() requires an even length, so that test would disable the EWBF path this code exists for. --- src/stratum.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/stratum.cpp b/src/stratum.cpp index 42e31a10d..16736e6e2 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -1334,7 +1334,16 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params) 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; } } From 5f40c8ede057adae3f98916677405b94227748d6 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 28 Aug 2026 23:31:47 -0500 Subject: [PATCH 51/68] stratum: stop paying every miner's blocks to whoever asked for work first CreateNewBlock() builds the stratum template with an OP_FALSE placeholder in the coinbase, and CustomizeWork() substituted the miner's own payout address only while that placeholder was still intact. GetWorkUnit() then wrote the customized coinbase straight back into the shared template: current_work.GetBlock().vtx[0] = cb; current_work.GetBlock().hashMerkleRoot = ...BuildMerkleTree(); which consumed the placeholder for everyone. The first client to request work after a tip change therefore captured the template. Every later client on that job got a mining.notify whose merkle root already committed to the first client's coinbase, CustomizeWork() was a no-op for them on submit, and SubmitBlock() read the shared root back -- so a block found by miner B was accepted paying miner A. It was silent: the daemon logged "GOT BLOCK!!! by " while the coinbase paid A. With untrusted miners that is a reward-theft primitive, and it is cheap: mining.authorize sets m_send_work, so re-sending it in a loop wins the race after every tip. Leave the template pristine and derive each client's header from a local copy, in GetWorkUnit for the notify and again in SubmitBlock from the coinbase CustomizeWork() just produced for that client. This is the refactor the TODO removed here was asking for. CustomizeWork() now stamps the payout script unconditionally, so a coinbase that somehow arrives already customized can never be inherited by another miner, and rejects an invalid address rather than silently building a coinbase that pays no one. Single-miner behaviour is unchanged, which is why this survived: it is only observable with two miners on one template. --- src/stratum.cpp | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/stratum.cpp b/src/stratum.cpp index 16736e6e2..7877dd171 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -656,9 +656,18 @@ 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; @@ -864,20 +873,24 @@ std::string GetWorkUnit(StratumClient& client) 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(); - } 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; @@ -1012,7 +1025,14 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork : 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; // block is constructed, now it's time to VerifyEH From fa16e740b602ee94de5a194e1be25583f340e968 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 28 Aug 2026 23:35:06 -0500 Subject: [PATCH 52/68] stratum: reject low-diff shares before spending a RandomX hash on them SubmitBlock validated in the wrong order. After two O(1) length checks it went straight to CheckRandomXSolution() -- a full ~65ms randomx_calculate_hash -- and only afterwards tested the share target. So 32 arbitrary bytes from any peer bought a RandomX hash, and the cost was paid in three bad places at once: on the shared HTTP/RPC libevent thread (stratum uses EventBase(), the same base ThreadHTTP dispatches, and RPC replies are posted back onto it), inside the read loop that holds cs_stratum so BlockWatcher cannot push new work to real miners, and holding the global cs_randomx_validator that block validation also takes. One connection writing submit lines pins that thread indefinitely. Move the share-target test above the RandomX verify. CBlockHeader:: GetHash() is SerializeHash over the header including nSolution, so meeting the target still requires real SHA256d grinding -- an attacker now pays for the hash instead of the node. Semantics are unchanged, including that an empty local_diff parses to zero and still rejects; only the position moved, and the diagnostics are recomputed locally since the old message used variables declared further down. This does not make the submit path cheap, only bounded: at the default share target the grind is small, so a submit rate limit and moving SubmitBlock off the event loop are both still wanted. Also add the authorization check every sibling handler has and mining.submit lacked. Being explicit about what that is worth: mining.authorize validates no credential, so the gate is parity and handshake-ordering, not authentication. The reorder above is the part that actually bounds an unknown peer. --- src/stratum.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/stratum.cpp b/src/stratum.cpp index 7877dd171..d9cfffb44 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -1035,6 +1035,22 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork } blkhdr.nNonce = (uint256) nonce; + // 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 (StratumIsRandomX()) { @@ -1092,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) { @@ -1343,6 +1357,18 @@ 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 */ From 2d6359ea74a118b94017269ace1fdea300949552 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 16:24:55 -0500 Subject: [PATCH 53/68] gtest: add job_id parser regression test for the 63-char abort fix Covers b3e81f1ed: a 63-character mining.submit job_id containing any non-hex byte must never complete to a 32-byte vector, so the EWBF completion loop skips it instead of constructing uint256() (which asserts vch.size()==32 and would abort the daemon from an unauthenticated client). Five cases pin the invariant using the real ParseHex/uint256 primitives: all-spaces, a non-hex byte mid-string, and a non-hex byte at the end never yield 32 bytes; a genuine 63-hex job_id completes to exactly 32 bytes for all 16 digits; plus ParseHex odd/even-length anchors. Wired gtest/test_stratum_jobid.cpp into Makefile.gtest.include (5 tests, all pass; full hush-gtest suite now 18/18). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PGhWvdBSgt6UxxHANr7gfN --- src/Makefile.gtest.include | 3 +- src/gtest/test_stratum_jobid.cpp | 98 ++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/gtest/test_stratum_jobid.cpp 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/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); +} From 3b2aa866aab113c1f25612d86b5b35cddce07708 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 16:32:20 -0500 Subject: [PATCH 54/68] rpc: honor a command-line rpcpassword across restarts; fix dead -rpcusername key hush_configfile(), on any restart where the auto-generated DRAGONX.conf already exists, hard-assigned mapArgs["-rpcpassword"] from the conf and wrote the username to mapArgs["-rpcusername"] -- a key nothing reads (InitRPCAuthentication in httprpc.cpp and bitcoin-cli.cpp both read "-rpcuser"). The hard assignment silently overwrote a -rpcpassword passed on the command line, so after the first run the effective RPC credentials became {cmdline-user}:{conf-password}, matching neither the command-line pair the operator passed nor the full conf pair. Automation or external clients that connect with the known command-line password broke on every restart. Use SoftSetArg for both, so a command-line (or explicitly configured) value wins and the conf-derived credential is only a fallback. Verified on regtest: after a restart an external client with the command-line password gets HTTP 200 and the conf's random password gets 401; the conf-only operator path (no command-line creds) still authenticates. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PGhWvdBSgt6UxxHANr7gfN --- src/hush_utils.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hush_utils.h b/src/hush_utils.h index 1b1cb2eeb..6241b93ba 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1383,8 +1383,14 @@ void hush_configfile(char *symbol,uint16_t rpcport) #endif } else { _hush_userpass(myusername,mypassword,fp); - mapArgs["-rpcpassword"] = mypassword; - mapArgs["-rpcusername"] = 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); } } From 60d66022f6dd0c285bcb1ef88b373a44643fbefb Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 17:26:21 -0500 Subject: [PATCH 55/68] regtest: default -checkpoints off so an isolated node leaves IBD chainparams_commandline() applies the DRAGONX mainnet checkpoint set to every SMART_CHAIN_SYMBOL=="DRAGONX" network, -regtest included, so an isolated regtest node (height ~hundreds) sits far below the top checkpoint (~3.2M). IsInitialBlockDownload() latches true whenever fCheckpointsEnabled && height < GetTotalBlocksEstimate(), so regtest was permanently in IBD -- disabling every ChainTip auto-op that gates on !IBD (autoshield, z_sweep, consolidation) and the below-checkpoint script-check skip, and forcing every regtest test to pass -checkpoints=0 by hand. Default -checkpoints to false on regtest (still overridable with -checkpoints=1). Verified: a fresh regtest node logs "Leaving InitialBlockDownload" after a few blocks with no flag, and -checkpoints=1 keeps it in IBD as before. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PGhWvdBSgt6UxxHANr7gfN --- src/init.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index df83709af..f1bb05347 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1432,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); From 5e0a70683967d2b8c164f2f775805716819e87d9 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 19:44:27 -0500 Subject: [PATCH 56/68] qa: repair the rpc-test harness so it can start a DragonX node at all The integration suite has never run against DragonX. Six independent defects stacked up, each only visible once the previous was fixed: 1. test_framework used python2 implicit relative imports ("from authproxy import ..."), removed in python3, so every test died at import. Made explicit relative imports. 2. It wrote ZZZ.conf -- a Komodo assetchain convention -- while DragonX reads DRAGONX.conf. The daemon therefore never saw the generated config, fell back to mainnet defaults and tried to bind RPC 21769, which on a seed node is already held by the real node. 3. start_node() was hard-wired for the -ac_name=ZZZ assetchain tests: it took the RPC port from extra_args[3], passed extra_args[0] as argv[0] of the CLI, and only wrote a config when extra_args[0] matched. Any test that passes no extra args crashed on len(None). The generic path now takes the port from rpc_port(i) -- the same helper initialize_datadir() already used -- and drives the CLI with -datadir. 4. dragonxd refuses to start without an asmap file, which no test datadir had. initialize_datadir() now provisions one. 5. -asmap relative paths resolve against the NET-SPECIFIC datadir, so a copy in is never found. Pass an absolute path. 6. Worst: -regtest was only ever set as "regtest=1" in the conf file, which DragonX ignores. Every "regtest" node therefore ran on MAINNET: real genesis, real seeds, real peers. An observed run synced 196,180 live blocks and 679MB into /tmp before the test timed out. -regtest is now passed as a command-line flag, with -connect=0 so an isolated regtest node stays off the public network. With these, nodes start, RPC answers, and tests run to a real result. They do not all pass yet -- getblocktemplate.py reaches an assertion -- but that is now a test outcome rather than a harness failure. --- qa/rpc-tests/test_framework/blockstore.py | 2 +- qa/rpc-tests/test_framework/blocktools.py | 4 +- qa/rpc-tests/test_framework/comptool.py | 6 +- qa/rpc-tests/test_framework/test_framework.py | 6 +- qa/rpc-tests/test_framework/util.py | 61 ++++++++++++++----- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/qa/rpc-tests/test_framework/blockstore.py b/qa/rpc-tests/test_framework/blockstore.py index 5a3e911e2..f2fcf20c4 100644 --- a/qa/rpc-tests/test_framework/blockstore.py +++ b/qa/rpc-tests/test_framework/blockstore.py @@ -7,7 +7,7 @@ # and for constructing a getheaders message # -from mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx +from .mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx import sys import cStringIO diff --git a/qa/rpc-tests/test_framework/blocktools.py b/qa/rpc-tests/test_framework/blocktools.py index 6f111f3bf..c294a8b59 100644 --- a/qa/rpc-tests/test_framework/blocktools.py +++ b/qa/rpc-tests/test_framework/blocktools.py @@ -3,8 +3,8 @@ # Distributed under the GPLv3 software license, see the accompanying # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html # blocktools.py - utilities for manipulating blocks and transactions -from mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint -from script import CScript, OP_0, OP_EQUAL, OP_HASH160 +from .mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint +from .script import CScript, OP_0, OP_EQUAL, OP_HASH160 # Create a block (with regtest difficulty) def create_block(hashprev, coinbase, nTime=None, nBits=None): diff --git a/qa/rpc-tests/test_framework/comptool.py b/qa/rpc-tests/test_framework/comptool.py index fb6084284..bc679e4d7 100755 --- a/qa/rpc-tests/test_framework/comptool.py +++ b/qa/rpc-tests/test_framework/comptool.py @@ -3,10 +3,10 @@ # Distributed under the GPLv3 software license, see the accompanying # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -from mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \ +from .mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \ msg_inv, msg_getheaders, msg_ping, msg_mempool, mininode_lock, MAX_INV_SZ -from blockstore import BlockStore, TxStore -from util import p2p_port +from .blockstore import BlockStore, TxStore +from .util import p2p_port import time diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 1728c8dba..619a9ccf8 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -11,8 +11,8 @@ import shutil import tempfile import traceback -from authproxy import JSONRPCException -from util import assert_equal, check_json_precision, \ +from .authproxy import JSONRPCException +from .util import assert_equal, check_json_precision, \ initialize_chain, initialize_chain_clean, \ start_nodes, connect_nodes_bi, stop_nodes, \ sync_blocks, sync_mempools, wait_bitcoinds @@ -91,7 +91,7 @@ class BitcoinTestFramework(object): parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", help="Don't stop nodes after the test execution") parser.add_option("--srcdir", dest="srcdir", default="../../src", - help="Source directory containing hushd/hush-cli (default: %default)") + help="Source directory containing dragonxd/dragonx-cli (default: %default)") parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"), help="Root directory for datadirs") parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true", diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index e8a16251d..e44f3ea38 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -18,7 +18,7 @@ import subprocess import time import re -from authproxy import AuthServiceProxy +from .authproxy import AuthServiceProxy def p2p_port(n): return 11000 + n + os.getpid()%999 @@ -97,8 +97,8 @@ def initialize_datadir(dirname, n): print("Creating dirs %s" % datadir) os.makedirs(datadir) - print("Writing to " + os.path.join(datadir,"ZZZ.conf")) - with open(os.path.join(datadir, "ZZZ.conf"), 'w') as f: + print("Writing to " + os.path.join(datadir,"DRAGONX.conf")) + with open(os.path.join(datadir, "DRAGONX.conf"), 'w') as f: f.write("regtest=1\n"); f.write("txindex=1\n"); #f.write("testnode=1\n"); @@ -116,7 +116,19 @@ def initialize_datadir(dirname, n): f.write("spentindex=1\n"); f.write("timestampindex=1\n"); #f.write("zindex=1\n"); - print("Done writing to %s" % os.path.join(datadir,"ZZZ.conf") ) + print("Done writing to %s" % os.path.join(datadir,"DRAGONX.conf") ) + + # dragonxd refuses to start without an asmap file ("Could not find any asmap file!"), + # so every regtest datadir needs one. Link the tree's copy rather than duplicating it. + for src in ("../../../asmap.dat", "../../../src/asmap.dat", + os.path.expanduser("~/.hush/DRAGONX/asmap.dat")): + cand = src if os.path.isabs(src) else os.path.join(os.path.dirname(os.path.abspath(__file__)), src) + if os.path.exists(cand): + dst = os.path.join(datadir, "asmap.dat") + if not os.path.exists(dst): + try: os.symlink(os.path.realpath(cand), dst) + except OSError: shutil.copyfile(cand, dst) + break return datadir @@ -133,11 +145,11 @@ def initialize_chain(test_dir): # Create cache directories, run hushds: for i in range(4): datadir=initialize_datadir("cache", i) - args = [ os.getenv("BITCOIND", "hushd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ] + args = [ os.getenv("BITCOIND", "dragonxd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ] 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", "dragonx-cli") cmd_args = cmd + " -datadir="+datadir + " -rpcwait getblockcount" if os.getenv("PYTHON_DEBUG", ""): print("initialize_chain: hushd started, calling: " + cmd_args) @@ -227,9 +239,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 +260,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 +293,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) From 4cc7e0491a916acd9656b13619bc53d65a80da89 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 21:29:08 -0500 Subject: [PATCH 57/68] qa: finish the python3 port far enough to build the shared test chain Follow-up to 5e0a70683, which got start_node() working. Three more defects sat behind it: - initialize_chain() builds the 4-node cache with its own daemon invocation, which 5e0a70683 did not touch. It therefore still omitted -regtest (so those cache nodes ran on MAINNET) and -asmap (so they refused to start at all). Every test that uses the cache -- which is most of the wallet suite -- died there. - reindex.py and getblocktemplate_longpoll.py each carried a single python2 print statement, which is the whole reason they would not even parse under python3. Shebangs updated to match. The suite still does not pass: initialize_chain hits a remaining py2 str+int concatenation, and getblocktemplate.py reaches a real test assertion. Both are beyond this commit, but the harness now gets far enough to start nodes, answer RPC and begin building the shared chain, which it could not do before. --- qa/rpc-tests/getblocktemplate_longpoll.py | 4 ++-- qa/rpc-tests/reindex.py | 4 ++-- qa/rpc-tests/test_framework/util.py | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) 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/reindex.py b/qa/rpc-tests/reindex.py index fa1454bae..a79207b50 100755 --- a/qa/rpc-tests/reindex.py +++ b/qa/rpc-tests/reindex.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # Copyright (c) 2014 The Bitcoin Core developers # Copyright (c) 2016-2024 The Hush developers # Released under the GPLv3 @@ -28,7 +28,7 @@ class ReindexTest(BitcoinTestFramework): wait_bitcoinds() self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug", "-reindex", "-checkblockindex=1"]) assert_equal(self.nodes[0].getblockcount(), 3) - print "Success" + print("Success") if __name__ == '__main__': ReindexTest().main() diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index e44f3ea38..05205c8ce 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -145,7 +145,13 @@ def initialize_chain(test_dir): # Create cache directories, run hushds: for i in range(4): datadir=initialize_datadir("cache", i) - args = [ os.getenv("BITCOIND", "dragonxd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ] + # Same two requirements as start_node(): -regtest must be a command-line flag (the + # conf key is ignored, and without it this cache node runs on MAINNET), and -asmap + # must be absolute or dragonxd refuses to start. + args = [ os.getenv("BITCOIND", "dragonxd"), "-regtest", "-connect=0", "-keypool=1", "-datadir="+datadir, "-discover=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) From af7d9e23003da02739fc80360f30ead49a888338 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 30 Aug 2026 22:48:52 -0500 Subject: [PATCH 58/68] release: bump to v1.3.0 and document the signing step that was never performed The tree stamped 1.2.0 in both configure.ac and src/clientversion.h, but v1.2.0 is an annotated tag already pushed at fad05d3ab and dev is 27 commits past it. Both trees therefore produced CLIENT_VERSION 1020050 and announced an identical /DragonX:1.2.0/ subversion, so a released binary would have been indistinguishable from the tag on the wire, in getnetworkinfo, and to the wallet's in-app updater -- destroying the only provenance check users have: build the tag, compare the binary. Bump MINOR rather than REVISION: the delta since v1.2.0 adds a subsystem (RandomX stratum) and a new RPC (stratummine). configure.ac, src/clientversion.h 1.2.0 -> 1.3.0 (CLIENT_VERSION 1030050) doc/man/*.1 version strings restamped contrib/debian/changelog 1.3.0 entry for the 27 commits src/chain.h stale "CLIENT_VERSION is 1010050" comment Man page *content* still needs a real regeneration: util/gen-manpages.sh requires help2man and built 1.3.0 binaries, so it belongs in the release build, where it will also pick up the new stratum options. doc/release-process.md is why v1.1.0 and v1.2.0 were tagged but never became releases. Followed verbatim it produced a release the wallet refuses to install: - it directed releases to a branch named `dragonx`, which does not exist; releases are cut on `master` - it never once mentioned signing, yet the updater pins an ed25519 key and sets kDaemonRequireSignature = true, so an unsigned release is refused outright and every user silently stays on their old daemon - it did not require the release tag to be annotated, and genbuild.sh calls `git describe` without --tags, so a lightweight tag stamps the build `v-` instead of the release version -- which is exactly what happened to v1.0.0 through v1.0.3 - it referenced util/build-debian-package-ARM.sh, which is not in tree Adds the signing and checksum-table steps, the annotated-tag requirement with a `git describe` verification, and the rule that a new version must exceed every existing tag including unpublished ones. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- configure.ac | 2 +- contrib/debian/changelog | 28 ++++++++++++++++++++++++++ doc/man/dragonx-cli.1 | 6 +++--- doc/man/dragonx-tx.1 | 6 +++--- doc/man/dragonxd.1 | 6 +++--- doc/release-process.md | 43 +++++++++++++++++++++++++--------------- src/chain.h | 2 +- src/clientversion.h | 2 +- 8 files changed, 67 insertions(+), 28 deletions(-) diff --git a/configure.ac b/configure.ac index 3c4e5dbc8..4aab55486 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ 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, 2) +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))) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index 31a3a94c9..9f8ecba11 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,31 @@ +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 diff --git a/doc/man/dragonx-cli.1 b/doc/man/dragonx-cli.1 index c6a2d4534..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-CLI "1" "August 2026" "dragonx-cli v1.2.0" "User Commands" +.TH DRAGONX-CLI "1" "August 2026" "dragonx-cli v1.3.0" "User Commands" .SH NAME -dragonx-cli \- manual page for dragonx-cli v1.2.0 +dragonx-cli \- manual page for dragonx-cli v1.3.0 .SH DESCRIPTION -DragonX RPC client version v1.2.0 +DragonX RPC client version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . diff --git a/doc/man/dragonx-tx.1 b/doc/man/dragonx-tx.1 index 1bbc5b625..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" "August 2026" "dragonx-tx v1.2.0" "User Commands" +.TH DRAGONX-TX "1" "August 2026" "dragonx-tx v1.3.0" "User Commands" .SH NAME -dragonx-tx \- manual page for dragonx-tx v1.2.0 +dragonx-tx \- manual page for dragonx-tx v1.3.0 .SH DESCRIPTION -hush\-tx utility version v1.2.0 +hush\-tx utility version v1.3.0 .SS "Usage:" .TP hush\-tx [options] [commands] diff --git a/doc/man/dragonxd.1 b/doc/man/dragonxd.1 index 765b2bea6..dba0f9bb7 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 DRAGONXD "1" "August 2026" "dragonxd v1.2.0" "User Commands" +.TH DRAGONXD "1" "August 2026" "dragonxd v1.3.0" "User Commands" .SH NAME -dragonxd \- manual page for dragonxd v1.2.0 +dragonxd \- manual page for dragonxd v1.3.0 .SH DESCRIPTION -DragonX Daemon version v1.2.0 +DragonX Daemon version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . 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/src/chain.h b/src/chain.h index 37a47689e..56771486d 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 1010050 (v1.1.0.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 diff --git a/src/clientversion.h b/src/clientversion.h index dd09dcc4f..734105201 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -29,7 +29,7 @@ //! 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 2 +#define CLIENT_VERSION_MINOR 3 #define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 50 From e2e10f6ef84c3363357b34c4c226a90899a1fb8b Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 00:23:56 -0500 Subject: [PATCH 59/68] doc: regenerate man pages from the v1.3.0 binaries Previously the version strings were restamped by hand in af7d9e230 because help2man and a built binary were both unavailable. Regenerated properly via util/gen-manpages.sh against freshly built v1.3.0 binaries, which also picks up two options that were never documented: -sietch-min-zouts= decoy Sapling outputs added to each z_sendmany -stratumtarget= pool share target, for solo/low-difficulty mining NOTE: the version lines read "v1.3.0-af7d9e230" because `git describe` has no annotated tag to find yet. Once v1.3.0 is tagged annotated, these must be regenerated once more so they read a clean "v1.3.0" -- that step stays open on the release checklist. Generated on seed 176 (glibc 2.35 runs the binaries; help2man is installed there and not on the primary), with an isolated HOME so nothing touched the node's datadir. The reindex running on that box was not disturbed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- doc/man/dragonx-cli.1 | 2 +- doc/man/dragonx-tx.1 | 2 +- doc/man/dragonxd.1 | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/man/dragonx-cli.1 b/doc/man/dragonx-cli.1 index 849ef114a..d2f721b43 100644 --- a/doc/man/dragonx-cli.1 +++ b/doc/man/dragonx-cli.1 @@ -3,7 +3,7 @@ .SH NAME dragonx-cli \- manual page for dragonx-cli v1.3.0 .SH DESCRIPTION -DragonX RPC client version v1.3.0 +DragonX RPC client version v1.3.0\-af7d9e230 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . diff --git a/doc/man/dragonx-tx.1 b/doc/man/dragonx-tx.1 index 126f7b444..af69003fe 100644 --- a/doc/man/dragonx-tx.1 +++ b/doc/man/dragonx-tx.1 @@ -3,7 +3,7 @@ .SH NAME dragonx-tx \- manual page for dragonx-tx v1.3.0 .SH DESCRIPTION -hush\-tx utility version v1.3.0 +hush\-tx utility version v1.3.0\-af7d9e230 .SS "Usage:" .TP hush\-tx [options] [commands] diff --git a/doc/man/dragonxd.1 b/doc/man/dragonxd.1 index dba0f9bb7..32684b93e 100644 --- a/doc/man/dragonxd.1 +++ b/doc/man/dragonxd.1 @@ -3,7 +3,7 @@ .SH NAME dragonxd \- manual page for dragonxd v1.3.0 .SH DESCRIPTION -DragonX Daemon version v1.3.0 +DragonX Daemon version v1.3.0\-af7d9e230 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . @@ -452,6 +452,13 @@ or create a wallet z\-address). Must be spendable by this wallet. 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 @@ -710,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 From c5fde124850a603b0ad35ac4f95776e18a006f20 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 01:53:43 -0500 Subject: [PATCH 60/68] qa: port 12 wallet/mining tests to python3 and fix three framework blockers Ports qa/rpc-tests from 6 python3 files to 18. Ran every ported test against the freshly built v1.3.0 dragonxd. Results, honestly: PASS (1) getblocktemplate_proposals.py NOT APPLICABLE (7) wallet.py, walletbackup.py, wallet_protectcoinbase.py, wallet_listnotes.py, wallet_mergetoaddress.py, getblocktemplate.py, wallet_shieldcoinbase.py BLOCKED (4) wallet_sapling, wallet_nullifiers, wallet_persistence, wallet_treestate The "not applicable" seven are inherited Zcash/Hush-era tests that exercise features DragonX deliberately removed. They assume transparent t->t value transfer, but ASSETCHAINS_PRIVATE=1 (hush_utils.h:1826) makes sendtoaddress and sendmany consensus-refuse; they assume Sprout joinsplits, which are gone from the RPC layer entirely; and they hardcode Bitcoin economics (10 coin/block, 100-block maturity) against DragonX's 3 DRGX and COINBASE_MATURITY=1. They are ported and left in place rather than deleted, but they cannot pass on this chain without being rewritten around z_shieldcoinbase/autoshield. Three framework fixes in test_framework/util.py, each of which broke every multi-node test: - initialize_chain() passed -connect=0, and init.cpp soft-sets -listen=0 when -connect is present, so cache node0 never opened its p2p port and nodes 1-3 could never sync to it -- initialize_chain() hung forever in sync_blocks(). Now passes -listen=1 -bind=127.0.0.1 -dnsseed=0 explicitly (an explicit arg beats SoftSetBoolArg) while keeping the cache nodes off the public network. - cache cleanup removed files from when dragonxd writes them one level deeper into the net-specific /regtest. - set_node_times() did print("..." + t) with t an int -> TypeError. - default binary paths corrected to src/dragonxd and src/dragonx-cli. The four BLOCKED tests are blocked by a daemon assert, not by the port; see the follow-up commit/report on BLOCK_VALID_CONTEXT. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- qa/rpc-tests/getblocktemplate.py | 2 +- qa/rpc-tests/getblocktemplate_proposals.py | 83 ++++++++++++++++--- qa/rpc-tests/test_framework/util.py | 27 +++++-- qa/rpc-tests/wallet.py | 58 ++++++++------ qa/rpc-tests/wallet_listnotes.py | 2 +- qa/rpc-tests/wallet_mergetoaddress.py | 24 +++--- qa/rpc-tests/wallet_nullifiers.py | 70 ++++++++++++++-- qa/rpc-tests/wallet_persistence.py | 47 +++++++++-- qa/rpc-tests/wallet_protectcoinbase.py | 86 ++++++++++++-------- qa/rpc-tests/wallet_sapling.py | 92 ++++++++++++++++++++-- qa/rpc-tests/wallet_shieldcoinbase.py | 46 ++++++++--- qa/rpc-tests/wallet_treestate.py | 17 +++- qa/rpc-tests/walletbackup.py | 63 ++++++++++++--- 13 files changed, 485 insertions(+), 132 deletions(-) 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_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", "dragonx-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) @@ -198,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)) @@ -352,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") From 6d282db216afdd5c7b2b05537dd11e90f9a99bb0 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 02:12:17 -0500 Subject: [PATCH 61/68] consensus: stop BLOCK_VALID_CONTEXT overwriting the block validity level BLOCK_VALID_CONTEXT was 6. The validity levels above it are sequential VALUES packed into a 3-bit field, not independent bits, so BLOCK_VALID_MASK is 1|2|3|4|5 == 7 and the value 6 sat entirely inside it. `pindex->nStatus |= BLOCK_VALID_CONTEXT` (main.cpp:5616, and :3285) therefore did not set a flag -- it overwrote the validity level. Consequences, all long-standing: - A header-only block raised to BLOCK_VALID_TREE(2) became 2|6 == 6, which reads as >= BLOCK_VALID_CHAIN(4) and >= BLOCK_VALID_SCRIPTS(5). A block merely written to disk reported full script validity. - Every later RaiseValidity() silently no-opped, because 6 >= every level. ConnectBlock's RaiseValidity(BLOCK_VALID_SCRIPTS) was a permanent no-op. - CheckBlockIndex's "CHAIN valid implies all parents are CHAIN valid" invariant was violated whenever a stored block sat above a still-header-only ancestor, i.e. ordinary out-of-order parallel block download. fDefaultConsistencyChecks is true only for regtest, so the abort was regtest-only -- but the garbled index is written identically on mainnet, where only the detection is off. Not a v1.3.0 regression: introduced upstream in Komodo fa309e5b0 (2019-04-02), inherited via Hush, and byte-identical in v1.0.3, which the production network runs today. Validity was only ever INFLATED, never deflated, so no valid block was rejected and no invalid block skipped validation -- ConnectBlock's CheckBlock and full script/proof verification always ran. The user-visible effects were misreports: getchaintips labelling never-connected forks "valid-fork", submitblock answering "duplicate" for unvalidated blocks, and ProcessGetData serving them. The material cost was to QA: multi-node regtest tests aborted the syncing node at random, making the rpc-test suite unusable. Moves the flag to 512, the next free bit above BLOCK_IN_TMPFILE(256), and adds static_asserts that every nStatus flag is disjoint from BLOCK_VALID_MASK so this cannot be reintroduced silently. nStatus is serialized as VARINT, so the wider value needs no format change. Verified under gdb on regtest. A connected block's nStatus: before 0x1e validity field 6 (measured on the previous binary) after 0x21d validity field 5 = SCRIPTS, context bit set wallet_sapling.py, which aborted the syncing node deterministically, now runs to completion with no assert. No migration: the original level is unrecoverable from a polluted entry (1|6, 3|6 and 5|6 all give 7; 2|6 and 4|6 both give 6), and the only safe guess is downward, which would risk revalidation work on a 3.25M-block index for a cosmetic gain. Legacy entries simply have the flag bit clear, so they re-run the contextual check they previously skipped -- more checking, not less -- and resolve on any reindex. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/chain.h | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/chain.h b/src/chain.h index 56771486d..aa039e83f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -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; From db42091ce3452abc3e155b5936785b931590d130 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 02:38:38 -0500 Subject: [PATCH 62/68] rpc/net: gate and harden stratummine, keep regtest off the live network, init nSPV filter stratummine (rpc/mining.cpp), a test-only reference miner, was registered unconditionally behind a bare #ifndef WIN32 with okSafeMode=true, so it shipped as a live RPC on every non-Windows release build. Four fixes: - Gated behind an explicit, default-off -stratummine, with regtest exempt so qa/rpc-tests can still drive it. Deliberately NOT gated on fExperimentalMode: that defaults to TRUE (init.cpp:1195), so such a gate is a no-op -- an error made and caught while testing this change. - mining.notify's three hash fields went straight into uint256(ParseHex(...)). uint256's vector ctor asserts on a wrong size (uint256.cpp:30) and NDEBUG is defined nowhere in this build, so that assert is live in release; ParseHex also truncates silently at the first non-hex character. A short or garbled field therefore ABORTED THE DAEMON. Added StratumHex256(), which requires 64 hex chars and a 32-byte result, and all three fields are validated before any is committed so a bad job is rejected rather than half-applied. - processLine ran inside the window where the RandomX cache, the VM and the socket are live, all released on the normal path only, and every get_str() throws on a type mismatch -- so a malformed message leaked 256 MB and the fd. Body wrapped in try/catch: ignore the line, keep mining. - Caller-supplied timeout clamped to [1, 3600]; it was unbounded, pinning an RPC worker and the cache indefinitely. okSafeMode -> false. Verified against a hostile stratum server on regtest: - 4 malformed mining.notify payloads (short hex, non-hex, wrong JSON type, 31 bytes) -> all rejected, daemon alive, 0 assertions. The 31-byte case is the one that previously hit the uint256 assert. - valid job first (so RandomX actually allocates) then garbage mid-mine -> RSS delta +2.2 MB, i.e. cache and VM released, not the ~256 MB a leak leaves. - regtest exemption confirmed: stratummine runs past the gate there. The non-regtest refusal path is by code reading only -- a testnet node on this host collides with the production daemon's RPC port, so it was not exercised. hush_utils.h: stop injecting the mainnet node1-node10.dragonx.is addnode seeds when -regtest or -testnet is set. regtest reuses mainnet's network magic, so a supposedly isolated node was handshaking production peers and pulling their headers into its own index. Gated at the injection site only -- isdragonx itself must stay true, because it also selects ac_private, ac_algo, blocktime and the reward/halving schedule (an earlier version of this patch gated isdragonx itself and silently turned ac_private off on regtest). hush_args() runs between ParseParameters() and ReadConfigFile() (bitcoind.cpp:115/144/158), so this sees a command-line -regtest, as qa/rpc-tests uses, but not a config-file regtest=1. hush_nSPV_fullnode.h: initialize `filter` at both sites. It was assigned only on the len-11 request form; the other two passed uninitialized stack memory to NSPV_getaddressutxos/NSPV_getaddresstxids, remotely reachable since HUSH_NSPV_FULLNODE is on by default. getblocktemplate_proposals.py still passes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/hush_nSPV_fullnode.h | 4 +-- src/hush_utils.h | 16 +++++++++-- src/rpc/mining.cpp | 60 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/hush_nSPV_fullnode.h b/src/hush_nSPV_fullnode.h index 9287c2103..e7efd144d 100644 --- a/src/hush_nSPV_fullnode.h +++ b/src/hush_nSPV_fullnode.h @@ -660,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]; 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 ) @@ -698,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 ) diff --git a/src/hush_utils.h b/src/hush_utils.h index 6241b93ba..3ff16791a 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1783,11 +1783,21 @@ 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 diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 5d6fb318e..0d9f15674 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1112,6 +1112,27 @@ static bool StratumMinerRecvLines(int fd, std::string& buf, int timeout_ms, std: // 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) @@ -1129,10 +1150,24 @@ UniValue stratummine(const UniValue& params, bool fHelp, const CPubKey& mypk) 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"; - const int64_t timeout = params.size() > 3 ? params[3].get_int64() : 120; + 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) @@ -1166,6 +1201,11 @@ UniValue stratummine(const UniValue& params, bool fHelp, const CPubKey& mypk) 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"); @@ -1185,16 +1225,26 @@ UniValue stratummine(const UniValue& params, bool fHelp, const CPubKey& mypk) 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 = uint256(ParseHex(p[2].get_str())); - hashMerkleRoot = uint256(ParseHex(p[3].get_str())); - hashReserved = uint256(ParseHex(p[4].get_str())); + 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; @@ -1302,7 +1352,7 @@ static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- #ifndef WIN32 - { "mining", "stratummine", &stratummine, true }, + { "mining", "stratummine", &stratummine, false }, #endif { "mining", "getlocalsolps", &getlocalsolps, true }, { "mining", "getnetworksolps", &getnetworksolps, true }, From 5634aed750412f3e8aef32c3b39489ce7d90f0b4 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 13:29:43 -0500 Subject: [PATCH 63/68] wallet: open salvaged wallets in a degraded mode, and detect a mismatched hdchain A wallet.dat repaired by v1.0.3-or-earlier -salvagewallet could not be opened by this build at all. Those builds' IsKeyType has no "hdchain" case, so salvage dropped the record; LoadWallet then returned DB_CORRUPT and told the user to restore from a seed phrase. There is no such recovery path: the node aborts before the RPC server exists, -usemnemonic defaulted to 0 in v1.0.3 so many of these wallets never had a phrase, and SetHDSeedFromMnemonic refuses a non-empty wallet, so "move wallet.dat aside" discards every non-HD key salvage preserved. The guard itself was right to refuse to DERIVE -- a missing hdchain falls back to SetNull defaults, clearing fMnemonicSeed and switching derivation from the 64-byte BIP39 seed to the raw 32-byte entropy, an entirely different key tree. It was wrong to refuse to OPEN. Split the two: - "hdchain" record ABSENT (the salvage signature, tracked by a new wss.fHDChainSeen set before any parse attempt) -> open DEGRADED. - "hdchain" record PRESENT but unreadable -> still DB_CORRUPT; that signals wider file damage. Degraded means the wallet spends, receives and rescans normally but refuses to derive any new HD key. Gated at the single choke point GetHDSeedForDerivation rather than at the two generators, so z_getnewaddress/sendmany/shieldcoinbase raise a clean error and autoshield skips. hdChain.seedFp is deliberately left null, which keeps IsHDTransparentEnabled false so getnewaddress falls back to the legacy random-key path -- gating it instead would break TopUpKeyPool, change addresses and block templates. -rescan is soft-set because an old salvage also dropped defaultkey and bestblock, which would otherwise look like a first run and skip the rescan, leaving a permanently zero balance. NEW DETECTOR, covering strictly more damage than the guard beside it. A wallet salvaged by v1.0.3 and then USED on v1.0.3 persists a SetNull-derived hdchain carrying seedFp=null. On this build fHDChainRead is then true, the guard never fires, the node starts perfectly clean -- and derives into the wrong key tree forever, silently. That state is unforgeable by any legitimate writer, since InstallHDSeed always stores seedFp = seed.Fingerprint(), so compare the loaded chain's seedFp against the fingerprint taken from the hdseed/chdseed record KEY (which works while an encrypted wallet is locked) and degrade on mismatch. Also: - init.cpp: abort in the DB_CORRUPT branch itself, as DB_NEED_REWRITE already does. Falling through ran several hundred more lines against a wallet just declared corrupt, including SetHDSeedOrigin(), which WRITES to it. Error text no longer advises -mnemonic (wrong twice over, see above). - rpcdump.cpp: z_exportwallet discarded GetHDSeedForDerivation's return and emitted the line regardless, so a failure wrote a blank seed next to a legitimate-looking BLAKE2b-of-empty fingerprint -- a backup that looks valid and restores nothing. - Corrected the guard's comment: the saplingAccountCounter half was overstated. Both generators loop while(Have...Key(...)), so a low counter walks forward past existing accounts rather than colliding with them. qa/r4-salvaged-wallet-harness.sh builds the victim wallets with the on-disk v1.0.1 release binary and asserts the behaviour end to end. It REFUSES to run outside a network namespace and re-checks getconnectioncount==0, because those old binaries predate the regtest seed-injection fix and would otherwise dial the live network. 11/11 pass: C healthy wallet opens and is NOT flagged (no false positives) A salvaged wallet opens, flagged, persists no hdchain, z_getnewaddress refused, getnewaddress still works, z_exportwallet emits no bogus HDSeed line B poisoned wallet opens and the seedFp detector fires Note the harness asserts "no hdchain synthesised", NOT "wallet.dat unchanged": a control run showed normal startup rewrites wallet.dat for healthy wallets too. This is the access/detection half. Proving fMnemonicSeed by re-deriving against held keys, and repairing the record, is deliberately left out: it requires a write, and the affected population is unmeasured. dev's own salvage already preserves hdchain, so the class is closed going forward. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- qa/r4-salvaged-wallet-harness.sh | 87 ++++++++++++++++++++++++++++++++ src/init.cpp | 20 ++++++-- src/wallet/rpcdump.cpp | 15 ++++-- src/wallet/wallet.cpp | 9 ++++ src/wallet/wallet.h | 11 ++++ src/wallet/walletdb.cpp | 82 +++++++++++++++++++++++++----- 6 files changed, 203 insertions(+), 21 deletions(-) create mode 100755 qa/r4-salvaged-wallet-harness.sh 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/src/init.cpp b/src/init.cpp index f1bb05347..4efe76104 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2260,10 +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. If this wallet was last opened " - "by an older version, move wallet.dat aside and restore from your seed " - "phrase with -mnemonic=\"\" -rescan (see debug.log for " - "the specific record at fault).") << "\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" diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 069404ca7..7eb29e42c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -746,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++) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e0cd0d926..ca29538ad 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2813,6 +2813,15 @@ bool CWallet::SetHDSeedFromMnemonic(const std::string& phrase) 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; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index cf77da83f..95527738b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -961,6 +961,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: /* @@ -1415,6 +1420,12 @@ 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 diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5537972fa..ffde7595b 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -421,6 +421,15 @@ public: // 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; @@ -429,6 +438,8 @@ public: nFileVersion = 0; fHDChainRead = false; fHDChainRepaired = false; + fHDChainSeen = false; + fHDSeedSeen = false; } }; @@ -836,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") { @@ -849,9 +862,15 @@ 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; // Keep an untouched copy: a failed >> has already consumed part of ssValue. CDataStream ssRetry(ssValue.begin(), ssValue.end(), ssValue.GetType(), ssValue.GetVersion()); @@ -1046,23 +1065,60 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) } } - // A wallet that holds an HD seed but whose hdchain record is missing or - // unreadable is NOT safe to run. hdChain would fall back to its SetNull - // defaults (walletdb.h:105-113), which (a) clears fMnemonicSeed, switching - // HD derivation from the 64-byte BIP39 seed to the raw 32-byte entropy - // (CWallet::GetHDSeedForDerivation, wallet.cpp:2615-2633) -> an entirely - // different key tree, and (b) resets saplingAccountCounter to 0, so the - // next GenerateNewSaplingZKey walks back over accounts that already exist. - // Both are silent today (a bad hdchain read is only DB_NONCRITICAL_ERROR). - // Fail loud instead of quietly deriving into the wrong tree. - if (pwallet->HaveHDSeed() && !wss.fHDChainRead) + // 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: HD seed present but the hdchain record is missing or corrupt. " - "Recover by restoring from the seed phrase: move wallet.dat aside and start with " - "-mnemonic=\"\" -rescan\n"); + 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) From 3aac75e94ff75398845c6ac2657e6e6e277ffd49 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 17:35:52 -0500 Subject: [PATCH 64/68] cli: report the port actually dialled, not a stale global dragonx-cli resolves -rpcport correctly -- CallRPC does int port = GetArg("-rpcport", BaseParams().RPCPort()); and dials that port. But both connection-failure messages printed ASSETCHAINS_RPCPORT instead, a separate global defined at the top of bitcoin-cli.cpp, initialised to the mainnet default 21769, and never assigned anywhere in the CLI. So every failure claimed port 21769 regardless of what was asked for: $ dragonx-cli -rpcport=21799 getblockcount error: couldn't connect to server at port 21769 That reads as "your -rpcport was ignored", which is a much more alarming and much more misleading diagnosis than "nothing is listening yet". It cost an hour of debugging on a node that was simply still loading a 15 GB txindex, and led to the wrong conclusion that the CLI could be talking to the production daemon when it was not. Both sites now print the local `port`. Verified against the previous binary: new: -rpcport=21799 -> "couldn't connect to server at port 21799" -rpcport=59999 -> "couldn't connect to server at port 59999" old: both -> "couldn't connect to server at port 21769" and the CLI still reaches a live daemon on a non-default port. ASSETCHAINS_RPCPORT is left defined; it is referenced by other translation units. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/bitcoin-cli.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 From a6b6f80db0bc1bf799ac2ead56d5b8b9decd8652 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 20:52:40 -0500 Subject: [PATCH 65/68] wallet: stop an interrupted rescan from hiding funds, and un-latch fAbortRescan Two independent fund-visibility bugs on the rescan path, both pre-existing. 1. AN INTERRUPTED RESCAN RECORDED ITSELF AS COMPLETE. ScanForWalletTransactions returns a bare `int ret` (a found-tx count) on both its abort and shutdown bail-outs, so the caller could not tell "finished" from "stopped at block H". init.cpp then ran, unconditionally: pwalletMain->ScanForWalletTransactions(pindexRescan, true); pwalletMain->SetBestChain(chainActive.GetLocator()); // TIP locator An interrupted scan therefore stamped the wallet as scanned all the way to the chain tip. On the next start, the `chainActive.Tip() != pindexRescan` guard just above sees no work to do and skips the rescan entirely, so every transaction in the never-scanned range stays out of mapWallet -- permanently invisible to getbalance and unspendable. That is exactly the case `-rescan` exists for: a key import, right after ClearNoteWitnessCache has run. Adds CWallet::fLastRescanCompleted, set false at scan entry and true only on the normal exit, and gates the init.cpp locator write on it. Deliberately NOT overloading the int return, which callers already use as a tx count. 2. fAbortRescan WAS NEVER RESET. wallet.h declares it, AbortRescan() sets it true, and nothing in src/ ever sets it false. The abortrescan RPC is live. After one call, for the remaining lifetime of the process: - every ScanForWalletTransactions returns immediately, so re-running an import silently no-ops while the RPC still reports success; - BuildWitnessCache bails on the same flag on EVERY call, including the routine per-block extension from ChainTip. Witness heights then diverge across notes while the chain advances, and GetSaplingNoteWitnesses elects the majority root as the anchor and returns boost::none for every note that disagrees. Those notes still show in the balance but cannot be spent. Cleared at scan entry and consumed in the abort branch. Also in BuildWitnessCache's abort branch: stop clearing fRescanning. A witness rebuild is not a rescan and must not touch a flag ScanForWalletTransactions owns. 3. Both bail-out log lines had two format specifiers and one argument: LogPrintf("%s: Rescan aborted at block %d\n", pwalletMain->rescanHeight); tinyformat's "too many conversion specifiers" guard is disabled in this tree (tinyformat.h: `if(*fmt != '\0' && 0 ) // disabled due to complaints`), so this does not throw -- verified by compiling the exact call against this tree's tinyformat.h. It emits "3841207: Rescan aborted at block " with the height in the __func__ slot, the real height dropped, and the trailing newline swallowed so the next log line concatenates onto it. On the one path an operator has to diagnose an interrupted rescan, the only record was corrupt. Fixed at both sites, and the state updates moved above the log call. 4. Makes SetBestChainINTERNAL report whether the atomic write committed, so a checkpointing caller can tell (six failure paths returned void). Adds SetBestChainNoFlush, which opens the wallet DB with fFlushOnClose=false: the default ctor makes ~CDB run a full BDB txn_checkpoint over the whole cache, which is fine hourly but not from inside a scan -- the same flush wallet.cpp already documents avoiding elsewhere "for performance reasons". Nothing calls it yet. 5. SetBestChainINTERNAL took each CWalletTx by value, deep-copying every note's witness deque (WITNESS_CACHE_SIZE entries) per transaction per call, purely to serialize it. Now by const reference. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/init.cpp | 15 +++++++++++++-- src/wallet/wallet.cpp | 33 +++++++++++++++++++++++++++------ src/wallet/wallet.h | 32 +++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 4efe76104..ce8322dc4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2654,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") diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ca29538ad..20df4ea69 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -801,10 +801,18 @@ bool CWallet::CommitAutomatedTx(const CTransaction& tx) { 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) { @@ -1436,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(); @@ -3434,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(); @@ -3456,15 +3471,18 @@ 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); 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); return ret; } @@ -3525,6 +3543,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; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 95527738b..107814971 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -833,6 +833,10 @@ public: 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? @@ -904,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 @@ -923,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: @@ -1282,8 +1292,12 @@ public: 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); From c1040028e4a5b73ad94dc7e3d659e94043b12f00 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 31 Aug 2026 20:54:37 -0500 Subject: [PATCH 66/68] wallet: checkpoint rescan progress when the scan is interrupted An aborted or shutdown-interrupted rescan discarded all of its progress: nothing advanced the on-disk best-block locator, so the next run started over. On a large shielded wallet that is a multi-minute witness rebuild repeated from scratch. Writes the locator ONCE, at the interrupt, rather than periodically through the scan. That delivers the whole stated benefit -- "resume where it stopped" -- at the cost of exactly one SetBestChain per interrupted scan. A periodic checkpoint would additionally survive SIGKILL and power loss, a much weaker requirement, and it is the part that carries all the cost: SetBestChainINTERNAL is O(whole wallet) regardless of progress, so on the reported 5.3k-tx / 7.5k-note wallet a checkpoint every 2500 blocks would plausibly cost more than the rebuild it was meant to save. If someone later demonstrates a need for crash resilience mid-scan, add it then, with a measured interval and fFlush=false -- not before. Two guards, both necessary: - CONTIGUITY. The RPC entry points (rescan / importprivkey / z_importkey / z_importviewingkey) take a caller-supplied start height validated only against chainActive.Height(), never against the wallet's own persisted locator. A scan beginning above that locator must not checkpoint at all, or it would record the skipped range as scanned and hide any funds in it. Logged once when it applies, so an operator can see why an interrupt did not persist. - MONOTONICITY. A checkpoint may only advance the locator, never move it back. The locator points at the last FULLY PROCESSED block -- the parent of the block we were about to scan -- so resume restarts one block early. CChain::GetLocator pushes its argument first and FindForkInGlobalIndex returns that same block, so resume begins AT it; the one-block overlap is deliberate and idempotent, since AddToWallet only takes its merge path when the transaction is already present. No witness work is done at the interrupt, which answers the two "//TODO: should we update witness caches?" comments this replaces: witnesses are re-derived from each note's own witnessHeight independently of the locator, and witnessRootValidated is never serialized, so every note is revalidated against hashFinalSaplingRoot on the next start. A mid-scan checkpoint does capture notes at mixed witnessHeights -- the in-loop BuildWitnessCache(pindex, true) returns before the extension phase and only seeds new notes at their own transaction's height -- but that state is recoverable: the post-loop BuildWitnessCache(tip, false) levels every note, and it now occurs at most once per scan instead of hundreds of times. Uses SetBestChainNoFlush so the checkpoint does not trigger a full BDB txn_checkpoint over the whole cache, and reports failure rather than silently leaving the operator to discover the replay. Depends on the fLastRescanCompleted gate in the previous commit: without it init.cpp would overwrite this checkpoint with a tip locator immediately after the scan returns. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/wallet/wallet.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 20df4ea69..443498cba 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3463,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); @@ -3478,11 +3523,13 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) 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()) { pwalletMain->fRescanning = false; LogPrintf("%s: Rescan interrupted by shutdown request at block %d\n", __func__, pwalletMain->rescanHeight); + checkpointProgress(pindex); return ret; } From 1d2d5f32d05289628d83d40dd5f6219019d48ff6 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 1 Sep 2026 19:06:54 -0500 Subject: [PATCH 67/68] version: bump PROTOCOL_VERSION for the v1.3.0 consensus changes doc/release-process.md requires PROTOCOL_VERSION to increase by 1 for any release carrying a consensus change. This one does: 6d282db21 stops BLOCK_VALID_CONTEXT overwriting the block validity level, 267e6f7ad drops the dead CBOPRET price validation from the coinbase check, and pow.cpp is 157 lines lighter. The value has not moved since 85c8d7f7d in March. MIN_PEER_PROTO_VERSION stays at 2000000. It currently equals PROTOCOL_VERSION, so raising it in step would disconnect every peer still announcing 2000000 -- which is every user, because no DragonX release has ever been installable: the wallet's updater pins an ed25519 key and sets kDaemonRequireSignature = true, and no release has ever shipped a .sig. Bumping only the advertised version lets 1.3.0 nodes be identified on the wire without partitioning the network on release day. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- src/version.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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. From fc26e2e0a085248d6f15a53099c7f817a17dd3cd Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 1 Sep 2026 19:06:54 -0500 Subject: [PATCH 68/68] doc: stamp the man pages with the release version, not a build sha The man pages were regenerated at e2e10f6ef from a binary built at af7d9e230 and carry "v1.3.0-af7d9e230". The release will be tagged v1.3.0, so the shipped man pages would contradict the tag they ship under -- the same provenance gap af7d9e230 itself was written to close. A faithful regeneration cannot fix this before the tag exists: gen-manpages.sh reads --version from the binary, genbuild.sh derives that from `git describe`, and that only prints exactly "v1.3.0" once the annotated tag is in place. Regenerating after tagging would mean committing on top of the tag and moving it. doc/release-process.md anticipates this and documents hardcoding the version instead. Content is untouched and needs no regeneration: all 150 daemon options in the man page match `dragonxd --help` exactly, including the five -autoshield* flags. Not changed: dragonx-tx.1 still describes itself as "hush-tx utility". That string comes from the binary's own --help, so editing the man page would make it diverge from what the tool actually prints. It is a binary-level branding fix, not a doc one. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FU87LdsJZiZkfq1eXubpeo --- doc/man/dragonx-cli.1 | 2 +- doc/man/dragonx-tx.1 | 2 +- doc/man/dragonxd.1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/man/dragonx-cli.1 b/doc/man/dragonx-cli.1 index d2f721b43..849ef114a 100644 --- a/doc/man/dragonx-cli.1 +++ b/doc/man/dragonx-cli.1 @@ -3,7 +3,7 @@ .SH NAME dragonx-cli \- manual page for dragonx-cli v1.3.0 .SH DESCRIPTION -DragonX RPC client version v1.3.0\-af7d9e230 +DragonX RPC client version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see . diff --git a/doc/man/dragonx-tx.1 b/doc/man/dragonx-tx.1 index af69003fe..126f7b444 100644 --- a/doc/man/dragonx-tx.1 +++ b/doc/man/dragonx-tx.1 @@ -3,7 +3,7 @@ .SH NAME dragonx-tx \- manual page for dragonx-tx v1.3.0 .SH DESCRIPTION -hush\-tx utility version v1.3.0\-af7d9e230 +hush\-tx utility version v1.3.0 .SS "Usage:" .TP hush\-tx [options] [commands] diff --git a/doc/man/dragonxd.1 b/doc/man/dragonxd.1 index 32684b93e..9fd809079 100644 --- a/doc/man/dragonxd.1 +++ b/doc/man/dragonxd.1 @@ -3,7 +3,7 @@ .SH NAME dragonxd \- manual page for dragonxd v1.3.0 .SH DESCRIPTION -DragonX Daemon version v1.3.0\-af7d9e230 +DragonX Daemon version v1.3.0 .PP In order to ensure you are adequately protecting your privacy when using DragonX, please see .