71 Commits

Author SHA1 Message Date
bf3c33c53a revert(net): remove header-accept RandomX check; keep nMinimumChainWork + #8
An adversarial re-review found the header-accept RandomX check (b9fdc7981 +
7e9b2c661 header-PoW + d2124a303 defer) to be a persistent source of
consensus-liveness bugs: it derives the RandomX key from the ACTIVE chain
(hush_chainactive), the wrong branch for reorg/side-branch/catch-up headers, so
it repeatedly false-rejected validly-mined headers and DoS(100)-hard-banned
honest peers (IBD-tail catch-up and deep-reorg cases); the defer fix and an
extend-tip fix each addressed one case while leaving/creating others (an
extend-tip variant re-opened an unbounded post-IBD side-branch flood). It only
mitigated a low-harm resource DoS -- forged headers bloat mapBlockIndex memory/
disk but are never SELECTED (nMinimumChainWork) and the full RandomX + target
check still runs at block-connect. Revert to fCheckPOW=0 at header-accept
(original behavior). A comment in AcceptBlockHeader records that any re-attempt
must derive the key from the header's OWN ancestry (pindexPrev->GetAncestor),
never the active chain.

Also hardens two issues the same review found:
- #8 IBD header cap now bounds against the VALIDATED chainActive.Height()
  (attacker-hard) instead of pindexBestHeader, which a forward-extending flood
  advanced in lockstep, defeating the cap.
- opreturn_burn only emits a change output above the dust threshold; a sub-dust
  change made the returned tx non-standard/unrelayable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 20:24:45 -05:00
5951ee118a fix(net): cap per-peer headers during IBD (header-flood DoS)
Audit #8. The HEADERS handler accepted unbounded headers per peer with no
cumulative cap; during IBD (fCheckPOW=0) a peer could flood cost-free PoW-less
headers into mapBlockIndex/leveldb (never selected -- nMinimumChainWork gates
that -- but still memory/disk growth). Add a per-peer nHeadersProcessed counter
in CNodeState; while IsInitialBlockDownload(), if one peer exceeds
2*max(pindexBestHeader height, checkpoint height) + 200000 headers,
Misbehaving(100) and drop it. The cap is ~2x the chain length, so honest sync
never approaches it; inert post-IBD (the RandomX header check handles forged
headers there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:54:35 -05:00
d2124a3038 fix(pow): defer RandomX header check when key block not yet connected
Follow-on to the post-IBD header-PoW verification (b9fdc7981 + 7e9b2c661).
CheckRandomXSolution derives the RandomX key from the block at
keyHeight = ((height-lag)/interval)*interval, looked up on the ACTIVE chain
(hush_chainactive), so that block must be CONNECTED. When a post-IBD node's
block tip lags the header tip by more than ~one RandomX interval -- the normal
IBD tail, or any node catching up -- the key block is not connected yet, so
GetRandomXKey returns empty. The old code returned an error, making
CheckBlockHeader DoS(100)-ban the honest peer that sent a perfectly valid tip
header we simply could not verify yet.

Observed live: a node finishing a mainnet reindex banned the pool box + seeds
and stalled ~2000 blocks short of the tip. Fix: on an empty key, DEFER (return
true) instead of error -- the header is fully RandomX-verified at block-connect,
where the key block is always connected (blocks connect in order,
keyHeight <= height-lag < the connected tip). Flood protection is preserved for
synced nodes (key present -> real check) and bounded during catch-up by the
per-peer IBD header cap + nMinimumChainWork.

Validated on the live 3.14M-block chain: the affected node caught up the full
~2135-block gap to the tip with zero peer bans (was stalled + banned before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:54:35 -05:00
b5050d06c0 fix(wallet): opreturn_burn return change + widen txfee to CAmount
#10 (HIGH) opreturn_burn selected UTXOs for nAmount+txfee but pushed only the
burn vout and returned - so the entire selected-input surplus was silently paid
as miner fee (e.g. a 500-coin UTXO burning 10 lost ~490). Push a change output
for (inputs - nAmount - txfee). Also widen the int32_t txfee (which truncated
large CAmount fees) to CAmount and MoneyRange-validate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:21:28 -05:00
a520441e3a fix(rpc): guard z_validateaddress against null pwalletMain under -disablewallet
#11 (HIGH) z_validateaddress locked LOCK2(cs_main, pwalletMain->cs_wallet) with
no availability guard; under -disablewallet pwalletMain is NULL, so the member
deref SIGSEGVs the daemon (execute() only catches std::exception). Use the
null-safe LOCK2 idiom already used by sibling RPCs so validation still works
without a wallet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:21:28 -05:00
11704e6023 fix(nspv): add missing length lower-bounds before request/vopret reads
nSPV handlers (gated behind non-default -nspv_msg) read request[1]/vopret[1]
before confirming the peer sent >=2 bytes:

#6 (MEDIUM) NSPV_UTXOS/NSPV_TXIDS evaluated request[1] whenever len<69 (incl
len==1); the 4351d5b73 value-clamp left this lower bound open. The TXIDS/MEMPOOL
else-branch debug prints also read request[1] unconditionally. Add len>=2 guards
/ drop request[1] from the prints.

#7 (LOW) NSPV_MEMPOOL_CCEVALCODE read vopret[1] on a possibly-1-byte vector.
Guard with vopret.size()>=2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:21:28 -05:00
7e9b2c6615 fix(net): verify RandomX at correct height in AcceptBlockHeader + cap locator
header-pow: AcceptBlockHeader passed the caller's reused *ppindex (and a height
derived from it, ==0 for a new header) to CheckBlockHeader instead of the
header's own local pindex + real height. Post-IBD this made
RandomXValidationRequired(0) false, so CheckRandomXSolution returned true WITHOUT
verifying (and the fRandomXVerified short-circuit could fire on an unverified
header) - silently defeating the header-flood PoW gate from b9fdc7981. Resolve
pindexPrev up-front, pass real height (parent+1) and the local (NULL) pindex so
the post-IBD RandomX check actually runs; IBD stays fast (fCheckPOW=0).
Stability-tested: 303 valid headers accepted across a 4-node RandomX net,
0 false rejects / bans.

#9 (MEDIUM) GETBLOCKS/GETHEADERS deserialized an unbounded CBlockLocator.vHave
(~130k hashes) and scanned it linearly under cs_main with no ban - a
message-thread liveness DoS. Add MAX_LOCATOR_SZ=101 + Misbehaving, matching the
adjacent vInv/headers caps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:21:28 -05:00
fc06a43dd7 fix(consensus): bound OP_RETURN opretlen + clamp notary pubkeys array
Defensive-audit findings (adversarially verified + fleet stability-tested):

#4 (CRITICAL) hush_voutupdate trusted an attacker-decoded OP_RETURN length
(opretlen, up to 65535 via OP_PUSHDATA2) with no check against the real script
length, driving up to ~64KB out-of-bounds reads through hush_stateupdate ->
hush_eventadd_opreturn -> hush_kvupdate (persisted to disk, leaked via kvsearch
RPC, reliable crash on block connect). Reject any opret claiming more bytes than
remain in the script, at the single taint source.

#5 (HIGH) notary-ratification loop did memcpy(pubkeys[numvalid++],..) into a
fixed uint8_t[64][33] with no bound; >64 crafted vouts smashed the stack. Clamp
numvalid < 64.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:21:28 -05:00
7e99311210 fix(net): enforce nMinimumChainWork in IBD (eclipse / low-work fake-chain protection)
nMinimumChainWork was defined in chainparams but never checked, and IsInitialBlockDownload
decided "synced" from tip timestamp/height alone -- so an eclipsed or bootstrapping node
could be fed a cheap low-work fake chain with recent timestamps and trust it. Reset the
stale mainnet floor (0x281b32ff3198a1 was ABOVE the live chain, would have bricked mainnet)
to the real chainwork at height ~3,100,000, and hold a node in IBD until its tip reaches the
floor. Gated to the DRAGONX symbol so ephemeral assetchains from the same binary are not
trapped in IBD; the check can only keep a node in IBD, never force it out (no false-sync risk).
Complements the header-flood fix (b9fdc7981): that stops invalid-PoW headers off the real tip;
this stops valid-but-cheap fake chains from a fake genesis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:21:12 -05:00
14e3fb6708 fix(wallet): reserve miner fee during z_sendmany note selection
The Sapling note-selection loop stopped once total_value >= nTotalOut, ignoring
the miner fee, so a wallet with notes covering the amount but not amount+fee
selected too few notes and failed later with a spurious "insufficient funds".
Reserve the fee (default or user-supplied) in the selection target.

Leto eb4fc52273.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:06:39 -05:00
b9fdc79818 fix(net): verify PoW at header-accept once synced (header-flood DoS)
AcceptBlockHeader called CheckBlockHeader with fCheckPOW=0, so a synced node
stored any well-formed PoW-less header off the tip into mapBlockIndex without
bound (memory/disk DoS). nMinimumChainWork is defined but unenforced and would
not stop tip-siblings anyway (they inherit the tip's chain work). Verify PoW at
header-accept time when not in IBD: forged headers now fail RandomX and the peer
is DoS-banned. IBD keeps fCheckPOW=0 for fast header sync; the full-block
RandomX/target check at connect is unchanged, so no valid header is rejected
(not a consensus-rule change).

fCheckPOW=0 call site is Leto (6a30b40415).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:06:39 -05:00
4351d5b733 fix(nspv/wallet): bound nSPV request buffers + fix uninitialized fee / null-deref
hush_nSPV_fullnode.h: bound the REMOTERPC method strcpy and json memcpy to their
fixed buffers (method[64], json[11000]); add lower-length and memcpy-source bounds
to the UTXOS/TXIDS coinaddr[64] copies and the MEMPOOL handler. These paths
deserialize attacker-controlled request bytes -> stack overflow / OOB read. The
nSPV server is opt-in via -nspv_msg (off by default; DragonX uses lightwalletd).

rpc/blockchain.cpp: getchaintxstats null-checks pwalletMain (crash under -disablewallet).
wallet/rpcwallet.cpp: z_sendmany initializes nFee to the default miners fee (was read
uninitialized when no fee param supplied).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:56:09 -05:00
4a0a334649 fix(consensus): guard NULL pindex deref in hush_validate_chain (crash DoS)
hush_validate_chain() enters its body when hush_getblockindex(srchash) returns
NULL (via || short-circuit) -- srchash comes from an attacker-controlled
notarization OP_RETURN -- then a debug fprintf dereferenced the NULL pindex.
A block carrying one crafted OP_RETURN tx crashed every synced node on connect,
and crash-looped on restart. Guard the deref: pindex ? GetHeight() : -1.

Introduced by Leto commit 4988ce6f2 ("much debug such wow", 2022).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:56:09 -05:00
d52550a6fc fix(consensus): reject Sprout JoinSplits (unverified proof + vpub_new inflation vector)
Sprout JoinSplit proofs/sigs/nullifiers/anchors are never verified (the verifier
arg to CheckTransaction is unused), yet vpub_new is counted as transparent
value-in -- a forged all-zero JoinSplit mints arbitrary value from nothing.
Reproduced on an isolated ac_private=1 chain: 500,000 minted into a z-addr,
accepted + mined + verifychain=true.

Reject any non-coinbase tx carrying a JoinSplit in ContextualCheckTransaction
(covers both mempool acceptance and ConnectBlock). DragonX is Sapling-only from
genesis with zero JoinSplits in its history (mainnet supply audit), so this is
inert on all legitimate traffic and never invalidates a historical block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 08:10:14 -05:00
d159e72086 feat: chain-level Sapling turnstile (reject blocks that would drive the pool negative)
Belt-and-suspenders inflation/counterfeiting guard on top of the per-tx Sapling
binding signature: ConnectBlock rejects any block whose cumulative Sapling value
pool would go negative (bad-sapling-value-pool-negative) -- a block can never
deshield more value than was ever shielded.

Enforced ONLY when the pool is reliably tracked from genesis (pprev's
nChainSaplingValue is engaged), so it can never false-reject a valid block or
split the chain on nodes that don't track the pool -- those stay dormant.

To make "not reliably tracked" propagate safely, nSaplingValue becomes a
boost::optional<CAmount> (was a plain CAmount). A version-gated dual-read in
CDiskBlockIndex reads records written before SAPLING_VALUE_OPTIONAL_VERSION
(1000350 = v1.0.3) as the legacy raw 8-byte CAmount but DISCARDS the value
(reads boost::none). Records written at >= 1000350 use the optional format and
persist, so from-genesis and reindexed v1.0.3 nodes are durably active across
restarts.

Tested on a 5-node RandomX fleet: old-format DB loads dormant (0 corruption);
from-genesis stays active with correct pool accumulation and 0 false-rejects
across shield/deshield cycles; dormant/active/reindexed nodes converge; a crafted
counterfeit block is rejected (guard fires, no crash); active state persists
across restart (verified at CLIENT_VERSION 1000351 with gate 1000350).

*** MANDATORY UPGRADE STEP (v1.0.3 dev/test nodes) ***
CLIENT_VERSION stays 1000350, and pre-turnstile v1.0.3 builds ALSO stamped
records at 1000350 but in the old plain-8-byte format. Those records now route to
the OPTIONAL read branch and MISPARSE: LoadBlockIndexDB throws and the node
ABORTS on startup (looks like block-DB corruption). Therefore any node that ran an
earlier v1.0.3 (1000350) build MUST have its block data wiped or be -reindexed
before running this build -- do NOT upgrade a 1000350 datadir in place.
Production mainnet (v1.0.2 = CLIENT_VERSION 1000250) is UNAFFECTED: those records
take the legacy branch and read correctly (dormant until reindex). v1.0.3 is
unreleased, so only dev/test datadirs are affected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 01:14:46 +02:00
adf2bacdbd feat: fee-ordered mempool eviction (TrimToSize/Expire) + displacement on admission
This fork never ported Bitcoin's mempool size-limiting: CTxMemPool had no TrimToSize/
Expire and LimitMempoolSize was commented out. An earlier commit added a blunt
DynamicMemoryUsage admission cap that bounded memory but bluntly REJECTED new txs when
full -- so a high-fee tx could not push out a low-fee one. This implements proper
fee-ordered eviction using the per-tx feerate index that already exists (mapTx index 1,
CompareTxMemPoolEntryByFee), with no new index and no descendant-tracking port.

- CTxMemPool::TrimToSize(sizelimit, pvNoSpendsRemaining): while DynamicMemoryUsage() is
  over the limit, evict the lowest-feerate tx (the tail of the feerate index) and its
  in-mempool descendants (recursive remove), re-deriving the tail each iteration.
  Terminates (pool strictly shrinks) and cleans every secondary index via remove().
- CTxMemPool::Expire(time): age-based sweep (entry time older than `time`), for
  LimitMempoolSize's -mempoolexpiry.
- LimitMempoolSize re-enabled (Expire + TrimToSize) and called from ConnectTip on every
  block connect. (No pcoinsTip->Uncache -- CCoinsViewCache has none in this fork; it is
  only a UTXO-cache perf hint.)
- AcceptToMemoryPool now ADDS the tx then TrimToSizes: a higher-fee tx displaces
  lower-fee ones; if this tx was itself the lowest-feerate (evicted), it is rejected
  ("mempool full"). Replaces the blunt reject-when-full cap.
- DEFAULT_MEMPOOL_EXPIRY 1 -> 72 hours (age-Expire is now live; 1h was too aggressive).

Known simplification (documented in code): per-tx feerate, not descendant-aggregate
(CPFP) scoring, and no rollingMinimumFeeRate anti-thrash. Adversarially reviewed
(termination, iterator safety, recursive-lock safety, index cleanup all confirmed) and
runtime-tested on the fleet: pool stays bounded under a 1600-tx flood, verifychain ok,
no hang/crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:01:06 +02:00
1ec3dbfee3 docs: note BIP39 cross-wallet restore parity is mainnet-only in -mnemonic help
The 24-word seed restores the same wallet in SilentDragonXLite only on mainnet;
testnet/regtest derive a different HD coin_type (per BIP44), so a phrase does not
round-trip across wallets there. Document that in the -mnemonic help so it is not
mistaken for a bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 08:10:35 +02:00
a568ab628e tune: raise adaptive dbcache ceiling to 64 GiB on 64-bit hosts
nMaxDbCache capped the adaptive UTXO/db cache (and a manual -dbcache) at 16 GiB, so the
help's "uses most of free RAM" was false above ~20 GB of RAM. Raise the 64-bit ceiling to
64 GiB. The adaptive controller + its RAM reserve still bound actual usage and shrink under
memory pressure, and small hosts are unaffected -- the ceiling only binds once RAM-minus-
reserve exceeds it. The coins cache grows lazily to the target, so nothing is pre-allocated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 06:59:25 +02:00
693d2290e0 fix: release RandomX pre-verify cache at shutdown; tune verify-threads help + -maxblocksintransit ceiling
Three small cleanups to the parallel RandomX pre-verify + P2P-window features:

- Call RandomXValidatorShutdown() in Shutdown() to release the ~256MB shared RandomX
  verify cache. It was allocated on first use but never freed, leaking at every exit.
  Safe here: threadGroup.interrupt_all() (earlier in Shutdown) stops the pre-verify
  worker, and the release takes g_rxvMutex so it can't race a mid-flight verify.
- Clarify the -randomxverifythreads help: the pool only helps NETWORK sync, not reindex
  (reindex runs with a window of 1, so the pool does nothing there).
- Clamp -maxblocksintransit to the real BLOCK_DOWNLOAD_WINDOW (1024) ceiling instead of a
  misleading 4096. Values above the window are a silent no-op (FindNextBlocksToDownload
  never fetches beyond pindexLastCommonBlock + BLOCK_DOWNLOAD_WINDOW); log when clamping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 06:59:25 +02:00
389c8c7383 fix: cap mempool memory usage (-maxmempool) to bound an OOM DoS
This fork never ported Bitcoin's fee-ordered mempool eviction: CTxMemPool has no
TrimToSize/Expire, and LimitMempoolSize's body + call site are both commented out and
reference an undefined DEFAULT_MAX_MEMPOOL_SIZE. So the mempool had no total-size
ceiling. Together with the just-restored removeExpired() and near-free tx admission, a
peer could flood transactions to exhaust every node's memory (incl. pool/payout nodes).

Add a simple admission cap in AcceptToMemoryPool: once the pool exceeds -maxmempool it
refuses new admissions with DoS(0) (no ban -- a full pool isn't the peer's fault). This
is not fee-ordered eviction (that needs the absent TrimToSize machinery) but it bounds
the footprint; removeExpired() already evicts unmineable expired txs on each block
connect. New DEFAULT_MAX_MEMPOOL_SIZE=300 (MB, Bitcoin's default) is far above DragonX's
normal mempool, so normal operation is unaffected. Reviewed: bytes-vs-bytes comparison,
read under LOCK(pool.cs) on a recursive mutex (no deadlock); only the reorg re-add path
and new sends route through it, and only at 300MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:58:39 +02:00
4e3c0f8f6f fix: apply real DoS score for invalid headers (Misbehaving nDoS/nDoS was always 1)
In the HEADERS handler, an invalid header scored Misbehaving(id, nDoS/nDoS). Because
the call is guarded by `if (nDoS > 0 ...)`, nDoS/nDoS is always exactly 1, so every
invalid header cost a fixed 1 misbehavior point regardless of severity -- it took
~banscore (default 101) invalid headers to ban a peer instead of 1, effectively
disarming the ban backstop against header spam. The two sibling call sites in the
same handler (tx-accept, block-accept) already pass nDoS directly.

Pass the real nDoS so a genuinely-invalid header (e.g. bad-diffbits, DoS 100) bans in
one message. Cannot over-ban honest peers: every DoS>0 header path is genuinely
invalid consensus, and benign/racy headers (unconnectable prevblock, future block,
clock-skew) either score DoS 0 or never reach Misbehaving (double-guarded by
IsInvalid + nDoS>0 + futureblock==0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:29:54 +02:00
2e54d9fb4d fix: restore removeExpired() mempool scan (was a no-op -> unbounded mempool DoS)
CTxMemPool::removeExpired() declared `transactionsToRemove` and looped over it
without ever populating it -- the mapTx scan that collects expired txs had been
dropped, so it evicted nothing. Expired txs (past nExpiryHeight) can never be mined
yet were never removed, so a peer could wedge them into every node's mempool
permanently at ~zero cost (never mined -> never pay a fee), growing the mempool
without bound: a memory-exhaustion DoS against every node (incl. pool/payout nodes).

Restore the upstream Zcash/Komodo scan: iterate mapTx, collect txs failing
IsExpiredTx(tx, tipHeight) into a separate list, then remove() them (collect-then-
remove avoids iterator invalidation; recursive=true also evicts the now-unmineable
descendants). Also drops an unused CBlockIndex* local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:29:54 +02:00
4238da9bea fix: cap indexed-node block-tree dbcache so adaptive dbcache feeds the UTXO set
With -addressindex/-spentindex, nBlockTreeDBCache was set to 3/4 of nTotalCache. That
rule was sized for the old fixed 512 MiB dbcache default (~384 MiB), but adaptive
dbcache now makes nTotalCache multi-GB, so on indexed pool/explorer nodes ~3/4 of
several GB was diverted to the block-index LevelDB read cache -- far more than it can
use -- while starving the in-memory UTXO set that actually speeds IBD, and that chunk
is not shrinkable by the memory-pressure controller.

Measured on an 8 GiB box with -addressindex: 4420 MiB block-index cache + 1097 MiB
UTXO set, vs 3859 MiB UTXO on a plain node. Cap the index cache at 1 GiB (ample for
the index read cache; tunable) so the adaptive budget flows to the coins cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:26:30 +02:00
4e67e687d7 perf: verify each block's RandomX solution once, not twice, during sync
RandomX PoW verification is ~84% of block-connect wall time during network IBD, and
CheckBlock was recomputing it TWICE per block: once in CheckBlockHeader and again in
hush_checkPOW (which has no CBlockIndex, so it cannot use the fRandomXVerified dedup
the parallel pre-verify pool relies on). Skip the redundant recompute inside
hush_checkPOW: CheckBlockHeader runs first in CheckBlock and rejects an invalid
solution before hush_checkPOW is reached, so the block is already verified once.
Equihash, PoW-target and notary checks in hush_checkPOW still run.

A scoped guard (ScopedRandomXSkip) SAVES and RESTORES the thread-local
fSkipRandomXValidation, so it neither clobbers the miner's own skip
(TestBlockValidity -> ConnectBlock re-entry, which would otherwise force the ~256MB
inline RandomX alloc the miner deliberately avoids) nor leaks the flag on an exception.

Measured on an isolated RandomX test chain: RandomX verifies per block 2.0 -> 1.03
(~40% faster network sync). The 2x behavior pre-exists in v1.0.2. Consensus-neutral:
RandomXPreVerify.ConsensusEquivalence gtest passes; each block is still verified
exactly once by CheckBlockHeader.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:26:30 +02:00
762e25294f fix: guard BuildWitnessCache against an off-active-chain pindex (heap overflow)
BuildWitnessCache sizes its blockCms buffer from pindex->GetHeight() but the
Phase-1 loop walks the active chain (chainActive.Next), terminating only on
pbi==pindex. If a reorg moved pindex off the active chain while the notify
thread lagged (cs_main is released between per-block ChainTip calls) and the new
active tip is taller, the loop never reaches pindex and, once past pindex's
height, writes blockCms[h-startHeight] out of bounds -- a heap overflow.
Rebuilding witnesses for an abandoned block is meaningless anyway, so bail early
when pindex is not on the active chain; cs_main is held for the whole function,
so the check cannot race the loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
3bb4eb3a5a fix: harden ThreadNotifyWallets IBD read-retry (crash + abandoned disconnects)
Two defects in the IBD ReadBlockFromDisk retry path of ThreadNotifyWallets:

- The connect-loop rebuild indexed recentlyConflicted.first.at(pindex), which
  throws std::out_of_range for a block whose conflict entry was drained in an
  earlier retry cycle. Uncaught under cs_main in the notify boost thread, that
  aborts the node and crash-loops. Use operator[] (empty-list default), i.e.
  best-effort conflict notifications, instead of throwing.

- The disconnect-loop IBD break fell through into the connect loop, which
  advanced pindexLastTip to the new tip and permanently abandoned the pending
  disconnect notifications (leaving wallet witness/anchor state desynced from
  the chain). Add a flag so the break skips the connect loop this cycle and
  truly retries the disconnect on the next one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
7914fca0f2 fix: gate the RandomX PoW-verification skip on the in-index checkpoint
RandomXValidationRequired skipped RandomX verification for blocks below the
static top checkpoint (GetTotalBlocksEstimate), while the fork-rejection guard
uses the in-index checkpoint (GetLastCheckpoint). Once the checkpoint list
extends above the RandomX activation height, that asymmetry opens a gap during
IBD/eclipse in which a peer with no RandomX hashpower can get SHA256-grinded,
RandomX-forged blocks accepted (CheckProofOfWork hashes the header including the
attacker-controlled nSolution). Gate the skip on GetLastCheckpoint()->GetHeight()
so a block is PoW-exempt only when provably below a checkpoint the node has
locked into its index -- the same boundary the fork guard uses. Currently inert
(top checkpoint 2838000 < activation 2838976) but becomes live at the next
checkpoint refresh; the check runs under cs_main and only ever adds verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
810bd6712f fix: make nCoinCacheUsage std::atomic to close an adaptive-dbcache data race
The scheduled AdjustCoinCacheForMemoryPressure task writes nCoinCacheUsage from
the scheduler thread holding no lock, while cs_main-holding threads
(FlushStateToDisk, VerifyDB) read it -- an unsynchronized read/write of a
non-atomic size_t (C++ UB). Make it std::atomic<size_t>; correct the comment
that incorrectly described the access as lock-free/race-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
bf5b066a8d fix: evaluate wolfSSL_pending() under cs_hSocket in the recv-drain loop
The bulk-streaming recv-drain loop called wolfSSL_pending(pnode->ssl) after
releasing cs_hSocket, racing with SocketSendData (wolfSSL_write) and
CloseSocketDisconnect (wolfSSL_free) on the same TLS session -- a data race and
potential use-after-free on any TLS peer. Capture the pending-byte count inside
the cs_hSocket-locked block and use the captured value for the drain decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
19e1ce6f00 fix: self-heal a corrupt/torn notarizations DB instead of aborting startup
A torn or corrupt notarizations (dPoW) leveldb -- a 0-byte log left by a torn
snapshot, or a corrupt MANIFEST -- threw at open and was caught by the block-DB
load try/catch, aborting startup with a misleading Error-opening-block-database
message and forcing a full resync. The notarizations DB is non-essential and
node-regenerable, so on open failure move it aside (notarizations.corrupt,
preserving the data in case the error was transient) and regenerate a fresh one;
if the fresh recreate also fails it still propagates as fatal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 05:48:39 +02:00
dc45e7d904 Harden ProcessGetData: log+disconnect instead of asserting on block-read failure
The legacy getdata block-serving path asserted whenever ReadBlockFromDisk failed for a block we had advertised (BLOCK_HAVE_DATA). A single transient I/O error or on-disk corruption, triggerable by any peer getdata, crashed the whole node (observed once during bulk-serve load testing on 176). Now log the failure and disconnect that peer so it can re-fetch from another node, matching the bulk GETBLOCKSTREAM serve path which already fails gracefully. Build-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 08:08:34 +02:00
1ed468e040 Merge remote-tracking branch 'origin/ibd-wallet-notify-fix' into dev 2026-07-07 07:43:58 +02:00
53b1fe332b Rebrand cleanups: getpeerinfo help example + 1.0.3 debian changelog entry
net.cpp: getpeerinfo help address example 18030->21768 and 'Hush server'->'DragonX server'. debian/changelog: prepend 1.0.3 release entry summarizing IBD speedups, witness fix, bulk streaming, seed phrases, assumeutxo removal. NOTE net.cpp change needs a daemon rebuild to surface in runtime RPC help. Staged on 176; not pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 06:35:46 +02:00
9c6ccbb726 Fix dragonx-cli -rpcport help default: 18030 (hush) -> 21769 (DragonX)
The -rpcport help string in bitcoin-cli.cpp hardcoded hush's 18030; the actual default (BaseParams().RPCPort()) is DragonX's 21769, so this was misleading help text only (the CLI already connects to 21769). Set to 21769 and regenerated doc/man/dragonx-cli.1 from the rebuilt binary. NOTE: a separate hush 18030 leftover remains in src/rpc/net.cpp:357 (getpeerinfo help example address) - daemon RPC help, out of scope here. Staged on 176; not pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 06:35:46 +02:00
34432e5848 Harvest DragonX packaging + legal artifacts from compliant-rebrand
Legal: correct GPLv3 LICENSE (fixes garbled 'GENERAL GENERAL'), AUTHORS DragonX attribution, COPYING. Packaging: man pages REGENERATED from the 1.0.3 binaries via help2man (dragonxd/dragonx-cli/dragonx-tx.1 -> v1.0.3, correct dates), wired into doc/man/Makefile.am (dist_man1_MANS), orphaned hush*.1 removed. Init/openrc/systemd scripts, Debian packaging (control/changelog/copyright rebranded hush->dragonx + install stubs), example confs taken from origin/compliant-rebrand (c05134e77). REMAINING follow-ups: (1) debian/changelog still tops at 1.0.0 - add a 1.0.3 entry; (2) dragonx-cli --help hardcodes rpcport default 18030 (hush) - fix the HelpMessage string in source then regen. Staged on 176 for review; not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 06:35:46 +02:00
4caf2fc68f Add BIP39 seed phrases (SilentDragonXLite-compatible) and HD transparent keys
Derive transparent (t-addr) keys from the HD seed and add BIP39 mnemonic seed
phrases that are byte-for-byte compatible with SilentDragonXLite, so the same
24 words recover the same shielded and transparent addresses in either wallet.

HD transparent keys:
- Derive t-keys from the seed at m/44'/coin'/0'/0/i (were random CKeys).
- CHDChain gains a version-gated transparent counter; existing wallets load
  unchanged. GenerateNewKey routes through DeriveNewChildKey when enabled
  (-hdtransparent, default on).
- Restore from a seed hex via -hdseed with gap-limit pre-derivation; birthday
  pinned to genesis so the rescan is not clipped.

BIP39 seed phrases:
- Wire the vendored trezor BIP39 lib (src/crypto/bip39) into the build, fix its
  BIP39_WORDS guard, and disable the insecure mnemonic cache.
- Match SDXLite exactly: English wordlist, empty passphrase, PBKDF2 64-byte
  seed, coin type 141, ZIP-32 m/32'/141'/i' and BIP44 m/44'/141'/0'/0/i. Store
  the 32-byte entropy and expand to the 64-byte seed on demand.
- Restore via -mnemonic, create via -usemnemonic, reveal via z_exportmnemonic.

Verified by gtests including a known-answer BIP39 seed vector and z/t address
derivation checks (src/gtest/test_hdtransparent.cpp, test_mnemonic_compat.cpp).
Docs in doc/hd-transparent-keys.md and doc/seed-phrase.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 01:57:18 -05:00
84aefb5475 Remove assumeutxo / UTXO-snapshot feature
Removes the dumptxoutset RPC, -loadutxosnapshot / -loadutxosnapshotunsafe,
the CCoinsViewDB Dump/LoadSnapshot machinery + CUTXOSnapshotHeader, the
AssumeutxoData chainparams anchor, the LoadSnapshotChainstate activation +
reorg-below-H guard, the persisted assumeutxo-height flag, and the gtest.

Rationale: it duplicated the existing bootstrap (same skip-the-genesis-grind
fast-sync, no speed advantage), its only real edge was a trust model we don't
need for this chain, and it was inert anyway (no published snapshot hash in
chainparams). The -loadutxosnapshot load path adopted an external UTXO set and
bypassed genesis validation, so removing it also drops that attack surface.
Builds clean (no dangling references); the kept IBD speedups (RandomX
pre-verify, adaptive dbcache, tlsmanager) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:42:37 -05:00
1f2b109d95 Add opt-in bulk block streaming (-bulkblocksync)
A single getblockstrm request makes a peer stream a contiguous range of old
blocks back-to-back as ordinary BLOCK messages, amortizing the per-block
round-trip over the whole range instead of the MAX_BLOCKS_IN_TRANSIT_PER_PEER
window. This targets the bandwidth-delay-product ceiling that dominates IBD
from few/high-latency peers below the checkpoint.

Design (off by default; negotiated via a NODE_BULKBLOCKS service bit; the
default getdata IBD path is untouched when disabled):
- protocol: NODE_BULKBLOCKS service bit + getblockstrm/blockstream messages.
- requester: in SendMessages, after FindNextBlocksToDownload, when the first
  needed block is >= BULK_TIP_MARGIN (5000) below the network tip and the peer
  advertises the bit and we are in IBD, request a contiguous range (<=128
  blocks) instead of per-block getdata; mark the range in-flight.
- server: stream the range (caps 128 blocks / 8 MiB; reads outside cs_main;
  per-peer flood throttle), then a trailing blockstream header with the actual
  count sent. Self-suppresses while the server itself is in IBD.
- received blocks ride the existing BLOCK -> ProcessNewBlock path (fully
  validated; checkpoints below 2.84M still apply); the trailing header
  reconciles partial deliveries and the range is freed on a 90s timeout, so a
  partial/withheld/refused batch falls back to the normal path (no leak, no
  permanent gap, no disconnect). In-flight tracking is by literal hash, so a
  reorg cannot orphan range entries.

Hardened against the issues found in two adversarial review passes (drain vs
timeout, partial reconciliation, ownership-guarded frees, one-shot header,
reorg-proof helpers, cs_main hold). Validated end-to-end between two local
v1.0.3 nodes (128/128 and partial serves; height advanced; no errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 21:22:54 -05:00
78ea2aac5b Add -maxblocksintransit: tunable per-peer block-download window
The per-peer in-flight block window (MAX_BLOCKS_IN_TRANSIT_PER_PEER) was a
hardcoded 16. On a single, high-latency peer during IBD the transfer is
bandwidth-delay-product bound (window / RTT), so with tiny sub-checkpoint
blocks the window, not bandwidth, is the ceiling — measured ~4x throughput
going 16 -> 64 on a 350ms-RTT peer. Make it a runtime flag (default 16,
clamped 1..4096), logged at startup. No behavior change at the default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 21:22:32 -05:00
2419ed7bf7 Fix flaky build: make version-probe pipes SIGPIPE-safe
util/build.sh runs with `set -eu -o pipefail`. `eval "$MAKE" --version | head -n2`
(and the analogous `as --version | head`) can race: head closes the pipe after N
lines, make/as catch SIGPIPE and exit non-zero, pipefail propagates the failure,
and errexit aborts the build before any compilation. Append `|| true` so these
purely-informational version prints can never fail the build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 21:21:29 -05:00
a9b1b4085f Fix automake -lcurl portability lint in Makefile
LIBBITCOIN_SERVER was fed into both EXTRA_LIBRARIES (a list of buildable
library files) and several _LDADD link lines. Embedding the -lcurl linker
flag inside it made automake reject it in the EXTRA_LIBRARIES context
("'-lcurl' is not a standard library name"). Make LIBBITCOIN_SERVER a pure
file and route -lcurl through its own LIBCURL variable, added to the
dragonxd, hush-gtest, and test_bitcoin link lines after libbitcoin_server.a
(whose objects reference curl symbols) so static link order stays correct.

Verified with a clean Windows cross-build (-DCURL_STATICLIB) and a native
Linux build: both link cleanly and the automake lint is gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:58:31 -05:00
f763e3f1e7 Merge ibd-sync-speedups into dev
Parallel RandomX PoW pre-verify, adaptive dbcache, UTXO snapshot, P2P/TLS sync fixes.
2026-06-28 16:11:38 -05:00
f8f13f9027 Merge sapling-witness-rebuild-fix into dev
Sapling witness desync fix + parallel witness-cache rebuild + version bump to 1.0.3.
2026-06-28 16:11:38 -05:00
bf1b4cffe0 Bump version to 1.0.3
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:04:51 -05:00
82d77344d2 Fix Sapling witness desync and parallelize witness cache rebuild
Wallets upgraded across the 1.0.1->1.0.2 network transition could end up
with note witnesses stuck at a stale height, causing z_sendmany /
z_mergetoaddress to fail to build a valid spend. Root cause was a trio of
issues that let a desynced witnessHeight perpetuate instead of self-healing:

- DecrementNoteWitnesses left witnessRootValidated and the witness deque in
  an asymmetric state on the size<=1 path.
- VerifyAndSetInitialWitness blindly trusted witnessHeight instead of
  validating the cached root against the chain, so a bad height survived.
- UpdatedNoteData copied witnessHeight even when no witnesses were present.
- witnessRootValidated was uninitialized and never serialized, so a garbage
  true value could short-circuit the self-heal.

Fixes:
- Default witnessRootValidated to false (in-memory only; never serialized).
- VerifyAndSetInitialWitness now validates the cached witness root against
  the block's hashFinalSaplingRoot and reseeds on mismatch.
- Symmetric reset of witness state in DecrementNoteWitnesses.
- Guard the witnessHeight copy in UpdatedNoteData behind a non-empty
  witnesses check.
- Defensive majority-root guard in GetSaplingNoteWitnesses.

Also rewrites BuildWitnessCache to rebuild the witness cache in parallel
(per-block commitment extraction + worker pool), cutting a full repair from
~28 min to ~2 min. Tunable via -witnessbuildthreads and -witnessfastrebuild;
output verified byte-identical to the serial path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:03:23 -05:00
1673cfb6dc IBD/sync speedups: parallel RandomX pre-verify, adaptive dbcache, P2P download fixes
- Parallel RandomX PoW pre-verification pool (CCheckQueue) run ahead of the serial
  connect; consensus-neutral (inline CheckRandomXSolution fallback still verifies
  anything not pre-verified). New -randomxverifythreads (default = -par).
- Adaptive dbcache: default sizes the UTXO/coins cache to most of RAM and shrinks
  under memory pressure, always leaving a reserve free; -dbcache pins a fixed value.
- P2P block download: bounded socket recv-drain loop (tlsmanager); frontier-block
  reassignment to break head-of-line stalls (-blockreassigntimeout); ProcessGetData
  serves a bounded batch of blocks per pass instead of one (fixes the serve-side
  one-block-per-tick throttle that caps download network-wide).
- assumeutxo: dumptxoutset RPC + LoadSnapshot machinery + AssumeutxoData chainparams.
- Signed bootstrap verification (util/bootstrap-dragonx.sh, util/sign-bootstrap.md).
- gtest: RandomX pre-verify consensus-equivalence test + UTXO-snapshot round-trip;
  revived the gtest harness (Makefile.am include fix, Makefile.gtest.include).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:30:10 -05:00
2f3f320d28 Handle ReadBlockFromDisk failure during IBD gracefully
During Initial Block Download, block data may not be flushed to disk
when the wallet notification thread tries to read it. Instead of
crashing with a fatal error, log a message and retry on the next cycle.
2026-03-27 14:01:50 -05:00
2b011d6ee2 fix windows build 2026-03-19 10:09:18 -05:00
M
d77088c1f2 Fix macOS Sequoia build with GCC 15 and update README
- Update compiler references from gcc-8 to gcc-15 across build system
  (build-mac.sh, darwin.mk, Makefile_custom)
- Use system Rust (rustup) instead of bundled Rust 1.32.0 for librustzcash
  to fix rlib linker incompatibility on macOS Sequoia
- Replace deprecated std::random_shuffle with std::shuffle (net.cpp,
  transaction_builder.cpp, wallet.cpp)
- Fix -std=gnu17 -> -std=gnu++17 for C++ targets (libzcash, libhush)
- Fix nodiscard warning in glibcxx_sanity.cpp
- Replace deprecated OSMemoryBarrier with std::atomic_thread_fence in LevelDB
- Add -Wno-error=deprecated-declarations to CXXFLAGS for third-party headers
- Fix REMAINING_ARGS unbound variable in build.sh
- Add --disable-tests handling to build-mac.sh
- Update README with correct macOS build dependencies and instructions
2026-03-19 09:30:50 -05:00
faa3e925cd fix windows bootstrap script, add mirror fallback 2026-03-17 18:37:40 -05:00
ddd851dc11 Bump version to 1.0.2 2026-03-17 04:11:32 -05:00
752590348b Fix sapling pool persistence and add subsidy/fees to getblock RPC
Lower SPROUT_VALUE_VERSION and SAPLING_VALUE_VERSION constants in
chain.h from upstream Zcash values (1001400/1010100) to 1000000.
When DragonX was rebranded from HUSH3, CLIENT_VERSION was reset from
3.10.5 to 1.0.0, falling below these thresholds. This caused
nSaplingValue to silently skip serialization, so the sapling pool
total reset to 0 on every node restart. Explorer nodes should reindex
once after upgrading.

Add subsidy and fees fields to the getblock RPC response so explorers
can display the correct 3 DRGX block reward separately from fees,
instead of showing the combined coinbase output as the reward.
2026-03-15 16:07:16 -05:00
f0cb958cac Fix fresh sync failure at diff reset height 2838976
Fresh-syncing nodes rejected the on-chain min-diff block at the
RANDOMX_VALIDATION activation height (2838976) because GetNextWorkRequired
computed the expected nBits from the preceding normal-difficulty blocks,
producing 469847994 instead of the on-chain 0x200f0f0f (HUSH_MINDIFF_NBITS).
This caused all seed nodes to be banned with "Incorrect diffbits" and the
node could never sync past that height.

Two changes:

1. GetNextWorkRequired (pow.cpp): Return nProofOfWorkLimit at the exact
   RANDOMX_VALIDATION activation height, matching the on-chain diff reset.

2. ContextualCheckBlockHeader (main.cpp): Raise DragonX daaForkHeight to
   RANDOMX_VALIDATION + 62000, covering the window where nBits was never
   validated (diff reset at 2838976 through the attack at ~2879907).

Tested by invalidating block 2838975 and reconsidering — node re-validated
through the diff reset and attack window, syncing back to tip with zero
bad-diffbits rejections.

Bump version to 1.0.1.
2026-03-12 01:25:21 -05:00
6d56ad8541 Add --linux-compat build option for Ubuntu 20.04 binaries
Build release binaries inside an Ubuntu 20.04 Docker container
to produce executables with lower GLIBC requirements, compatible
with older Linux distributions.

- Add Dockerfile.compat (Ubuntu 20.04 base, full depends rebuild)
- Add .dockerignore to exclude host build artifacts from context
- Add --linux-compat flag to build.sh with Docker build/extract/package
- Strip binaries inside container to avoid root ownership issues
2026-03-10 19:39:55 -05:00
449a00434e test scripts 2026-03-10 17:07:16 -05:00
5cda31b505 update checkpoints 2026-03-09 16:39:00 -05:00
ec517f86e6 update checkpoints again 2026-03-09 16:29:55 -05:00
33e5f646a7 update checkpoints 2026-03-06 18:10:31 -06:00
c1408871cc Fix Windows cross-compilation linker error and gitignore .exe artifacts 2026-03-05 05:22:44 -06:00
0a01ad8bba Fix nBits validation bypass and restore CheckProofOfWork rejection for HACs
Two critical vulnerabilities allowed an attacker to flood the DragonX chain
with minimum-difficulty blocks starting at height 2879907:

1. ContextualCheckBlockHeader only validated nBits for HUSH3 mainnet
   (gated behind `if (ishush3)`), never for HAC/smart chains. An attacker
   could submit blocks claiming any difficulty and the node accepted them.
   Add nBits validation for all non-HUSH3 smart chains, gated above
   daaForkHeight (default 450000) to maintain consensus with early chain
   history that was mined by a different binary.

2. The rebrand commit (85c8d7f7d) commented out the `return false` block
   in CheckProofOfWork that rejects blocks whose hash does not meet the
   claimed target. This made PoW validation a no-op — any hash passed.
   Restore the rejection block and add RANDOMX_VALIDATION height-gated
   logic so blocks after the activation height are always validated even
   during initial block loading.

Vulnerability #1 was inherited from the upstream hush3 codebase.
Vulnerability #2 was introduced by the DragonX rebrand.
2026-03-05 03:09:38 -06:00
85c8d7f7dd Rebrand hush3 to DragonX and share RandomX dataset across mining threads
Minimal rebrand (see compliant-rebrand branch for full rebrand):
- Rename binaries: hushd/hush-cli/hush-tx → dragonxd/dragonx-cli/dragonx-tx
- Default to DRAGONX chain params without -ac_* flags (randomx, blocktime=36, private=1)
- Update configure.ac: AC_INIT([DragonX],[1.0.0])
- Update client version string and user-agent to /DragonX:1.0.0/
- Add chainparams.cpp with DRAGONX network parameters
- Update build.sh, miner.cpp, pow.cpp for DragonX
- Add bootstrap-dragonx.sh utility script
- Update .gitignore for release directory

Share single RandomX dataset across all mining threads:
- Add RandomXDatasetManager with readers-writer lock, reducing RAM from
  ~2GB per thread to ~2GB total plus ~2MB per thread for the VM scratchpad
- Add LogProcessMemory() diagnostic helper for Linux and Windows
2026-03-04 18:42:42 -06:00
d6ba1aed4e Fix RandomX validation exploit: verify nSolution contains valid RandomX hash
- Add CheckRandomXSolution() to validate RandomX PoW in nSolution field
- Add ASSETCHAINS_RANDOMX_VALIDATION activation height per chain
  (DRAGONX: 2838976, TUMIN: 1200, others: height 1)
- Add CRandomXInput serializer for deterministic RandomX hash input
- Fix CheckProofOfWork() to properly reject invalid PoW (was missing
  SMART_CHAIN_SYMBOL check, allowing bypass)
- Call CheckRandomXSolution() in hush_checkPOW and CheckBlockHeader

Without this fix, attackers could submit blocks with invalid RandomX
hashes that passed validation, as CheckProofOfWork returned early
during block loading and the nSolution field was never verified.
2026-03-03 17:28:49 -06:00
Duke
7e1b5701a6 Merge branch 'dev' 2026-03-02 12:10:02 -05:00
Duke
4cbad44688 Update debian changelog 2026-03-02 12:08:19 -05:00
Duke
876c32ed1f Add ac_clearnet to relnotes 2026-03-02 12:00:35 -05:00
Duke
f889ded55e Update release process doc 2026-03-02 11:51:48 -05:00
Duke
07738d75ab Update man pages for 3.10.5 2026-03-02 11:49:27 -05:00
Duke
04916cdf57 update release process doc 2026-03-02 11:43:31 -05:00
Duke
0d139e0bdc Update relnotes and release process doc 2026-03-02 11:41:19 -05:00
Duke
4c86d11b13 Update relnotes 2026-02-28 12:18:38 -05:00
Duke
978d4d739b Ignore configure backups 2026-02-28 12:13:06 -05:00
115 changed files with 8917 additions and 680 deletions

27
.dockerignore Normal file
View File

@@ -0,0 +1,27 @@
.git
release
depends/built
depends/work
depends/x86_64-unknown-linux-gnu
depends/x86_64-w64-mingw32
src/RandomX/build
src/*.o
src/*.a
src/*.la
src/*.lo
src/.libs
src/.deps
src/univalue/.libs
src/univalue/.deps
src/cc/*.o
src/cc/*.a
src/dragonxd
src/dragonx-cli
src/dragonx-tx
src/dragonxd.exe
src/dragonx-cli.exe
src/dragonx-tx.exe
sapling-output.params
sapling-spend.params
config.status
config.log

10
.gitignore vendored
View File

@@ -28,6 +28,7 @@ build-aux/test-driver
config.log
config.status
configure
configure~
libtool
src/config/bitcoin-config.h
src/config/bitcoin-config.h.in
@@ -166,3 +167,12 @@ REGTEST_7776
src/cc/librogue.so
src/cc/games/prices
src/cc/games/tetris
release-linux/
release/
src/dragonxd
src/dragonx-cli
src/dragonx-tx
src/dragonxd.exe
src/dragonx-cli.exe
src/dragonx-tx.exe
doc/relnotes/

View File

@@ -1,3 +1,7 @@
# The DragonX Developers
Dan S https://git.dragonx.is/dan
# The Hush Developers
Duke Leto https://git.hush.is/duke https://github.com/leto

View File

@@ -1,3 +1,4 @@
Copyright (c) 2024-2026 The DragonX developers
Copyright (c) 2018-2025 The Hush developers
Copyright (c) 2009-2017 The Bitcoin Core developers
Copyright (c) 2009-2018 Bitcoin Developers

31
Dockerfile.compat Normal file
View File

@@ -0,0 +1,31 @@
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool \
ncurses-dev unzip python3 zlib1g-dev wget bsdmainutils automake cmake \
libcurl4-openssl-dev curl git binutils \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY . /build/
# Clean host-built depends and src artifacts to force full rebuild inside container
RUN rm -rf /build/depends/built /build/depends/work \
/build/depends/x86_64-unknown-linux-gnu \
/build/depends/x86_64-w64-mingw32 \
/build/src/RandomX/build \
&& find /build/src -name '*.o' -o -name '*.a' -o -name '*.la' -o -name '*.lo' \
-o -name '*.lai' | xargs rm -f \
&& rm -rf /build/src/univalue/.libs /build/src/univalue/.deps \
&& rm -rf /build/src/.libs /build/src/.deps \
&& rm -rf /build/src/cc/*.o /build/src/cc/*.a \
&& rm -f /build/config.status /build/config.log
RUN cd /build && ./util/build.sh --disable-tests -j$(nproc)
# Strip binaries inside the container so extracted files are already small
RUN strip /build/src/dragonxd /build/src/dragonx-cli /build/src/dragonx-tx
CMD ["/bin/bash"]

26
LICENSE
View File

@@ -1,4 +1,4 @@
GENERAL GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
@@ -7,15 +7,15 @@
Preamble
The GENERAL General Public License is a free, copyleft license for
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GENERAL General Public License is intended to guarantee your freedom to
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GENERAL General Public License for most of our software; it applies also to
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
@@ -37,7 +37,7 @@ freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GENERAL GPL protect your rights with two steps:
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
@@ -72,7 +72,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GENERAL General Public License.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -549,35 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GENERAL Affero General Public License.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GENERAL Affero General Public License into a single
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GENERAL Affero General Public License,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GENERAL General Public License from time to time. Such new versions will
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GENERAL General
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GENERAL General Public License, you may choose any version ever published
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GENERAL General Public License can be used, that proxy's
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

View File

@@ -27,6 +27,21 @@ the entire history of Hush transactions; depending on the speed of your
computer and network connection, it will likely take a few hours at least, but
some people report full nodes syncing in less than 1.5 hours.
# Fastest way to sync (bootstrap)
The quickest way to get a fully-synced node is the signed bootstrap snapshot, which
installs a pre-built blockchain so you skip re-validating the whole chain from genesis:
```sh
# Stop dragonxd first if it is running, then:
./util/bootstrap-dragonx.sh
```
The script preserves your `wallet.dat` and `DRAGONX.conf`, verifies the download's
checksums and (once a release key is published) its cryptographic signature, then starts
you near the chain tip. If you prefer to sync from the network instead, a larger
`-dbcache` (e.g. `-dbcache=2048`) noticeably speeds up the initial block download.
# Banned by GitHub
In working on this release, Duke Leto was suspended from Github, which gave Hush developers
@@ -106,19 +121,32 @@ apt-get install -y gcc-7 g++-7 && \
# Build on Mac
```
sudo port update
sudo port upgrade outdated
sudo port install qt5
Install Xcode Command Line Tools and [Homebrew](https://brew.sh/), then install dependencies:
```sh
xcode-select --install
brew install gcc autoconf automake pkgconf libtool cmake curl
# Install Rust (needed for librustzcash on macOS Sequoia+)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# clone git repo
git clone https://git.hush.is/hush/hush3
cd hush3
# Build
# This uses 3 build processes, you need 2GB of RAM for each.
# Build (uses 3 build processes, you need 2GB of RAM for each)
# Make sure libtool gnubin and cargo are on PATH
export PATH="$HOME/.cargo/bin:/usr/local/opt/libtool/libexec/gnubin:$PATH"
./build.sh -j3
```
For a release build:
```sh
export PATH="$HOME/.cargo/bin:/usr/local/opt/libtool/libexec/gnubin:$PATH"
./build.sh --mac-release -j$(sysctl -n hw.ncpu)
```
# Installing Hush binaries
1. [Download the release](https://git.hush.is/hush/hush3/releases) with a .deb file extension.

204
build.sh
View File

@@ -1,19 +1,211 @@
#!/usr/bin/env bash
# Copyright (c) 2016-2024 The Hush developers
# Copyright (c) 2024-2026 The DragonX developers
# Distributed under the GPLv3 software license, see the accompanying
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
set -eu -o pipefail
# run correct build script for detected OS
VERSION="1.0.3"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RELEASE_DIR="$SCRIPT_DIR/release"
# Parse release flags
BUILD_LINUX_RELEASE=0
BUILD_WIN_RELEASE=0
BUILD_MAC_RELEASE=0
BUILD_LINUX_COMPAT=0
REMAINING_ARGS=()
for arg in "$@"; do
case "$arg" in
--linux-release)
BUILD_LINUX_RELEASE=1
;;
--linux-compat)
BUILD_LINUX_COMPAT=1
;;
--win-release)
BUILD_WIN_RELEASE=1
;;
--mac-release)
BUILD_MAC_RELEASE=1
;;
--all-release)
BUILD_LINUX_RELEASE=1
BUILD_WIN_RELEASE=1
BUILD_MAC_RELEASE=1
;;
*)
REMAINING_ARGS+=("$arg")
;;
esac
done
# Clean artifacts that may conflict between platform builds
clean_for_platform() {
local platform="$1"
echo "Cleaning build artifacts for $platform build..."
# Use make clean if Makefile exists (safer than manual deletion)
if [ -f src/Makefile ]; then
make -C src clean 2>/dev/null || true
fi
# Remove final binaries
if [ -d src ]; then
rm -f src/dragonxd src/dragonx-cli src/dragonx-tx 2>/dev/null || true
rm -f src/dragonxd.exe src/dragonx-cli.exe src/dragonx-tx.exe 2>/dev/null || true
rm -f src/hushd src/hush-cli src/hush-tx 2>/dev/null || true
rm -f src/hushd.exe src/hush-cli.exe src/hush-tx.exe 2>/dev/null || true
fi
# Clean RandomX build for cross-platform compatibility
rm -rf src/RandomX/build 2>/dev/null || true
# Clean cryptoconditions
rm -rf src/cc/*.o src/cc/*.a 2>/dev/null || true
# Clean config cache (forces reconfigure for cross-platform)
rm -f config.status config.log 2>/dev/null || true
echo "Clean complete for $platform"
}
# Package release for a platform
package_release() {
local platform="$1"
local release_subdir="$RELEASE_DIR/dragonx-$VERSION-$platform"
echo "Packaging release for $platform..."
mkdir -p "$release_subdir"
# Copy bootstrap script (platform-appropriate)
if [ "$platform" = "win64" ]; then
cp "$SCRIPT_DIR/util/bootstrap-dragonx.bat" "$release_subdir/"
else
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$release_subdir/"
fi
# Copy common files
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$release_subdir/" 2>/dev/null || true
cp "$SCRIPT_DIR/sapling-output.params" "$release_subdir/" 2>/dev/null || true
cp "$SCRIPT_DIR/sapling-spend.params" "$release_subdir/" 2>/dev/null || true
case "$platform" in
linux-amd64)
cp "$SCRIPT_DIR/src/dragonxd" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-cli" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-tx" "$release_subdir/"
strip "$release_subdir/dragonxd" "$release_subdir/dragonx-cli" "$release_subdir/dragonx-tx"
;;
win64)
cp "$SCRIPT_DIR/src/dragonxd.exe" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-cli.exe" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-tx.exe" "$release_subdir/"
x86_64-w64-mingw32-strip "$release_subdir/"*.exe 2>/dev/null || strip "$release_subdir/"*.exe 2>/dev/null || true
;;
macos)
cp "$SCRIPT_DIR/src/dragonxd" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-cli" "$release_subdir/"
cp "$SCRIPT_DIR/src/dragonx-tx" "$release_subdir/"
strip "$release_subdir/dragonxd" "$release_subdir/dragonx-cli" "$release_subdir/dragonx-tx" 2>/dev/null || true
;;
esac
echo "Release packaged: $release_subdir"
ls -la "$release_subdir"
}
# Handle release builds
if [ $BUILD_LINUX_COMPAT -eq 1 ] || [ $BUILD_LINUX_RELEASE -eq 1 ] || [ $BUILD_WIN_RELEASE -eq 1 ] || [ $BUILD_MAC_RELEASE -eq 1 ]; then
mkdir -p "$RELEASE_DIR"
if [ $BUILD_LINUX_COMPAT -eq 1 ]; then
echo "=== Building Linux compat release (Ubuntu 20.04 via Docker) ==="
if ! command -v docker &>/dev/null; then
echo "Error: docker is required for --linux-compat builds"
exit 1
fi
# Use sudo for docker if the user isn't in the docker group
DOCKER_CMD="docker"
if ! docker info &>/dev/null 2>&1; then
echo "Note: Using sudo for docker (add yourself to the docker group to avoid this)"
DOCKER_CMD="sudo docker"
fi
DOCKER_IMAGE="dragonx-compat-builder"
COMPAT_PLATFORM="linux-amd64-ubuntu2004"
COMPAT_RELEASE_DIR="$RELEASE_DIR/dragonx-$VERSION-$COMPAT_PLATFORM"
echo "Building Docker image (Ubuntu 20.04 base)..."
$DOCKER_CMD build -f Dockerfile.compat -t "$DOCKER_IMAGE" .
echo "Extracting binaries from Docker image..."
CONTAINER_ID=$($DOCKER_CMD create "$DOCKER_IMAGE")
mkdir -p "$COMPAT_RELEASE_DIR"
for bin in dragonxd dragonx-cli dragonx-tx; do
$DOCKER_CMD cp "$CONTAINER_ID:/build/src/$bin" "$COMPAT_RELEASE_DIR/$bin"
done
$DOCKER_CMD rm "$CONTAINER_ID" >/dev/null
# Fix ownership (docker cp creates root-owned files)
# Binaries are already stripped inside the Docker container
if [ "$(stat -c '%U' "$COMPAT_RELEASE_DIR/dragonxd")" = "root" ]; then
sudo chown "$(id -u):$(id -g)" "$COMPAT_RELEASE_DIR"/dragonx*
fi
# Copy common files
cp "$SCRIPT_DIR/util/bootstrap-dragonx.sh" "$COMPAT_RELEASE_DIR/"
cp "$SCRIPT_DIR/contrib/asmap/asmap.dat" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
cp "$SCRIPT_DIR/sapling-output.params" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
cp "$SCRIPT_DIR/sapling-spend.params" "$COMPAT_RELEASE_DIR/" 2>/dev/null || true
echo "Compat release packaged: $COMPAT_RELEASE_DIR"
ls -la "$COMPAT_RELEASE_DIR"
# Show glibc version requirement
echo ""
echo "Binary compatibility info:"
objdump -T "$COMPAT_RELEASE_DIR/dragonxd" | grep -oP 'GLIBC_\d+\.\d+' | sort -uV | tail -1 && echo "(max GLIBC version required)"
fi
if [ $BUILD_LINUX_RELEASE -eq 1 ]; then
echo "=== Building Linux release ==="
clean_for_platform linux
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
package_release linux-amd64
fi
if [ $BUILD_WIN_RELEASE -eq 1 ]; then
echo "=== Building Windows release ==="
clean_for_platform windows
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
package_release win64
fi
if [ $BUILD_MAC_RELEASE -eq 1 ]; then
echo "=== Building macOS release ==="
clean_for_platform macos
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
package_release macos
fi
echo ""
echo "=== Release builds complete ==="
ls -la "$RELEASE_DIR"/
exit 0
fi
# Standard build (auto-detect OS)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
./util/build.sh --disable-tests $@
./util/build.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
elif [[ "$OSTYPE" == "darwin"* ]]; then
./util/build-mac.sh --disable-tests $@
./util/build-mac.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
elif [[ "$OSTYPE" == "msys"* ]]; then
./util/build-win.sh --disable-tests $@
#elif [[ "$OSTYPE" == "freebsd"* ]]; then
# placeholder
./util/build-win.sh --disable-tests ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
else
echo "Unable to detect your OS. What are you using?"
fi

View File

@@ -1,23 +1,23 @@
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 3)
define(_CLIENT_VERSION_MAJOR, 1)
dnl Must be kept in sync with src/clientversion.h , ugh!
define(_CLIENT_VERSION_MINOR, 10)
define(_CLIENT_VERSION_REVISION, 5)
define(_CLIENT_VERSION_MINOR, 0)
define(_CLIENT_VERSION_REVISION, 3)
define(_CLIENT_VERSION_BUILD, 50)
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2026)
AC_INIT([Hush],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.hush.is/hush/hush3],[hush])
AC_INIT([DragonX],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://git.dragonx.is/DragonX/dragonx],[dragonx])
AC_CONFIG_SRCDIR([src/main.cpp])
AC_CONFIG_HEADERS([src/config/bitcoin-config.h])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([build-aux/m4])
BITCOIN_DAEMON_NAME=hushd
BITCOIN_CLI_NAME=hush-cli
BITCOIN_TX_NAME=hush-tx
BITCOIN_DAEMON_NAME=dragonxd
BITCOIN_CLI_NAME=dragonx-cli
BITCOIN_TX_NAME=dragonx-tx
dnl Unless the user specified ARFLAGS, force it to be cr
AC_ARG_VAR(ARFLAGS, [Flags for the archiver, defaults to <cr> if not set])

View File

@@ -1,3 +1,50 @@
dragonx (1.0.3) stable; urgency=medium
* IBD/sync speedups: parallel RandomX pre-verification, adaptive -dbcache, P2P download fixes
* Fix Sapling witness desync and parallelize witness cache rebuild
* Opt-in bulk block streaming (-bulkblocksync) for faster initial sync
* BIP39 seed phrases (SilentDragonXLite-compatible) and HD transparent keys
* Remove assumeutxo / UTXO-snapshot feature
-- DragonX <dan-s-dev@proton.me> Tue, 07 Jul 2026 05:49:59 +0200
dragonx (1.0.0) stable; urgency=medium
* Initial release of DragonX, forked from Hush Full Node
* Full legal-compliant rebrand: binaries, config, documentation
* RandomX proof-of-work, 36-second block time, fully shielded transactions
* New binary names: dragonxd, dragonx-cli, dragonx-tx
-- DragonX <dan-s-dev@proton.me> Mon, 03 Mar 2026 00:00:00 +0000
hush (3.10.5) stable; urgency=medium
* DragonX is no longer supported by this codebase
* DragonX devs have forked our code and we wish them the best
* Find the latest place to download a DragonX full node via dragonx.is
* Concurrent `z_sendmany` now works
* A longstanding bug relating to run multiple `z_sendmany` operations at
once has been fixed. You can now queue up many `z_sendmany` operations
and they will succeed because they now understand how to avoid spending
coins that another `z_sendmany` process is trying to spend.
* Updated Autonomous System Map (asmap)
* New RPC `z_listlockunspent`
* Lists shielded notes (coins inside a zaddr) which are temporarily unspendable because an RPC process is currently trying to spend them.
* If that operation succeeds, they will become spent. If it fails they will be unlocked and become spendable again.
* New option to `z_shieldcoinbase` allows privately donating a percentage of coinbase funds during shielding
* This new option defaults to OFF and allows CLI users to opt-in to donating between 0% and 10% of coinbase funds to developers
* No GUI currently utilizes this but that feature is planned for SD
* The donation has extremely good privacy:
* It cannot be determined from public blockchain data if a donation is being made, as it takes the place of a Sietch output
* It cannot be determined from public blockchain data the amount of the donation
* Donations do not create new transactions, do not use additional blockspace and cannot be detected by anyone but the sender or reciever
* New HAC option `ac_clearnet` can be used to disable clearnet networking for an entire blockchain instead of just a single node
* Updated test framework and tests which allowed the fixing of the `z_sendmany` bug above
* Faster compiling of RandomX internals
* GMP dependency removed, as it is no longer needed
* Hush is now compatible with GCC15 and now correctly supports customizing the compiler for a build via the CC env var
* New HTML man pages are now available at doc/man/hushd.html and doc/man/hush-cli.html
hush (3.10.4) stable; urgency=medium
* Updated seed node list

View File

@@ -1,18 +1,18 @@
Source: hush
Source: dragonx
Section: utils
Priority: optional
Maintainer: Hush <myhushteam@gmail.com>
Homepage: https://hush.is
Maintainer: DragonX <dan-s-dev@proton.me>
Homepage: https://dragonx.is
Build-Depends: autoconf, automake, bsdmainutils, build-essential,
cmake, curl, git, g++-multilib, libc6-dev, libsodium-dev,
libtool, m4, ncurses-dev, pkg-config, python,
unzip, wget, zlib1g-dev
Vcs-Git: https://git.hush.is/hush/hush3.git
Vcs-Browser: https://git.hush.is/hush/hush3
Vcs-Git: https://git.dragonx.is/DragonX/dragonx.git
Vcs-Browser: https://git.dragonx.is/DragonX/dragonx
Package: hush
Package: dragonx
Architecture: amd64 arm64
Depends: ${shlibs:Depends}
Description: Cryptocoin full node for Hush
Speak And Transact Freely with Hush, which inherits from Bitcoin Protocol and
Zcash Protocol and is focused on private communications.
Description: Privacy-focused cryptocurrency full node for DragonX
DragonX is a privacy-focused cryptocurrency using RandomX proof-of-work.
All transactions are shielded by default. Fork of the Hush Full Node.

View File

@@ -1,8 +1,9 @@
Files: *
Copyright: 2016-2026, The Hush developers
Copyright: 2024-2026, The DragonX developers
2016-2026, The Hush developers
2009-2016, Bitcoin Core developers
License: GPLv3
Comment: https://hush.is
Comment: https://dragonx.is
Files: depends/sources/libsodium-*.tar.gz
Copyright: 2013-2016 Frank Denis

View File

@@ -0,0 +1 @@
DEBIAN/examples/DRAGONX.conf

View File

@@ -0,0 +1,3 @@
usr/bin/dragonxd
usr/bin/dragonx-cli
usr/bin/dragonx-tx

View File

@@ -0,0 +1,3 @@
DEBIAN/manpages/dragonx-cli.1
DEBIAN/manpages/dragonx-tx.1
DEBIAN/manpages/dragonxd.1

View File

@@ -0,0 +1,209 @@
## DRAGONX.conf configuration file. Lines beginning with # are comments.
# Network-related settings:
# Run a regression test network
#regtest=0
# Run a test node (which means you can mine with no peers)
#testnode=1
#set a custom client name/user agent
#clientName=GoldenSandtrout
# Rescan from block height
#rescan=123
# Connect via a SOCKS5 proxy
#proxy=127.0.0.1:9050
# Automatically create Tor hidden service
#listenonion=1
#Use separate SOCKS5 proxy to reach peers via Tor hidden services
#onion=1.2.3.4:9050
# Only connect to nodes in network <net> (ipv4, ipv6, onion or i2p)"));
#onlynet=<net>
#Tor control port to use if onion listening enabled
#torcontrol=127.0.0.1:9051
# Bind to given address and always listen on it. Use [host]:port notation for IPv6
#bind=<addr>
# Bind to given address and allowlist peers connecting to it. Use [host]:port notation for IPv6
#allowbind=<addr>
##############################################################
## Quick Primer on addnode vs connect ##
## Let's say for instance you use addnode=4.2.2.4 ##
## addnode will connect you to and tell you about the ##
## nodes connected to 4.2.2.4. In addition it will tell ##
## the other nodes connected to it that you exist so ##
## they can connect to you. ##
## connect will not do the above when you 'connect' to it. ##
## It will *only* connect you to 4.2.2.4 and no one else.##
## ##
## So if you're behind a firewall, or have other problems ##
## finding nodes, add some using 'addnode'. ##
## ##
## If you want to stay private, use 'connect' to only ##
## connect to "trusted" nodes. ##
## ##
## If you run multiple nodes on a LAN, there's no need for ##
## all of them to open lots of connections. Instead ##
## 'connect' them all to one node that is port forwarded ##
## and has lots of connections. ##
## Thanks goes to [Noodle] on Freenode. ##
##############################################################
# Use as many addnode= settings as you like to connect to specific peers
#addnode=69.164.218.197
#addnode=10.0.0.2:8233
# Alternatively use as many connect= settings as you like to connect ONLY to specific peers
#connect=69.164.218.197
#connect=10.0.0.1:8233
# Listening mode, enabled by default except when 'connect' is being used
#listen=1
# Maximum number of inbound+outbound connections.
#maxconnections=
#
# JSON-RPC options (for controlling a running dragonxd process)
#
# server=1 tells node to accept JSON-RPC commands (set as default if not specified)
#server=1
# Bind to given address to listen for JSON-RPC connections. Use [host]:port notation for IPv6.
# This option can be specified multiple times (default: bind to all interfaces)
#rpcbind=<addr>
# You must set rpcuser and rpcpassword to secure the JSON-RPC api
# These will automatically be created for you
#rpcuser=user
#rpcpassword=supersecretpassword
# How many seconds node will wait for a complete RPC HTTP request.
# after the HTTP connection is established.
#rpcclienttimeout=30
# By default, only RPC connections from localhost are allowed.
# Specify as many rpcallowip= settings as you like to allow connections from other hosts,
# either as a single IPv4/IPv6 or with a subnet specification.
# NOTE: opening up the RPC port to hosts outside your local trusted network is NOT RECOMMENDED,
# because the rpcpassword is transmitted over the network unencrypted and also because anyone
# that can authenticate on the RPC port can steal your keys + take over the account running dragonxd
#rpcallowip=10.1.1.34/255.255.255.0
#rpcallowip=1.2.3.4/24
#rpcallowip=2001:db8:85a3:0:0:8a2e:370:7334/96
# Listen for RPC connections on this TCP port:
#rpcport=1234
# You can use dragonxd to send commands to dragonxd
# running on another host using this option:
#rpcconnect=127.0.0.1
# Transaction Fee
# Send transactions as zero-fee transactions if possible (default: 0)
#sendfreetransactions=0
# Create transactions that have enough fees (or priority) so they are likely to # begin confirmation within n blocks (default: 1).
# This setting is overridden by the -paytxfee option.
#txconfirmtarget=n
# Miscellaneous options
# Enable mining at startup
#gen=1
# Set the number of threads to be used for mining (-1 = all cores).
#genproclimit=1
# Specify a different Equihash solver (e.g. "tromp") to try to mine
# faster when gen=1.
#equihashsolver=default
# Pre-generate this many public/private key pairs, so wallet backups will be valid for
# both prior transactions and several dozen future transactions.
#keypool=100
# Pay an optional transaction fee every time you send a tx. Transactions with fees
# are more likely than free transactions to be included in generated blocks, so may
# be validated sooner. This setting does not affect private transactions created with
# 'z_sendmany'.
#paytxfee=0.00
#Rewind the chain to specific block height. This is useful for creating snapshots at a given block height.
#rewind=555
#Stop the chain a specific block height. This is useful for creating snapshots at a given block height.
#stopat=1000000
#Set an address to use as change address for all transactions. This value must be set to a 33 byte pubkey. All mined coins will also be sent to this address.
#pubkey=027dc7b5cfb5efca96674b45e9fda18df069d040b9fd9ff32c35df56005e330392
# Disable clearnet (ipv4 and ipv6) connections to this node
#clearnet=0
# Disable ipv4
#disableipv4=1
# Disable ipv6
#disableipv6=1
# Enable transaction index
#txindex=1
# Enable address index
#addressindex=1
# Enable timestamp index
#timestampindex=1
# Enable spent index
#spentindex=1
# Enable shielded stats index
#zindex=1
# Attempt to salvage a corrupt wallet
# salvagewallet=1
# Mine all blocks to this address (not good for your privacy and not recommended!)
# Disallowed if clearnet=0
# mineraddress=XXX
# Disable wallet
#disablewallet=1
# Allow mining to an address that is not in the current wallet
#minetolocalwallet=0
# Delete all wallet transactions
#zapwallettxes=1
# Enable sapling consolidation
# consolidation=1
# Enable stratum server
# stratum=1
# Run a command each time a new block is seen
# %s in command is replaced by block hash
#blocknotify=/my/awesome/script.sh %s
# Run a command when wallet gets a new tx
# %s in command is replaced with txid
#walletnotify=/my/cool/script.sh %s
# Run a command when tx expires
# %s in command is replaced with txid
#txexpirynotify=/my/elite/script.sh %s
# Execute this commend to send a tx
# %s is replaced with tx hex
#txsend=/send/it.sh %s

View File

@@ -0,0 +1,59 @@
description "Hush Daemon"
start on runlevel [2345]
stop on starting rc RUNLEVEL=[016]
env HUSHD_BIN="/usr/bin/dragonxd"
env HUSHD_USER="hush"
env HUSHD_GROUP="hush"
env HUSHD_PIDDIR="/var/run/dragonxd"
# upstart can't handle variables constructed with other variables
env HUSHD_PIDFILE="/var/run/dragonxd/dragonxd.pid"
env HUSHD_CONFIGFILE="/etc/hush/hush.conf"
env HUSHD_DATADIR="/var/lib/dragonxd"
expect fork
respawn
respawn limit 5 120
kill timeout 60
pre-start script
# this will catch non-existent config files
# dragonxd will check and exit with this very warning, but it can do so
# long after forking, leaving upstart to think everything started fine.
# since this is a commonly encountered case on install, just check and
# warn here.
if ! grep -qs '^rpcpassword=' "$HUSHD_CONFIGFILE" ; then
echo "ERROR: You must set a secure rpcpassword to run dragonxd."
echo "The setting must appear in $HUSHD_CONFIGFILE"
echo
echo "This password is security critical to securing wallets "
echo "and must not be the same as the rpcuser setting."
echo "You can generate a suitable random password using the following"
echo "command from the shell:"
echo
echo "bash -c 'tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo'"
echo
exit 1
fi
mkdir -p "$HUSHD_PIDDIR"
chmod 0755 "$HUSHD_PIDDIR"
chown $HUSHD_USER:$HUSHD_GROUP "$HUSHD_PIDDIR"
chown $HUSHD_USER:$HUSHD_GROUP "$HUSHD_CONFIGFILE"
chmod 0660 "$HUSHD_CONFIGFILE"
end script
exec start-stop-daemon \
--start \
--pidfile "$HUSHD_PIDFILE" \
--chuid $HUSHD_USER:$HUSHD_GROUP \
--exec "$HUSHD_BIN" \
-- \
-pid="$HUSHD_PIDFILE" \
-conf="$HUSHD_CONFIGFILE" \
-datadir="$HUSHD_DATADIR" \
-disablewallet \
-daemon

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env bash
#
# dragonxd The hush core server.
#
#
# chkconfig: 345 80 20
# description: dragonxd
# processname: dragonxd
#
# Source function library.
. /etc/init.d/functions
# you can override defaults in /etc/sysconfig/dragonxd, see below
if [ -f /etc/sysconfig/dragonxd ]; then
. /etc/sysconfig/dragonxd
fi
RETVAL=0
prog=dragonxd
# you can override the lockfile via HUSHD_LOCKFILE in /etc/sysconfig/dragonxd
lockfile=${HUSHD_LOCKFILE-/var/lock/subsys/dragonxd}
# dragonxd defaults to /usr/bin/dragonxd, override with HUSHD_BIN
dragonxd=${HUSHD_BIN-/usr/bin/dragonxd}
# dragonxd opts default to -disablewallet, override with HUSHD_OPTS
dragonxd_opts=${HUSHD_OPTS--disablewallet}
start() {
echo -n $"Starting $prog: "
daemon $DAEMONOPTS $dragonxd $dragonxd_opts
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $lockfile
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f $lockfile
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status $prog
;;
restart)
stop
start
;;
*)
echo "Usage: service $prog {start|stop|status|restart}"
exit 1
;;
esac

View File

@@ -0,0 +1,87 @@
#!/sbin/runscript
# backward compatibility for existing gentoo layout
#
if [ -d "/var/lib/hush/.hush" ]; then
HUSHD_DEFAULT_DATADIR="/var/lib/hush/.hush"
else
HUSHD_DEFAULT_DATADIR="/var/lib/dragonxd"
fi
HUSHD_CONFIGFILE=${HUSHD_CONFIGFILE:-/etc/hush/hush.conf}
HUSHD_PIDDIR=${HUSHD_PIDDIR:-/var/run/dragonxd}
HUSHD_PIDFILE=${HUSHD_PIDFILE:-${HUSHD_PIDDIR}/dragonxd.pid}
HUSHD_DATADIR=${HUSHD_DATADIR:-${HUSHD_DEFAULT_DATADIR}}
HUSHD_USER=${HUSHD_USER:-${HUSH_USER:-hush}}
HUSHD_GROUP=${HUSHD_GROUP:-hush}
HUSHD_BIN=${HUSHD_BIN:-/usr/bin/dragonxd}
HUSHD_NICE=${HUSHD_NICE:-${NICELEVEL:-0}}
HUSHD_OPTS="${HUSHD_OPTS:-${HUSH_OPTS}}"
name="Hush Full Node Daemon"
description="Hush cryptocurrency P2P network daemon"
command="/usr/bin/dragonxd"
command_args="-pid=\"${HUSHD_PIDFILE}\" \
-conf=\"${HUSHD_CONFIGFILE}\" \
-datadir=\"${HUSHD_DATADIR}\" \
-daemon \
${HUSHD_OPTS}"
required_files="${HUSHD_CONFIGFILE}"
start_stop_daemon_args="-u ${HUSHD_USER} \
-N ${HUSHD_NICE} -w 2000"
pidfile="${HUSHD_PIDFILE}"
# The retry schedule to use when stopping the daemon. Could be either
# a timeout in seconds or multiple signal/timeout pairs (like
# "SIGKILL/180 SIGTERM/300")
retry="${HUSHD_SIGTERM_TIMEOUT}"
depend() {
need localmount net
}
# verify
# 1) that the datadir exists and is writable (or create it)
# 2) that a directory for the pid exists and is writable
# 3) ownership and permissions on the config file
start_pre() {
checkpath \
-d \
--mode 0750 \
--owner "${HUSHD_USER}:${HUSHD_GROUP}" \
"${HUSHD_DATADIR}"
checkpath \
-d \
--mode 0755 \
--owner "${HUSHD_USER}:${HUSHD_GROUP}" \
"${HUSHD_PIDDIR}"
checkpath -f \
-o ${HUSHD_USER}:${HUSHD_GROUP} \
-m 0660 \
${HUSHD_CONFIGFILE}
checkconfig || return 1
}
checkconfig()
{
if ! grep -qs '^rpcpassword=' "${HUSHD_CONFIGFILE}" ; then
eerror ""
eerror "ERROR: You must set a secure rpcpassword to run dragonxd."
eerror "The setting must appear in ${HUSHD_CONFIGFILE}"
eerror ""
eerror "This password is security critical to securing wallets "
eerror "and must not be the same as the rpcuser setting."
eerror "You can generate a suitable random password using the following"
eerror "command from the shell:"
eerror ""
eerror "bash -c 'tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo'"
eerror ""
eerror ""
return 1
fi
}

View File

@@ -0,0 +1,33 @@
# /etc/conf.d/dragonxd: config file for /etc/init.d/dragonxd
# Config file location
#HUSHD_CONFIGFILE="/etc/hush/hush.conf"
# What directory to write pidfile to? (created and owned by $HUSHD_USER)
#HUSHD_PIDDIR="/var/run/dragonxd"
# What filename to give the pidfile
#HUSHD_PIDFILE="${HUSHD_PIDDIR}/dragonxd.pid"
# Where to write dragonxd data (be mindful that the blockchain is large)
#HUSHD_DATADIR="/var/lib/dragonxd"
# User and group to own dragonxd process
#HUSHD_USER="hush"
#HUSHD_GROUP="hush"
# Path to dragonxd executable
#HUSHD_BIN="/usr/bin/dragonxd"
# Nice value to run dragonxd under
#HUSHD_NICE=0
# Additional options (avoid -conf and -datadir, use flags above)
HUSHD_OPTS="-disablewallet"
# The timeout in seconds OpenRC will wait for dragonxd to terminate
# after a SIGTERM has been raised.
# Note that this will be mapped as argument to start-stop-daemon's
# '--retry' option, which means you can specify a retry schedule
# here. For more information see man 8 start-stop-daemon.
HUSHD_SIGTERM_TIMEOUT=60

View File

@@ -0,0 +1,22 @@
[Unit]
Description=Hush: Speak And Transact Freely
After=network.target
[Service]
User=hush
Group=hush
Type=forking
PIDFile=/var/lib/dragonxd/dragonxd.pid
ExecStart=/usr/bin/dragonxd -daemon -pid=/var/lib/dragonxd/dragonxd.pid \
-conf=/etc/hush/hush.conf -datadir=/var/lib/dragonxd -disablewallet
Restart=always
PrivateTmp=true
TimeoutStopSec=60s
TimeoutStartSec=2s
StartLimitInterval=120s
StartLimitBurst=5
[Install]
WantedBy=multi-user.target

View File

@@ -1,5 +1,5 @@
build_darwin_CC = gcc-8
build_darwin_CXX = g++-8
build_darwin_CC = gcc-15
build_darwin_CXX = g++-15
build_darwin_AR: = $(shell xcrun -f ar)
build_darwin_RANLIB: = $(shell xcrun -f ranlib)
build_darwin_STRIP: = $(shell xcrun -f strip)
@@ -10,8 +10,8 @@ build_darwin_SHA256SUM = shasum -a 256
build_darwin_DOWNLOAD = curl --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -L -f -o
#darwin host on darwin builder. overrides darwin host preferences.
darwin_CC= gcc-8
darwin_CXX= g++-8
darwin_CC= gcc-15
darwin_CXX= g++-15
darwin_AR:=$(shell xcrun -f ar)
darwin_RANLIB:=$(shell xcrun -f ranlib)
darwin_STRIP:=$(shell xcrun -f strip)

View File

@@ -2,8 +2,8 @@ OSX_MIN_VERSION=10.12
OSX_SDK_VERSION=10.12
OSX_SDK=$(SDK_PATH)/MacOSX$(OSX_SDK_VERSION).sdk
LD64_VERSION=253.9
darwin_CC=gcc-8 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
darwin_CXX=g++-8 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
darwin_CC=gcc-15 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
darwin_CXX=g++-15 -target $(host) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(OSX_SDK) -mlinker-version=$(LD64_VERSION)
darwin_CFLAGS=-pipe
darwin_CXXFLAGS=$(darwin_CFLAGS)

View File

@@ -40,9 +40,15 @@ define $(package)_preprocess_cmds
cat $($(package)_patch_dir)/cargo.config | sed 's|CRATE_REGISTRY|$(host_prefix)/$(CRATE_REGISTRY)|' > .cargo/config
endef
ifeq ($(build_os),darwin)
define $(package)_build_cmds
CARGO=$(HOME)/.cargo/bin/cargo RUSTC=$(HOME)/.cargo/bin/rustc $(HOME)/.cargo/bin/cargo build --package librustzcash $($(package)_build_opts)
endef
else
define $(package)_build_cmds
$(host_prefix)/native/bin/cargo build --package librustzcash $($(package)_build_opts)
endef
endif
define $(package)_stage_cmds
mkdir $($(package)_staging_dir)$(host_prefix)/lib/ && \

8
depends/strip-rlib-metadata.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
# Strip rust.metadata.bin from .rlib archives for macOS linker compatibility
RLIB_DIR="$1"
if [ -d "$RLIB_DIR" ]; then
for rlib in "$RLIB_DIR"/*.rlib; do
[ -f "$rlib" ] && ar d "$rlib" rust.metadata.bin 2>/dev/null || true
done
fi

7
doc/beefy-DRAGONX.conf Normal file
View File

@@ -0,0 +1,7 @@
rpcuser=dontuseweakusernameoryougetrobbed
rpcpassword=dontuseweakpasswordoryougetrobbed
txindex=1
server=1
rpcworkqueue=64
addnode=1.2.3.4
addnode=5.6.7.8

29
doc/dragonxd-systemd.md Normal file
View File

@@ -0,0 +1,29 @@
# Systemd script for the DragonX daemon
## Set it up
First set it up as follows:
* Copy dragonxd.service to the systemd user directory, which is /usr/lib/systemd/user directory
## Basic Usage
How to start the script:
`systemctl start --user dragonxd.service`
How to stop the script:
`systemctl stop --user dragonxd.service`
How to restart the script:
`systemctl restart --user dragonxd.service`
## How to watch it as it starts
Use the following on most Linux distros:
`watch systemctl status --user dragonxd.service`
Or watch the log directly:
`tail -f ~/.hush/DRAGONX/debug.log`
## Troubleshooting
* Don't run it with sudo or root, or it won't work with the wallet.

9
doc/dragonxd.service Normal file
View File

@@ -0,0 +1,9 @@
[Unit]
Description=DragonX daemon
After=network.target
[Service]
ExecStart=/usr/bin/dragonxd
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,87 @@
# HD transparent keys
DragonX derives **transparent** (t-address) keys deterministically from the
wallet's HD seed, so they can be recovered from the seed alone — the same way
Sapling (shielded) keys already are.
## Derivation
Transparent keys are derived over secp256k1 using BIP32/BIP44:
```
m / 44' / coin_type' / 0' / 0 / i
```
* `coin_type` is `Params().BIP44CoinType()`**141** on mainnet, **1** on
test/regtest.
* Account is fixed at `0'` and the chain at `0` (external). The internal/change
chain (`1`) is **not** used: on this `ac_private=1` chain a non-coinbase
transparent output is consensus-invalid, so transparent change can never carry
value.
* `i` is `CHDChain.transparentChildCounter`, a monotonic index persisted in the
wallet so the same addresses regenerate after a seed-only restore.
Each derived key records its `hdKeypath` and the seed fingerprint (`seedFp`) in
its `CKeyMetadata`, matching the Sapling scheme.
## Why this matters on a private chain
On DragonX (`ac_private=1` from genesis) a normal user can never *receive* to a
transparent address — inbound t-payments are rejected by consensus. The only
thing that legitimately lands spendable value on a t-address is a **mining
coinbase** (plus notary/burn special cases). There is no "coinbase must be
shielded" rule, so mature coinbase is directly spendable.
So HD transparent keys exist to let a **miner recover coinbase rewards** that
were paid to wallet-derived t-addresses, using only the seed.
## Enabling / disabling
Controlled by `-hdtransparent` (default **on**). When on and the wallet has an
HD seed, every newly generated transparent key (receive address, change,
coinbase payout drawn from the keypool) is HD-derived.
```
-hdtransparent=0 # keep the legacy behaviour (random transparent keys)
```
## Backing up and restoring
* **Back up the seed.** `z_exportwallet <file>` writes the 32-byte HD seed as a
`# HDSeed=<hex>` line. Guard this value like a private key.
* **Restore into a fresh/empty wallet** by starting the node with:
```
-hdseed=<64-hex-character seed>
-hdtransparentgaplimit=<n> # HD transparent keys to pre-derive (default 1000)
```
On restore the node injects the seed, pre-derives `n` transparent keys with a
genesis birthday, and the normal startup rescan finds any coinbase paid to
them. Raise `-hdtransparentgaplimit` if the wallet minted more than `n`
distinct coinbase addresses.
> **Warning:** passing `-hdseed` on the command line exposes the seed to your
> shell history and the process list. Prefer putting it in `DRAGONX.conf` with
> tight file permissions, and remove it after the restore completes.
## Limitations (read before relying on recovery)
* **Legacy random keys are not recoverable.** Any transparent key created before
this feature (or with `-hdtransparent=0`) came from the CSPRNG, not the seed,
and the phrase/seed will **not** regenerate it. Keep `wallet.dat` /
`dumpwallet` backups for those. A wallet that predates the feature and then
enables it becomes a *mix* of random (old) and HD (new) keys.
* **Gap limit.** A rescan only discovers keys already present in the wallet.
Restore pre-derives `-hdtransparentgaplimit` keys; coinbase paid to an index
beyond that window is not found until you derive further and rescan again.
* **Scope.** Recovers transparent **coinbase** value only, per the consensus
rules above. Shielded funds are recovered separately via the Sapling HD keys.
## On-disk compatibility
The transparent counter is stored in `CHDChain` under a new serialization
version (`VERSION_HD_TRANSPARENT = 2`). Existing v1 `wallet.dat` records load
unchanged (the counter defaults to 0); the record is rewritten as v2 the first
time an HD transparent key is derived. Downgrading a v2 wallet to an older
binary is not supported.

View File

@@ -1 +1 @@
dist_man1_MANS=hushd.1 hush-cli.1 hush-tx.1
dist_man1_MANS=dragonxd.1 dragonx-cli.1 dragonx-tx.1

View File

@@ -1,21 +1,21 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
.TH HUSH-CLI "1" "July 2025" "hush-cli v3.10.4" "User Commands"
.\" 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"
.SH NAME
hush-cli \- manual page for hush-cli v3.10.4
DragonX \- manual page for DragonX RPC client version v1.0.3-95aeaed0c-dirty
.SH DESCRIPTION
Hush RPC client version v3.10.4\-7e63e2f01\-dirty
DragonX RPC client version v1.0.3\-95aeaed0c\-dirty
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
In order to ensure you are adequately protecting your privacy when using
DragonX, please see <https://dragonx.is/security/>.
.SS "Usage:"
.TP
hush\-cli [options] <command> [params]
Send command to Hush
dragonx\-cli [options] <command> [params]
Send command to DragonX
.TP
hush\-cli [options] help
dragonx\-cli [options] help
List commands
.TP
hush\-cli [options] help <command>
dragonx\-cli [options] help <command>
Get help for a command
.SH OPTIONS
.HP
@@ -25,7 +25,7 @@ This help message
.HP
\fB\-conf=\fR<file>
.IP
Specify configuration file (default: HUSH3.conf)
Specify configuration file (default: DRAGONX.conf)
.HP
\fB\-datadir=\fR<dir>
.IP
@@ -47,7 +47,7 @@ Send commands to node running on <ip> (default: 127.0.0.1)
.HP
\fB\-rpcport=\fR<port>
.IP
Connect to JSON\-RPC on <port> (default: 18030 )
Connect to JSON\-RPC on <port> (default: 21769 )
.HP
\fB\-rpcwait\fR
.IP
@@ -70,20 +70,25 @@ 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
In order to ensure you are adequately protecting your privacy when using
DragonX, please see <https://dragonx.is/security/>.
.SH 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
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
This is experimental Free Software! Fuck Yeah!!!!!
.PP
Distributed under the GPLv3 software license, see the accompanying file COPYING
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.
or <https://www.gnu.org/licenses/gpl\-3.0.en.html>.

View File

@@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
.TH HUSH-TX "1" "July 2025" "hush-tx v3.10.4" "User Commands"
.\" 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"
.SH NAME
hush-tx \- manual page for hush-tx v3.10.4
dragonx-tx \- DragonX transaction utility
.SH DESCRIPTION
hush\-tx utility version v3.10.4\-7e63e2f01\-dirty
hush\-tx utility version v1.0.3\-4caf2fc68
.SS "Usage:"
.TP
hush\-tx [options] <hex\-tx> [commands]
@@ -84,20 +84,3 @@ 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 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>.

View File

@@ -1,16 +1,16 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
.TH HUSHD "1" "July 2025" "hushd v3.10.4" "User Commands"
.\" 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"
.SH NAME
hushd \- manual page for hushd v3.10.4
DragonX \- manual page for DragonX Daemon version v1.0.3-4caf2fc68
.SH DESCRIPTION
Hush Daemon version v3.10.4\-7e63e2f01\-dirty
DragonX Daemon version v1.0.3\-4caf2fc68
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://hush.is/security/>.
In order to ensure you are adequately protecting your privacy when using
DragonX, please see <https://dragonx.is/security/>.
.SS "Usage:"
.TP
hushd [options]
Start a Hush Daemon
dragonxd [options]
Start DragonX Daemon
.SH OPTIONS
.HP
\-?
@@ -32,11 +32,11 @@ How thorough the block verification of \fB\-checkblocks\fR is (0\-4, default: 3)
.HP
\fB\-clientname=\fR<SomeName>
.IP
Full node client name, default 'GoldenSandtrout'
Full node client name, default 'DragonX'
.HP
\fB\-conf=\fR<file>
.IP
Specify configuration file (default: HUSH3.conf)
Specify configuration file (default: DRAGONX.conf)
.HP
\fB\-daemon\fR
.IP
@@ -52,7 +52,11 @@ Specify directory to be used when exporting data
.HP
\fB\-dbcache=\fR<n>
.IP
Set database cache size in megabytes (4 to 16384, default: 512)
Set database cache size in megabytes (4 to 16384). Default: adaptive \-
uses most free RAM to speed up initial block download (far fewer
UTXO flushes to disk) and automatically shrinks if other
applications need memory, always leaving a reserve free. Setting
a fixed value disables adaptive sizing.
.HP
\fB\-loadblock=\fR<file>
.IP
@@ -78,9 +82,15 @@ applied)
.HP
\fB\-par=\fR<n>
.IP
Set the number of script verification threads (\fB\-8\fR to 16, 0 = auto, <0 =
Set the number of script verification threads (\fB\-4\fR to 16, 0 = auto, <0 =
leave that many cores free, default: 0)
.HP
\fB\-randomxverifythreads=\fR<n>
.IP
Number of threads for parallel RandomX PoW pre\-verification of
post\-checkpoint blocks during sync (0 = inline only, max 16,
default: same as \fB\-par\fR)
.HP
\fB\-pid=\fR<file>
.IP
Specify pid file (default: hushd.pid)
@@ -337,6 +347,40 @@ Do not load the wallet and disable wallet RPC calls
.IP
Set key pool size to <n> (default: 100)
.HP
\fB\-hdtransparent\fR
.IP
Derive transparent addresses from the HD seed so they can be recovered
from it (default: 1)
.HP
\fB\-hdseed=\fR<hex>
.IP
Restore a fresh/empty wallet from a 32\- or 64\-byte HD seed hex (the
value shown in z_exportwallet's '# HDSeed=' line). WARNING:
exposes the seed to your shell history and process list.
.HP
\fB\-mnemonic=\fR<words>
.IP
Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible
with SilentDragonXLite (English, no passphrase). WARNING: exposes
the phrase to your shell history and process list; prefer
DRAGONX.conf with tight permissions.
.HP
\fB\-usemnemonic\fR
.IP
Create new wallets from a fresh BIP39 seed phrase so the 24 words can be
exported (z_exportmnemonic) and used in SilentDragonXLite
(default: 0)
.HP
\fB\-hdtransparentgaplimit=\fR<n>
.IP
On \fB\-mnemonic\fR/\-hdseed restore, pre\-derive this many HD transparent keys
so a rescan can find coinbase paid to them (default: 1000)
.HP
\fB\-mnemonicsaplinggap=\fR<n>
.IP
On \fB\-mnemonic\fR/\-hdseed restore, pre\-derive this many shielded (Sapling)
addresses so a rescan can find notes sent to them (default: 100)
.HP
\fB\-consolidation\fR
.IP
Enable auto Sapling note consolidation (default: false)
@@ -649,8 +693,8 @@ multiple times (default: bind to all interfaces)
.HP
\fB\-stratumport=\fR<port>
.IP
Listen for Stratum work requests on <port> (default: 19031 or testnet:
19031)
Listen for Stratum work requests on <port> (default: 22769 or testnet:
22769)
.HP
\fB\-stratumallowip=\fR<ip>
.IP
@@ -659,7 +703,7 @@ single IP (e.g. 1.2.3.4), a network/netmask (e.g.
1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This
option can be specified multiple times
.PP
Hush Arrakis Chain options:
DragonX Chain options:
.HP
\fB\-ac_algo\fR
.IP
@@ -686,6 +730,12 @@ OP_RETURN minimum fee per tx, regardless of tx size, default is 1 coin
.IP
CODA integration
.HP
\fB\-ac_clearnet\fR
.IP
Enable or disable clearnet connections for the entire blockchain.
Setting to 0 will disable clearnet and use sane defaults for
Tor/i2p and require all nodes to do the same (default: 1)
.HP
\fB\-ac_decay\fR
.IP
Percentage of block reward decrease at each halving
@@ -754,20 +804,25 @@ Starting supply, default is 10
\fB\-ac_txpow\fR
.IP
Enforce transaction\-rate limit, default 0
.PP
In order to ensure you are adequately protecting your privacy when using
DragonX, please see <https://dragonx.is/security/>.
.SH COPYRIGHT
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
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
This is experimental Free Software! Fuck Yeah!!!!!
.PP
Distributed under the GPLv3 software license, see the accompanying file COPYING
or <https://www.gnu.org/licenses/gpl-3.0.en.html>.
or <https://www.gnu.org/licenses/gpl\-3.0.en.html>.

View File

@@ -71,11 +71,11 @@ Install deps on Linux:
- To make a pre-release "beta" you can modify `CLIENT_VERSION_BUILD` but that is rarely done in Hush world.
- A `CLIENT_VERSION_BUILD` of 50 means "actual non-beta release"
- Make sure to keep the values in configure.ac and src/clientversion.h the same. The variables are prefixed wth an underscore in configure.ac
- Run `make manpages`, commit + push results
- hushd must be running so the script can automatically get the correct version number
- There is a hack in the script where you can hardcode a version number if hushd isn't running.
- Comment out the HUSHVER line and uncomment the line above it with a hardcoded version number
- Run `./util/gen-manpages.sh`, commit + push results
- There is a hack in the script where you can hardcode a version number if hushd isn't compiled on this machine
- Comment out the HUSHVER line and uncomment the line above it with a hardcoded version number
- PROTIP: Man page creation must be done after updating the version number and recompiling and before Debian package creation
- TODO: How to regenerate html man pages?
- Update checkpoints in src/chainparams.cpp via util/checkpoints.pl
- Run "./util/checkpoints.pl help" to get example usage
- hushd must be running to run this script, since it uses hush-cli to get the data
@@ -97,7 +97,6 @@ Install deps on Linux:
- They only provide limited security, because they talk about the past, not future block heights.
- 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 copyright years if applicable. Example: `./util/update-copyrights.h 2022 2023`
- Update doc/relnotes/README.md
- To get the stats of file changes: `git diff --stat master...dev`
- Do a fresh clone and fresh sync with new checkpoints

View File

@@ -10,24 +10,35 @@ and no longer on Github, since they banned Duke Leto and
also because they censor many people around the world and work with
evil organizations. They also use all your "private" repos to train their AI.
# Hush 3.10.5 ""
# Hush 3.10.5 "Atelic Alpaca"
This is an OPTIONAL but RECOMMENDED upgrade.
* DragonX is no longer supported by this codebase
* DragonX devs have forked our code and we wish them the best
* Find the latest place to download a DragonX full node via dragonx.is
* Concurrent `z_sendmany` now works
* A longstanding bug relating to run multiple `z_sendmany` operations at
once has been fixed. You can now queue up many `z_sendmany` operations
and they will succeed because they now understand how to avoid spending
coins that another `z_sendmany` process is trying to spend.
* Updated Autonomous System Map (asmap)
* New RPC `z_listlockunspent`
* Lists shielded notes (coins inside a zaddr) which are temporarily unspendable because an RPC process is currently trying to spend them.
* If that operation succeeds, they will become spent. If it fails they will be unlocked and become spendable again.
* Fixed DragonX checkpoints
* Hush checkpoints were mistakenly listed as checkpoints in the 3.10.4
release, which caused some nodes to be unable to sync.
* This release fixes this issue.
* New option to `z_shieldcoinbase` allows privately donating a percentage of coinbase funds during shielding
* This new option defaults to OFF and allows CLI users to opt-in to donating between 0% and 10% of coinbase funds to developers
* No GUI currently utilizes this but that feature is planned for SD
* The donation has extremely good privacy:
* It cannot be determined from public blockchain data if a donation is being made, as it takes the place of a Sietch output
* It cannot be determined from public blockchain data the amount of the donation
* Donations do not create new transactions, do not use additional blockspace and cannot be detected by anyone but the sender or reciever
* New HAC option `ac_clearnet` can be used to disable clearnet networking for an entire blockchain instead of just a single node
* Updated test framework and tests which allowed the fixing of the `z_sendmany` bug above
* Faster compiling of RandomX internals
* GMP dependency removed, as it is no longer needed
* Hush is now compatible with GCC15 and now correctly supports customizing the compiler for a build via the CC env var
* New HTML man pages are now available at doc/man/hushd.html and doc/man/hush-cli.html
# Hush 3.10.4 "Hazy Hākuturi"

90
doc/seed-phrase.md Normal file
View File

@@ -0,0 +1,90 @@
# BIP39 seed phrases (SilentDragonXLite-compatible)
DragonX full-node wallets can be created from and restored to a **BIP39 24-word
seed phrase** that is **byte-for-byte compatible with SilentDragonXLite**: the
same words produce the same transparent (t-) and shielded (z-) addresses in
either wallet, so funds move between the light wallet and the full node with one
backup.
## What makes them compatible
Compatibility requires the mnemonic, the seed derivation, and every HD path to
match exactly. They do:
| Detail | Value (both wallets) |
|---|---|
| Word list | BIP39 English, 2048 words |
| Passphrase | empty (no "25th word") |
| Mnemonic → seed | PBKDF2-HMAC-SHA512, 2048 rounds, salt `"mnemonic"`, 64-byte output |
| Coin type | 141 (KMD SLIP-0044) |
| Shielded path | `m/32'/141'/i'` (ZIP-32) |
| Transparent path | `m/44'/141'/0'/0/i` (BIP44) |
The node stores the 32-byte BIP39 **entropy** (SilentDragonXLite's on-disk
convention) and expands it to the 64-byte seed on demand for derivation. The
node's vendored BIP39 library (`src/crypto/bip39`) is byte-identical to
SilentDragonXLite's `tiny-bip39` 0.6.2, and the derivation is anchored by a
known-answer test (`src/gtest/test_mnemonic_compat.cpp`).
## Restore from a phrase
Start the node once, on a **fresh/empty datadir**, with the phrase:
```
dragonxd -mnemonic="word1 word2 ... word24"
```
or, preferably (keeps the phrase out of your shell history and process list),
put it in `DRAGONX.conf` with tight permissions:
```
mnemonic=word1 word2 ... word24
```
On restore the node pre-derives keys and rescans from genesis to recover funds:
* `-hdtransparentgaplimit=<n>` — HD transparent keys to pre-derive (default 1000)
* `-mnemonicsaplinggap=<n>` — shielded addresses to pre-derive (default 100)
Raise these if the wallet used many addresses. Restore only works on a wallet
with no seed yet (a brand-new datadir); it refuses to overwrite an existing seed.
## Create a new phrase on the node
By default new node wallets use a random (non-mnemonic) seed. To create a new
wallet from a fresh 24-word phrase instead — so you can export it and use it in
SilentDragonXLite — start with:
```
dragonxd -usemnemonic
```
## Show / back up the phrase
For a mnemonic wallet (created with `-usemnemonic` or restored with `-mnemonic`):
```
dragonx-cli z_exportmnemonic
```
returns the 24 words and the seed fingerprint. The wallet must be unlocked.
Guard the phrase like a private key.
## Limitations
* **English + empty passphrase only.** Any other word list or a BIP39 passphrase
would break compatibility, so they are not accepted.
* **Legacy / random-seed wallets have no phrase.** A wallet created before this
feature (or without `-usemnemonic`) has a random seed; `z_exportmnemonic`
returns an error for it — use `z_exportwallet` to back up the raw seed. Such
wallets are not SilentDragonXLite-compatible.
* **Scope.** Recovers HD-derived shielded funds and transparent coinbase (see
[hd-transparent-keys.md](hd-transparent-keys.md) for why only coinbase lands on
t-addresses on this `ac_private=1` chain). Keys imported with `z_importkey` are
not seed-derived and are not recovered by the phrase.
## On-disk compatibility
Mnemonic wallets set `CHDChain` version 3 (`VERSION_HD_MNEMONIC`). Older wallet
records load unchanged. Downgrading a mnemonic wallet to an older binary is not
supported.

View File

@@ -39,15 +39,22 @@ BITCOIN_INCLUDES += -I$(srcdir)/univalue/include
BITCOIN_INCLUDES += -I$(srcdir)/leveldb/include
if TARGET_WINDOWS
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
LIBBITCOIN_SERVER=libbitcoin_server.a
endif
if TARGET_DARWIN
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
LIBBITCOIN_SERVER=libbitcoin_server.a
endif
if TARGET_LINUX
LIBBITCOIN_SERVER=libbitcoin_server.a -lcurl
LIBBITCOIN_SERVER=libbitcoin_server.a
endif
# libcurl is a linker flag, not a buildable library file. It must NOT live inside
# LIBBITCOIN_SERVER, which is also fed into EXTRA_LIBRARIES (a list of files automake
# builds) where a -l flag is illegal and triggers a portability error. Keep it as its
# own variable, added to each binary's _LDADD after libbitcoin_server.a (whose objects
# reference curl symbols) so static link order stays correct.
LIBCURL = -lcurl
LIBBITCOIN_WALLET=libbitcoin_wallet.a
LIBBITCOIN_COMMON=libbitcoin_common.a
LIBBITCOIN_CLI=libbitcoin_cli.a
@@ -94,11 +101,11 @@ noinst_PROGRAMS =
TESTS =
#if BUILD_BITCOIND
bin_PROGRAMS += hushd
bin_PROGRAMS += dragonxd
#endif
if BUILD_BITCOIN_UTILS
bin_PROGRAMS += hush-cli hush-tx
bin_PROGRAMS += dragonx-cli dragonx-tx
endif
if ENABLE_WALLET
bin_PROGRAMS += wallet-utility
@@ -318,6 +325,7 @@ libbitcoin_wallet_a_SOURCES = \
wallet/asyncrpcoperation_shieldcoinbase.cpp \
wallet/crypter.cpp \
wallet/db.cpp \
wallet/mnemonic.cpp \
zcash/Note.cpp \
transaction_builder.cpp \
wallet/rpcdump.cpp \
@@ -354,6 +362,23 @@ crypto_libbitcoin_crypto_a_SOURCES = \
crypto/sha512.cpp \
crypto/sha512.h
# Vendored trezor-crypto BIP39 (mnemonic seed phrases). Kept self-contained so
# the same 24 words are compatible with SilentDragonXLite (tiny-bip39 0.6.2).
crypto_libbitcoin_crypto_a_SOURCES += \
crypto/bip39/bip39.c \
crypto/bip39/bip39.h \
crypto/bip39/bip39_english.h \
crypto/bip39/pbkdf2.c \
crypto/bip39/pbkdf2.h \
crypto/bip39/hmac.c \
crypto/bip39/hmac.h \
crypto/bip39/sha2.c \
crypto/bip39/sha2.h \
crypto/bip39/memzero.c \
crypto/bip39/memzero.h \
crypto/bip39/options.h \
crypto/bip39/rand.h
if EXPERIMENTAL_ASM
crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp
endif
@@ -453,17 +478,18 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h
#
# hushd binary #
hushd_SOURCES = bitcoind.cpp
hushd_CPPFLAGS = -fPIC $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
hushd_CXXFLAGS = -fPIC $(AM_CXXFLAGS) $(PIE_FLAGS)
hushd_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
dragonxd_SOURCES = bitcoind.cpp
dragonxd_CPPFLAGS = -fPIC $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
dragonxd_CXXFLAGS = -fPIC $(AM_CXXFLAGS) $(PIE_FLAGS)
dragonxd_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_WINDOWS
hushd_SOURCES += bitcoind-res.rc
dragonxd_SOURCES += bitcoind-res.rc
endif
hushd_LDADD = \
dragonxd_LDADD = \
$(LIBBITCOIN_SERVER) \
$(LIBCURL) \
$(LIBBITCOIN_COMMON) \
$(LIBUNIVALUE) \
$(LIBBITCOIN_UTIL) \
@@ -476,10 +502,10 @@ hushd_LDADD = \
$(LIBRANDOMX)
if ENABLE_WALLET
hushd_LDADD += $(LIBBITCOIN_WALLET)
dragonxd_LDADD += $(LIBBITCOIN_WALLET)
endif
hushd_LDADD += \
dragonxd_LDADD += \
$(BOOST_LIBS) \
$(BDB_LIBS) \
$(SSL_LIBS) \
@@ -490,27 +516,27 @@ hushd_LDADD += \
$(LIBZCASH_LIBS)
if TARGET_DARWIN
hushd_LDADD += libcc.dylib $(LIBSECP256K1)
dragonxd_LDADD += libcc.dylib $(LIBSECP256K1)
endif
if TARGET_WINDOWS
hushd_LDADD += libcc.dll $(LIBSECP256K1)
dragonxd_LDADD += libcc.dll $(LIBSECP256K1)
endif
if TARGET_LINUX
hushd_LDADD += libcc.so $(LIBSECP256K1)
dragonxd_LDADD += libcc.so $(LIBSECP256K1)
endif
# [+] Decker: use static linking for libstdc++.6.dylib, libgomp.1.dylib, libgcc_s.1.dylib
if TARGET_DARWIN
hushd_LDFLAGS += -static-libgcc
dragonxd_LDFLAGS += -static-libgcc
endif
# hush-cli binary #
hush_cli_SOURCES = bitcoin-cli.cpp
hush_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
hush_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
hush_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
dragonx_cli_SOURCES = bitcoin-cli.cpp
dragonx_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
dragonx_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
dragonx_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_DARWIN
hush_cli_LDFLAGS += -static-libgcc
dragonx_cli_LDFLAGS += -static-libgcc
endif
# wallet-utility binary #
@@ -522,10 +548,10 @@ wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
endif
if TARGET_WINDOWS
hush_cli_SOURCES += bitcoin-cli-res.rc
dragonx_cli_SOURCES += bitcoin-cli-res.rc
endif
hush_cli_LDADD = \
dragonx_cli_LDADD = \
$(LIBBITCOIN_CLI) \
$(LIBUNIVALUE) \
$(LIBBITCOIN_UTIL) \
@@ -554,16 +580,16 @@ wallet_utility_LDADD = \
endif
# hush-tx binary #
hush_tx_SOURCES = hush-tx.cpp
hush_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
hush_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
hush_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
dragonx_tx_SOURCES = hush-tx.cpp
dragonx_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
dragonx_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
dragonx_tx_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
if TARGET_WINDOWS
hush_tx_SOURCES += bitcoin-tx-res.rc
dragonx_tx_SOURCES += bitcoin-tx-res.rc
endif
hush_tx_LDADD = \
dragonx_tx_LDADD = \
$(LIBUNIVALUE) \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_UTIL) \
@@ -574,7 +600,7 @@ hush_tx_LDADD = \
$(LIBZCASH_LIBS) \
$(LIBRANDOMX)
hush_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
dragonx_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
# Zcash Protocol Primitives
libzcash_a_SOURCES = \
@@ -594,7 +620,7 @@ libzcash_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBOOST_SPIRIT_THREADSAFE -DHAV
#libzcash_a_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
#libzcash_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMONTGOMERY_OUTPUT
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
libzcash_a_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
libzcash_a_LDFLAGS = $(SAN_LDFLAGS) $(HARDENED_LDFLAGS)
libzcash_a_CPPFLAGS += -DMONTGOMERY_OUTPUT
@@ -636,7 +662,7 @@ libhush_a_SOURCES = \
libhush_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) -pipe -O1 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES)
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu17
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing -std=gnu++17
libhush_a_LDFLAGS = $(HARDENED_LDFLAGS)
@@ -685,5 +711,5 @@ endif
if ENABLE_TESTS
#include Makefile.test-hush.include
#include Makefile.test.include
#include Makefile.gtest.include
include Makefile.gtest.include
endif

View File

@@ -4,65 +4,61 @@ TESTS += hush-gtest
bin_PROGRAMS += hush-gtest
# tool for generating our public parameters
# NOTE: the original test list used an invalid automake form (comment after a trailing
# backslash, and `zcash_gtest_SOURCES +=` with no prior `=`), which is why the whole
# gtest harness was disabled via a `#include`. Minimal valid set: the harness + the
# Re-add other gtest sources here as they are revived.
hush_gtest_SOURCES = \
gtest/main.cpp \
gtest/utils.cpp \
gtest/test_checktransaction.cpp \
gtest/json_test_vectors.cpp \
gtest/json_test_vectors.h \
gtest/test_wallet_zkeys.cpp \
# These tests are order-dependent, because they
# depend on global state (see #1539)
if ENABLE_WALLET
zcash_gtest_SOURCES += \
wallet/gtest/test_wallet_zkeys.cpp
endif
zcash_gtest_SOURCES += \
gtest/test_tautology.cpp \
gtest/test_deprecation.cpp \
gtest/test_equihash.cpp \
gtest/test_httprpc.cpp \
gtest/test_keys.cpp \
gtest/test_keystore.cpp \
gtest/test_noteencryption.cpp \
gtest/test_mempool.cpp \
gtest/test_merkletree.cpp \
gtest/test_metrics.cpp \
gtest/test_miner.cpp \
gtest/test_pow.cpp \
gtest/test_random.cpp \
gtest/test_rpc.cpp \
gtest/test_sapling_note.cpp \
gtest/test_transaction.cpp \
gtest/test_transaction_builder.cpp \
gtest/test_upgrades.cpp \
gtest/test_validation.cpp \
gtest/test_circuit.cpp \
gtest/test_txid.cpp \
gtest/test_libzcash_utils.cpp \
gtest/test_proofs.cpp \
gtest/test_pedersen_hash.cpp \
gtest/test_checkblock.cpp \
gtest/test_zip32.cpp
if ENABLE_WALLET
zcash_gtest_SOURCES += \
wallet/gtest/test_wallet.cpp
endif
gtest/test_randomx_preverify.cpp \
gtest/test_hdtransparent.cpp \
gtest/test_mnemonic_compat.cpp
hush_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES)
hush_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
hush_gtest_LDADD = -lgtest -lgmock $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1)
# Mirror dragonxd_LDADD's working library set/order (the old list used a non-existent
# $(LIBBITCOIN_UNIVALUE) so univalue was never linked, and omitted LIBHUSH/LIBRANDOMX/libcc).
hush_gtest_LDADD = -lgtest -lgmock \
$(LIBBITCOIN_SERVER) \
$(LIBCURL) \
$(LIBBITCOIN_COMMON) \
$(LIBUNIVALUE) \
$(LIBBITCOIN_UTIL) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH) \
$(LIBHUSH) \
$(LIBLEVELDB) \
$(LIBMEMENV) \
$(LIBSECP256K1) \
$(LIBRANDOMX)
if ENABLE_WALLET
hush_gtest_LDADD += $(LIBBITCOIN_WALLET)
endif
hush_gtest_LDADD += $(LIBZCASH_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(LIBZCASH) $(LIBZCASH_LIBS)
hush_gtest_LDADD += \
$(BOOST_LIBS) \
$(BOOST_UNIT_TEST_FRAMEWORK_LIB) \
$(BDB_LIBS) \
$(SSL_LIBS) \
$(CRYPTO_LIBS) \
$(EVENT_PTHREADS_LIBS) \
$(EVENT_LIBS) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH_LIBS)
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
if TARGET_DARWIN
hush_gtest_LDADD += libcc.dylib $(LIBSECP256K1)
endif
if TARGET_WINDOWS
hush_gtest_LDADD += libcc.dll $(LIBSECP256K1)
endif
if TARGET_LINUX
hush_gtest_LDADD += libcc.so $(LIBSECP256K1)
endif
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static
hush_gtest_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
hush-gtest-expected-failures: hush-gtest FORCE
./hush-gtest --gtest_filter=*DISABLED_* --gtest_also_run_disabled_tests

View File

@@ -112,13 +112,13 @@ endif
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) -fopenmp $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS)
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \
$(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS)
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
if ENABLE_WALLET
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
endif
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBCURL) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
$(LIBLEVELDB) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)

View File

@@ -13,7 +13,7 @@
"ac_perc": "11111111",
"ac_eras": "3",
"ac_script": "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac",
"clientname": "GoldenSandtrout",
"clientname": "DragonX",
"addnode": [
"node1.hush.is",
"node2.hush.is",

View File

@@ -29,8 +29,8 @@
#include <event2/keyvalq_struct.h>
#include "support/events.h"
uint16_t ASSETCHAINS_RPCPORT = 18031;
uint16_t BITCOIND_RPCPORT = 18031;
uint16_t ASSETCHAINS_RPCPORT = 21769;
uint16_t BITCOIND_RPCPORT = 21769;
char SMART_CHAIN_SYMBOL[65];
extern uint16_t ASSETCHAINS_RPCPORT;
@@ -47,13 +47,13 @@ std::string HelpMessageCli()
std::string strUsage;
strUsage += HelpMessageGroup(_("Options:"));
strUsage += HelpMessageOpt("-?", _("This help message"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.conf"));
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
strUsage += HelpMessageOpt("-testnet", _("Use the test network"));
strUsage += HelpMessageOpt("-regtest", _("Enter regression test mode, which uses a special chain in which blocks can be "
"solved instantly. This is intended for regression testing tools and app development."));
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), "127.0.0.1"));
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u )"), 18030));
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u )"), 21769));
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
@@ -87,19 +87,19 @@ static int AppInitRPC(int argc, char* argv[])
ParseParameters(argc, argv);
std:string name;
// default HAC is HUSH3 itself, which to the internals, is also a HAC
name = GetArg("-ac_name","HUSH3");
// default HAC is DRAGONX itself, which to the internals, is also a HAC
name = GetArg("-ac_name","DRAGONX");
if ( !name.empty() )
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) {
std::string strUsage = _("Hush RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
std::string strUsage = _("DragonX RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
if (!mapArgs.count("-version")) {
strUsage += "\n" + _("Usage:") + "\n" +
" hush-cli [options] <command> [params] " + _("Send command to Hush") + "\n" +
" hush-cli [options] help " + _("List commands") + "\n" +
" hush-cli [options] help <command> " + _("Get help for a command") + "\n";
" dragonx-cli [options] <command> [params] " + _("Send command to DragonX") + "\n" +
" dragonx-cli [options] help " + _("List commands") + "\n" +
" dragonx-cli [options] help <command> " + _("Get help for a command") + "\n";
strUsage += "\n" + HelpMessageCli();
} else {

View File

@@ -117,14 +117,14 @@ bool AppInit(int argc, char* argv[])
// Process help and version before taking care about datadir
if (mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version"))
{
std::string strUsage = _("Hush Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
std::string strUsage = _("DragonX Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo();
if (mapArgs.count("-version"))
{
strUsage += LicenseInfo();
} else {
strUsage += "\n" + _("Usage:") + "\n" +
" hushd [options] " + _("Start a Hush Daemon") + "\n";
" dragonxd [options] " + _("Start DragonX Daemon") + "\n";
strUsage += "\n" + HelpMessage(HMM_BITCOIND);
}
@@ -167,13 +167,13 @@ bool AppInit(int argc, char* argv[])
"\n"
"You can look at the example configuration file for suggestions of default\n"
"options that you may want to change. It should be in one of these locations,\n"
"depending on how you installed Hush\n") +
"depending on how you installed DragonX\n") +
_("- Source code: %s\n"
"- .deb package: %s\n")).c_str(),
GetConfigFile().string().c_str(),
"contrib/debian/examples/HUSH3.conf",
"/usr/share/doc/hush/examples/HUSH3.conf",
"https://git.hush.is/hush/hush3/src/branch/master/contrib/debian/examples/HUSH3.conf");
"contrib/debian/examples/DRAGONX.conf",
"/usr/share/doc/dragonx/examples/DRAGONX.conf",
"https://git.dragonx.is/DragonX/dragonx/src/branch/main/contrib/debian/examples/DRAGONX.conf");
return false;
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
@@ -183,15 +183,15 @@ bool AppInit(int argc, char* argv[])
// Command-line RPC
bool fCommandLine = false;
for (int i = 1; i < argc; i++) {
// detect accidental use of RPC in hushd
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "hush:")) {
// detect accidental use of RPC in dragonxd
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "dragonx:")) {
fCommandLine = true;
}
}
if (fCommandLine)
{
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in hushd. Use the hush-cli utility instead.\n");
fprintf(stderr, "Error: Ooops! There is no RPC client functionality in dragonxd. Use the dragonx-cli utility instead.\n");
exit(EXIT_FAILURE);
}
@@ -199,7 +199,7 @@ bool AppInit(int argc, char* argv[])
fDaemon = GetBoolArg("-daemon", false);
if (fDaemon)
{
fprintf(stdout, "Hush %s server starting\n",SMART_CHAIN_SYMBOL);
fprintf(stdout, "DragonX %s server starting\n",SMART_CHAIN_SYMBOL);
// Daemonize
pid_t pid = fork();

View File

@@ -1,5 +1,5 @@
SHELL = /bin/sh
CC_DARWIN = g++-8
CC_DARWIN = g++-15
CC_WIN = x86_64-w64-mingw32-gcc-posix
CC_AARCH64 = aarch64-linux-gnu-g++
CFLAGS_DARWIN = -DBUILD_CUSTOMCC -std=c++11 -arch x86_64 -I../secp256k1/include -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../leveldb/include -I.. -I. -fPIC -Wl,-undefined -Wl,dynamic_lookup -Wno-write-strings -shared -dynamiclib

BIN
src/cc/customcc.dylib Normal file

Binary file not shown.

View File

@@ -32,8 +32,18 @@ class CChainPower;
#include <boost/foreach.hpp>
extern bool fZindex;
static const int SPROUT_VALUE_VERSION = 1001400;
static const int SAPLING_VALUE_VERSION = 1010100;
// These version thresholds control whether nSproutValue/nSaplingValue are
// serialized in the block index. They must be <= CLIENT_VERSION or the
// values will never be persisted, causing nChainSaplingValue to reset
// to 0 after node restart. DragonX CLIENT_VERSION is 1000350 (v1.0.3.50).
static const int SPROUT_VALUE_VERSION = 1000000;
static const int SAPLING_VALUE_VERSION = 1000000;
// Block-index records written at >= this version store nSaplingValue as a boost::optional
// (1-byte discriminant + value). Earlier records stored it as a raw 8-byte CAmount; the
// deserializer consumes those bytes but reads the value as boost::none (untrusted) so the
// turnstile guard stays dormant until such a node reindexes. Keep == the CLIENT_VERSION that
// introduced the optional format (v1.0.3, CLIENT_VERSION 1000350).
static const int SAPLING_VALUE_OPTIONAL_VERSION = 1000350;
extern int32_t ASSETCHAINS_LWMAPOS;
extern char SMART_CHAIN_SYMBOL[65];
extern uint64_t ASSETCHAINS_NOTARY_PAY[];
@@ -369,10 +379,10 @@ public:
//! Will be boost::none if nChainTx is zero.
boost::optional<CAmount> nChainSproutValue;
//! Change in value held by the Sapling circuit over this block.
//! Not a boost::optional because this was added before Sapling activated, so we can
//! rely on the invariant that every block before this was added had nSaplingValue = 0.
CAmount nSaplingValue;
//! Change in value held by the Sapling circuit over this block. boost::none for blocks
//! before nSaplingValue was tracked, or on nodes that loaded an older-format block index
//! (see SAPLING_VALUE_OPTIONAL_VERSION) -- propagates to nChainSaplingValue == none.
boost::optional<CAmount> nSaplingValue;
//! (memory only) Total value held by the Sapling circuit up to and including this block.
//! Will be boost::none if nChainTx is zero.
@@ -395,7 +405,16 @@ public:
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
uint32_t nSequenceId;
//! (memory only) Set true once this block's RandomX PoW has been verified by the parallel
//! pre-verification pool, letting the inline check in CheckBlockHeader skip the recompute.
//! Written by exactly one pre-verify worker (1:1 with the block) and read by the connect
//! thread only AFTER the pool barrier (CCheckQueue::Wait provides the happens-before), so a
//! plain bool is race-free here. NOT serialized — a pure optimization hint; the inline
//! CheckRandomXSolution remains the consensus authority. (Plain bool, not std::atomic, so
//! CBlockIndex stays copyable for CDiskBlockIndex's `CBlockIndex(*pindex)` construction.)
bool fRandomXVerified;
void SetNull()
{
phashBlock = NULL;
@@ -410,6 +429,7 @@ public:
chainPower = CChainPower();
nTx = 0;
nChainTx = 0;
fRandomXVerified = false;
// Shieldex Index chain stats
nChainPayments = 0;
@@ -446,7 +466,7 @@ public:
nSequenceId = 0;
nSproutValue = boost::none;
nChainSproutValue = boost::none;
nSaplingValue = 0;
nSaplingValue = boost::none;
nChainSaplingValue = boost::none;
nVersion = 0;
@@ -653,10 +673,22 @@ public:
READWRITE(nSproutValue);
}
// Only read/write nSaplingValue if the client version used to create
// this index was storing them.
if ((s.GetType() & SER_DISK) && (nVersion >= SAPLING_VALUE_VERSION)) {
READWRITE(nSaplingValue);
// nSaplingValue is a boost::optional so "not reliably tracked" reads back as none,
// keeping the turnstile guard dormant on old/snapshot-bootstrapped DBs. Records written
// before SAPLING_VALUE_OPTIONAL_VERSION stored it as a raw 8-byte CAmount: consume those
// bytes for alignment but DISCARD the value (read as none), since it may be understated on
// a node that never tracked the pool from genesis. That node stays dormant until it
// reindexes, which rewrites records at the current version -> the optional path below.
if (s.GetType() & SER_DISK) {
if (nVersion >= SAPLING_VALUE_OPTIONAL_VERSION) {
READWRITE(nSaplingValue); // new format: boost::optional<CAmount>
} else if (nVersion >= SAPLING_VALUE_VERSION) {
CAmount nLegacySaplingValue = 0; // old format: raw 8 bytes present in the stream
READWRITE(nLegacySaplingValue); // consume for alignment; value is discarded
if (ser_action.ForRead())
nSaplingValue = boost::none;
}
// else (< SAPLING_VALUE_VERSION): field was never stored -> nSaplingValue stays none
}
// These values only serialized when -zindex enabled

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ class CBaseMainParams : public CBaseChainParams
public:
CBaseMainParams()
{
nRPCPort = 18031;
nRPCPort = 21769;
}
};
static CBaseMainParams mainParams;

View File

@@ -32,7 +32,7 @@
* for both bitcoind and bitcoin-core, to make it harder for attackers to
* target servers or GUI users specifically.
*/
const std::string CLIENT_NAME = GetArg("-clientname", "GoldenSandtrout");
const std::string CLIENT_NAME = GetArg("-clientname", "DragonX");
/**
* Client version number

View File

@@ -28,9 +28,9 @@
// client versioning and copyright year
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
// Must be kept in sync with configure.ac , ugh!
#define CLIENT_VERSION_MAJOR 3
#define CLIENT_VERSION_MINOR 10
#define CLIENT_VERSION_REVISION 5
#define CLIENT_VERSION_MAJOR 1
#define CLIENT_VERSION_MINOR 0
#define CLIENT_VERSION_REVISION 3
#define CLIENT_VERSION_BUILD 50
//! Set to true for release, false for prerelease or test build
@@ -40,7 +40,7 @@
* Copyright year (2009-this)
* Todo: update this when changing our copyright comments in the source
*/
#define COPYRIGHT_YEAR 2024
#define COPYRIGHT_YEAR 2026
#endif //HAVE_CONFIG_H

View File

@@ -47,7 +47,7 @@ bool sanity_test_range_fmt()
{
std::string test;
try {
test.at(1);
(void)test.at(1);
} catch (const std::out_of_range&) {
return true;
} catch (...) {

View File

@@ -33,10 +33,13 @@
#include "rand.h"
#include "sha2.h"
#if USE_BIP39_CACHE
// BIP39_WORDS is used unconditionally by the wordlist helpers below, so it must
// be defined even when the BIP39 cache is disabled (upstream places it inside
// the cache block by mistake).
int BIP39_WORDS = 2048;
#if USE_BIP39_CACHE
static int bip39_cache_index = 0;
static CONFIDENTIAL struct {

View File

@@ -56,8 +56,10 @@
#endif
// implement BIP39 caching
// Disabled: caching keeps the plaintext mnemonic/passphrase/seed in a static
// process-lifetime buffer, which we do not want in a wallet daemon.
#ifndef USE_BIP39_CACHE
#define USE_BIP39_CACHE 1
#define USE_BIP39_CACHE 0
#define BIP39_CACHE_SIZE 4
#endif

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Tests for HD-derived transparent keys (m/44'/coin'/0'/0/i) and the
// version-gated CHDChain serialization used to persist the transparent counter.
#include <gtest/gtest.h>
#include "key.h"
#include "chainparams.h"
#include "streams.h"
#include "uint256.h"
#include "util.h"
#include "version.h"
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#include "zcash/zip32.h"
// Build an in-memory wallet with a known seed + hdChain so that the
// HD-transparent path (IsHDTransparentEnabled) is active.
static void LoadSeedForTest(CWallet& wallet, const HDSeed& seed)
{
wallet.LoadHDSeed(seed);
CHDChain chain;
chain.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
chain.seedFp = seed.Fingerprint();
chain.nCreateTime = 1;
wallet.SetHDChain(chain, true /* memonly */);
}
// Same seed must reproduce the same transparent addresses in the same order:
// this is the recovery guarantee that lets a seed-only restore find coinbase.
TEST(hdtransparent_tests, DeterministicFromSeed)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x42);
HDSeed seed(rawSeed);
std::vector<CKeyID> keysA;
{
CWallet wallet;
LoadSeedForTest(wallet, seed);
ASSERT_TRUE(wallet.IsHDTransparentEnabled());
LOCK(wallet.cs_wallet);
for (int i = 0; i < 5; i++) {
CPubKey pk = wallet.GenerateNewKey();
keysA.push_back(pk.GetID());
const CKeyMetadata& md = wallet.mapKeyMetadata[pk.GetID()];
EXPECT_EQ(md.seedFp, seed.Fingerprint());
EXPECT_EQ(md.hdKeypath, std::string("m/44'/141'/0'/0/") + std::to_string(i));
}
}
// Fresh wallet, same seed -> identical keys.
{
CWallet wallet;
LoadSeedForTest(wallet, seed);
LOCK(wallet.cs_wallet);
for (int i = 0; i < 5; i++) {
CPubKey pk = wallet.GenerateNewKey();
EXPECT_EQ(pk.GetID(), keysA[i]);
}
}
}
// Pin the exact derivation path so it can never silently change.
TEST(hdtransparent_tests, KnownDerivationPath)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x42);
HDSeed seed(rawSeed);
// Independently derive m/44'/141'/0'/0/0.
RawHDSeed raw = seed.RawSeed();
CExtKey m, purpose, coinType, account, external, child;
m.SetMaster(raw.data(), raw.size());
m.Derive(purpose, 44 | BIP32_HARDENED_KEY_LIMIT);
purpose.Derive(coinType, 141 | BIP32_HARDENED_KEY_LIMIT);
coinType.Derive(account, 0 | BIP32_HARDENED_KEY_LIMIT);
account.Derive(external, 0);
external.Derive(child, 0);
CKeyID expected = child.key.GetPubKey().GetID();
CWallet wallet;
LoadSeedForTest(wallet, seed);
LOCK(wallet.cs_wallet);
CPubKey pk = wallet.GenerateNewKey();
EXPECT_EQ(pk.GetID(), expected);
}
// A pre-existing v1 CHDChain record (no transparent counter) must still
// deserialize under v2 code, leaving transparentChildCounter at 0; and a v2
// record must round-trip the counter.
TEST(hdtransparent_tests, HDChainVersionCompat)
{
CHDChain v1;
v1.nVersion = CHDChain::VERSION_HD_BASE; // 1: transparentChildCounter not serialized
v1.seedFp = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
v1.nCreateTime = 12345;
v1.saplingAccountCounter = 7;
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
ss << v1;
CHDChain out; // default-constructed: SetNull() zeroes transparentChildCounter
ss >> out;
EXPECT_EQ(out.nVersion, +CHDChain::VERSION_HD_BASE); // unary + -> rvalue, avoid ODR-use of static const
EXPECT_EQ(out.seedFp, v1.seedFp);
EXPECT_EQ(out.nCreateTime, (int64_t)12345);
EXPECT_EQ(out.saplingAccountCounter, (uint32_t)7);
EXPECT_EQ(out.transparentChildCounter, (uint32_t)0);
CHDChain v2;
v2.nVersion = CHDChain::VERSION_HD_TRANSPARENT;
v2.saplingAccountCounter = 3;
v2.transparentChildCounter = 42;
CDataStream ss2(SER_DISK, PROTOCOL_VERSION);
ss2 << v2;
CHDChain out2;
ss2 >> out2;
EXPECT_EQ(out2.nVersion, +CHDChain::VERSION_HD_TRANSPARENT);
EXPECT_EQ(out2.saplingAccountCounter, (uint32_t)3);
EXPECT_EQ(out2.transparentChildCounter, (uint32_t)42);
}
// Restoring from a 32-byte seed hex reproduces the same keys as the source
// wallet, and refuses to run when a seed already exists.
TEST(hdtransparent_tests, RestoreFromSeedHex)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed rawSeed(HD_WALLET_SEED_LENGTH, 0x7a);
HDSeed seed(rawSeed);
std::string seedHex = HexStr(seed.RawSeed());
// Source wallet: derive some keys.
std::vector<CKeyID> expected;
{
CWallet wallet;
LoadSeedForTest(wallet, seed);
LOCK(wallet.cs_wallet);
for (int i = 0; i < 3; i++)
expected.push_back(wallet.GenerateNewKey().GetID());
}
// Restored wallet: inject the seed hex, pre-derive, and compare.
{
CWallet wallet;
ASSERT_TRUE(wallet.SetHDSeedFromHex(seedHex));
// Second attempt must fail: a seed already exists.
EXPECT_FALSE(wallet.SetHDSeedFromHex(seedHex));
wallet.TopUpHDTransparentKeys(3, 1);
LOCK(wallet.cs_wallet);
for (int i = 0; i < 3; i++)
EXPECT_TRUE(wallet.HaveKey(expected[i]));
}
// Bad input is rejected.
{
CWallet wallet;
EXPECT_FALSE(wallet.SetHDSeedFromHex("nothex"));
EXPECT_FALSE(wallet.SetHDSeedFromHex("abcd")); // too short
}
}

View File

@@ -0,0 +1,141 @@
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Proves that a BIP39 seed phrase produces the SAME transparent and shielded
// addresses on the DragonX full node as in SilentDragonXLite. The proof chain:
// phrase -> entropy (round-trip) -> 64-byte BIP39 seed (known-answer)
// -> z/t addresses (wallet path == direct ZIP-32/BIP44 derivation).
// The 64-byte seed is anchored to the well-known BIP39 value for the all-zero
// "abandon...art" entropy with an EMPTY passphrase, which is exactly what
// SilentDragonXLite's tiny-bip39 0.6.2 feeds into the same coin_type=141 paths.
#include <gtest/gtest.h>
#include "chainparams.h"
#include "key.h"
#include "key_io.h"
#include "util.h"
#include "wallet/mnemonic.h"
#include "wallet/wallet.h"
#include "zcash/Address.hpp"
#include "zcash/zip32.h"
// The canonical 24-word phrase for 32 bytes of all-zero entropy.
static const char* ABANDON_ART =
"abandon abandon abandon abandon abandon abandon abandon abandon "
"abandon abandon abandon abandon abandon abandon abandon abandon "
"abandon abandon abandon abandon abandon abandon abandon art";
// The standard BIP39 seed for that phrase with an EMPTY passphrase
// (PBKDF2-HMAC-SHA512, 2048 rounds, salt "mnemonic"). Matches tiny-bip39.
static const char* SEED64_HEX =
"408b285c123836004f4b8842c89324c1f01382450c0d439af345ba7fc49acf70"
"5489c6fc77dbd4e3dc1dd8cc6bc9f043db8ada1e243c4a0eafb290d399480840";
// First shielded address for a 64-byte seed: m/32'/141'/0' default address.
static std::string DeriveZAddrFromSeed64(RawHDSeed seed64)
{
HDSeed s(seed64);
auto m = libzcash::SaplingExtendedSpendingKey::Master(s);
auto xsk = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT)
.Derive(141 | ZIP32_HARDENED_KEY_LIMIT)
.Derive(0 | ZIP32_HARDENED_KEY_LIMIT);
return EncodePaymentAddress(xsk.DefaultAddress());
}
// First transparent address for a BIP32 master over `seedBytes`:
// m/44'/141'/0'/0/0.
static std::string DeriveTAddrFromSeedBytes(RawHDSeed seedBytes)
{
CExtKey master, purpose, coinType, account, external, child;
master.SetMaster(seedBytes.data(), seedBytes.size());
master.Derive(purpose, 44 | BIP32_HARDENED_KEY_LIMIT);
purpose.Derive(coinType, 141 | BIP32_HARDENED_KEY_LIMIT);
coinType.Derive(account, 0 | BIP32_HARDENED_KEY_LIMIT);
account.Derive(external, 0);
external.Derive(child, 0);
return EncodeDestination(child.key.GetPubKey().GetID());
}
// The 64-byte seed derived from the mnemonic must equal the known BIP39 value.
// This is the cross-wallet anchor: SilentDragonXLite feeds the identical seed.
TEST(mnemonic_compat, Bip39SeedKnownAnswer)
{
RawHDSeed entropy(32, 0);
RawHDSeed seed64;
ASSERT_TRUE(Bip39SeedFromEntropy(entropy, seed64));
ASSERT_EQ(seed64.size(), (size_t)64);
EXPECT_EQ(HexStr(seed64.begin(), seed64.end()), std::string(SEED64_HEX));
}
TEST(mnemonic_compat, EntropyPhraseRoundTrip)
{
RawHDSeed zeros(32, 0);
std::string phrase;
ASSERT_TRUE(EntropyToMnemonic(zeros, phrase));
EXPECT_EQ(phrase, std::string(ABANDON_ART));
EXPECT_TRUE(MnemonicIsValid(ABANDON_ART));
RawHDSeed entropy;
ASSERT_TRUE(MnemonicToEntropy(ABANDON_ART, entropy));
EXPECT_EQ(entropy.size(), (size_t)32);
EXPECT_EQ(HexStr(entropy.begin(), entropy.end()), std::string(64, '0'));
// Bad checksum / unknown words are rejected.
EXPECT_FALSE(MnemonicIsValid("abandon abandon abandon"));
EXPECT_FALSE(MnemonicIsValid("clearly not valid bip39 words at all here"));
RawHDSeed junk;
EXPECT_FALSE(MnemonicToEntropy("clearly not valid bip39 words at all here", junk));
}
// The wallet's mnemonic derivation must reproduce the exact addresses obtained
// by driving ZIP-32 / BIP44 directly from the known 64-byte seed, and must be
// deterministic across wallets.
TEST(mnemonic_compat, WalletDerivesSdxliteAddresses)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed zeros(32, 0), seed64;
ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64));
const std::string expZ = DeriveZAddrFromSeed64(seed64);
const std::string expT = DeriveTAddrFromSeedBytes(seed64);
EXPECT_EQ(expZ.substr(0, 2), "zs"); // sapling HRP for mainnet
CWallet wallet;
ASSERT_TRUE(wallet.SetHDSeedFromMnemonic(ABANDON_ART));
ASSERT_TRUE(wallet.IsMnemonicSeed());
{
LOCK(wallet.cs_wallet);
EXPECT_EQ(EncodePaymentAddress(wallet.GenerateNewSaplingZKey()), expZ);
EXPECT_EQ(EncodeDestination(wallet.GenerateNewKey().GetID()), expT);
}
// Same phrase, fresh wallet -> identical first addresses.
CWallet wallet2;
ASSERT_TRUE(wallet2.SetHDSeedFromMnemonic(ABANDON_ART));
{
LOCK(wallet2.cs_wallet);
EXPECT_EQ(EncodePaymentAddress(wallet2.GenerateNewSaplingZKey()), expZ);
EXPECT_EQ(EncodeDestination(wallet2.GenerateNewKey().GetID()), expT);
}
// The phrase round-trips out of the wallet.
std::string exported;
ASSERT_TRUE(wallet.GetMnemonicPhrase(exported));
EXPECT_EQ(exported, std::string(ABANDON_ART));
}
// Negative: feeding the 32-byte entropy DIRECTLY as the seed (the classic
// interop bug) must produce a different address than the 64-byte BIP39 seed.
TEST(mnemonic_compat, RawEntropyDiffersFromMnemonicSeed)
{
SelectParams(CBaseChainParams::MAIN);
RawHDSeed zeros(32, 0), seed64;
ASSERT_TRUE(Bip39SeedFromEntropy(zeros, seed64));
const std::string seedT = DeriveTAddrFromSeedBytes(seed64); // correct (64-byte)
const std::string entropyT = DeriveTAddrFromSeedBytes(zeros); // wrong (32-byte)
EXPECT_NE(seedT, entropyT);
}

View File

@@ -0,0 +1,172 @@
// Copyright (c) 2024-2026 The DragonX developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
//
// Consensus-equivalence test for the parallel RandomX pre-verification pool. The pool is purely an
// optimization: a block's transient fRandomXVerified flag (set by CRandomXCheck on a real hash
// match) only lets CheckBlockHeader SKIP the inline recompute. So for every block the pool's
// outcome must equal the inline CheckRandomXSolution outcome — `(preVerified || inline) == inline`.
// We exercise a valid solution, a corrupted solution, and confirm the pool never "succeeds" on a
// block the inline check would reject.
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "arith_uint256.h"
#include "chain.h"
#include "chainparams.h"
#include "pow.h"
#include "primitives/block.h"
#include "RandomX/src/randomx.h"
#include "hush_defs.h"
#include "util.h"
#include <boost/thread.hpp>
#include <memory>
extern int32_t HUSH_LOADINGBLOCKS;
extern bool fCheckpointsEnabled;
namespace {
// Compute the correct RandomX solution for a header using a standalone reference light VM, via the
// SAME key + input helpers the validator uses (so the bytes/key match exactly).
void ReferenceRandomXHash(const CBlockHeader& hdr, const std::string& key, unsigned char out[RANDOMX_HASH_SIZE])
{
std::vector<unsigned char> in = GetRandomXInput(hdr);
randomx_flags flags = randomx_get_flags();
randomx_cache* c = randomx_alloc_cache(flags);
ASSERT_NE(c, nullptr);
randomx_init_cache(c, key.data(), key.size());
randomx_vm* vm = randomx_create_vm(flags, c, nullptr);
ASSERT_NE(vm, nullptr);
randomx_calculate_hash(vm, in.data(), in.size(), out);
randomx_destroy_vm(vm);
randomx_release_cache(c);
}
} // namespace
TEST(RandomXPreVerify, ConsensusEquivalence)
{
// Force RandomX validation to actually run at low heights in the test harness.
uint32_t savedAlgo = ASSETCHAINS_ALGO, savedRx = ASSETCHAINS_RANDOMX;
int32_t savedVal = ASSETCHAINS_RANDOMX_VALIDATION, savedLoad = HUSH_LOADINGBLOCKS;
bool savedCkpt = fCheckpointsEnabled;
ASSETCHAINS_RANDOMX = 2; // a distinct nonzero algo id
ASSETCHAINS_ALGO = ASSETCHAINS_RANDOMX;
ASSETCHAINS_RANDOMX_VALIDATION = 1; // enforce from height 1
HUSH_LOADINGBLOCKS = 0; // not in initial-load (else RandomX skipped)
fCheckpointsEnabled = false; // avoid the below-checkpoint skip
const int32_t height = 10; // < interval+lag -> the chain-params initial key (no chainActive needed)
CBlockHeader hdr;
hdr.nVersion = 4;
hdr.hashPrevBlock = uint256S("0x0000000000000000000000000000000000000000000000000000000000000001");
hdr.hashMerkleRoot = uint256S("0x0000000000000000000000000000000000000000000000000000000000000002");
hdr.hashFinalSaplingRoot = uint256S("0x0000000000000000000000000000000000000000000000000000000000000003");
hdr.nTime = 1700000000;
hdr.nBits = 0x200f0f0f;
hdr.nNonce = uint256S("0x0000000000000000000000000000000000000000000000000000000000000004");
std::string key = GetRandomXKey(height);
ASSERT_FALSE(key.empty());
unsigned char good[RANDOMX_HASH_SIZE];
ReferenceRandomXHash(hdr, key, good);
// Run the pool path synchronously on this thread (CRandomXCheck creates its own thread_local VM).
auto poolVerifies = [&](const CBlockHeader& h) -> bool {
RandomXValidatorPrepareKey(key); // load the shared cache with this key
bool slot = false;
CRandomXCheck chk(key, GetRandomXInput(h), h.nSolution.data(), &slot);
chk();
return slot;
};
// Case 1 — valid solution: both inline and pool accept; equivalence holds.
hdr.nSolution.assign(good, good + RANDOMX_HASH_SIZE);
EXPECT_TRUE(CheckRandomXSolution(&hdr, height));
EXPECT_TRUE(poolVerifies(hdr));
EXPECT_EQ(poolVerifies(hdr) || CheckRandomXSolution(&hdr, height), CheckRandomXSolution(&hdr, height));
// Case 2 — corrupted solution: both reject; the pool must NOT set verified.
{
CBlockHeader bad = hdr;
bad.nSolution[0] ^= 0xff;
EXPECT_FALSE(CheckRandomXSolution(&bad, height));
EXPECT_FALSE(poolVerifies(bad));
EXPECT_EQ(poolVerifies(bad) || CheckRandomXSolution(&bad, height), CheckRandomXSolution(&bad, height));
}
// Case 3 — a verified flag on the block lets CheckBlockHeader skip, but verified is only ever set
// by a real hash match, so it can never mask an invalid block. (Pool returns false for the bad
// block above, so its fRandomXVerified stays false and the inline path rejects it at connect.)
ASSETCHAINS_ALGO = savedAlgo; ASSETCHAINS_RANDOMX = savedRx;
ASSETCHAINS_RANDOMX_VALIDATION = savedVal; HUSH_LOADINGBLOCKS = savedLoad;
fCheckpointsEnabled = savedCkpt;
}
// A/B: serial inline verification (single VM) vs the parallel pool (worker threads). Directly
// measures the speedup the pool delivers. We don't care about validity here (mismatched solutions
// still cost a full hash), only wall-clock. parallel must beat serial whenever >1 core is used.
TEST(RandomXPreVerify, ParallelSpeedup)
{
uint32_t savedAlgo = ASSETCHAINS_ALGO, savedRx = ASSETCHAINS_RANDOMX;
int32_t savedVal = ASSETCHAINS_RANDOMX_VALIDATION, savedLoad = HUSH_LOADINGBLOCKS;
bool savedCkpt = fCheckpointsEnabled;
ASSETCHAINS_RANDOMX = 2; ASSETCHAINS_ALGO = ASSETCHAINS_RANDOMX;
ASSETCHAINS_RANDOMX_VALIDATION = 1; HUSH_LOADINGBLOCKS = 0; fCheckpointsEnabled = false;
const int32_t height = 10;
std::string key = GetRandomXKey(height);
ASSERT_FALSE(key.empty());
ASSERT_TRUE(RandomXValidatorPrepareKey(key));
const int M = 16; // blocks to verify in the window
std::vector<CBlockHeader> hdrs(M);
for (int i = 0; i < M; i++) {
hdrs[i].nVersion = 4;
hdrs[i].nTime = 1700000000 + i;
hdrs[i].nBits = 0x200f0f0f;
hdrs[i].nNonce = ArithToUint256(arith_uint256(i + 1)); // distinct inputs
hdrs[i].nSolution.assign(RANDOMX_HASH_SIZE, 0); // arbitrary; we time the hash
}
// Serial baseline: inline single-VM verification (each call hashes, then mismatches -> false).
int64_t t0 = GetTimeMicros();
for (int i = 0; i < M; i++) CheckRandomXSolution(&hdrs[i], height);
int64_t serialUs = GetTimeMicros() - t0;
// Parallel: spawn K-1 workers + the master (this thread) joining via Wait().
int K = std::min(8, std::max(2, (int)boost::thread::hardware_concurrency()));
boost::thread_group workers;
for (int i = 0; i < K - 1; i++) workers.create_thread(&ThreadRandomXVerify);
std::unique_ptr<bool[]> slots(new bool[M]());
std::vector<CRandomXCheck> checks;
checks.reserve(M);
for (int i = 0; i < M; i++)
checks.push_back(CRandomXCheck(key, GetRandomXInput(hdrs[i]), hdrs[i].nSolution.data(), &slots[i]));
int64_t t1 = GetTimeMicros();
{
CCheckQueueControl<CRandomXCheck> control(&rxCheckQueue);
control.Add(checks);
control.Wait();
}
int64_t parallelUs = GetTimeMicros() - t1;
workers.interrupt_all();
workers.join_all();
printf("[ RandomX A/B ] %d blocks: serial(1 VM)=%ldms, parallel(%d threads)=%ldms, speedup=%.1fx\n",
M, (long)(serialUs / 1000), K, (long)(parallelUs / 1000),
(double)serialUs / (double)std::max<int64_t>(1, parallelUs));
EXPECT_LT(parallelUs, serialUs); // parallel must be faster than serial on a multi-core box
ASSETCHAINS_ALGO = savedAlgo; ASSETCHAINS_RANDOMX = savedRx;
ASSETCHAINS_RANDOMX_VALIDATION = savedVal; HUSH_LOADINGBLOCKS = savedLoad;
fCheckpointsEnabled = savedCkpt;
}

View File

@@ -510,7 +510,9 @@ int32_t hush_validate_chain(uint256 srchash,int32_t notarized_height)
return(0);
if ( IsInitialBlockDownload() == 0 && ((pindex= hush_getblockindex(srchash)) == 0 || pindex->GetHeight() != notarized_height) )
{
fprintf(stderr,"%s: Not in IBD, height=%d\n", __func__, pindex->GetHeight() );
// SECURITY (null-deref crash DoS): this branch is entered when pindex==0 (srchash, taken
// from an attacker-controlled notarization OP_RETURN, is not a known block). Guard the deref.
fprintf(stderr,"%s: Not in IBD, height=%d\n", __func__, pindex != 0 ? pindex->GetHeight() : -1 );
if ( sp->NOTARIZED_HEIGHT > 0 && sp->NOTARIZED_HEIGHT < notarized_height )
rewindtarget = sp->NOTARIZED_HEIGHT - 1;
else if ( notarized_height > 101 )
@@ -589,6 +591,14 @@ int32_t hush_voutupdate(bool fJustCheck,int32_t *isratificationp,int32_t notaryi
opretlen += (scriptbuf[len++] << 8);
}
opoffset = len;
// SECURITY (Finding #4): opretlen is attacker-controlled (up to 65535 via OP_PUSHDATA2)
// and was previously used with no bounds check. scriptbuf is a fixed DRAGON_MAXSCRIPTSIZE
// stack buffer in hush_connectblock, so an oversized opretlen drives out-of-bounds reads in
// the downstream 'K'/KV and notarization paths (persisted to disk, leaked via kvsearch RPC,
// reliable crash on block connect). Reject any opret claiming more bytes than actually
// remain in the real script; this mirrors the no-OP_RETURN fall-through so nothing valid changes.
if ( opretlen < 0 || opretlen > scriptlen - len )
return(notaryid);
matched = 0;
if ( SMART_CHAIN_SYMBOL[0] == 0 )
{
@@ -931,7 +941,7 @@ int32_t hush_connectblock(bool fJustCheck, CBlockIndex *pindex,CBlock& block)
if ( len >= sizeof(uint32_t) && len <= sizeof(scriptbuf) )
{
memcpy(scriptbuf,(uint8_t *)&block.vtx[i].vout[j].scriptPubKey[0],len);
if ( len == 35 && scriptbuf[0] == 33 && scriptbuf[34] == 0xac )
if ( len == 35 && scriptbuf[0] == 33 && scriptbuf[34] == 0xac && numvalid < (int32_t)(sizeof(pubkeys)/sizeof(pubkeys[0])) )
{
memcpy(pubkeys[numvalid++],scriptbuf+1,33);
for (k=0; k<33; k++)

View File

@@ -580,70 +580,103 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
char pchBuf[0x10000];
bool bIsSSL = false;
int nBytes = 0, nRet = 0;
// Drain the socket in a bounded loop rather than one read per select pass: a single
// 64K read per pass underfills high-bandwidth/high-latency links. Cap the reads per
// pass and honor the receive-flood back-pressure so one peer can neither exhaust
// memory nor starve other peers within this pass.
int nDrainReads = 0;
const int MAX_DRAIN_READS = 16; // up to ~1 MiB per peer per pass (fairness across peers)
// Pre-read back-pressure: gate on the flood ceiling BEFORE each read so the per-peer
// recv buffer high-water stays at ReceiveFloodSize()+one read (matching the select()
// FD_SET gate), and track bytes locally to avoid the O(n) GetTotalRecvSize() per pass.
const int64_t nRecvBase = (int64_t)pnode->GetTotalRecvSize();
int64_t nPassBytes = 0;
bool fKeepReading = true;
while (fKeepReading) {
int nSSLPending = 0;
if (nRecvBase + nPassBytes > (int64_t)ReceiveFloodSize())
break;
{
LOCK(pnode->cs_hSocket);
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
LogPrint("tls", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
return -1;
}
if (pnode->hSocket == INVALID_SOCKET) {
LogPrint("tls", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
return -1;
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL) {
wolfSSL_ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
nBytes = wolfSSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
// Capture TLS buffered-byte count while still under cs_hSocket; the drain-continuation
// check below runs unlocked, so touching pnode->ssl there would race with
// SocketSendData / CloseSocketDisconnect (which free ssl) -> data race / use-after-free.
nSSLPending = wolfSSL_pending(pnode->ssl);
} else {
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL) {
wolfSSL_ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
nBytes = wolfSSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
} else {
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
if (nBytes > 0) {
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
pnode->CloseSocketDisconnect();
pnode->nLastRecv = GetTime();
pnode->nRecvBytes += nBytes;
pnode->RecordBytesRecv(nBytes);
} else if (nBytes == 0) {
if (bIsSSL) {
unsigned long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read err: %s\n",
__FILE__, __func__, __LINE__, error_str);
}
// socket closed gracefully (peer disconnected)
if (!pnode->fDisconnect)
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
pnode->CloseSocketDisconnect();
} else if (nBytes < 0) {
// error
if (bIsSSL) {
if (nRet != WOLFSSL_ERROR_WANT_READ && nRet != WOLFSSL_ERROR_WANT_WRITE)
{
if (!pnode->fDisconnect)
LogPrintf("TLS: ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
if (nBytes > 0) {
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes)) {
pnode->CloseSocketDisconnect();
fKeepReading = false;
}
pnode->nLastRecv = GetTime();
pnode->nRecvBytes += nBytes;
pnode->RecordBytesRecv(nBytes);
nPassBytes += nBytes;
// Keep draining only while the socket likely has more data (we filled the
// buffer, or TLS has buffered decrypted bytes) and within the per-pass cap.
// The flood ceiling is enforced pre-read at the top of the loop.
if (fKeepReading) {
bool fMore = (nBytes == (int)sizeof(pchBuf)) || (bIsSSL && nSSLPending > 0);
if (!fMore || ++nDrainReads >= MAX_DRAIN_READS)
fKeepReading = false;
}
} else if (nBytes == 0) {
if (bIsSSL) {
unsigned long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read - code[0x%x], err: %s\n",
__FILE__, __func__, __LINE__, nRet, error_str);
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read err: %s\n",
__FILE__, __func__, __LINE__, error_str);
}
// socket closed gracefully (peer disconnected)
if (!pnode->fDisconnect)
LogPrint("tls", "socket closed (%s)\n", pnode->addr.ToString());
pnode->CloseSocketDisconnect();
fKeepReading = false;
} else if (nBytes < 0) {
// error
if (bIsSSL) {
if (nRet != WOLFSSL_ERROR_WANT_READ && nRet != WOLFSSL_ERROR_WANT_WRITE)
{
if (!pnode->fDisconnect)
LogPrintf("TLS: ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
pnode->CloseSocketDisconnect();
unsigned long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
LogPrint("tls", "TLS: WARNING: %s: %s():%d - SSL_read - code[0x%x], err: %s\n",
__FILE__, __func__, __LINE__, nRet, error_str);
} else {
// preventive measure from exhausting CPU usage
MilliSleep(1); // 1 msec
}
} else {
// preventive measure from exhausting CPU usage
MilliSleep(1); // 1 msec
}
} else {
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) {
if (!pnode->fDisconnect)
LogPrintf("TLS: ERROR: socket recv %s\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) {
if (!pnode->fDisconnect)
LogPrintf("TLS: ERROR: socket recv %s\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
}
}
fKeepReading = false;
}
}
}

View File

@@ -403,7 +403,7 @@ int32_t notarizedtxid_height(char *dest,char *txidstr,int32_t *hushnotarized_hei
params[0] = 0;
*hushnotarized_heightp = 0;
if ( strcmp(dest,"HUSH3") == 0 ) {
port = HUSH3_PORT;
port = DRAGONX_PORT;
userpass = HUSHUSERPASS;
} else if ( strcmp(dest,"BTC") == 0 )
{
@@ -498,7 +498,7 @@ int32_t hush_verifynotarization(char *symbol,char *dest,int32_t height,int32_t N
{
if ( SMART_CHAIN_SYMBOL[0] != 0 )
{
jsonstr = hush_issuemethod(HUSHUSERPASS,(char *)"getrawtransaction",params,HUSH3_PORT);
jsonstr = hush_issuemethod(HUSHUSERPASS,(char *)"getrawtransaction",params,DRAGONX_PORT);
//printf("userpass.(%s) got (%s)\n",HUSHUSERPASS,jsonstr);
}
}//else jsonstr = _dex_getrawtransaction();
@@ -1693,6 +1693,11 @@ int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height)
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckEquihashSolution failed\n",slowflag,height);
return(-1);
}
if ( !CheckRandomXSolution(pblock, height) )
{
fprintf(stderr,"hush_checkPOW slowflag.%d ht.%d CheckRandomXSolution failed\n",slowflag,height);
return(-1);
}
hash = pblock->GetHash();
bnTarget.SetCompact(pblock->nBits,&fNegative,&fOverflow);
bhash = UintToArith256(hash);

View File

@@ -568,6 +568,7 @@ extern uint64_t ASSETCHAINS_SUPPLY, ASSETCHAINS_FOUNDERS_REWARD;
extern int32_t ASSETCHAINS_LWMAPOS, ASSETCHAINS_SAPLING, ASSETCHAINS_OVERWINTER,ASSETCHAINS_BLOCKTIME;
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
extern uint32_t ASSETCHAINS_ALGO,ASSETCHAINS_EQUIHASH,ASSETCHAINS_RANDOMX, HUSH_INITDONE;
extern int32_t ASSETCHAINS_RANDOMX_VALIDATION;
extern int32_t HUSH_MININGTHREADS,HUSH_LONGESTCHAIN,ASSETCHAINS_SEED,IS_HUSH_NOTARY,USE_EXTERNAL_PUBKEY,HUSH_CHOSEN_ONE,HUSH_ON_DEMAND,HUSH_PASSPORT_INITDONE,ASSETCHAINS_STAKED,HUSH_NSPV;
extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_LASTERA,ASSETCHAINS_CBOPRET;
extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_NOTARY_PAY[ASSETCHAINS_MAX_ERAS+1], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[],ASSETCHAINS_NK[2];

View File

@@ -93,6 +93,7 @@ uint64_t ASSETCHAINS_NONCEMASK[] = {0xffff};
uint32_t ASSETCHAINS_NONCESHIFT[] = {32};
uint32_t ASSETCHAINS_HASHESPERROUND[] = {1};
uint32_t ASSETCHAINS_ALGO = _ASSETCHAINS_EQUIHASH;
int32_t ASSETCHAINS_RANDOMX_VALIDATION = -1; // activation height for RandomX validation (-1 = disabled)
// min diff returned from GetNextWorkRequired needs to be added here for each algo, so they can work with ac_staked.
uint32_t ASSETCHAINS_MINDIFF[] = {537857807};
int32_t ASSETCHAINS_LWMAPOS = 0; // percentage of blocks should be PoS
@@ -101,7 +102,7 @@ int32_t ASSETCHAINS_OVERWINTER = -1;
int32_t ASSETCHAINS_STAKED;
uint64_t ASSETCHAINS_COMMISSION,ASSETCHAINS_SUPPLY = 10,ASSETCHAINS_FOUNDERS_REWARD;
uint32_t HUSH_INITDONE;
char HUSHUSERPASS[8192+512+1],BTCUSERPASS[8192]; uint16_t HUSH3_PORT = 18031,BITCOIND_RPCPORT = 18031;
char HUSHUSERPASS[8192+512+1],BTCUSERPASS[8192]; uint16_t DRAGONX_PORT = 21769,BITCOIND_RPCPORT = 21769;
uint64_t PENDING_HUSH_TX;
extern int32_t HUSH_LOADINGBLOCKS;
unsigned int MAX_BLOCK_SIGOPS = 20000;

View File

@@ -324,7 +324,7 @@ int32_t NSPV_mempoolfuncs(bits256 *satoshisp,int32_t *vindexp,std::vector<uint25
CScript scriptPubKey = tx.vout[tx.vout.size()-1].scriptPubKey;
if ( GetOpReturnData(scriptPubKey,vopret) != 0 )
{
if ( vopret[0] == evalcode && vopret[1] == func )
if ( vopret.size() >= 2 && vopret[0] == evalcode && vopret[1] == func )
{
txids.push_back(hash);
num++;
@@ -417,7 +417,9 @@ int32_t NSPV_remoterpc(struct NSPV_remoterpcresp *ptr,char *json,int n)
{
request.read(json,n);
jreq.parse(request);
strcpy(ptr->method,jreq.strMethod.c_str());
// SECURITY (stack overflow): strMethod is attacker-controlled; bound the copy to the fixed buffer.
strncpy(ptr->method,jreq.strMethod.c_str(),sizeof(ptr->method)-1);
ptr->method[sizeof(ptr->method)-1] = '\0';
len+=sizeof(ptr->method);
std::map<std::string, bool>::iterator it = nspv_remote_commands.find(jreq.strMethod);
if (it==nspv_remote_commands.end())
@@ -438,8 +440,10 @@ int32_t NSPV_remoterpc(struct NSPV_remoterpcresp *ptr,char *json,int n)
{
rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id);
response=rpc_result.write();
memcpy(ptr->json,response.c_str(),response.size());
len+=response.size();
// SECURITY (stack overflow): clamp to the fixed json buffer.
size_t rlen = response.size(); if ( rlen > sizeof(ptr->json) ) rlen = sizeof(ptr->json);
memcpy(ptr->json,response.c_str(),rlen);
len+=rlen;
return (len);
}
else throw JSONRPCError(RPC_MISC_ERROR, "Error in executing RPC on remote node");
@@ -459,8 +463,10 @@ int32_t NSPV_remoterpc(struct NSPV_remoterpcresp *ptr,char *json,int n)
rpc_result = JSONRPCReplyObj(NullUniValue,JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
response=rpc_result.write();
}
memcpy(ptr->json,response.c_str(),response.size());
len+=response.size();
// SECURITY (stack overflow): the error path echoes attacker-controlled jreq.id; clamp to the buffer.
size_t rlen = response.size(); if ( rlen > sizeof(ptr->json) ) rlen = sizeof(ptr->json);
memcpy(ptr->json,response.c_str(),rlen);
len+=rlen;
return (len);
}
@@ -651,10 +657,10 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
if ( timestamp > pfrom->prevtimes[ind] )
{
struct NSPV_utxosresp U;
if ( len < 64+5 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) )
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;
memcpy(coinaddr,&request[2],request[1]);
memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write
coinaddr[request[1]] = 0;
if ( request[1] == len-3 )
isCC = (request[len-1] != 0);
@@ -691,10 +697,10 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
if ( timestamp > pfrom->prevtimes[ind] )
{
struct NSPV_txidsresp T;
if ( len < 64+5 && (request[1] == len-3 || request[1] == len-7 || request[1] == len-11) )
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;
memcpy(coinaddr,&request[2],request[1]);
memcpy(coinaddr,&request[2],request[1]); // request[1] < 64 bounds the copy + the terminator write
coinaddr[request[1]] = 0;
if ( request[1] == len-3 )
isCC = (request[len-1] != 0);
@@ -724,7 +730,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
}
NSPV_txidsresp_purge(&T);
}
} else fprintf(stderr,"len.%d req1.%d\n",len,request[1]);
} else fprintf(stderr,"len.%d\n",len);
}
}
else if ( request[0] == NSPV_MEMPOOL )
@@ -732,7 +738,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
if ( timestamp > pfrom->prevtimes[ind] )
{
struct NSPV_mempoolresp M; char coinaddr[64];
if ( len < sizeof(M)+64 )
if ( len >= 40 && len < sizeof(M)+64 ) // SECURITY: lower bound guards the fixed-offset reads request[1..39]
{
int32_t vout; uint256 txid; uint8_t funcid,isCC = 0;
n = 1;
@@ -741,7 +747,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
n += dragon_rwnum(0,&request[n],sizeof(vout),&vout);
n += dragon_rwbignum(0,&request[n],sizeof(txid),(uint8_t *)&txid);
slen = request[n++];
if ( slen < 63 )
if ( slen < 63 && n + slen <= len ) // SECURITY: bound the memcpy source read within request
{
memcpy(coinaddr,&request[n],slen), n += slen;
coinaddr[slen] = 0;
@@ -761,7 +767,7 @@ void hush_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a reque
NSPV_mempoolresp_purge(&M);
}
}
} else fprintf(stderr,"len.%d req1.%d\n",len,request[1]);
} else fprintf(stderr,"len.%d\n",len);
}
}
else if ( request[0] == NSPV_NTZS )

View File

@@ -1413,20 +1413,20 @@ void hush_configfile(char *symbol,uint16_t rpcport)
#ifdef _WIN32
while ( fname[strlen(fname)-1] != '\\' )
fname[strlen(fname)-1] = 0;
strcat(fname,"HUSH3.conf");
strcat(fname,"DRAGONX.conf");
#else
while ( fname[strlen(fname)-1] != '/' )
fname[strlen(fname)-1] = 0;
#ifdef __APPLE__
strcat(fname,"HUSH3.conf");
strcat(fname,"DRAGONX.conf");
#else
strcat(fname,"HUSH3.conf");
strcat(fname,"DRAGONX.conf");
#endif
#endif
if ( (fp= fopen(fname,"rb")) != 0 )
{
if ( (hushport= _hush_userpass(username,password,fp)) != 0 )
HUSH3_PORT = hushport;
DRAGONX_PORT = hushport;
sprintf(HUSHUSERPASS,"%s:%s",username,password);
fclose(fp);
//printf("HUSH.(%s) -> userpass.(%s)\n",fname,HUSHUSERPASS);
@@ -1790,38 +1790,28 @@ void hush_args(char *argv0)
}
name = GetArg("-ac_name","HUSH3");
name = GetArg("-ac_name","DRAGONX");
fprintf(stderr,".oO Starting %s Full Node (Extreme Privacy!) with genproc=%d notary=%d\n",name.c_str(),HUSH_MININGTHREADS, IS_HUSH_NOTARY);
vector<string> HUSH_nodes = {};
// Only HUSH3 uses these by default, other HACs must opt-in via -connect/-addnode
const bool ishush3 = strncmp(name.c_str(), "HUSH3",5) == 0 ? true : false;
vector<string> DRAGONX_nodes = {};
// Only DRAGONX connects to these by default, other chains must opt-in via -connect/-addnode
const bool isdragonx = strncmp(name.c_str(), "DRAGONX",7) == 0 ? true : false;
LogPrint("net", "%s: ishush3=%d\n", __func__, ishush3);
if (ishush3) {
HUSH_nodes = {"node1.hush.is","node2.hush.is","node3.hush.is",
"node4.hush.is","node5.hush.is","node6.hush.is",
"node7.hush.is","node8.hush.is",
"178.250.189.141",
"31.202.19.157",
"45.132.75.69",
"45.63.58.167",
"b2dln7mw7ydnuopls444tuixujhcw5kn5o22cna6gqfmw2fl6drb5nad.onion",
"dslbaa5gut5kapqtd44pbg65tpl5ydsamfy62hjbldhfsvk64qs57pyd.onion",
"vsqdumnh5khjbrzlxoeucbkiuaictdzyc3ezjpxpp2ph3gfwo2ptjmyd.onion",
"plrobkepqjxs2cmig273mxnqh3qhuhdaioyb2n5kafn264ramb7tqxid.onion"
LogPrint("net", "%s: isdragonx=%d\n", __func__, isdragonx);
if (isdragonx) {
DRAGONX_nodes = {"node1.dragonx.is","node2.dragonx.is","node3.dragonx.is",
"node4.dragonx.is","node5.dragonx.is"
};
}
vector<string> more_nodes = mapMultiArgs["-addnode"];
if (more_nodes.size() > 0) {
fprintf(stderr,"%s: Adding %lu more nodes via custom -addnode arguments\n", __func__, more_nodes.size() );
}
// Add default HUSH nodes after custom addnodes, if applicable
if(HUSH_nodes.size() > 0) {
LogPrint("net", "%s: adding %d HUSH3 hostname-based nodes\n", __func__, HUSH_nodes.size() );
more_nodes.insert( more_nodes.end(), HUSH_nodes.begin(), HUSH_nodes.end() );
// Add default DRAGONX nodes after custom addnodes, if applicable
if(DRAGONX_nodes.size() > 0) {
LogPrint("net", "%s: adding %d DRAGONX hostname-based nodes\n", __func__, DRAGONX_nodes.size() );
more_nodes.insert( more_nodes.end(), DRAGONX_nodes.begin(), DRAGONX_nodes.end() );
}
mapMultiArgs["-addnode"] = more_nodes;
@@ -1830,10 +1820,15 @@ void hush_args(char *argv0)
WITNESS_CACHE_SIZE = MAX_REORG_LENGTH+10;
ASSETCHAINS_CC = GetArg("-ac_cc",0);
HUSH_CCACTIVATE = GetArg("-ac_ccactivate",0);
ASSETCHAINS_BLOCKTIME = GetArg("-ac_blocktime",60);
// We do not support ac_public=1 chains, Hush is a platform for privacy
// Set defaults based on chain
int default_blocktime = isdragonx ? 36 : 60;
int default_private = isdragonx ? 1 : 0;
ASSETCHAINS_BLOCKTIME = GetArg("-ac_blocktime", default_blocktime);
// We do not support ac_public=1 chains, DragonX is a platform for privacy
ASSETCHAINS_PUBLIC = 0;
ASSETCHAINS_PRIVATE = GetArg("-ac_private",0);
ASSETCHAINS_PRIVATE = GetArg("-ac_private", default_private);
HUSH_SNAPSHOT_INTERVAL = GetArg("-ac_snapshot",0);
Split(GetArg("-ac_nk",""), sizeof(ASSETCHAINS_NK)/sizeof(*ASSETCHAINS_NK), ASSETCHAINS_NK, 0);
@@ -1871,7 +1866,9 @@ void hush_args(char *argv0)
ASSETCHAINS_EARLYTXIDCONTRACT = GetArg("-ac_earlytxidcontract",0);
if ( name.c_str()[0] != 0 )
{
std::string selectedAlgo = GetArg("-ac_algo", std::string(ASSETCHAINS_ALGORITHMS[0]));
// Default algo is randomx for DRAGONX, equihash for others
std::string default_algo = isdragonx ? "randomx" : std::string(ASSETCHAINS_ALGORITHMS[0]);
std::string selectedAlgo = GetArg("-ac_algo", default_algo);
for ( int i = 0; i < ASSETCHAINS_NUMALGOS; i++ )
{
@@ -1900,12 +1897,20 @@ void hush_args(char *argv0)
// Set our symbol from -ac_name value
strncpy(SMART_CHAIN_SYMBOL,name.c_str(),sizeof(SMART_CHAIN_SYMBOL)-1);
const bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
// Set RandomX validation activation height per chain
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0) {
ASSETCHAINS_RANDOMX_VALIDATION = 2838976; // TBD: set to coordinated upgrade height
} else if (strncmp(SMART_CHAIN_SYMBOL, "TUMIN", 5) == 0) {
ASSETCHAINS_RANDOMX_VALIDATION = 1200; // TBD: set to coordinated upgrade height
} else {
ASSETCHAINS_RANDOMX_VALIDATION = 1; // all other RandomX HACs: enforce from height 1
}
printf("ASSETCHAINS_RANDOMX_VALIDATION set to %d for %s\n", ASSETCHAINS_RANDOMX_VALIDATION, SMART_CHAIN_SYMBOL);
}
ASSETCHAINS_LASTERA = GetArg("-ac_eras", 1);
if(ishush3) {
ASSETCHAINS_LASTERA = 3;
}
if ( ASSETCHAINS_LASTERA < 1 || ASSETCHAINS_LASTERA > ASSETCHAINS_MAX_ERAS )
{
ASSETCHAINS_LASTERA = 1;
@@ -1939,33 +1944,13 @@ void hush_args(char *argv0)
ASSETCHAINS_SCRIPTPUB = GetArg("-ac_script","");
fprintf(stderr,"%s: Setting custom %s reward HUSH3=%d reward,halving,subsidy chain values...\n",__func__, SMART_CHAIN_SYMBOL, ishush3);
if(ishush3) {
// Migrated from hushd script
ASSETCHAINS_CC = 2;
ASSETCHAINS_BLOCKTIME = 150; // this will change to 75 at the correct block
ASSETCHAINS_COMMISSION = 11111111;
// 6250000 - (Sprout pool at block 500,000)
ASSETCHAINS_SUPPLY = 6178674;
ASSETCHAINS_FOUNDERS = 1;
fprintf(stderr,"%s: Setting custom %s reward isdragonx=%d reward,halving,subsidy chain values...\n",__func__, SMART_CHAIN_SYMBOL, isdragonx);
if(isdragonx) {
// DragonX chain parameters (previously set via wrapper script)
// -ac_name=DRAGONX -ac_algo=randomx -ac_halving=3500000 -ac_reward=300000000 -ac_blocktime=36 -ac_private=1
ASSETCHAINS_SAPLING = 1;
// this corresponds to FR address RHushEyeDm7XwtaTWtyCbjGQumYyV8vMjn
ASSETCHAINS_SCRIPTPUB = "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac";
// we do not want to change the magic of HUSH3 mainnet so we do not call devtax_scriptpub_for_height() here,
// instead we call it whenever ASSETCHAINS_SCRIPTPUB is used later on
// Over-ride HUSH3 values from CLI params. Changing our blocktime to 75s changes things
ASSETCHAINS_REWARD[0] = 0;
ASSETCHAINS_REWARD[1] = 1125000000;
ASSETCHAINS_REWARD[2] = 281250000; // 2.8125 HUSH goes to miners per block after 1st halving at Block 340K
ASSETCHAINS_REWARD[3] = 140625000; // 1.40625 HUSH after 2nd halving at Block 2020000
ASSETCHAINS_HALVING[0] = 129;
ASSETCHAINS_HALVING[1] = GetArg("-z2zheight",340000);
ASSETCHAINS_HALVING[2] = 2020000; // 2020000 = 340000 + 1680000 (1st halving block plus new halving interval)
ASSETCHAINS_HALVING[3] = 3700000; // ASSETCHAINS_HALVING[2] + 1680000;
ASSETCHAINS_ENDSUBSIDY[0] = 129;
ASSETCHAINS_ENDSUBSIDY[1] = GetArg("-z2zheight",340000);
ASSETCHAINS_ENDSUBSIDY[2] = 2*5422111; // TODO: Fix this, twice the previous end of rewards is an estimate
ASSETCHAINS_REWARD[0] = 300000000; // 3 DRAGONX per block
ASSETCHAINS_HALVING[0] = 3500000; // halving every 3.5M blocks
}
Split(GetArg("-ac_decay",""), sizeof(ASSETCHAINS_DECAY)/sizeof(*ASSETCHAINS_DECAY), ASSETCHAINS_DECAY, 0);
Split(GetArg("-ac_notarypay",""), sizeof(ASSETCHAINS_NOTARY_PAY)/sizeof(*ASSETCHAINS_NOTARY_PAY), ASSETCHAINS_NOTARY_PAY, 0);
@@ -2480,11 +2465,11 @@ void hush_args(char *argv0)
void hush_nameset(char *symbol,char *dest,char *source)
{
if ( source[0] == 0 ) {
strcpy(symbol,(char *)"HUSH3");
strcpy(symbol,(char *)"DRAGONX");
strcpy(dest,(char *)"BTC");
} else {
strcpy(symbol,source);
strcpy(dest,(char *)"HUSH3");
strcpy(dest,(char *)"DRAGONX");
}
}

View File

@@ -43,6 +43,7 @@
#endif
#include "main.h"
#include "metrics.h"
#include "pow.h"
#include "miner.h"
#include "net.h"
#include "rpc/server.h"
@@ -176,7 +177,7 @@ public:
// Writes do not need similar protection, as failure to write is handled by the caller.
};
static CCoinsViewDB *pcoinsdbview = NULL;
CCoinsViewDB *pcoinsdbview = NULL; // global (declared extern in main.h) for UTXO-snapshot dump/load
static CCoinsViewErrorCatcher *pcoinscatcher = NULL;
static boost::scoped_ptr<ECCVerifyHandle> globalVerifyHandle;
@@ -282,6 +283,7 @@ void Shutdown()
}
#endif
UnregisterAllValidationInterfaces();
RandomXValidatorShutdown(); // release the ~256MB shared RandomX pre-verify cache (was leaked at exit)
#ifdef ENABLE_WALLET
delete pwalletMain;
pwalletMain = NULL;
@@ -377,8 +379,8 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
strUsage += HelpMessageOpt("-checkblocks=<n>", strprintf(_("How many blocks to check at startup (default: %u, 0 = all)"), 288));
strUsage += HelpMessageOpt("-checklevel=<n>", strprintf(_("How thorough the block verification of -checkblocks is (0-4, default: %u)"), 3));
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'GoldenSandtrout'"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "HUSH3.conf"));
strUsage += HelpMessageOpt("-clientname=<SomeName>", _("Full node client name, default 'DragonX'"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), "DRAGONX.conf"));
if (mode == HMM_BITCOIND)
{
#if !defined(WIN32)
@@ -387,7 +389,7 @@ std::string HelpMessage(HelpMessageMode mode)
}
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
strUsage += HelpMessageOpt("-exportdir=<dir>", _("Specify directory to be used when exporting data"));
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d). Default: adaptive - uses most free RAM to speed up initial block download (far fewer UTXO flushes to disk) and automatically shrinks if other applications need memory, always leaving a reserve free. Setting a fixed value disables adaptive sizing."), nMinDbCache, nMaxDbCache));
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));
strUsage += HelpMessageOpt("-maxdebugfilesize=<n>", strprintf(_("Set the max size of the debug.log file (default: %u)"), 15));
strUsage += HelpMessageOpt("-maxorphantx=<n>", strprintf(_("Keep at most <n> unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS));
@@ -395,6 +397,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-mempooltxinputlimit=<n>", _("[DEPRECATED/IGNORED] Set the maximum number of transparent inputs in a transaction that the mempool will accept (default: 0 = no limit applied)"));
strUsage += HelpMessageOpt("-par=<n>", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"),
-(int)boost::thread::hardware_concurrency(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS));
strUsage += HelpMessageOpt("-randomxverifythreads=<n>", strprintf(_("Number of threads for parallel RandomX PoW pre-verification of post-checkpoint blocks during network sync; no effect on reindex (0 = inline only, max %d, default: same as -par)"), MAX_SCRIPTCHECK_THREADS));
#ifndef _WIN32
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "hushd.pid"));
#endif
@@ -465,6 +468,12 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageGroup(_("Wallet options:"));
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), 100));
strUsage += HelpMessageOpt("-hdtransparent", strprintf(_("Derive transparent addresses from the HD seed so they can be recovered from it (default: %u)"), 1));
strUsage += HelpMessageOpt("-hdseed=<hex>", _("Restore a fresh/empty wallet from a 32- or 64-byte HD seed hex (the value shown in z_exportwallet's '# HDSeed=' line). WARNING: exposes the seed to your shell history and process list."));
strUsage += HelpMessageOpt("-mnemonic=<words>", _("Restore/create a fresh/empty wallet from a BIP39 seed phrase, compatible with SilentDragonXLite (English, no passphrase; cross-wallet restore parity is mainnet-only -- testnet/regtest derive a different HD coin_type). WARNING: exposes the phrase to your shell history and process list; prefer DRAGONX.conf with tight permissions."));
strUsage += HelpMessageOpt("-usemnemonic", strprintf(_("Create new wallets from a fresh BIP39 seed phrase so the 24 words can be exported (z_exportmnemonic) and used in SilentDragonXLite (default: %u)"), 0));
strUsage += HelpMessageOpt("-hdtransparentgaplimit=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many HD transparent keys so a rescan can find coinbase paid to them (default: %u)"), 1000));
strUsage += HelpMessageOpt("-mnemonicsaplinggap=<n>", strprintf(_("On -mnemonic/-hdseed restore, pre-derive this many shielded (Sapling) addresses so a rescan can find notes sent to them (default: %u)"), 100));
strUsage += HelpMessageOpt("-consolidation", _("Enable auto Sapling note consolidation (default: false)"));
strUsage += HelpMessageOpt("-consolidationinterval", _("Block interval between consolidations (default: 25)"));
strUsage += HelpMessageOpt("-consolidatesaplingaddress=<zaddr>", _("Specify Sapling Address to Consolidate. (default: all)"));
@@ -605,7 +614,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-stratumallowip=<ip>", _("Allow Stratum work requests from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times"));
// "ac" stands for "affects consensus" or Arrakis Chain
strUsage += HelpMessageGroup(_("Hush Arrakis Chain options:"));
strUsage += HelpMessageGroup(_("DragonX Chain options:"));
strUsage += HelpMessageOpt("-ac_algo", _("Choose PoW mining algorithm, either 'equihash' or 'randomx'. default is Equihash (200,9)"));
strUsage += HelpMessageOpt("-ac_blocktime", _("Block time in seconds, default is 60"));
strUsage += HelpMessageOpt("-ac_beam", _("BEAM integration"));
@@ -987,6 +996,123 @@ bool AppInitServers(boost::thread_group& threadGroup)
*/
extern int32_t HUSH_REWIND;
// --- Adaptive coins-cache sizing -------------------------------------------------------------
// The in-memory UTXO/coins cache (nCoinCacheUsage) is the biggest lever on IBD speed: a bigger
// cache means far fewer chainstate flushes to disk. We size it to use most of RAM, but a scheduled
// background task (AdjustCoinCacheForMemoryPressure, registered in AppInit2) shrinks the target when
// free system memory runs low — e.g. the user opens other apps — and grows it back when memory frees
// up, always leaving a reserve free for the rest of the system. The existing per-block flush
// (FlushStateToDisk, FLUSH_STATE_IF_NEEDED, which fires when cacheSize > nCoinCacheUsage) enforces
// whatever target is current, so the task only moves the threshold: it never touches cs_main or the
// flush path. NOTE: the coins cache is application heap, not OS file cache — "freeing" it means an
// early flush that clears the map; on Linux the allocator returns the pages, on Windows the heap
// returns them best-effort (RSS may lag), but either way the node stops growing past the target.
// windows.h / <unistd.h> arrive via compat.h (net.h). Memory helpers return 0 if undeterminable.
static int64_t GetPhysicalMemoryMB()
{
#ifdef WIN32
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
if (GlobalMemoryStatusEx(&status))
return (int64_t)(status.ullTotalPhys / (1024 * 1024));
return 0;
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
long pages = sysconf(_SC_PHYS_PAGES);
long pageSize = sysconf(_SC_PAGESIZE);
if (pages > 0 && pageSize > 0)
return (int64_t)((int64_t)pages * (int64_t)pageSize / (1024 * 1024));
return 0;
#else
return 0;
#endif
}
// Currently-available (allocatable) physical RAM in MiB. On Linux uses MemAvailable (counts
// reclaimable page cache), falling back to truly-free pages.
static int64_t GetAvailableMemoryMB()
{
#ifdef WIN32
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
if (GlobalMemoryStatusEx(&status))
return (int64_t)(status.ullAvailPhys / (1024 * 1024));
return 0;
#else
FILE* f = fopen("/proc/meminfo", "r");
if (f) {
char line[256];
long long availKB = -1;
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "MemAvailable: %lld kB", &availKB) == 1)
break;
}
fclose(f);
if (availKB >= 0)
return (int64_t)(availKB / 1024);
}
#if defined(_SC_AVPHYS_PAGES) && defined(_SC_PAGESIZE)
long pages = sysconf(_SC_AVPHYS_PAGES);
long pageSize = sysconf(_SC_PAGESIZE);
if (pages > 0 && pageSize > 0)
return (int64_t)((int64_t)pages * (int64_t)pageSize / (1024 * 1024));
#endif
return 0;
#endif
}
// RAM (MiB) to always keep free for the OS and other applications: 20% of total, at least 2 GiB.
static int64_t GetMemoryReserveMB()
{
int64_t ramMB = GetPhysicalMemoryMB();
int64_t reserve = (ramMB > 0) ? ramMB / 5 : 2048; // 20%
if (reserve < 2048) reserve = 2048;
return reserve;
}
// Startup -dbcache default: use most of RAM (total minus the reserve), clamped to
// [nDefaultDbCache, nMaxDbCache] MiB. Falls back to the fixed default if RAM can't be detected.
static int64_t GetDefaultDbCacheMB()
{
int64_t ramMB = GetPhysicalMemoryMB();
if (ramMB <= 0)
return nDefaultDbCache;
int64_t cacheMB = ramMB - GetMemoryReserveMB();
if (cacheMB < nDefaultDbCache) cacheMB = nDefaultDbCache;
if (cacheMB > nMaxDbCache) cacheMB = nMaxDbCache;
return cacheMB;
}
// Ceiling (bytes) the adaptive task may grow the coins cache back up to (the startup nCoinCacheUsage).
static size_t g_nMaxCoinCacheUsage = 0;
static const int64_t g_nMinCoinCacheMB = 256; // never thrash below this working set
// Scheduled task: nudge nCoinCacheUsage toward "use all RAM except the reserve". If free RAM is below
// the reserve we shrink the target (the next per-block flush releases the excess); if there is spare
// RAM we grow it back toward the startup ceiling. nCoinCacheUsage is std::atomic<size_t>, so this
// cross-thread write (vs the cs_main-held reads in FlushStateToDisk/VerifyDB) is well-defined, no lock needed.
static void AdjustCoinCacheForMemoryPressure()
{
if (g_nMaxCoinCacheUsage == 0)
return; // adaptive sizing disabled (user pinned -dbcache) or RAM undetectable
int64_t availMB = GetAvailableMemoryMB();
if (availMB <= 0)
return; // can't measure pressure; leave the target untouched
int64_t reserveMB = GetMemoryReserveMB();
// Error term: free RAM beyond the reserve. >0 => spare, grow; <0 => pressure, shrink.
int64_t errMB = availMB - reserveMB;
// Deadband: ignore small fluctuations so the target settles instead of oscillating.
if (errMB > -256 && errMB < 256)
return;
int64_t curTargetMB = (int64_t)(nCoinCacheUsage >> 20);
// Damped proportional step (gain 1/4) toward "free RAM == reserve"; the clamps bound it and the
// per-block flush (FLUSH_STATE_IF_NEEDED) enforces a lowered target within ~one block during IBD.
int64_t newTargetMB = curTargetMB + errMB / 4;
int64_t ceilMB = (int64_t)(g_nMaxCoinCacheUsage >> 20);
if (newTargetMB > ceilMB) newTargetMB = ceilMB;
if (newTargetMB < g_nMinCoinCacheMB) newTargetMB = g_nMinCoinCacheMB;
nCoinCacheUsage = (size_t)(newTargetMB << 20);
}
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
{
//fprintf(stderr,"%s start\n", __FUNCTION__);
@@ -1309,6 +1435,33 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
// Parallel RandomX pre-verification threads (speeds up post-checkpoint sync). Defaults to the
// script-check thread count — RandomX pre-verify and script checks do not run simultaneously
// within a single connect, so they can share the same budget. 0 disables (inline-only).
nRandomXVerifyThreads = GetArg("-randomxverifythreads", nScriptCheckThreads);
if (nRandomXVerifyThreads < 0)
nRandomXVerifyThreads = 0;
else if (nRandomXVerifyThreads > MAX_SCRIPTCHECK_THREADS)
nRandomXVerifyThreads = MAX_SCRIPTCHECK_THREADS;
// Per-peer block-download window (see MAX_BLOCKS_IN_TRANSIT_PER_PEER). Raising this lifts
// the bandwidth-delay-product ceiling on high-latency peers during IBD. Clamp to a sane range.
MAX_BLOCKS_IN_TRANSIT_PER_PEER = GetArg("-maxblocksintransit", DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER);
if (MAX_BLOCKS_IN_TRANSIT_PER_PEER < 1)
MAX_BLOCKS_IN_TRANSIT_PER_PEER = 1;
else if (MAX_BLOCKS_IN_TRANSIT_PER_PEER > (int)BLOCK_DOWNLOAD_WINDOW) {
// Values above BLOCK_DOWNLOAD_WINDOW are a silent no-op: FindNextBlocksToDownload never fetches
// beyond pindexLastCommonBlock + BLOCK_DOWNLOAD_WINDOW, so clamp to the real effective ceiling.
LogPrintf("-maxblocksintransit=%d exceeds the effective ceiling BLOCK_DOWNLOAD_WINDOW=%u; clamping\n", MAX_BLOCKS_IN_TRANSIT_PER_PEER, BLOCK_DOWNLOAD_WINDOW);
MAX_BLOCKS_IN_TRANSIT_PER_PEER = (int)BLOCK_DOWNLOAD_WINDOW;
}
LogPrintf("Per-peer max blocks in transit: %d\n", MAX_BLOCKS_IN_TRANSIT_PER_PEER);
// Opt-in bulk block streaming (DragonX). Drives the requester branch in SendMessages and, when
// set, also advertises NODE_BULKBLOCKS below so we serve bulk ranges to peers. OFF by default.
fBulkBlockSync = GetBoolArg("-bulkblocksync", DEFAULT_BULKBLOCKSYNC);
LogPrintf("Bulk block streaming: %s\n", fBulkBlockSync ? "enabled" : "disabled");
fServer = GetBoolArg("-server", false);
//fprintf(stderr,"%s tik6\n", __FUNCTION__);
@@ -1545,6 +1698,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
threadGroup.create_thread(&ThreadScriptCheck);
}
// Spawn the parallel RandomX pre-verification worker pool (the connect thread joins as the Nth
// worker via CCheckQueueControl::Wait, so spawn N-1 here, mirroring ThreadScriptCheck).
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && nRandomXVerifyThreads > 0) {
LogPrintf("Using %u threads for parallel RandomX pre-verification\n", nRandomXVerifyThreads);
for (int i = 0; i < nRandomXVerifyThreads - 1; i++)
threadGroup.create_thread(&ThreadRandomXVerify);
}
//fprintf(stderr,"%s tik13\n", __FUNCTION__);
// Start the lightweight task scheduler thread
@@ -1619,7 +1780,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
return InitError(strprintf("User Agent comment (%s) contains unsafe characters.", cmt));
uacomments.push_back(SanitizeString(cmt, SAFE_CHARS_UA_COMMENT));
}
strSubVersion = FormatSubVersion(GetArg("-clientname","GoldenSandtrout"), CLIENT_VERSION, uacomments);
strSubVersion = FormatSubVersion(GetArg("-clientname","DragonX"), CLIENT_VERSION, uacomments);
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
return InitError(strprintf("Total length of network version string %i exceeds maximum of %i characters. Reduce the number and/or size of uacomments.",
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
@@ -1840,14 +2001,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("* Compression is %s\n", dbCompression ? "enabled" : "disabled");
// cache size calculations
int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
int64_t nTotalCache = (GetArg("-dbcache", GetDefaultDbCacheMB()) << 20);
nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache
int64_t nBlockTreeDBCache = nTotalCache / 8;
if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) {
// enable 3/4 of the cache if addressindex and/or spentindex is enabled
// Give indexed (address/spent-index) nodes a larger index LevelDB read cache, but CAP it.
// With adaptive dbcache, nTotalCache is now multi-GB, so 3/4 of it is several GB -- far more
// than the index cache can use, while starving the in-memory UTXO set that actually speeds
// IBD (and this chunk is not shrinkable by the memory-pressure controller, which only adjusts
// the coins cache). Cap at 1 GiB so the adaptive budget flows to the coins cache. Tunable.
nBlockTreeDBCache = nTotalCache * 3 / 4;
if (nBlockTreeDBCache > ((int64_t)1024 << 20))
nBlockTreeDBCache = ((int64_t)1024 << 20);
} else {
if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) {
nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB
@@ -1857,6 +2024,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
nTotalCache -= nCoinDBCache;
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
// Adaptive sizing: unless the user pinned -dbcache, grow/shrink the coins cache with free system
// memory (AdjustCoinCacheForMemoryPressure), using the startup size as the ceiling.
if (!mapArgs.count("-dbcache")) {
g_nMaxCoinCacheUsage = nCoinCacheUsage;
scheduler.scheduleEvery(&AdjustCoinCacheForMemoryPressure, 5);
LogPrintf("* Adaptive dbcache enabled: ceiling %.0fMiB, keeping >= %lldMiB RAM free for the system\n",
nCoinCacheUsage * (1.0 / 1024 / 1024), (long long)GetMemoryReserveMB());
}
LogPrintf("Cache configuration:\n");
LogPrintf("* Max cache setting possible %.1fMiB\n", nMaxDbCache);
LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
@@ -1908,7 +2083,25 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex);
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex);
try {
pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex);
} catch (const std::exception& e) {
// The notarizations (dPoW) DB is non-essential and node-regenerable. It has been seen to
// snapshot/flush torn (0-byte log -> leveldb "Database I/O error" on reopen), which
// previously aborted startup with a spurious "Error opening block database" and forced a
// full resync. Wipe and recreate it instead of failing hard.
LogPrintf("%s: notarizations DB failed to open (%s); moving aside and regenerating (non-fatal)\n", __FUNCTION__, e.what());
// Move (do NOT delete) the old DB aside, so a transient open failure (fd
// exhaustion, disk full, permissions) cannot permanently destroy notarization
// history. If the recreate below also fails it propagates as fatal and the old
// data survives in notarizations.corrupt for recovery.
{
boost::filesystem::path ndir = GetDataDir() / "notarizations";
boost::filesystem::remove_all(ndir.string() + ".corrupt");
boost::filesystem::rename(ndir, ndir.string() + ".corrupt");
}
pnotarizations = new NotarizationDB(100*1024*1024, false, true);
}
if (fReindex) {
@@ -1938,6 +2131,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
strLoadError = _("Error initializing block database");
break;
}
HUSH_LOADINGBLOCKS = 0;
// Check for changed -txindex state
if (fTxIndex != GetBoolArg("-txindex", true)) {
@@ -2107,8 +2301,54 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (!pwalletMain->HaveHDSeed())
{
// generate a new HD seed
pwalletMain->GenerateNewSeed();
std::string mnemonic = GetArg("-mnemonic", "");
std::string hdSeedHex = GetArg("-hdseed", "");
bool restoring = false;
if (!mnemonic.empty() && !hdSeedHex.empty())
return InitError(_("Specify only one of -mnemonic or -hdseed, not both"));
if (!mnemonic.empty())
{
// Restore/create a wallet from a BIP39 seed phrase, byte-compatible
// with SilentDragonXLite. Must be a fresh/empty wallet.
if (!pwalletMain->SetHDSeedFromMnemonic(mnemonic))
return InitError(_("Invalid -mnemonic: expected a valid BIP39 English phrase on a fresh/empty wallet"));
LogPrintf("%s: restoring wallet from -mnemonic seed phrase\n", __func__);
restoring = true;
}
else if (!hdSeedHex.empty())
{
// Restore from a previously exported HD seed hex (z_exportwallet's
// "# HDSeed=" line): 32 bytes (raw) or 64 bytes (BIP39-derived).
if (!pwalletMain->SetHDSeedFromHex(hdSeedHex))
return InitError(_("Invalid -hdseed: expected a 32- or 64-hex-character seed on a fresh/empty wallet"));
LogPrintf("%s: restoring wallet from -hdseed\n", __func__);
restoring = true;
}
else
{
// generate a new HD seed
pwalletMain->GenerateNewSeed();
}
if (restoring)
{
// Pre-derive keys (birthday = genesis) so the startup rescan finds
// funds paid to them: transparent coinbase + shielded notes.
int64_t tGap = GetArg("-hdtransparentgaplimit", 1000);
if (tGap < 0) tGap = 0;
pwalletMain->TopUpHDTransparentKeys((unsigned int)tGap, 1);
int64_t zGap = GetArg("-mnemonicsaplinggap", 100);
if (zGap < 0) zGap = 0;
{
LOCK(pwalletMain->cs_wallet);
for (int i = 0; i < (int)zGap; i++)
pwalletMain->GenerateNewSaplingZKey();
}
LogPrintf("%s: pre-derived %d transparent and %d sapling keys for restore rescan\n", __func__, (int)tGap, (int)zGap);
}
}
//Set Sapling Consolidation
@@ -2380,6 +2620,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
nLocalServices |= NODE_ADDRINDEX;
if ( GetBoolArg("-spentindex", DEFAULT_SPENTINDEX) != 0 )
nLocalServices |= NODE_SPENTINDEX;
// Advertise willingness to SERVE bulk block streams (full nodes only) when opted in.
if ( fBulkBlockSync )
nLocalServices |= NODE_BULKBLOCKS;
fprintf(stderr,"nLocalServices %llx %d, %d\n",(long long)nLocalServices,GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX),GetBoolArg("-spentindex", DEFAULT_SPENTINDEX));
}
// ********************************************************* Step 10: import blocks

View File

@@ -39,6 +39,9 @@
*/
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
/** BIP32: child indices at or above this are hardened. */
const unsigned int BIP32_HARDENED_KEY_LIMIT = 0x80000000;
/** An encapsulated private key. */
class CKey
{

View File

@@ -51,8 +51,9 @@ namespace port {
// Mac OS
#elif defined(OS_MACOSX)
#include <atomic>
inline void MemoryBarrier() {
OSMemoryBarrier();
std::atomic_thread_fence(std::memory_order_seq_cst);
}
#define LEVELDB_HAVE_MEMORY_BARRIER

BIN
src/libcc.dylib Normal file

Binary file not shown.

View File

@@ -89,6 +89,12 @@ static int64_t nTimeBestReceived = 0;
CWaitableCriticalSection csBestBlock;
CConditionVariable cvBlockChange;
int nScriptCheckThreads = 0;
int MAX_BLOCKS_IN_TRANSIT_PER_PEER = DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER;
bool fBulkBlockSync = DEFAULT_BULKBLOCKSYNC;
// Server-side flood throttle: minimum interval between bulk serves to the same peer (main.cpp-local
// since only the serve handler uses it; kept out of main.h to avoid a full-tree recompile).
static const int64_t BULK_MIN_SERVE_INTERVAL_US = 50000; // 50 ms => <= 20 bulk serves/s/peer
int nRandomXVerifyThreads = 0; // parallel RandomX pre-verification worker count (0 = inline only)
bool fExperimentalMode = true;
bool fImporting = false;
bool fReindex = false;
@@ -103,7 +109,7 @@ bool fIsBareMultisigStd = true;
bool fCheckBlockIndex = false;
bool fCheckpointsEnabled = true;
bool fCoinbaseEnforcedProtectionEnabled = true;
size_t nCoinCacheUsage = 5000 * 300;
std::atomic<size_t> nCoinCacheUsage(5000 * 300);
uint64_t nPruneTarget = 0;
// If the tip is older than this (in seconds), the node is considered to be in initial block download.
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
@@ -247,6 +253,7 @@ namespace {
int64_t nTime; //! Time of "getdata" request in microseconds.
bool fValidatedHeaders; //! Whether this block has validated headers at the time of request.
int64_t nTimeDisconnect; //! The timeout for this block request (for disconnecting a slow peer)
bool fBulk; //! Requested as part of a bulk stream range (exempt from the front() stall-disconnect).
};
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
@@ -306,6 +313,23 @@ namespace {
int nBlocksInFlightValidHeaders;
//! Whether we consider this a preferred download peer.
bool fPreferredDownload;
//! Opt-in bulk block streaming (DragonX): whether a bulk range request is outstanding to this peer.
bool fBulkInFlight;
//! Time (us) the outstanding bulk request was issued, for the response timeout/fallback.
int64_t nBulkSince;
//! Height of the first block in the outstanding bulk range.
int nBulkRangeStart;
//! Number of blocks requested in the outstanding bulk range.
int nBulkRangeCount;
//! Hash of the first block of the outstanding bulk range (request identity; the server echoes it
//! in the BLOCKSTREAM header so a stale/duplicate header for an old request can be ignored).
uint256 nBulkHashStart;
//! Whether the (one-shot) trailing BLOCKSTREAM header for the outstanding request was processed.
bool fBulkHeaderSeen;
//! (server side) time (us) we last served a bulk stream to this peer, for flood throttling.
int64_t nLastBulkServeTime;
//! (#8 IBD header-flood cap) cumulative headers this peer made us process while in IBD.
int64_t nHeadersProcessed;
CNodeState() {
fCurrentlyConnected = false;
@@ -319,6 +343,14 @@ namespace {
nBlocksInFlight = 0;
nBlocksInFlightValidHeaders = 0;
fPreferredDownload = false;
fBulkInFlight = false;
nBulkSince = 0;
nBulkRangeStart = 0;
nBulkRangeCount = 0;
nBulkHashStart.SetNull();
fBulkHeaderSeen = false;
nLastBulkServeTime = 0;
nHeadersProcessed = 0;
}
};
@@ -385,14 +417,14 @@ namespace {
void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age)
{
/* int expired = pool.Expire(GetTime() - age);
if (expired != 0)
LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
int expired = pool.Expire(GetTime() - age);
if (expired != 0)
LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
std::vector<uint256> vNoSpendsRemaining;
pool.TrimToSize(limit, &vNoSpendsRemaining);
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining)
pcoinsTip->Uncache(removed);*/
// Fee-order trim to the size limit. (Upstream also pcoinsTip->Uncache()s the coins freed
// by eviction, but CCoinsViewCache has no Uncache() in this fork -- it is only a UTXO-cache
// perf hint, not eviction correctness, so it is skipped.)
pool.TrimToSize(limit);
}
// Requires cs_main.
@@ -413,7 +445,7 @@ namespace {
}
// Requires cs_main.
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Params& consensusParams, CBlockIndex *pindex = NULL) {
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Params& consensusParams, CBlockIndex *pindex = NULL, bool fBulk = false) {
CNodeState *state = State(nodeid);
assert(state != NULL);
@@ -421,7 +453,7 @@ namespace {
MarkBlockAsReceived(hash);
int64_t nNow = GetTimeMicros();
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams)};
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams), fBulk};
nQueuedValidatedHeaders += newentry.fValidatedHeaders;
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
state->nBlocksInFlight++;
@@ -429,6 +461,36 @@ namespace {
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
}
// Opt-in bulk block streaming (DragonX): free this peer's still-in-flight bulk blocks whose height
// falls in [hStart, hEnd), so the normal per-block path re-fetches them. We scan the peer's OWN
// vBlocksInFlight by the LITERAL hash marked at request time (via the stored pindex) rather than
// re-deriving hashes from the mutable pindexBestKnownBlock - the latter would miss the real entries
// after a reorg (leaking in-flight slots) and can never touch another peer's blocks. Requires cs_main.
void FreeBulkRangeInFlight(CNodeState* state, int hStart, int hEnd) {
if (state == NULL) return;
std::vector<uint256> toFree; // collect first: MarkBlockAsReceived erases from vBlocksInFlight
BOOST_FOREACH(const QueuedBlock& q, state->vBlocksInFlight) {
if (q.fBulk && q.pindex != NULL) {
int h = q.pindex->GetHeight();
if (h >= hStart && h < hEnd) toFree.push_back(q.hash);
}
}
BOOST_FOREACH(const uint256& hh, toFree)
MarkBlockAsReceived(hh);
}
// True if any of this peer's bulk blocks with height in [hStart, hEnd) is still in flight (range not
// fully drained). Completion is decided by the RANGE draining, not the global per-peer window count.
bool BulkRangeInFlight(CNodeState* state, int hStart, int hEnd) {
if (state == NULL) return false;
BOOST_FOREACH(const QueuedBlock& q, state->vBlocksInFlight) {
if (q.fBulk && q.pindex != NULL) {
int h = q.pindex->GetHeight();
if (h >= hStart && h < hEnd) return true;
}
}
return false;
}
/** Check whether the last unknown block a peer advertized is not yet known. */
void ProcessBlockAvailability(NodeId nodeid) {
CNodeState *state = State(nodeid);
@@ -485,7 +547,7 @@ namespace {
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
* at most count entries. */
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller) {
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller, CBlockIndex** pFrontierStuck = NULL) {
if (count == 0)
return;
@@ -562,8 +624,9 @@ namespace {
return;
}
} else if (waitingfor == -1) {
// This is the first already-in-flight block.
// This is the first already-in-flight block (the download frontier).
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
if (pFrontierStuck) *pFrontierStuck = pindex;
}
}
}
@@ -1223,6 +1286,16 @@ bool ContextualCheckTransaction(int32_t slowflag,const CBlock *block, CBlockInde
const bool overwinterActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
const bool saplingActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
// SECURITY FIX (JoinSplit inflation): reject Sprout JoinSplits at consensus. DragonX is
// Sapling-only from genesis (zero JoinSplits in its entire history, verified by the mainnet
// supply audit); a tx carrying one is illegitimate. Their zk-proof/sig/nullifier/anchor are
// never verified while vpub_new is counted as transparent value-in -> unlimited inflation.
// Coinbase/notary (IsMint) exempt. Unconditional is safe: no historical block has a JoinSplit.
if (!tx.IsMint() && !tx.vjoinsplit.empty()) {
return state.DoS(100, error("ContextualCheckTransaction(): Sprout JoinSplits are disabled (inflation vector)"),
REJECT_INVALID, "bad-txns-joinsplit-disabled");
}
if (saplingActive) {
// Reject transactions with valid version but missing overwintered flag
if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) {
@@ -2024,6 +2097,17 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
if (fSpentIndex) {
pool.addSpentIndex(entry, view);
}
// Bound mempool memory with fee-ordered eviction. Now that the tx is in, if the pool
// exceeds -maxmempool, TrimToSize drops the lowest-feerate txs -- so a higher-fee tx
// DISPLACES lower-fee ones instead of being bluntly rejected. If this very tx was the one
// evicted (its feerate was the lowest in the pool), it does not belong here -- reject it.
size_t maxmempool = (size_t)GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
if ( pool.DynamicMemoryUsage() > maxmempool ) {
pool.TrimToSize(maxmempool);
if ( !pool.exists(hash) )
return state.DoS(0, error("AcceptToMemoryPool: mempool full, tx %s evicted (feerate too low)", hash.ToString()), REJECT_INSUFFICIENTFEE, "mempool-full");
}
}
}
return true;
@@ -2375,6 +2459,17 @@ bool IsInitialBlockDownload()
//fprintf(stderr,"nullptr in IsInitialDownload\n");
return true;
}
// SECURITY: enforce the known-good minimum chain work (defined in chainparams but previously
// never checked). Keeps an eclipsed/bootstrapping node from trusting a cheap low-work fake
// chain -- a recent tip timestamp alone (below) is not sufficient. Gated to the DRAGONX symbol
// so ephemeral assetchains (fresh, low work) run from the same binary are not trapped in IBD.
if (strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0 &&
ptr->chainPower.chainWork < UintToArith256(chainParams.GetConsensus().nMinimumChainWork))
{
return true;
}
state = ((chainActive.Height() < ptr->GetHeight() - 24*60) ||
ptr->GetBlockTime() < (GetTime() - nMaxTipAge));
if ( HUSH_INSYNC != 0 )
@@ -3472,6 +3567,19 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
error("ConnectBlock(): block's hashFinalSaplingRoot is incorrect"),
REJECT_INVALID, "bad-sapling-root-in-block");
}
// Turnstile / inflation guard (belt-and-suspenders to the per-tx binding-signature check):
// the cumulative Sapling value pool must never go negative -- a block cannot deshield more
// value than was ever shielded. Enforced ONLY when the pool is reliably tracked: pprev's
// nChainSaplingValue is engaged only if every ancestor since genesis had a known per-block
// value (nSaplingValue). On old/snapshot-bootstrapped nodes it is none -> guard dormant
// (until reindex), so this can never false-reject a valid block or split the chain.
if (pindex->pprev && pindex->pprev->nChainSaplingValue) {
CAmount blockSaplingValue = 0;
for (const CTransaction& btx : block.vtx)
blockSaplingValue += -btx.valueBalance;
if (*pindex->pprev->nChainSaplingValue + blockSaplingValue < 0)
return state.DoS(100, error("ConnectBlock(): Sapling value pool would go negative (turnstile/inflation violation)"), REJECT_INVALID, "bad-sapling-value-pool-negative");
}
}
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
@@ -4004,6 +4112,10 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
uiInterface.NotifyTxExpiration(id);
}
// Bound mempool memory on each block: age-expire (-mempoolexpiry) then fee-order trim to
// -maxmempool, evicting the lowest-feerate txs (+ descendants) and uncaching their coins.
LimitMempoolSize(mempool, GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
// Update chainActive & related variables.
UpdateTip(pindexNew);
@@ -4188,6 +4300,7 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
return state.DoS(100, error("ActivateBestChainStep(): pindexOldTip->GetHeight().%d > notarizedht %d && pindexFork->GetHeight().%d is < notarizedht %d, so ignore it",(int32_t)pindexOldTip->GetHeight(),notarizedht,(int32_t)pindexFork->GetHeight(),notarizedht),
REJECT_INVALID, "past-notarized-height");
}
// - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
// - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
// then pindexFork will be null, and we would need to remove the entire chain including
@@ -4258,6 +4371,36 @@ static bool ActivateBestChainStep(bool fSkipdpow, CValidationState &state, CBloc
}
nHeight = nTargetHeight;
// Parallel RandomX pre-verification (Stage 4): verify this about-to-be-connected window's
// PoW on the worker pool BEFORE the serial connect, so ConnectBlock rarely pays the
// ~tens-of-ms light-mode hash. Pure optimization — CheckBlockHeader's inline
// CheckRandomXSolution still verifies anything not pre-verified, so consensus is unchanged.
// We hold cs_main; key derivation + the disk reads happen here on the main thread, and the
// pool workers receive only value-type work items (no cs_main, no chainstate pointers).
if (nRandomXVerifyThreads > 0 && rxCheckQueue.IsIdle()) {
std::map<std::string, std::vector<CRandomXCheck> > rxGroups; // grouped by RandomX key
BOOST_FOREACH(CBlockIndex *pidx, vpindexToConnect) {
if (pidx->fRandomXVerified || !RandomXValidationRequired(pidx->GetHeight()))
continue;
std::string rxKey = GetRandomXKey(pidx->GetHeight());
if (rxKey.empty())
continue; // can't derive key -> inline fallback
CBlock blk;
if (!ReadBlockFromDisk(blk, pidx, false))
continue; // -> inline fallback
if (blk.nSolution.size() != 32) // RANDOMX_HASH_SIZE; wrong size -> inline (will error)
continue;
rxGroups[rxKey].push_back(CRandomXCheck(rxKey, GetRandomXInput(blk), blk.nSolution.data(), &pidx->fRandomXVerified));
}
for (std::map<std::string, std::vector<CRandomXCheck> >::iterator it = rxGroups.begin(); it != rxGroups.end(); ++it) {
if (!RandomXValidatorPrepareKey(it->first))
break; // cache alloc failed -> leave the rest for the inline fallback
CCheckQueueControl<CRandomXCheck> control(&rxCheckQueue);
control.Add(it->second);
control.Wait();
}
}
// Connect new blocks.
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
@@ -4719,8 +4862,8 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
} else {
pindex->nChainSproutValue = boost::none;
}
if (pindex->pprev->nChainSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
if (pindex->pprev->nChainSaplingValue && pindex->nSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + *pindex->nSaplingValue;
} else {
pindex->nChainSaplingValue = boost::none;
}
@@ -4993,6 +5136,12 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
{
if ( !CheckEquihashSolution(&blockhdr, Params()) )
return state.DoS(100, error("CheckBlockHeader(): Equihash solution invalid"),REJECT_INVALID, "invalid-solution");
// Skip the inline RandomX recompute only if the parallel pre-verify pool already verified
// THIS block (fRandomXVerified set 1:1 on a real hash match). Every other case — pool miss,
// straggler, disabled pool, or any pindex==NULL caller (TestBlockValidity/VerifyDB/header
// accept) — falls through to the inline check, so consensus is unchanged.
if ( !(pindex && pindex->fRandomXVerified) && !CheckRandomXSolution(&blockhdr, height) )
return state.DoS(100, error("CheckBlockHeader(): RandomX solution invalid"),REJECT_INVALID, "invalid-randomx-solution");
}
// Check proof of work matches claimed amount
/*hush_index2pubkey33(pubkey33,pindex,height);
@@ -5003,6 +5152,14 @@ bool CheckBlockHeader(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,
int32_t hush_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height);
// RAII: save+restore the thread-local RandomX-skip flag around the verify-once dedup in CheckBlock,
// so it can never clobber the miner's own fSkipRandomXValidation (TestBlockValidity -> ConnectBlock
// re-entry) nor leak TRUE on an exception thrown out of hush_checkPOW.
struct ScopedRandomXSkip {
bool prev;
ScopedRandomXSkip() : prev(GetSkipRandomXValidation()) { SetSkipRandomXValidation(true); }
~ScopedRandomXSkip() { SetSkipRandomXValidation(prev); }
};
bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const CBlock& block, CValidationState& state,
libzcash::ProofVerifier& verifier,
bool fCheckPOW, bool fCheckMerkleRoot)
@@ -5033,8 +5190,16 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C
fprintf(stderr," failed hash ht.%d\n",height);
return state.DoS(50, error("CheckBlock: proof of work failed"),REJECT_INVALID, "high-hash");
}
if ( ASSETCHAINS_STAKED == 0 && hush_checkPOW(1,(CBlock *)&block,height) < 0 ) // checks Equihash
return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW");
if ( ASSETCHAINS_STAKED == 0 ) {
// verify-once: CheckBlockHeader above already verified this block RandomX solution; skip the
// redundant recompute inside hush_checkPOW (the un-deduped 2nd verify, ~half the RandomX cost
// that dominates IBD). The scoped guard saves/restores the skip flag (never hardcodes false)
// so the miner's own skip is preserved and nothing leaks on throw. Equihash + PoW-target in
// hush_checkPOW still run.
ScopedRandomXSkip _rxskip;
if ( hush_checkPOW(1,(CBlock *)&block,height) < 0 )
return state.DoS(100, error("CheckBlock: failed slow_checkPOW"),REJECT_INVALID, "failed-slow_checkPOW");
}
}
// Check the merkle root.
@@ -5105,7 +5270,14 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
assert(pindexPrev);
int daaForkHeight = GetArg("-daaforkheight", 450000);
// For HUSH3, nBits validation starts above the original DAA fork height (450000).
// For DragonX, nBits was never validated before the standalone binary, so the
// chain contains blocks with incorrect nBits during the vulnerable window
// (diff reset at RANDOMX_VALIDATION height through the attack at ~2879907).
// Set daaForkHeight past that window so fresh sync accepts historical blocks.
bool isdragonx = strncmp(SMART_CHAIN_SYMBOL, "DRAGONX", 7) == 0;
int defaultDaaForkHeight = isdragonx ? ASSETCHAINS_RANDOMX_VALIDATION + 62000 : 450000;
int daaForkHeight = GetArg("-daaforkheight", defaultDaaForkHeight);
int nHeight = pindexPrev->GetHeight()+1;
bool ishush3 = strncmp(SMART_CHAIN_SYMBOL, "HUSH3",5) == 0 ? true : false;
// Check Proof-of-Work difficulty
@@ -5138,6 +5310,26 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
}
}
// Check Proof-of-Work difficulty for smart chains (HACs)
// Without this check, an attacker can submit blocks with arbitrary nBits
// (e.g., powLimit / diff=1) and they will be accepted, allowing the chain
// to be flooded with minimum-difficulty blocks.
// Only enforce above daaForkHeight to avoid consensus mismatch with early
// chain blocks that were mined by a different binary version.
if (!ishush3 && SMART_CHAIN_SYMBOL[0] != 0 && nHeight > daaForkHeight) {
unsigned int nNextWork = GetNextWorkRequired(pindexPrev, &block, consensusParams);
if (fDebug) {
LogPrintf("%s: HAC nbits height=%d expected=%lu actual=%lu\n",
__func__, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits);
}
if (block.nBits != nNextWork) {
return state.DoS(100,
error("%s: Incorrect diffbits for %s at height %d: expected %lu got %lu",
__func__, SMART_CHAIN_SYMBOL, nHeight, (unsigned long)nNextWork, (unsigned long)block.nBits),
REJECT_INVALID, "bad-diffbits");
}
}
// Check timestamp against prev
if (ASSETCHAINS_ADAPTIVEPOW <= 0 || nHeight < 30) {
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast() )
@@ -5265,7 +5457,15 @@ bool AcceptBlockHeader(int32_t *futureblockp,const CBlockHeader& block, CValidat
}
return true;
}
if (!CheckBlockHeader(futureblockp,*ppindex!=0?(*ppindex)->GetHeight():0,*ppindex, block, state,0)) {
// Header-accept does NOT verify RandomX PoW (fCheckPOW=0). The RandomX key for a header is derived
// from the block at keyHeight on the header's OWN branch, which is not reliably resolvable at
// header-accept time (reorg / side-branch / catch-up headers are not on the active chain), so a
// header-time RandomX check repeatedly false-rejected valid reorg headers and hard-banned honest
// peers (see audit notes; reverted b9fdc7981/7e9b2c661/defer). The full RandomX + target check runs
// at block-connect with the correct branch key. Fake low-work chains are gated from SELECTION by
// nMinimumChainWork; the per-peer IBD header cap bounds flood memory. Do NOT re-enable a header-time
// RandomX check without first deriving the key from the header's own ancestry (pindexPrev->GetAncestor).
if (!CheckBlockHeader(futureblockp,*ppindex!=0?(*ppindex)->GetHeight():0,*ppindex, block, state, 0)) {
if ( *futureblockp == 0 ) {
LogPrintf("%s: CheckBlockHeader futureblock=0\n", __func__);
return false;
@@ -5815,8 +6015,8 @@ bool static LoadBlockIndexDB()
} else {
pindex->nChainSproutValue = boost::none;
}
if (pindex->pprev->nChainSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
if (pindex->pprev->nChainSaplingValue && pindex->nSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + *pindex->nSaplingValue;
} else {
pindex->nChainSaplingValue = boost::none;
}
@@ -6192,7 +6392,7 @@ bool RewindBlockIndex(const CChainParams& params, bool& clearWitnessCaches)
pindexIter->nChainTx = 0;
pindexIter->nSproutValue = boost::none;
pindexIter->nChainSproutValue = boost::none;
pindexIter->nSaplingValue = 0;
pindexIter->nSaplingValue = boost::none;
pindexIter->nChainSaplingValue = boost::none;
pindexIter->nSequenceId = 0;
@@ -6717,6 +6917,13 @@ void static ProcessGetData(CNode* pfrom)
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
vector<CInv> vNotFound;
// Serve up to this many blocks per ProcessGetData pass. The old code broke after a SINGLE block,
// so a 16-block getdata was dribbled out one block per message-handler tick (~100ms), throttling
// block download for every peer fetching from us. Bound the per-pass work (cs_main is held while
// reading blocks from disk); any remainder is served on the next pass (the message handler keeps
// fSleep=false while vRecvGetData is non-empty, so there is no 100ms park between passes).
const unsigned int nMaxBlocksServedPerPass = 16;
unsigned int nBlocksServed = 0;
LOCK(cs_main);
@@ -6759,7 +6966,15 @@ void static ProcessGetData(CNode* pfrom)
CBlock block;
if (!ReadBlockFromDisk(block, (*mi).second,1))
{
assert(!"cannot load block from disk");
// A block we advertised (BLOCK_HAVE_DATA) failed to load from disk: a transient
// I/O error or on-disk corruption. This previously asserted and crashed the whole
// node -- any peer getdata for such a block could take us down. Log and drop this
// peer instead; it can re-fetch from another node. (The bulk GETBLOCKSTREAM serve
// path already fails gracefully rather than asserting.)
LogPrintf("%s: ReadBlockFromDisk failed for block %s (peer=%i);"
" disconnecting peer instead of asserting\n",
__func__, inv.hash.ToString(), pfrom->GetId());
pfrom->fDisconnect = true;
}
else
{
@@ -6834,7 +7049,10 @@ void static ProcessGetData(CNode* pfrom)
}
}
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
// Serve a bounded batch of blocks per pass rather than one (see nMaxBlocksServedPerPass
// above). The send-buffer gate at the top of the loop still pauses us if the buffer fills;
// this counter bounds the cs_main hold for a (possibly malicious) large getdata.
if ((inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK) && ++nBlocksServed >= nMaxBlocksServedPerPass)
break;
}
}
@@ -7377,6 +7595,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
uint256 hashStop;
vRecv >> locator >> hashStop;
// Bound the locator before FindForkInGlobalIndex() scans it linearly under cs_main. An honest
// GetLocator() never exceeds MAX_LOCATOR_SZ, so this cannot reject a valid peer; an oversized
// vHave (~130k hashes fit in one message) is a message-thread liveness DoS. Ban like the
// adjacent vInv > MAX_INV_SZ path.
if (locator.vHave.size() > MAX_LOCATOR_SZ) {
Misbehaving(pfrom->GetId(), 20);
return true;
}
LOCK(cs_main);
// Find the last block the caller has in the main chain
@@ -7409,6 +7636,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
uint256 hashStop;
vRecv >> locator >> hashStop;
// Bound the locator before FindForkInGlobalIndex() scans it linearly under cs_main. An honest
// GetLocator() never exceeds MAX_LOCATOR_SZ, so this cannot reject a valid peer; an oversized
// vHave (~130k hashes fit in one message) is a message-thread liveness DoS. Ban like the
// adjacent vInv > MAX_INV_SZ path.
if (locator.vHave.size() > MAX_LOCATOR_SZ) {
Misbehaving(pfrom->GetId(), 20);
return true;
}
LOCK(cs_main);
@@ -7637,12 +7873,38 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
if (state.IsInvalid(nDoS) && futureblock == 0)
{
if (nDoS > 0 && futureblock == 0)
Misbehaving(pfrom->GetId(), nDoS/nDoS);
Misbehaving(pfrom->GetId(), nDoS);
return error("invalid header received");
}
}
}
// SECURITY (#8: IBD header-flood cap): bound how many headers a single peer can make us
// store while in IBD. Honest headers-first sync needs at most ~chain-length headers from a
// peer; one that floods far past 2x the known chain length is only trying to bloat
// mapBlockIndex/leveldb (such headers are never selected -- nMinimumChainWork gates that --
// but they still cost memory/disk). Cap per-peer and drop the peer. IBD-only: post-IBD there is
// no header-accept PoW check (removed as bug-prone), so a post-IBD flood is bounded only by
// nMinimumChainWork gating selection -- memory/disk growth there is accepted as low-severity.
if (IsInitialBlockDownload()) {
CNodeState *hstate = State(pfrom->GetId());
if (hstate != NULL) {
hstate->nHeadersProcessed += (int64_t)nCount;
// Cap RELATIVE TO THE VALIDATED ACTIVE-CHAIN HEIGHT (attacker-hard -- advancing it requires
// connecting real PoW blocks), NOT pindexBestHeader: a forward-extending header flood advances
// pindexBestHeader in lockstep with the attacker, so a pindexBestHeader-relative cap never fires.
// The checkpoint height is a fixed floor so honest IBD (blocks still lagging headers) is never capped.
int knownH = std::max((int)chainActive.Height(),
Checkpoints::GetTotalBlocksEstimate(chainparams.Checkpoints()));
int64_t headerCap = 2 * (int64_t)knownH + 200000;
if (hstate->nHeadersProcessed > headerCap) {
Misbehaving(pfrom->GetId(), 100);
return error("%s: peer=%d flooded %lld headers during IBD (cap %lld)", __func__,
pfrom->id, (long long)hstate->nHeadersProcessed, (long long)headerCap);
}
}
}
if (pindexLast)
UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash());
@@ -7659,6 +7921,118 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
}
CheckBlockIndex();
} else if (strCommand == NetMsgType::GETBLOCKSTREAM) {
// Opt-in bulk block streaming (DragonX): a peer asks us to stream a contiguous range of
// old blocks as back-to-back BLOCK messages. We only honor it if we advertised the bit
// (i.e. were started with -bulkblocksync) and we are not mid-import/reindex.
if ((nLocalServices & NODE_BULKBLOCKS) == 0 || fImporting || fReindex)
return true;
uint256 hashStart; int32_t nStartHeight; uint16_t nCount;
vRecv >> hashStart >> nStartHeight >> nCount;
// Resolve the range under cs_main (cheap, no disk I/O), then read + stream the blocks WITHOUT
// holding the lock, so a 128-block / 8 MiB serve never holds cs_main across disk reads (the
// analogous ProcessGetData caps per-pass work precisely because it reads under cs_main).
std::vector<CBlockIndex*> vSend;
int firstH = -1;
bool refuse = false;
{
LOCK(cs_main);
if (nCount == 0 || nCount > BULK_MAX_BLOCKS_PER_REQUEST) {
Misbehaving(pfrom->GetId(), 20); // mirrors the getdata MAX_INV_SZ penalty
return true;
}
// Light flood throttle: at most one bulk serve per peer per BULK_MIN_SERVE_INTERVAL_US. On
// throttle, send a refusal header so the requester falls back immediately (not after 90s).
int64_t nNowServe = GetTimeMicros();
CNodeState* sst = State(pfrom->GetId());
if (sst != NULL && sst->nLastBulkServeTime > nNowServe - BULK_MIN_SERVE_INTERVAL_US) {
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)-1, (uint16_t)0);
return true;
}
if (sst != NULL) sst->nLastBulkServeTime = nNowServe;
BlockMap::iterator mi = mapBlockIndex.find(hashStart);
// Don't flood old blocks while WE are still syncing (unless allowlisted); only serve blocks
// on our active chain at the height the requester expects (nStartHeight, tamper-checked).
if ((IsInitialBlockDownload() && !pfrom->fAllowlisted) ||
mi == mapBlockIndex.end() || !chainActive.Contains(mi->second) ||
mi->second->GetHeight() != nStartHeight) {
refuse = true;
} else {
CBlockIndex* pindex = mi->second;
firstH = pindex->GetHeight();
for (uint16_t i = 0; i < nCount && pindex != NULL; i++, pindex = chainActive.Next(pindex)) {
if ((pindex->nStatus & BLOCK_HAVE_DATA) == 0) break; // pruned/missing
vSend.push_back(pindex);
}
}
}
if (refuse) {
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)-1, (uint16_t)0);
return true;
}
// Read from disk + stream OUTSIDE cs_main. CBlockIndex pointers are stable and block files are
// append-only, so reading by pindex without the lock is safe (a concurrent reorg cannot delete
// block data, and the requester validates every block against its own headers regardless).
uint16_t nSent = 0;
size_t cumBytes = 0;
BOOST_FOREACH(CBlockIndex* pb, vSend) {
if (pfrom->nSendSize >= SendBufferSize()) break; // send-buffer backpressure
boost::this_thread::interruption_point();
CBlock block;
if (!ReadBlockFromDisk(block, pb, 1)) break; // graceful, never assert
size_t sz = GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
if (nSent > 0 && cumBytes + sz > BULK_MAX_RESPONSE_BYTES) break; // total byte cap
cumBytes += sz;
pfrom->PushMessage(NetMsgType::BLOCK, block);
nSent++;
}
// Trailing control header carries the ACTUAL count sent (authoritative), so the requester can
// free any undelivered tail immediately rather than waiting for the bulk response timeout.
pfrom->PushMessage(NetMsgType::BLOCKSTREAM, hashStart, (int32_t)firstH, nSent);
LogPrint("net", "Bulk stream serve: %u/%u blocks from height %d (%lu bytes) peer=%d\n",
(unsigned)nSent, (unsigned)nCount, firstH, (unsigned long)cumBytes, pfrom->id);
return true;
} else if (strCommand == NetMsgType::BLOCKSTREAM) {
// Opt-in bulk block streaming (DragonX): the trailing control header for a streamed range. The
// blocks themselves arrive as ordinary BLOCK messages (handled below); this reconciles what the
// peer actually delivered so the undelivered tail (or a refusal) falls back at once instead of
// waiting for the bulk timeout. Service bits are unauthenticated, so we ignore anything that
// doesn't match our exact outstanding request.
uint256 hashStart; int32_t nFirstHeight; uint16_t nBlocks;
vRecv >> hashStart >> nFirstHeight >> nBlocks;
LOCK(cs_main);
CNodeState* state = State(pfrom->GetId());
if (state == NULL || !state->fBulkInFlight)
return true; // nothing outstanding
if (hashStart != state->nBulkHashStart)
return true; // header for a different/stale request; ignore
if (state->fBulkHeaderSeen)
return true; // one-shot: already reconciled this request
state->fBulkHeaderSeen = true;
// nBlocks==0 (refusal) or an over-count => free our whole outstanding range and fall back.
// 0 < nBlocks <= count => the peer commits to that many; free only the undelivered tail now.
// FreeBulkRangeInFlight scans THIS peer's vBlocksInFlight by literal hash, so it only ever frees
// heights still genuinely in flight to this peer (no cross-peer effect, reorg-proof).
bool refuse = (nBlocks == 0 || nBlocks > state->nBulkRangeCount);
int deliver = refuse ? 0 : (int)nBlocks;
FreeBulkRangeInFlight(state, state->nBulkRangeStart + deliver,
state->nBulkRangeStart + state->nBulkRangeCount);
if (refuse) {
state->fBulkInFlight = false;
pfrom->nServices &= ~(uint64_t)NODE_BULKBLOCKS; // local hint: don't retry bulk on this peer
LogPrint("net", "Bulk stream refused by peer=%d (nBlocks=%u), falling back\n", pfrom->id, (unsigned)nBlocks);
} else {
// Track only what was promised; fBulkInFlight clears once that prefix fully drains
// (range-drain check in SendMessages) or via the timeout fallback.
state->nBulkRangeCount = deliver;
if (deliver == 0)
state->fBulkInFlight = false;
}
return true;
} else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing
{
CBlock block;
@@ -8104,24 +8478,108 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
LogPrint("net", "Reducing block download timeout for peer=%d block=%s, orig=%d new=%d\n", pto->id, queuedBlock.hash.ToString(), queuedBlock.nTimeDisconnect, nTimeoutIfRequestedNow);
queuedBlock.nTimeDisconnect = nTimeoutIfRequestedNow;
}
if (queuedBlock.nTimeDisconnect < nNow) {
if (queuedBlock.nTimeDisconnect < nNow && !queuedBlock.fBulk) {
// Bulk-stream blocks are exempt: a 128-block batch shares one request time, so the
// front() entry could expire before the tail streams in. The bulk response timeout
// below frees the range without disconnecting instead.
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
pto->fDisconnect = true;
}
}
// Opt-in bulk block streaming (DragonX): manage the outstanding bulk range, then (below)
// possibly issue a new one. Clearing fBulkInFlight once the batch has drained below the
// normal window re-enables the next bulk request; a never-fully-delivered batch is freed
// after BULK_RESPONSE_TIMEOUT_US so the normal per-block path re-fetches it (no disconnect).
if (state.fBulkInFlight) {
int hEnd = state.nBulkRangeStart + state.nBulkRangeCount;
if (!BulkRangeInFlight(&state, state.nBulkRangeStart, hEnd)) {
// Whole (possibly shrunk) range received -> done. Completion is keyed on the RANGE
// draining, NOT on the global in-flight count crossing the window, so a partially
// delivered batch can never leave undelivered heights stuck in-flight.
state.fBulkInFlight = false;
} else if (state.nBulkSince > 0 && state.nBulkSince < nNow - BULK_RESPONSE_TIMEOUT_US) {
// Promised blocks never fully arrived: free the still-in-flight remainder (the normal
// per-block path re-fetches it), give up bulk on this unresponsive peer. No disconnect.
FreeBulkRangeInFlight(&state, state.nBulkRangeStart, hEnd);
state.fBulkInFlight = false;
pto->nServices &= ~(uint64_t)NODE_BULKBLOCKS;
LogPrint("net", "Bulk stream timeout peer=%d, freed range [%d,%d)\n",
pto->id, state.nBulkRangeStart, hEnd);
}
}
// Message: getdata (blocks)
static uint256 zero;
vector<CInv> vGetData;
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER && !state.fBulkInFlight) {
vector<CBlockIndex*> vToDownload;
NodeId staller = -1;
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
CBlockIndex *pFrontierStuck = NULL;
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, &pFrontierStuck);
// Opt-in bulk block streaming (DragonX): if the first block we need is in the deep,
// stable region (>= BULK_TIP_MARGIN below the NETWORK tip) and the peer advertised the
// capability, request a whole contiguous range in one shot instead of per-block getdata.
// FindNextBlocksToDownload already advanced the cursor past what we have, so
// vToDownload.front() is the correct, cursor-managed starting point.
bool didBulk = false;
if (fBulkBlockSync && (pto->nServices & NODE_BULKBLOCKS) && IsInitialBlockDownload()
&& !vToDownload.empty() && state.pindexBestKnownBlock != NULL) {
CBlockIndex* pfirst = vToDownload.front();
int cursorH = pfirst->GetHeight();
int maxH = state.pindexBestKnownBlock->GetHeight() - BULK_TIP_MARGIN;
if (cursorH <= maxH) {
int want = std::min(maxH - cursorH + 1, (int)BULK_MAX_BLOCKS_PER_REQUEST);
uint16_t n = 0;
for (int i = 0; i < want; i++) {
CBlockIndex* pb = state.pindexBestKnownBlock->GetAncestor(cursorH + i);
if (pb == NULL || mapBlocksInFlight.count(pb->GetBlockHash())) break;
MarkBlockAsInFlight(pto->GetId(), pb->GetBlockHash(), consensusParams, pb, true);
n++;
}
if (n > 0) {
pto->PushMessage(NetMsgType::GETBLOCKSTREAM, pfirst->GetBlockHash(), (int32_t)cursorH, n);
state.fBulkInFlight = true;
state.nBulkSince = nNow;
state.nBulkRangeStart = cursorH;
state.nBulkRangeCount = n;
state.nBulkHashStart = pfirst->GetBlockHash(); // request identity (matched in BLOCKSTREAM)
state.fBulkHeaderSeen = false; // arm the one-shot header reconciliation
didBulk = true;
LogPrint("net", "Requesting bulk stream [%d..%d] (%u blocks) peer=%d\n",
cursorH, cursorH + n - 1, (unsigned)n, pto->id);
}
}
}
if (!didBulk) {
BOOST_FOREACH(CBlockIndex *pindex, vToDownload) {
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
LogPrint("net", "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->GetHeight(), pto->id);
}
}
// Frontier reassignment: when this peer has nothing new to fetch because the next-needed
// (frontier) block is in flight from another, slow peer and has been stuck beyond a short
// threshold, re-request it from THIS (responsive) peer instead of waiting out the long
// (~72s) timeout or disconnecting the slow peer. This breaks the head-of-line stall that
// throttles IBD when downloading from few, distant peers. Trustless: the block is still
// fully validated on arrival - we only change which peer serves it. -blockreassigntimeout
// = seconds (0 disables; default 5).
static const int64_t nReassignUs = GetArg("-blockreassigntimeout", 5) * 1000000LL;
if (nReassignUs > 0 && vToDownload.empty() && pFrontierStuck != NULL &&
staller != -1 && staller != pto->GetId()) {
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itF =
mapBlocksInFlight.find(pFrontierStuck->GetBlockHash());
if (itF != mapBlocksInFlight.end() && itF->second.first == staller &&
itF->second.second->nTime < nNow - nReassignUs) {
uint256 hReassign = pFrontierStuck->GetBlockHash();
LogPrint("net", "Reassigning stalled frontier block %s (%d) from peer=%d to peer=%d\n",
hReassign.ToString(), pFrontierStuck->GetHeight(), staller, pto->id);
MarkBlockAsReceived(hReassign); // free from slow peer (no disconnect)
vGetData.push_back(CInv(MSG_BLOCK, hReassign));
MarkBlockAsInFlight(pto->GetId(), hReassign, consensusParams, pFrontierStuck); // re-request from this peer
}
}
if (state.nBlocksInFlight == 0 && staller != -1) {
if (State(staller)->nStallingSince == 0) {
State(staller)->nStallingSince = nNow;

View File

@@ -43,6 +43,7 @@
#include "txmempool.h"
#include "uint256.h"
#include <atomic>
#include <algorithm>
#include <exception>
#include <map>
@@ -64,7 +65,9 @@ class CValidationState;
class PrecomputedTransactionData;
struct CNodeStateStats;
#define DEFAULT_MEMPOOL_EXPIRY 1
#define DEFAULT_MEMPOOL_EXPIRY 72 // hours; age-based Expire is now live via LimitMempoolSize -- was 1, too aggressive
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
#define DEFAULT_MAX_MEMPOOL_SIZE 300
#define _COINBASE_MATURITY 100
/** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/
@@ -95,13 +98,44 @@ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
static const int MAX_SCRIPTCHECK_THREADS = 16;
/** -par default (number of script-checking threads, 0 = auto) */
static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
/** Number of blocks that can be requested at any given time from a single peer. */
static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
/** Number of blocks that can be requested at any given time from a single peer.
* Runtime-tunable via -maxblocksintransit. The default of 16 caps single-peer IBD
* throughput at (window / RTT): on a high-latency peer with tiny (sub-checkpoint)
* blocks the transfer is bandwidth-delay-product bound, so a larger window lifts the
* ceiling at negligible bandwidth cost. */
static const int DEFAULT_MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16;
extern int MAX_BLOCKS_IN_TRANSIT_PER_PEER;
/** Opt-in bulk block streaming (DragonX, -bulkblocksync). A single GETBLOCKSTREAM request makes a
* peer stream a contiguous range of old blocks as back-to-back BLOCK messages, amortizing the
* per-block round-trip over the whole range instead of the MAX_BLOCKS_IN_TRANSIT_PER_PEER window.
* OFF by default; negotiated via NODE_BULKBLOCKS; only used during IBD for blocks more than
* BULK_TIP_MARGIN below the active tip; never alters the default getdata path. */
static const bool DEFAULT_BULKBLOCKSYNC = false;
extern bool fBulkBlockSync;
/** Only bulk-stream blocks at least this far below the active tip (near-tip uses the normal path). */
static const int BULK_TIP_MARGIN = 5000;
/** Hard DoS cap: max blocks a single GETBLOCKSTREAM may request/serve. */
static const uint16_t BULK_MAX_BLOCKS_PER_REQUEST = 128;
/** Hard DoS cap: max total bytes streamed in response to one GETBLOCKSTREAM. */
static const size_t BULK_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
/** Requester fallback: if a promised bulk range doesn't fully arrive within this many microseconds,
* free the in-flight range so the normal per-block path re-fetches it. */
static const int64_t BULK_RESPONSE_TIMEOUT_US = 90 * 1000000LL;
/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */
static const unsigned int BLOCK_STALLING_TIMEOUT = 2;
/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends
* less than this number, we reached its tip. Changing this value is a protocol upgrade. */
* less than this number, we reached its tip. Changing this value is a protocol upgrade: the
* continuation logic (main.cpp, "nCount == MAX_HEADERS_RESULTS") and the serve-side limit must
* match across the network, so a single node raising it unilaterally would mis-detect a stock
* peer's 160-header reply as "tip reached" and stall header sync. Raise only as a coordinated
* network upgrade (with a protocol-version bump). */
static const unsigned int MAX_HEADERS_RESULTS = 160;
/** Maximum number of entries we accept in a CBlockLocator.vHave (GETBLOCKS / GETHEADERS). An honest
* CChain::GetLocator() emits ~10 linear hashes then exponentially-spaced ones, so even a chain of
* 2^91 blocks stays well under this bound (GetLocator reserves 32). Matches upstream Bitcoin Core's
* MAX_LOCATOR_SZ. A larger vHave is a peer trying to make FindForkInGlobalIndex() linearly scan a
* huge list under cs_main (message-thread liveness DoS). */
static const unsigned int MAX_LOCATOR_SZ = 101;
/** Size of the "block download window": how far ahead of our current height do we fetch?
* Larger windows tolerate larger download speed differences between peer, but increase the potential
* degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning
@@ -155,6 +189,7 @@ extern bool fExperimentalMode;
extern bool fImporting;
extern bool fReindex;
extern int nScriptCheckThreads;
extern int nRandomXVerifyThreads;
extern bool fTxIndex;
extern bool fZindex;
extern bool fIsBareMultisigStd;
@@ -163,7 +198,7 @@ extern bool fCheckpointsEnabled;
// TODO: remove this flag by structuring our code such that
// it is unneeded for testing
extern bool fCoinbaseEnforcedProtectionEnabled;
extern size_t nCoinCacheUsage;
extern std::atomic<size_t> nCoinCacheUsage;
extern CFeeRate minRelayTxFee;
extern int64_t nMaxTipAge;
@@ -930,6 +965,10 @@ extern CChain chainActive;
/** Global variable that points to the active CCoinsView (protected by cs_main) */
extern CCoinsViewCache *pcoinsTip;
/** Global variable that points to the coins database (chainstate/, protected by cs_main). */
class CCoinsViewDB;
extern CCoinsViewDB *pcoinsdbview;
/** Global variable that points to the active block tree (protected by cs_main) */
extern CBlockTreeDB *pblocktree;

View File

@@ -289,7 +289,7 @@ int printMiningStatus(bool mining)
lines++;
} else {
std::cout << _("You are currently not mining.") << std::endl;
std::cout << _("To enable mining, add 'gen=1' to your HUSH3.conf and restart.") << std::endl;
std::cout << _("To enable mining, add 'gen=1' to your DRAGONX.conf and restart.") << std::endl;
lines += 2;
}
std::cout << std::endl;

View File

@@ -23,6 +23,7 @@
#include "pow/tromp/equi_miner.h"
#endif
#include <atomic>
#include "amount.h"
#include "chainparams.h"
#include "consensus/consensus.h"
@@ -51,6 +52,7 @@
#include "transaction_builder.h"
#include "sodium.h"
#include <boost/thread.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/tuple/tuple.hpp>
#ifdef ENABLE_MINING
#include <functional>
@@ -1011,8 +1013,213 @@ enum RandomXSolverCancelCheck
Reason2
};
int GetRandomXInterval() { return GetArg("-ac_randomx_interval",1024); }
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
int GetRandomXInterval();
int GetRandomXBlockLag();
#ifdef _WIN32
#include <windows.h>
static void LogProcessMemory(const char* label) {
// Use K32GetProcessMemoryInfo from kernel32.dll (available on Win7+)
// to avoid linking psapi.lib
typedef struct {
DWORD cb;
DWORD PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivateUsage;
} PMC_EX;
typedef BOOL (WINAPI *PFN)(HANDLE, PMC_EX*, DWORD);
static PFN pfn = (PFN)GetProcAddress(GetModuleHandleA("kernel32.dll"), "K32GetProcessMemoryInfo");
if (pfn) {
PMC_EX pmc = {};
pmc.cb = sizeof(pmc);
if (pfn(GetCurrentProcess(), &pmc, sizeof(pmc))) {
LogPrintf("MemDiag [%s]: WorkingSet=%.1fMB, PrivateUsage=%.1fMB, PagefileUsage=%.1fMB\n",
label,
pmc.WorkingSetSize / (1024.0 * 1024.0),
pmc.PrivateUsage / (1024.0 * 1024.0),
pmc.PagefileUsage / (1024.0 * 1024.0));
}
}
}
#else
static void LogProcessMemory(const char* label) {
// Linux: read /proc/self/status
FILE *f = fopen("/proc/self/status", "r");
if (f) {
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "VmRSS:", 6) == 0 || strncmp(line, "VmSize:", 7) == 0) {
// Remove newline
line[strlen(line)-1] = '\0';
LogPrintf("MemDiag [%s]: %s\n", label, line);
}
}
fclose(f);
}
}
#endif
// Shared RandomX dataset manager — all miner threads share a single ~2GB dataset
// instead of each allocating their own. The dataset is read-only after initialization
// and RandomX explicitly supports multiple VMs sharing one dataset.
struct RandomXDatasetManager {
randomx_flags flags;
randomx_cache *cache;
randomx_dataset *dataset;
unsigned long datasetItemCount;
std::string currentKey;
std::mutex mtx; // protects Init/Shutdown/CreateVM
boost::shared_mutex datasetMtx; // readers-writer lock: shared for hashing, exclusive for rebuild
bool initialized;
RandomXDatasetManager() : flags(randomx_get_flags()), cache(nullptr), dataset(nullptr),
datasetItemCount(0), initialized(false) {}
bool Init() {
std::lock_guard<std::mutex> lock(mtx);
if (initialized) return true;
flags |= RANDOMX_FLAG_FULL_MEM;
LogPrintf("RandomXDatasetManager: flags=0x%x (JIT=%d, HARD_AES=%d, FULL_MEM=%d, LARGE_PAGES=%d)\n",
(int)flags,
!!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES),
!!(flags & RANDOMX_FLAG_FULL_MEM), !!(flags & RANDOMX_FLAG_LARGE_PAGES));
LogProcessMemory("before cache alloc");
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE);
if (cache == nullptr) {
LogPrintf("RandomXDatasetManager: cache alloc failed with large pages, trying without...\n");
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
if (cache == nullptr) {
LogPrintf("RandomXDatasetManager: cache alloc failed with secure, trying basic...\n");
cache = randomx_alloc_cache(flags);
if (cache == nullptr) {
LogPrintf("RandomXDatasetManager: cannot allocate cache!\n");
return false;
}
}
}
LogProcessMemory("after cache alloc");
// Try to allocate dataset with large pages first for better performance
dataset = randomx_alloc_dataset(flags | RANDOMX_FLAG_LARGE_PAGES);
if (dataset == nullptr) {
LogPrintf("RandomXDatasetManager: dataset alloc failed with large pages, trying without...\n");
dataset = randomx_alloc_dataset(flags);
if (dataset == nullptr) {
LogPrintf("RandomXDatasetManager: cannot allocate dataset!\n");
randomx_release_cache(cache);
cache = nullptr;
return false;
}
}
datasetItemCount = randomx_dataset_item_count();
initialized = true;
LogProcessMemory("after dataset alloc");
// Log the actual memory addresses to help diagnose sharing issues
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
size_t datasetSize = datasetItemCount * RANDOMX_DATASET_ITEM_SIZE;
LogPrintf("RandomXDatasetManager: allocated shared dataset:\n");
LogPrintf(" - Dataset struct at: %p\n", (void*)dataset);
LogPrintf(" - Dataset memory at: %p (size: %.2f GB)\n", (void*)datasetMemory, datasetSize / (1024.0 * 1024.0 * 1024.0));
LogPrintf(" - Items: %lu, Item size: %d bytes\n", datasetItemCount, RANDOMX_DATASET_ITEM_SIZE);
LogPrintf(" - Expected total process memory: ~%.2f GB + ~2MB per mining thread\n", datasetSize / (1024.0 * 1024.0 * 1024.0));
return true;
}
// Initialize cache with a key and rebuild the dataset.
// Thread-safe: acquires exclusive lock so all hashing threads must finish first.
void UpdateKey(const void *key, size_t keySize) {
std::string newKey((const char*)key, keySize);
// Fast check with shared lock — skip if key hasn't changed
{
boost::shared_lock<boost::shared_mutex> readLock(datasetMtx);
if (newKey == currentKey) return; // already up to date
}
// Acquire exclusive lock — blocks until all hashing threads release their shared locks
boost::unique_lock<boost::shared_mutex> writeLock(datasetMtx);
// Double-check after acquiring exclusive lock (another thread may have rebuilt first)
if (newKey == currentKey) return;
LogPrintf("RandomXDatasetManager: updating key (size=%lu)\n", keySize);
randomx_init_cache(cache, key, keySize);
currentKey = newKey;
// Rebuild dataset using all available CPU threads
const int initThreadCount = std::thread::hardware_concurrency();
if (initThreadCount > 1) {
std::vector<std::thread> threads;
uint32_t startItem = 0;
const auto perThread = datasetItemCount / initThreadCount;
const auto remainder = datasetItemCount % initThreadCount;
for (int i = 0; i < initThreadCount; ++i) {
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
threads.push_back(std::thread(&randomx_init_dataset, dataset, cache, startItem, count));
startItem += count;
}
for (unsigned i = 0; i < threads.size(); ++i) {
threads[i].join();
}
} else {
randomx_init_dataset(dataset, cache, 0, datasetItemCount);
}
LogPrintf("RandomXDatasetManager: dataset rebuilt\n");
LogProcessMemory("after dataset init");
}
// Creates a per-thread VM using the shared dataset.
// Caller must hold a shared lock on datasetMtx.
// The VM itself is small (~2MB scratchpad + ~84KB JIT code) — the ~2GB dataset is shared via pointer.
// VMs should be created ONCE per thread and reused across blocks to avoid
// heap fragmentation on Windows (repeated 2MB alloc/free causes address-space bloat).
randomx_vm *CreateVM() {
static std::atomic<int> vmCount{0};
LogProcessMemory("before CreateVM");
randomx_vm *vm = randomx_create_vm(flags, nullptr, dataset);
if (vm != nullptr) {
int id = ++vmCount;
uint8_t *datasetMemory = (uint8_t*)randomx_get_dataset_memory(dataset);
LogPrintf("RandomXDatasetManager: VM #%d created — VM at %p, shared dataset at %p\n",
id, (void*)vm, (void*)datasetMemory);
LogPrintf(" Per-thread overhead: ~2MB scratchpad + ~84KB JIT (dataset NOT copied)\n");
LogProcessMemory("after CreateVM");
}
return vm;
}
void Shutdown() {
std::lock_guard<std::mutex> lock(mtx);
if (dataset != nullptr) {
randomx_release_dataset(dataset);
dataset = nullptr;
}
if (cache != nullptr) {
randomx_release_cache(cache);
cache = nullptr;
}
initialized = false;
currentKey.clear();
LogPrintf("RandomXDatasetManager: shutdown complete\n");
}
~RandomXDatasetManager() {
Shutdown();
}
};
// Global shared dataset manager, created by GenerateBitcoins before spawning miner threads
static RandomXDatasetManager *g_rxDatasetManager = nullptr;
#ifdef ENABLE_WALLET
void static RandomXMiner(CWallet *pwallet)
@@ -1050,33 +1257,12 @@ void static RandomXMiner()
);
miningTimer.start();
randomx_flags flags = randomx_get_flags();
flags |= RANDOMX_FLAG_FULL_MEM;
randomx_cache *randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_SECURE );
if (randomxCache == NULL) {
LogPrintf("RandomX cache is null, trying without large pages...\n");
randomxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_SECURE);
if (randomxCache == NULL) {
LogPrintf("RandomX cache is null, trying without secure...\n");
}
randomxCache = randomx_alloc_cache(flags);
if (randomxCache == NULL) {
LogPrintf("RandomX cache is null, cannot mine!\n");
}
}
rxdebug("%s: created randomx flags + cache\n");
randomx_dataset *randomxDataset = randomx_alloc_dataset(flags);
rxdebug("%s: created dataset\n");
if( randomxDataset == nullptr) {
LogPrintf("%s: allocating randomx dataset failed!\n", __func__);
// Use the shared dataset manager — no per-thread dataset allocation
if (g_rxDatasetManager == nullptr || !g_rxDatasetManager->initialized) {
LogPrintf("HushRandomXMiner: shared dataset manager not initialized, aborting!\n");
return;
}
auto datasetItemCount = randomx_dataset_item_count();
rxdebug("%s: dataset items=%lu\n", datasetItemCount);
char randomxHash[RANDOMX_HASH_SIZE];
rxdebug("%s: created randomxHash of size %d\n", RANDOMX_HASH_SIZE);
char randomxKey[82]; // randomx spec says keysize of >60 bytes is implementation-specific
@@ -1147,48 +1333,37 @@ void static RandomXMiner()
// fprintf(stderr,"RandomXMiner: using initial key with interval=%d and lag=%d\n", randomxInterval, randomxBlockLag);
rxdebug("%s: using initial key, interval=%d, lag=%d, Mining_height=%u\n", randomxInterval, randomxBlockLag, Mining_height);
// Use the initial key at the start of the chain, until the first key block
// Update the shared dataset key — only one thread will actually rebuild,
// others will see the key is already current and skip.
if( (Mining_height) < randomxInterval + randomxBlockLag) {
randomx_init_cache(randomxCache, &randomxKey, sizeof randomxKey);
rxdebug("%s: initialized cache with initial key\n");
g_rxDatasetManager->UpdateKey(randomxKey, strlen(randomxKey));
rxdebug("%s: updated shared dataset with initial key\n");
} else {
rxdebug("%s: calculating keyHeight with randomxInterval=%d\n", randomxInterval);
// At heights between intervals, we use the same block key and wait randomxBlockLag blocks until changing
const int keyHeight = ((Mining_height - randomxBlockLag) / randomxInterval) * randomxInterval;
uint256 randomxBlockKey = chainActive[keyHeight]->GetBlockHash();
randomx_init_cache(randomxCache, &randomxBlockKey, sizeof randomxBlockKey);
rxdebug("%s: initialized cache with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
g_rxDatasetManager->UpdateKey(&randomxBlockKey, sizeof randomxBlockKey);
rxdebug("%s: updated shared dataset with keyHeight=%d, randomxBlockKey=%s\n", keyHeight, randomxBlockKey.ToString().c_str());
}
const int initThreadCount = std::thread::hardware_concurrency();
if(initThreadCount > 1) {
rxdebug("%s: initializing dataset with %d threads\n", initThreadCount);
std::vector<std::thread> threads;
uint32_t startItem = 0;
const auto perThread = datasetItemCount / initThreadCount;
const auto remainder = datasetItemCount % initThreadCount;
for (int i = 0; i < initThreadCount; ++i) {
const auto count = perThread + (i == initThreadCount - 1 ? remainder : 0);
threads.push_back(std::thread(&randomx_init_dataset, randomxDataset, randomxCache, startItem, count));
startItem += count;
// Create a per-thread VM once and reuse across blocks.
// The VM just stores a pointer to the shared dataset — the pointer
// remains valid across key changes since UpdateKey rebuilds the dataset
// contents in-place without reallocating. Reusing the VM avoids
// repeated 2MB scratchpad + 84KB JIT alloc/free churn that causes
// Windows heap fragmentation and apparent memory growth per thread.
if (myVM == nullptr) {
// First iteration: acquire shared lock briefly to create VM
boost::shared_lock<boost::shared_mutex> initLock(g_rxDatasetManager->datasetMtx);
myVM = g_rxDatasetManager->CreateVM();
if (myVM == nullptr) {
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
return;
}
for (unsigned i = 0; i < threads.size(); ++i) {
threads[i].join();
}
threads.clear();
} else {
rxdebug("%s: initializing dataset with 1 thread\n");
randomx_init_dataset(randomxDataset, randomxCache, 0, datasetItemCount);
}
rxdebug("%s: dataset initialized\n");
myVM = randomx_create_vm(flags, nullptr, randomxDataset);
if(myVM == NULL) {
LogPrintf("RandomXMiner: Cannot create RandomX VM, aborting!\n");
return;
}
// Acquire shared lock to prevent dataset rebuild while we're hashing
boost::shared_lock<boost::shared_mutex> datasetLock(g_rxDatasetManager->datasetMtx);
//fprintf(stderr,"RandomXMiner: Mining_start=%u\n", Mining_start);
#ifdef ENABLE_WALLET
CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, pindexPrev->GetHeight()+1, gpucount, 0);
@@ -1268,16 +1443,17 @@ void static RandomXMiner()
arith_uint256 hashTarget;
hashTarget = HASHTarget;
CRandomXInput rxInput(pblocktemplate->block);
CDataStream randomxInput(SER_NETWORK, PROTOCOL_VERSION);
// Use the current block as randomx input
randomxInput << pblocktemplate->block;
// Serialize block header without nSolution but with nNonce for deterministic RandomX input
randomxInput << rxInput;
// std::cerr << "RandomXMiner: randomxInput=" << HexStr(randomxInput) << "\n";
// fprintf(stderr,"RandomXMiner: created randomxKey=%s , randomxInput.size=%lu\n", randomxKey, randomxInput.size() ); //randomxInput);
rxdebug("%s: randomxKey=%s randomxInput=%s\n", randomxKey, HexStr(randomxInput).c_str());
rxdebug("%s: calculating randomx hash\n");
randomx_calculate_hash(myVM, &randomxInput, sizeof randomxInput, randomxHash);
randomx_calculate_hash(myVM, &randomxInput[0], randomxInput.size(), randomxHash);
rxdebug("%s: calculated randomx hash\n");
rxdebug("%s: randomxHash=");
@@ -1324,14 +1500,28 @@ void static RandomXMiner()
CValidationState state;
//{ LOCK(cs_main);
if ( !TestBlockValidity(state,B, chainActive.LastTip(), true, false))
// Skip RandomX re-validation during TestBlockValidity — we already
// computed the correct hash, and re-verifying allocates ~256MB which
// can trigger the OOM killer on memory-constrained systems.
SetSkipRandomXValidation(true);
bool fValid = TestBlockValidity(state,B, chainActive.LastTip(), true, false);
SetSkipRandomXValidation(false);
if ( !fValid )
{
h = UintToArith256(B.GetHash());
fprintf(stderr,"RandomXMiner: Invalid randomx block mined, try again ");
fprintf(stderr,"RandomXMiner: TestBlockValidity FAILED at ht.%d nNonce=%s hash=",
Mining_height, pblock->nNonce.ToString().c_str());
for (z=31; z>=0; z--)
fprintf(stderr,"%02x",((uint8_t *)&h)[z]);
gotinvalid = 1;
fprintf(stderr," nSolution.size=%lu\n", B.nSolution.size());
// Dump nSolution hex for comparison with validator
fprintf(stderr,"RandomXMiner: nSolution=");
for (unsigned i = 0; i < B.nSolution.size(); i++)
fprintf(stderr,"%02x", B.nSolution[i]);
fprintf(stderr,"\n");
LogPrintf("RandomXMiner: TestBlockValidity FAILED at ht.%d, gotinvalid=1, state=%s\n",
Mining_height, state.GetRejectReason());
gotinvalid = 1;
return(false);
}
//}
@@ -1399,21 +1589,24 @@ void static RandomXMiner()
pblock->nBits = savebits;
}
rxdebug("%s: going to destroy rx VM\n");
randomx_destroy_vm(myVM);
rxdebug("%s: destroyed VM\n");
// Release shared lock so UpdateKey can acquire exclusive lock for dataset rebuild
// VM is kept alive — its dataset pointer remains valid across rebuilds
datasetLock.unlock();
}
} catch (const boost::thread_interrupted&) {
miningTimer.stop();
c.disconnect();
randomx_destroy_vm(myVM);
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
randomx_release_dataset(randomxDataset);
rxdebug("%s: released dataset via thread interrupt\n");
randomx_release_cache(randomxCache);
rxdebug("%s: released cache via thread interrupt\n");
if (myVM != nullptr) {
randomx_destroy_vm(myVM);
myVM = nullptr;
LogPrintf("%s: destroyed vm via thread interrupt\n", __func__);
} else {
LogPrintf("%s: WARNING myVM already null in thread interrupt handler, skipping destroy (would double-free)\n", __func__);
fprintf(stderr, "%s: WARNING myVM already null in thread interrupt, would have double-freed!\n", __func__);
}
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
LogPrintf("HushRandomXMiner terminated\n");
throw;
@@ -1422,20 +1615,21 @@ void static RandomXMiner()
c.disconnect();
fprintf(stderr,"RandomXMiner: runtime error: %s\n", e.what());
randomx_destroy_vm(myVM);
LogPrintf("%s: destroyed vm because of error\n", __func__);
randomx_release_dataset(randomxDataset);
rxdebug("%s: released dataset because of error\n");
randomx_release_cache(randomxCache);
rxdebug("%s: released cache because of error\n");
if (myVM != nullptr) {
randomx_destroy_vm(myVM);
myVM = nullptr;
LogPrintf("%s: destroyed vm because of error\n", __func__);
}
// Dataset and cache are owned by g_rxDatasetManager — do NOT release here
return;
}
randomx_release_dataset(randomxDataset);
rxdebug("%s: released dataset in normal exit\n");
randomx_release_cache(randomxCache);
rxdebug("%s: released cache in normal exit\n");
// Only destroy per-thread VM, dataset/cache are shared
if (myVM != nullptr) {
randomx_destroy_vm(myVM);
myVM = nullptr;
}
miningTimer.stop();
c.disconnect();
}
@@ -1879,8 +2073,18 @@ void static BitcoinMiner()
if (minerThreads != NULL)
{
minerThreads->interrupt_all();
// Wait for all miner threads to fully terminate before destroying shared resources
minerThreads->join_all();
delete minerThreads;
minerThreads = NULL;
// Shutdown shared RandomX dataset manager after all threads are done
if (g_rxDatasetManager != nullptr) {
g_rxDatasetManager->Shutdown();
delete g_rxDatasetManager;
g_rxDatasetManager = nullptr;
LogPrintf("%s: destroyed shared RandomX dataset manager\n", __func__);
}
}
if(fDebug)
@@ -1895,6 +2099,21 @@ void static BitcoinMiner()
minerThreads = new boost::thread_group();
// Initialize shared RandomX dataset manager before spawning miner threads
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX) {
g_rxDatasetManager = new RandomXDatasetManager();
if (!g_rxDatasetManager->Init()) {
LogPrintf("%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
fprintf(stderr, "%s: FATAL - Failed to initialize shared RandomX dataset manager\n", __func__);
delete g_rxDatasetManager;
g_rxDatasetManager = nullptr;
delete minerThreads;
minerThreads = NULL;
return;
}
LogPrintf("%s: shared RandomX dataset manager initialized\n", __func__);
}
for (int i = 0; i < nThreads; i++) {
#ifdef ENABLE_WALLET
if ( ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ) {

View File

@@ -33,6 +33,8 @@
#include "crypto/common.h"
#include "hush/utiltls.h"
#include <random.h>
#include <random>
#include <limits>
#ifdef _WIN32
#include <string.h>
#else
@@ -2004,7 +2006,7 @@ void ThreadMessageHandler()
// Randomize the order in which we process messages from/to our peers.
// This prevents attacks in which an attacker exploits having multiple
// consecutive connections in the vNodes list.
random_shuffle(vNodesCopy.begin(), vNodesCopy.end(), GetRandInt);
std::shuffle(vNodesCopy.begin(), vNodesCopy.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())));
BOOST_FOREACH(CNode* pnode, vNodesCopy)
{
@@ -2516,7 +2518,7 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss)
// We always round down, except when we have only 1 connection
auto newSize = (vNodes.size() / 2) == 0 ? 1 : (vNodes.size() / 2);
random_shuffle( vRelayNodes.begin(), vRelayNodes.end(), GetRandInt );
std::shuffle( vRelayNodes.begin(), vRelayNodes.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
vRelayNodes.resize(newSize);
if (HUSH_TESTNODE==1 && vNodes.size() == 0) {

View File

@@ -28,6 +28,7 @@
#include <tuple>
constexpr uint64_t CNetAddr::V1_SERIALIZATION_SIZE;
constexpr uint64_t CNetAddr::MAX_ADDRV2_SIZE;
/** check whether a given address is in a network we can probably connect to */
bool CNetAddr::IsReachableNetwork() {

View File

@@ -18,6 +18,7 @@
* *
******************************************************************************/
#include "pow.h"
#include "checkpoints.h"
#include "consensus/upgrades.h"
#include "arith_uint256.h"
#include "chain.h"
@@ -28,6 +29,10 @@
#include "uint256.h"
#include "util.h"
#include "sodium.h"
#include "RandomX/src/randomx.h"
#include <mutex>
#include <boost/thread/shared_mutex.hpp>
#include <boost/thread/locks.hpp>
#ifdef ENABLE_RUST
#include "librustzcash.h"
@@ -313,6 +318,16 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
if (pindexLast == NULL )
return nProofOfWorkLimit;
// DragonX difficulty reset at the RANDOMX_VALIDATION activation height.
// The chain transitioned to a new binary at this height and difficulty was
// reset to minimum (powLimit). Without this, fresh-syncing nodes compute
// a different nBits from GetNextWorkRequired (based on pre-reset blocks)
// and reject the on-chain min-diff block, banning all seed nodes.
if (ASSETCHAINS_RANDOMX_VALIDATION > 0 && pindexLast->GetHeight() + 1 == ASSETCHAINS_RANDOMX_VALIDATION) {
LogPrintf("%s: difficulty reset to powLimit at height %d\n", __func__, ASSETCHAINS_RANDOMX_VALIDATION);
return nProofOfWorkLimit;
}
//{
// Comparing to pindexLast->nHeight with >= because this function
// returns the work required for the block after pindexLast.
@@ -337,6 +352,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
memset(zflags,0,sizeof(zflags));
if ( pindexLast != 0 )
height = (int32_t)pindexLast->GetHeight() + 1;
if ( ASSETCHAINS_ADAPTIVEPOW > 0 && pindexFirst != 0 && pblock != 0 && height >= (int32_t)(sizeof(ct)/sizeof(*ct)) )
{
tipdiff = (pblock->nTime - pindexFirst->nTime);
@@ -683,6 +699,281 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param
return true;
}
int GetRandomXInterval() { return GetArg("-ac_randomx_interval", 1024); }
int GetRandomXBlockLag() { return GetArg("-ac_randomx_lag", 64); }
// Cached RandomX validation state — reused across calls, protected by mutex
static std::mutex cs_randomx_validator;
static randomx_cache *s_rxCache = nullptr;
static randomx_vm *s_rxVM = nullptr;
static std::string s_rxCurrentKey; // tracks current key to avoid re-init
static int64_t nTimeRandomX = 0; // cumulative RandomX validation time (us), reported under -debug=bench
// Thread-local flag: skip CheckRandomXSolution when the miner is validating its own block
// The miner already computed the correct RandomX hash — re-verifying with a separate
// cache+VM would allocate ~256MB extra memory and can trigger the OOM killer.
thread_local bool fSkipRandomXValidation = false;
void SetSkipRandomXValidation(bool skip) { fSkipRandomXValidation = skip; }
bool GetSkipRandomXValidation() { return fSkipRandomXValidation; }
CBlockIndex *hush_chainactive(int32_t height);
// Centralized predicate: does a block at this height actually require a RandomX hash check?
// Shared by CheckRandomXSolution (inline path) and the parallel pre-verify pool so the two can
// never drift. Returns false when the recompute is unnecessary:
// - non-RandomX chain, or RandomX validation disabled (activation height < 0)
// - below the RandomX activation height (those blocks used Equihash, validated elsewhere)
// - during initial on-disk block loading / reindex (HUSH_LOADINGBLOCKS)
// - below the last hardcoded checkpoint (chain pinned by checkpoint hash + linkage + work)
// Deliberately does NOT consider the thread-local fSkipRandomXValidation (miner self-check) — that
// is a property of the calling thread, handled only in the inline CheckRandomXSolution below.
bool RandomXValidationRequired(int32_t height)
{
if (ASSETCHAINS_ALGO != ASSETCHAINS_RANDOMX)
return false;
if (ASSETCHAINS_RANDOMX_VALIDATION < 0)
return false;
if (height < ASSETCHAINS_RANDOMX_VALIDATION)
return false;
extern int32_t HUSH_LOADINGBLOCKS;
if (HUSH_LOADINGBLOCKS != 0)
return false;
extern bool fCheckpointsEnabled;
// Gate the RandomX skip on the last checkpoint actually LOCKED INTO this node's block index
// (GetLastCheckpoint), NOT the static top checkpoint (GetTotalBlocksEstimate). The fork-rejection
// guard uses this same in-index boundary, so a block below it is provably on the checkpoint-pinned
// chain and cannot be a forged fork. Using the static boundary would, once checkpoints extend above
// the RandomX activation height, leave a gap (in-index checkpoint .. static top) during IBD/eclipse
// where a no-hashpower peer could get SHA256-grinded, RandomX-forged blocks accepted. Safe to walk
// mapBlockIndex here: called only under cs_main (ActivateBestChainStep + inline CheckRandomXSolution).
if (fCheckpointsEnabled) {
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(Params().Checkpoints());
if (pcheckpoint != NULL && height < pcheckpoint->GetHeight())
return false;
}
return true;
}
// Serialize the RandomX hash input: the block header without nSolution (but with nNonce). Used by
// both the inline CheckRandomXSolution and the parallel pre-verify pool, so the bytes are identical.
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block)
{
CRandomXInput rxInput(block);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rxInput;
return std::vector<unsigned char>(ss.begin(), ss.end());
}
// Derive the RandomX key string for a block at `height`. Below interval+lag it is the chain-params
// initial key; otherwise the block hash at the key-rotation height. MUST be called under cs_main
// (reads chainActive via hush_chainactive). Returns empty if the key-height block is unavailable.
std::string GetRandomXKey(int32_t height)
{
static int randomxInterval = GetRandomXInterval();
static int randomxBlockLag = GetRandomXBlockLag();
if (height < randomxInterval + randomxBlockLag) {
char initialKey[82];
snprintf(initialKey, 81, "%08x%s%08x", ASSETCHAINS_MAGIC, SMART_CHAIN_SYMBOL, ASSETCHAINS_RPCPORT);
return std::string(initialKey, strlen(initialKey));
}
int keyHeight = ((height - randomxBlockLag) / randomxInterval) * randomxInterval;
CBlockIndex *pKeyIndex = hush_chainactive(keyHeight);
if (pKeyIndex == nullptr)
return std::string();
uint256 blockKey = pKeyIndex->GetBlockHash();
return std::string((const char*)&blockKey, sizeof(blockKey));
}
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)
{
// Centralized height gate (shared with the parallel pre-verify pool, Stage 0).
if (!RandomXValidationRequired(height))
return true;
// Skip when the miner is validating its own freshly-mined block via TestBlockValidity
// (thread-local; never set on the connect thread or the pre-verify worker threads).
if (fSkipRandomXValidation)
return true;
// nSolution must be exactly RANDOMX_HASH_SIZE (32) bytes
if (pblock->nSolution.size() != RANDOMX_HASH_SIZE) {
return error("CheckRandomXSolution(): nSolution size %u != expected %d at height %d",
pblock->nSolution.size(), RANDOMX_HASH_SIZE, height);
}
// Derive the key (shared helper) and serialize the input (identical bytes to the pool path).
std::string rxKey = GetRandomXKey(height);
if (rxKey.empty())
return error("CheckRandomXSolution(): cannot derive RandomX key for height %d", height);
std::vector<unsigned char> ssInput = GetRandomXInput(*pblock);
char computedHash[RANDOMX_HASH_SIZE];
// Measurement (Track 1): isolate RandomX verification cost during IBD. The
// expensive parts are the per-key cache (re)init (~every GetRandomXInterval()
// blocks) and the hash computation itself; both happen under the lock below.
int64_t nTimeRxStart = GetTimeMicros();
bool fKeyInit = false;
{
std::lock_guard<std::mutex> lock(cs_randomx_validator);
// Initialize cache + VM if needed, or re-init if key changed
if (s_rxCache == nullptr) {
randomx_flags flags = randomx_get_flags();
// Try large pages for the 256MB validator cache: fewer TLB misses → ~15-30% faster
// light-mode validation where the OS has hugepages configured. Falls back transparently
// when unavailable, exactly as the miner does (miner.cpp:1097). Page size does not affect
// the computed hash, so this is consensus-neutral.
bool fLargePages = true;
s_rxCache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
if (s_rxCache == nullptr) {
fLargePages = false;
s_rxCache = randomx_alloc_cache(flags);
}
if (s_rxCache == nullptr) {
return error("CheckRandomXSolution(): failed to allocate RandomX cache");
}
// Confirm the fast paths are active (JIT off would be ~9x slower; see randomx-benchmark).
LogPrint("bench", "CheckRandomXSolution: RandomX flags=0x%x JIT=%d HARD_AES=%d largePages=%d\n",
(unsigned int)flags, !!(flags & RANDOMX_FLAG_JIT), !!(flags & RANDOMX_FLAG_HARD_AES), (int)fLargePages);
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
s_rxCurrentKey = rxKey;
fKeyInit = true;
s_rxVM = randomx_create_vm(flags, s_rxCache, nullptr);
if (s_rxVM == nullptr) {
randomx_release_cache(s_rxCache);
s_rxCache = nullptr;
return error("CheckRandomXSolution(): failed to create RandomX VM");
}
} else if (s_rxCurrentKey != rxKey) {
randomx_init_cache(s_rxCache, rxKey.data(), rxKey.size());
s_rxCurrentKey = rxKey;
fKeyInit = true;
randomx_vm_set_cache(s_rxVM, s_rxCache);
}
randomx_calculate_hash(s_rxVM, ssInput.data(), ssInput.size(), computedHash);
}
int64_t nTimeRxEnd = GetTimeMicros();
nTimeRandomX += nTimeRxEnd - nTimeRxStart;
LogPrint("bench", " - RandomX verify ht=%d: %.2fms%s [%.2fs]\n",
height, (nTimeRxEnd - nTimeRxStart) * 0.001,
fKeyInit ? " (key-init)" : "", nTimeRandomX * 0.000001);
// Compare computed hash against nSolution
if (memcmp(computedHash, pblock->nSolution.data(), RANDOMX_HASH_SIZE) != 0) {
// Debug: dump both hashes for diagnosis
std::string computedHex, solutionHex;
for (int i = 0; i < RANDOMX_HASH_SIZE; i++) {
char buf[4];
snprintf(buf, sizeof(buf), "%02x", (uint8_t)computedHash[i]);
computedHex += buf;
snprintf(buf, sizeof(buf), "%02x", pblock->nSolution[i]);
solutionHex += buf;
}
fprintf(stderr, "CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
fprintf(stderr, " computed : %s\n", computedHex.c_str());
fprintf(stderr, " nSolution: %s\n", solutionHex.c_str());
fprintf(stderr, " rxKey size=%lu, input size=%lu, nNonce=%s\n",
rxKey.size(), ssInput.size(), pblock->nNonce.ToString().c_str());
fprintf(stderr, " nSolution.size()=%lu, RANDOMX_HASH_SIZE=%d\n",
pblock->nSolution.size(), RANDOMX_HASH_SIZE);
// Also log to debug.log
LogPrintf("CheckRandomXSolution(): HASH MISMATCH at height %d\n", height);
LogPrintf(" computed : %s\n", computedHex);
LogPrintf(" nSolution: %s\n", solutionHex);
LogPrintf(" rxKey size=%lu, input size=%lu, nNonce=%s\n",
rxKey.size(), ssInput.size(), pblock->nNonce.ToString());
return false;
}
LogPrint("randomx", "CheckRandomXSolution(): valid at height %d\n", height);
return true;
}
// ============================================================================================
// Parallel RandomX pre-verification pool (Stage 2).
// One shared light-mode cache (holding a single key at a time) + per-thread VMs, mirroring the
// miner's RandomXDatasetManager pattern (miner.cpp). The connect thread (ActivateBestChainStep)
// loads the cache key for a same-key group of about-to-be-connected blocks, dispatches them to
// this pool, and barrier-waits; each worker hashes on its own VM (sharing the read-only cache)
// and, on a match, sets the block's transient fRandomXVerified flag so the inline check in
// CheckBlockHeader can be skipped. The inline path remains the consensus authority for anything
// not pre-verified, so the pool can only ever flip false->true on a real hash match.
static boost::shared_mutex g_rxvMutex; // shared = hashing; exclusive = cache (re)init
static randomx_cache* g_rxvCache = nullptr; // shared, read-only during hashing
static std::string g_rxvKey; // key currently loaded into g_rxvCache
static randomx_flags g_rxvFlags;
static thread_local randomx_vm* tls_rxvVM = nullptr;
static thread_local std::string tls_rxvVMKey;
CCheckQueue<CRandomXCheck> rxCheckQueue(1); // batch size 1: each item is ~tens of ms
bool RandomXValidatorPrepareKey(const std::string& rxKey)
{
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
if (g_rxvCache == nullptr) {
g_rxvFlags = randomx_get_flags();
g_rxvCache = randomx_alloc_cache(g_rxvFlags | RANDOMX_FLAG_LARGE_PAGES);
if (g_rxvCache == nullptr)
g_rxvCache = randomx_alloc_cache(g_rxvFlags);
if (g_rxvCache == nullptr) {
LogPrintf("RandomXValidatorPrepareKey: cache alloc failed; parallel pre-verify disabled\n");
return false;
}
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
g_rxvKey = rxKey;
return true;
}
if (g_rxvKey != rxKey) {
randomx_init_cache(g_rxvCache, rxKey.data(), rxKey.size());
g_rxvKey = rxKey;
}
return true;
}
bool CRandomXCheck::operator()()
{
boost::shared_lock<boost::shared_mutex> lock(g_rxvMutex);
// The connect thread set the shared cache to one key before dispatching this group. If this
// item's key doesn't match (e.g. a key-rotation straggler) or the cache is unavailable, skip it
// and leave *presult false — the inline CheckRandomXSolution will verify it.
if (g_rxvCache == nullptr || g_rxvKey != rxKey)
return true;
if (tls_rxvVM == nullptr) {
tls_rxvVM = randomx_create_vm(g_rxvFlags, g_rxvCache, nullptr);
if (tls_rxvVM == nullptr)
return true; // cannot verify here -> inline fallback
tls_rxvVMKey = g_rxvKey;
} else if (tls_rxvVMKey != g_rxvKey) {
// Cache was re-initialized to a new key since this VM last ran; rebind.
randomx_vm_set_cache(tls_rxvVM, g_rxvCache);
tls_rxvVMKey = g_rxvKey;
}
unsigned char h[RANDOMX_HASH_SIZE];
randomx_calculate_hash(tls_rxvVM, input.data(), input.size(), h);
if (memcmp(h, expected, RANDOMX_HASH_SIZE) == 0 && presult != nullptr)
*presult = true;
return true; // ALWAYS true: never short-circuit the queue; per-block result is in *presult
}
void ThreadRandomXVerify()
{
RenameThread("hush-rxverify");
rxCheckQueue.Thread();
}
void RandomXValidatorShutdown()
{
boost::unique_lock<boost::shared_mutex> lock(g_rxvMutex);
// Per-thread VMs are intentionally leaked (process exiting); release the shared cache.
if (g_rxvCache != nullptr) {
randomx_release_cache(g_rxvCache);
g_rxvCache = nullptr;
}
}
int32_t hush_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
int32_t hush_currentheight();
void hush_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height);
@@ -726,9 +1017,19 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
// Check proof of work matches claimed amount
if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget )
{
if ( HUSH_LOADINGBLOCKS != 0 )
return true;
// During initial block loading/sync, skip PoW validation for blocks
// before RandomX validation height. After activation, always validate
// to prevent injection of blocks with fake PoW.
if ( HUSH_LOADINGBLOCKS != 0 ) {
if (ASSETCHAINS_ALGO == ASSETCHAINS_RANDOMX && ASSETCHAINS_RANDOMX_VALIDATION > 0 && height >= ASSETCHAINS_RANDOMX_VALIDATION) {
// Fall through to reject the block — do NOT skip validation after activation
} else {
return true;
}
}
if ( SMART_CHAIN_SYMBOL[0] != 0 || height > 792000 )
{
if ( Params().NetworkIDString() != "regtest" )
{
for (i=31; i>=0; i--)
@@ -745,6 +1046,7 @@ bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t
fprintf(stderr," <- origpubkey\n");
}
return false;
}
}
/*for (i=31; i>=0; i--)
fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);

View File

@@ -21,8 +21,13 @@
#define HUSH_POW_H
#include "chain.h"
#include "checkqueue.h"
#include "consensus/params.h"
#include <stdint.h>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
class CBlockHeader;
class CBlockIndex;
@@ -38,6 +43,68 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
/** Check whether the Equihash solution in a block header is valid */
bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&);
/** Check whether a block header contains a valid RandomX solution */
bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height);
/** Whether a block at this height requires a RandomX hash check (shared gate used by both the
* inline CheckRandomXSolution and the parallel pre-verification pool). */
bool RandomXValidationRequired(int32_t height);
/** Serialize the RandomX hash input (block header without nSolution) — identical bytes to the
* inline CheckRandomXSolution path, so the parallel pool computes the same hash. */
std::vector<unsigned char> GetRandomXInput(const CBlockHeader& block);
/** Derive the RandomX key string for a block at `height`. MUST be called under cs_main (reads
* chainActive). Returns empty string if the key-height block is unavailable. */
std::string GetRandomXKey(int32_t height);
/** A single RandomX pre-verification work item for the parallel validator pool. Pure value type
* (no chainstate pointers) so workers need no cs_main. On a hash match it sets *presult=true; on
* any failure it leaves *presult untouched — the inline CheckRandomXSolution remains the
* consensus authority and re-verifies anything not pre-verified. operator() ALWAYS returns true,
* so one block's failure never short-circuits the rest of the CCheckQueue batch. */
class CRandomXCheck
{
private:
std::string rxKey; // RandomX key for this block's height
std::vector<unsigned char> input; // serialized CRandomXInput(header)
unsigned char expected[32]; // block.nSolution (claimed RandomX hash)
bool* presult; // -> pindex->fRandomXVerified (set true only on a hash match)
public:
CRandomXCheck() : presult(nullptr) { memset(expected, 0, sizeof(expected)); }
CRandomXCheck(const std::string& keyIn, std::vector<unsigned char> inputIn,
const unsigned char* expectedIn, bool* presultIn)
: rxKey(keyIn), input(std::move(inputIn)), presult(presultIn)
{ memcpy(expected, expectedIn, sizeof(expected)); }
bool operator()();
void swap(CRandomXCheck& c) {
rxKey.swap(c.rxKey);
input.swap(c.input);
std::swap(presult, c.presult);
for (int i = 0; i < 32; i++) std::swap(expected[i], c.expected[i]);
}
};
/** The RandomX pre-verification check queue (parallel pool). */
extern CCheckQueue<CRandomXCheck> rxCheckQueue;
/** Worker entry point (spawn N at startup, mirrors ThreadScriptCheck). */
void ThreadRandomXVerify();
/** Load `rxKey` into the shared validator cache (alloc on first use); call before dispatching a
* same-key group of checks. Returns false on allocation failure. */
bool RandomXValidatorPrepareKey(const std::string& rxKey);
/** Release the shared validator cache at shutdown. */
void RandomXValidatorShutdown();
/** Set thread-local flag to skip RandomX validation (used by miner during TestBlockValidity) */
void SetSkipRandomXValidation(bool skip);
bool GetSkipRandomXValidation();
/** Return the RandomX key rotation interval in blocks */
int GetRandomXInterval();
/** Return the RandomX key change lag in blocks */
int GetRandomXBlockLag();
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params);
CChainPower GetBlockProof(const CBlockIndex& block);

View File

@@ -237,6 +237,33 @@ public:
}
};
/**
* Custom serializer for CBlockHeader that includes nNonce but omits nSolution,
* for use as deterministic input to RandomX hashing.
*/
class CRandomXInput : private CBlockHeader
{
public:
CRandomXInput(const CBlockHeader &header)
{
CBlockHeader::SetNull();
*((CBlockHeader*)this) = header;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot);
READWRITE(hashFinalSaplingRoot);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
}
};
/** Describes a place in the block chain to another node such that if the
* other node doesn't have the same branch, it can find a recent common trunk.

View File

@@ -75,6 +75,8 @@ const char *GETNSPV="getnSPV"; //used
const char *NSPV="nSPV"; //used
const char *ALERT="alert"; //used
const char *REJECT="reject"; //used
const char *GETBLOCKSTREAM="getblockstrm"; // 12 chars (COMMAND_SIZE max); "getblockstream" would truncate
const char *BLOCKSTREAM="blockstream";
} // namespace NetMsgType
/** All known message types. Keep this in the same order as the list of
@@ -119,6 +121,8 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::NSPV,
NetMsgType::ALERT,
NetMsgType::REJECT,
NetMsgType::GETBLOCKSTREAM,
NetMsgType::BLOCKSTREAM,
};
CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn)

View File

@@ -285,6 +285,10 @@ extern const char* GETNSPV;
extern const char* NSPV;
extern const char* ALERT;
extern const char* REJECT;
/** Opt-in bulk block streaming (DragonX): request a contiguous range of old blocks. */
extern const char* GETBLOCKSTREAM;
/** Opt-in bulk block streaming (DragonX): control header preceding a streamed block range. */
extern const char* BLOCKSTREAM;
}; // namespace NetMsgType
/* Get a vector of all valid message types (see above) */
@@ -304,6 +308,9 @@ enum ServiceFlags : uint64_t {
NODE_NSPV = (1 << 30),
NODE_ADDRINDEX = (1 << 29),
NODE_SPENTINDEX = (1 << 28),
// Opt-in bulk block streaming (DragonX). Unauthenticated advertisement; serve/request
// handlers validate every block regardless, so robustness against false advertisement holds.
NODE_BULKBLOCKS = (1 << 27),
// Bits 24-31 are reserved for temporary experiments. Just pick a bit that
// isn't getting used, or one not being used much, and notify the

View File

@@ -30,7 +30,9 @@
#include "rpc/server.h"
#include "streams.h"
#include "sync.h"
#include "txdb.h"
#include "util.h"
#include <boost/filesystem.hpp>
#include "script/script.h"
#include "script/script_error.h"
#include "script/sign.h"
@@ -322,6 +324,15 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.push_back(Pair("anchor", blockindex->hashFinalSproutRoot.GetHex()));
result.push_back(Pair("blocktype", "mined"));
// Report block subsidy and fees separately so explorers don't have to
// reimplement the reward schedule to display them.
CAmount nSubsidy = GetBlockSubsidy(blockindex->GetHeight(), Params().GetConsensus());
CAmount nCoinbase = block.vtx[0].GetValueOut();
CAmount nFees = nCoinbase - nSubsidy;
if (nFees < 0) nFees = 0; // block 1 has premine, avoid negative
result.push_back(Pair("subsidy", ValueFromAmount(nSubsidy)));
result.push_back(Pair("fees", ValueFromAmount(nFees)));
UniValue valuePools(UniValue::VARR);
valuePools.push_back(ValuePoolDesc("sapling", blockindex->nChainSaplingValue, blockindex->nSaplingValue));
result.push_back(Pair("valuePools", valuePools));
@@ -851,6 +862,7 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp, const CPubKey& mypk
return ret;
}
UniValue getblockmerkletree(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 1 )
@@ -1642,7 +1654,7 @@ UniValue getchaintxstats(const UniValue& params, bool fHelp, const CPubKey& mypk
ret.pushKV("deshielding_payments", (int64_t)pindex->nChainDeshieldingPayments);
ret.pushKV("shielding_payments", (int64_t)pindex->nChainShieldingPayments);
int64_t nullifierCount = pwalletMain->NullifierCount();
int64_t nullifierCount = pwalletMain ? pwalletMain->NullifierCount() : 0; // null under -disablewallet
//TODO: this is unreliable, is only a cache or subset of total nullifiers
ret.pushKV("nullifiers", (int64_t)nullifierCount);
ret.pushKV("shielded_pool_size", (int64_t)(pindex->nChainShieldedOutputs - pindex->nChainShieldedSpends));

View File

@@ -169,7 +169,7 @@ UniValue getgenerate(const UniValue& params, bool fHelp, const CPubKey& mypk)
throw runtime_error(
"getgenerate\n"
"\nReturn if the server is set to mine coins or not. The default is false.\n"
"It is set with the command line argument -gen (or HUSH3.conf setting gen).\n"
"It is set with the command line argument -gen (or DRAGONX.conf setting gen).\n"
"It can also be set with the setgenerate call.\n"
"\nResult\n"
"{\n"

View File

@@ -560,7 +560,7 @@ UniValue z_validateaddress(const UniValue& params, bool fHelp, const CPubKey& my
#ifdef ENABLE_WALLET
LOCK2(cs_main, pwalletMain->cs_wallet);
LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : NULL);
#else
LOCK(cs_main);
#endif

View File

@@ -133,7 +133,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"pingtime\": n, (numeric) ping time\n"
" \"pingwait\": n, (numeric) ping wait\n"
" \"version\": v, (numeric) The peer version, such as 170002\n"
" \"subver\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) The string version\n"
" \"subver\": \"/DragonX:x.y.z[-v]/\", (string) The string version\n"
" \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n"
" \"startingheight\": n, (numeric) The starting height (block) of the peer\n"
" \"banscore\": n, (numeric) The ban score\n"
@@ -354,7 +354,7 @@ UniValue getaddednodeinfo(const UniValue& params, bool fHelp, const CPubKey& myp
" \"connected\" : true|false, (boolean) If connected\n"
" \"addresses\" : [\n"
" {\n"
" \"address\" : \"192.168.0.201:18030\", (string) The Hush server host and port\n"
" \"address\" : \"192.168.0.201:21768\", (string) The DragonX server host and port\n"
" \"connected\" : \"outbound\" (string) connection, inbound or outbound\n"
" }\n"
" ,...\n"
@@ -505,7 +505,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
"\nResult:\n"
"{\n"
" \"version\": xxxxx, (numeric) the server version\n"
" \"subversion\": \"/GoldenSandtrout:x.y.z[-v]/\", (string) the server subversion string\n"
" \"subversion\": \"/DragonX:x.y.z[-v]/\", (string) the server subversion string\n"
" \"protocolversion\": xxxxx, (numeric) the protocol version\n"
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
" \"timeoffset\": xxxxx, (numeric) the time offset (deprecated, always 0)\n"

View File

@@ -474,6 +474,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "z_listaddresses", &z_listaddresses, true },
{ "wallet", "z_listnullifiers", &z_listnullifiers, true },
{ "wallet", "z_exportkey", &z_exportkey, true },
{ "wallet", "z_exportmnemonic", &z_exportmnemonic, true },
{ "wallet", "z_importkey", &z_importkey, true },
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },

View File

@@ -353,6 +353,7 @@ extern UniValue nspv_listccmoduleunspent(const UniValue& params, bool fHelp, con
extern UniValue getblocksubsidy(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
extern UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdump.cpp

View File

@@ -14,7 +14,7 @@
"ac_perc": "11111111",
"ac_eras": "3",
"ac_script": "76a9145eb10cf64f2bab1b457f1f25e658526155928fac88ac",
"clientname": "GoldenSandtrout",
"clientname": "DragonX",
"addnode": [
"1.1.1.1"
]

View File

@@ -11,6 +11,8 @@
#include <boost/optional/optional_io.hpp>
#include <librustzcash.h>
#include "zcash/Note.hpp"
#include <random>
#include <limits>
extern bool fZDebug;
SpendDescriptionInfo::SpendDescriptionInfo(
@@ -66,7 +68,7 @@ void TransactionBuilder::AddSaplingOutput(
void TransactionBuilder::ShuffleOutputs()
{
LogPrintf("%s: Shuffling %d zouts\n", __func__, outputs.size() );
random_shuffle( outputs.begin(), outputs.end(), GetRandInt );
std::shuffle( outputs.begin(), outputs.end(), std::mt19937(GetRand(std::numeric_limits<uint32_t>::max())) );
}
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value, uint32_t _nSequence)

View File

@@ -21,9 +21,11 @@
#include "txdb.h"
#include "chainparams.h"
#include "clientversion.h"
#include "hash.h"
#include "main.h"
#include "pow.h"
#include "streams.h"
#include "uint256.h"
#include "core_io.h"
#include <stdint.h>

View File

@@ -52,7 +52,7 @@ class uint256;
//! -dbcache default (MiB)
static const int64_t nDefaultDbCache = 512;
//! max. -dbcache (MiB)
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 65536 : 1024; // 64 GiB ceiling on 64-bit so adaptive dbcache can use most of RAM on large hosts (was 16384)
//! min. -dbcache in (MiB)
static const int64_t nMinDbCache = 4;

View File

@@ -469,10 +469,17 @@ extern char SMART_CHAIN_SYMBOL[];
std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
{
CBlockIndex *tipindex;
// Remove expired txs from the mempool
// Remove expired txs from the mempool. (Regression fix: the scan that populates
// transactionsToRemove had been dropped, making this a no-op, so expired txs -- which
// can never be mined -- were never evicted and accumulated without bound.)
LOCK(cs);
list<CTransaction> transactionsToRemove;
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->GetTx();
if (IsExpiredTx(tx, nBlockHeight)) {
transactionsToRemove.push_back(tx);
}
}
std::vector<uint256> ids;
for (const CTransaction& tx : transactionsToRemove) {
@@ -484,6 +491,45 @@ std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
return ids;
}
// Age-based eviction: remove txs whose entry time is older than `time`. Distinct from
// removeExpired() (which drops txs past their consensus nExpiryHeight); this is the wall-clock
// sweep LimitMempoolSize wants. Collect-then-remove to avoid iterating mapTx while mutating it.
int CTxMemPool::Expire(int64_t time)
{
LOCK(cs);
std::list<CTransaction> toRemove;
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
if (it->GetTime() < time)
toRemove.push_back(it->GetTx());
}
for (const CTransaction& tx : toRemove) {
std::list<CTransaction> removed;
remove(tx, removed, true);
}
return (int)toRemove.size();
}
// Fee-ordered eviction: drop the lowest-feerate txs (and their in-mempool descendants, via the
// recursive remove) until DynamicMemoryUsage() is at or below sizelimit. Uses the per-tx feerate
// index (mapTx index 1, sorted feerate DESCENDING, so the worst tx is the tail). NOTE: this is a
// per-tx feerate, not a descendant-aggregate score, so a low-fee parent funded by a high-fee child
// (CPFP) can be evicted -- an accepted simplification (no descendant tracking in this fork). The
// admission cap in AcceptToMemoryPool bounds growth between block connects (no rollingMinFee here).
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining)
{
LOCK(cs);
while (DynamicMemoryUsage() > sizelimit && !mapTx.empty()) {
// Re-derive the tail each iteration: remove() invalidates iterators.
CTransaction tx = std::prev(mapTx.get<1>().end())->GetTx();
std::list<CTransaction> removed;
remove(tx, removed, true);
if (pvNoSpendsRemaining) {
for (const CTransaction& r : removed)
pvNoSpendsRemaining->push_back(r.GetHash());
}
}
}
// Called when a block is connected. Removes from mempool and updates the miner fee estimator.
void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
std::list<CTransaction>& conflicts, bool fCurrentEstimate)

View File

@@ -219,6 +219,8 @@ public:
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
std::vector<uint256> removeExpired(unsigned int nBlockHeight);
int Expire(int64_t time);
void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining = NULL);
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
std::list<CTransaction>& conflicts, bool fCurrentEstimate = true);
void removeWithoutBranchId(uint32_t nMemPoolBranchId);

View File

@@ -710,7 +710,7 @@ boost::filesystem::path GetConfigFile()
if ( SMART_CHAIN_SYMBOL[0] != 0 ) {
sprintf(confname,"%s.conf",SMART_CHAIN_SYMBOL);
} else {
strcpy(confname,"HUSH3.conf");
strcpy(confname,"DRAGONX.conf");
}
boost::filesystem::path pathConfigFile(GetArg("-conf",confname));
if (!pathConfigFile.is_complete())
@@ -731,7 +731,7 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it)
{
// Don't overwrite existing settings so command line settings override HUSH3.conf
// Don't overwrite existing settings so command line settings override DRAGONX.conf
string strKey = string("-") + it->string_key;
if (mapSettingsRet.count(strKey) == 0)
{
@@ -1029,14 +1029,16 @@ void SetThreadPriority(int nPriority)
std::string PrivacyInfo()
{
return "\n" +
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using Hush, please see <%s>."),
"https://hush.is/security/")) + "\n";
FormatParagraph(strprintf(_("In order to ensure you are adequately protecting your privacy when using DragonX, please see <%s>."),
"https://dragonx.is/security/")) + "\n";
}
std::string LicenseInfo()
{
return "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-%i Duke Leto and The Hush Developers"), COPYRIGHT_YEAR)) + "\n" +
FormatParagraph(strprintf(_("Copyright (C) 2024-%i The DragonX Developers"), COPYRIGHT_YEAR)) + "\n" +
"\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-2024 Duke Leto and The Hush Developers"))) + "\n" +
"\n" +
FormatParagraph(strprintf(_("Copyright (C) 2016-2020 jl777 and SuperNET developers"))) + "\n" +
"\n" +

View File

@@ -156,10 +156,17 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
assert(pcoinsTip->GetSaplingAnchorAt(SaplingMerkleTree::empty_root(), oldSaplingTree));
}
// Use operator[] (empty-list default), NOT .at(): a block's conflict entry can be
// absent if it was drained in an earlier cycle whose connect loop hit the IBD
// ReadBlockFromDisk retry below (that break leaves pindexLastTip behind, so the block
// gets rebuilt here but its conflicts were already cleared and are never re-inserted).
// .at() would throw std::out_of_range which, uncaught under cs_main in this boost
// thread, aborts the node (and crash-loops since pindexLastTip cannot advance). A
// missing entry simply means no conflict notifications for this block (best-effort).
blockStack.emplace_back(
pindex,
std::make_pair(oldSproutTree, oldSaplingTree),
recentlyConflicted.first.at(pindex));
recentlyConflicted.first[pindex]);
pindex = pindex->pprev;
}
@@ -174,11 +181,23 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// network message processing thread.
//
// Notify block disconnects
// Notify block disconnects. If an IBD read fails mid-disconnect we must NOT fall through
// to the connect loop (which advances pindexLastTip to the new tip and permanently abandons
// the remaining disconnects -> wallet witness/anchor desync); skip connects this cycle and
// retry the whole disconnect next cycle, exactly as the connect-side break retries.
bool fDisconnectIncomplete = false;
while (pindexLastTip && pindexLastTip != pindexFork) {
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, pindexLastTip,1)) {
if (IsInitialBlockDownload()) {
// During IBD, block data may not be flushed to disk yet.
// Sleep briefly and retry on the next cycle instead of crashing.
LogPrintf("%s: block at height %d not yet readable, will retry\n",
__func__, pindexLastTip->GetHeight());
fDisconnectIncomplete = true;
break;
}
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for details"),
@@ -198,14 +217,23 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
pindexLastTip = pindexLastTip->pprev;
}
// Notify block connections
while (!blockStack.empty()) {
// Notify block connections (skipped this cycle if the disconnect loop broke to retry an
// unreadable block, so pindexLastTip is not advanced past the un-disconnected blocks).
while (!fDisconnectIncomplete && !blockStack.empty()) {
auto blockData = blockStack.back();
blockStack.pop_back();
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, blockData.pindex, 1)) {
if (IsInitialBlockDownload()) {
// During IBD, block data may not be flushed to disk yet.
// Push unprocessed blocks back and retry on the next cycle.
LogPrintf("%s: block at height %d not yet readable, will retry\n",
__func__, blockData.pindex->GetHeight());
blockStack.push_back(blockData);
break;
}
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block connects");
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for details"),

View File

@@ -21,7 +21,8 @@
#define HUSH_VERSION_H
// network protocol versioning
static const int PROTOCOL_VERSION = 1987429;
// DragonX 1.0.0 - bumped to separate from old HUSH/DragonX nodes with RandomX bug
static const int PROTOCOL_VERSION = 2000000;
//! initial proto version, to be increased after version/verack negotiation
static const int INIT_PROTO_VERSION = 209;
//! In this version, 'getheaders' was introduced.
@@ -30,8 +31,9 @@ static const int GETHEADERS_VERSION = 31800;
//! disconnect from peers older than this proto version (HUSH mainnet)
static const int MIN_HUSH_PEER_PROTO_VERSION = 1987426;
//! disconnect from peers older than this proto version (HACs)
static const int MIN_PEER_PROTO_VERSION = 1987420;
//! disconnect from peers older than this proto version (DragonX/HACs)
//! Set to 2000000 to reject nodes without RandomX validation fix
static const int MIN_PEER_PROTO_VERSION = 2000000;
//! nTime field added to CAddress, starting with this version;
//! if possible, avoid requesting addresses nodes older than this

View File

@@ -13,7 +13,7 @@
char SMART_CHAIN_SYMBOL[HUSH_SMART_CHAIN_MAXLEN];
int64_t MAX_MONEY = 200000000 * 100000000LL;
uint64_t ASSETCHAINS_SUPPLY;
uint16_t BITCOIND_RPCPORT = 18031;
uint16_t BITCOIND_RPCPORT = 21769;
uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT;
uint32_t ASSETCHAIN_INIT,ASSETCHAINS_CC;
uint32_t ASSETCHAINS_MAGIC = 2387029918;

View File

@@ -312,7 +312,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"AsyncRPCOperation_sendmany: HD seed not found");

View File

@@ -377,7 +377,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"AsyncRPCOperation_sendmany::main_impl(): HD seed not found");

View File

@@ -197,7 +197,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
if (!pwalletMain->GetHDSeedForDerivation(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"CWallet::GenerateNewSaplingZKey(): HD seed not found");

99
src/wallet/mnemonic.cpp Normal file
View File

@@ -0,0 +1,99 @@
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
#include "wallet/mnemonic.h"
#include "random.h"
#include "support/cleanse.h"
#include <mutex>
#include <cstring>
extern "C" {
#include "crypto/bip39/bip39.h"
}
// The vendored BIP39 library references random_buffer() (used by its
// mnemonic_generate()). We do not compile trezor's insecure rand.c; instead we
// route it to the node CSPRNG so any BIP39 randomness is cryptographically
// sound. random_buffer is declared weak in rand.c, so this strong definition
// is the one that links.
extern "C" void random_buffer(uint8_t* buf, size_t len)
{
GetRandBytes(buf, (int)len);
}
// mnemonic_from_data()/mnemonic_to_seed() use process-static scratch buffers,
// so serialize all access behind one lock and copy results out immediately.
static std::mutex cs_bip39;
bool MnemonicIsValid(const std::string& phrase)
{
std::lock_guard<std::mutex> lock(cs_bip39);
return mnemonic_check(phrase.c_str()) != 0;
}
bool MnemonicToEntropy(const std::string& phrase, RawHDSeed& entropyOut)
{
std::lock_guard<std::mutex> lock(cs_bip39);
// Reject bad checksum / unknown words first.
if (mnemonic_check(phrase.c_str()) == 0) {
return false;
}
// mnemonic_to_entropy() writes 33 bytes (entropy || 1 checksum byte) and
// returns the total bit count (words * 11).
uint8_t buf[33];
int totalBits = mnemonic_to_entropy(phrase.c_str(), buf);
if (totalBits <= 0) {
return false;
}
int words = totalBits / 11;
if (words != 12 && words != 18 && words != 24) {
memory_cleanse(buf, sizeof(buf));
return false;
}
int entropyBytes = words * 4 / 3; // 12->16, 18->24, 24->32
entropyOut.assign(buf, buf + entropyBytes);
memory_cleanse(buf, sizeof(buf));
return true;
}
bool EntropyToMnemonic(const RawHDSeed& entropy, std::string& phraseOut)
{
std::lock_guard<std::mutex> lock(cs_bip39);
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
if (phrase == nullptr) {
return false;
}
phraseOut.assign(phrase);
mnemonic_clear(); // wipe the static buffer
return true;
}
bool Bip39SeedFromEntropy(const RawHDSeed& entropy, RawHDSeed& seed64Out)
{
std::lock_guard<std::mutex> lock(cs_bip39);
// Regenerate the canonical phrase from entropy (matches SDXLite's
// Mnemonic::from_entropy(entropy).phrase()), then PBKDF2 with an EMPTY
// passphrase to get the standard 64-byte BIP39 seed.
const char* phrase = mnemonic_from_data(entropy.data(), (int)entropy.size());
if (phrase == nullptr) {
return false;
}
uint8_t seed[64];
mnemonic_to_seed(phrase, "", seed, nullptr);
mnemonic_clear();
seed64Out.assign(seed, seed + 64);
memory_cleanse(seed, sizeof(seed));
return true;
}
bool GenerateMnemonicEntropy(int bits, RawHDSeed& entropyOut)
{
if (bits != 128 && bits != 160 && bits != 192 && bits != 224 && bits != 256) {
return false;
}
entropyOut.resize(bits / 8);
GetRandBytes(entropyOut.data(), (int)entropyOut.size());
return true;
}

39
src/wallet/mnemonic.h Normal file
View File

@@ -0,0 +1,39 @@
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
#ifndef HUSH_WALLET_MNEMONIC_H
#define HUSH_WALLET_MNEMONIC_H
#include <string>
#include "zcash/zip32.h" // RawHDSeed
// Thin, thread-safe C++ wrapper over the vendored BIP39 (trezor-crypto) library.
// It reproduces SilentDragonXLite's tiny-bip39 0.6.2 conventions EXACTLY so the
// same 24 words yield the same addresses in both wallets:
// - English wordlist only (byte-identical to tiny-bip39's english.txt)
// - empty BIP39 passphrase (no "25th word")
// - PBKDF2-HMAC-SHA512, 2048 rounds, 64-byte seed
// - the seed is derived from the CANONICAL phrase regenerated from entropy,
// matching SDXLite's Mnemonic::from_entropy(entropy).phrase() round-trip.
//! True if `phrase` is a valid BIP39 mnemonic (word list + checksum).
bool MnemonicIsValid(const std::string& phrase);
//! Parse `phrase` into its BIP39 entropy (16/20/24/28/32 bytes). Validates the
//! checksum first. Returns false on any invalid input.
bool MnemonicToEntropy(const std::string& phrase, RawHDSeed& entropyOut);
//! Regenerate the canonical English mnemonic phrase from `entropy`.
bool EntropyToMnemonic(const RawHDSeed& entropy, std::string& phraseOut);
//! Derive the 64-byte BIP39 seed used for HD derivation from `entropy`, exactly
//! as SilentDragonXLite does: canonical phrase from entropy, then PBKDF2 with an
//! empty passphrase.
bool Bip39SeedFromEntropy(const RawHDSeed& entropy, RawHDSeed& seed64Out);
//! Generate fresh BIP39 entropy of `bits` (128/160/192/224/256) from the node
//! CSPRNG, for creating a new mnemonic wallet.
bool GenerateMnemonicEntropy(int bits, RawHDSeed& entropyOut);
#endif // HUSH_WALLET_MNEMONIC_H

View File

@@ -742,7 +742,9 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
{
HDSeed hdSeed;
pwalletMain->GetHDSeed(hdSeed);
// Dump the 64-byte derivation seed (for mnemonic wallets this is the
// expanded BIP39 seed), so re-importing the hex reproduces the same keys.
pwalletMain->GetHDSeedForDerivation(hdSeed);
auto rawSeed = hdSeed.RawSeed();
file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex());
file << "\n";
@@ -1026,6 +1028,50 @@ UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
return EncodeSpendingKey(sk.get());
}
UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 0)
throw runtime_error(
"z_exportmnemonic\n"
"\nReveal the wallet's BIP39 seed phrase (24 words).\n"
"The phrase is byte-compatible with SilentDragonXLite: the same words\n"
"restore the same transparent and shielded addresses in either wallet.\n"
"Only works for wallets created or restored from a mnemonic (see the\n"
"-mnemonic and -usemnemonic options). Requires the wallet be unlocked.\n"
"\nResult:\n"
"{\n"
" \"mnemonic\" : \"word1 ... word24\", (string) the BIP39 seed phrase\n"
" \"seedfp\" : \"hex\" (string) the HD seed fingerprint\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_exportmnemonic", "")
+ HelpExampleRpc("z_exportmnemonic", "")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
if (!pwalletMain->IsMnemonicSeed()) {
throw JSONRPCError(RPC_WALLET_ERROR,
"This wallet's seed was not derived from a mnemonic, so no seed phrase is available. "
"Use z_exportwallet to back up the raw HD seed instead.");
}
std::string phrase;
if (!pwalletMain->GetMnemonicPhrase(phrase)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not retrieve the seed phrase (is the wallet unlocked?)");
}
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("mnemonic", phrase));
ret.push_back(Pair("seedfp", pwalletMain->GetHDChain().seedFp.GetHex()));
return ret;
}
UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))

View File

@@ -305,7 +305,7 @@ void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends,
//Decrypt sapling outgoing t to z transaction using HDseed
if (wtx.vShieldedSpend.size()==0) {
HDSeed seed;
if (pwalletMain->GetHDSeed(seed)) {
if (pwalletMain->GetHDSeedForDerivation(seed)) {
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);

Some files were not shown because too many files have changed in this diff Show More