4 Commits

Author SHA1 Message Date
1ec6590fb7 wallet: add z_autoshieldstatus
Auto-shielding could silently decline to run with no way to ask why. The
destination it resolves was equally invisible: the only evidence was a LogPrintf
emitted once per round, so an operator wanting to know where their mined coinbase
was going had to grep debug.log.

z_autoshieldstatus reports the enable state, whether a round is in flight, the
next height, interval, fee, minimum utxos, and the resolved destination -- plus
the HD seed provenance in both numeric and readable form, whether the seed is
phrase-recoverable, and a disabled_reason explaining why it is off when it is.

That last field is the point. "autoshield": false on its own does not distinguish
an operator who passed -autoshield=0 from a wallet whose seed provenance is not
known-recoverable, and those need different responses.

Mirrors z_sweepstatus in shape and registration.

Verified on all three branches:
  fresh wallet    -> autoshield true, origin 1 "created on an empty wallet",
                     seed_recoverable true, disabled_reason ""
  -autoshield=0   -> disabled_reason "disabled by -autoshield=0"
  upgraded wallet -> autoshield false, origin 4 "predates provenance recording",
                     seed_recoverable false, disabled_reason "HD seed origin is
                     not known-recoverable; back the seed up and pass -autoshield=1"

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 06:04:43 +02:00
92e6c7008d wallet: define CHDChain's static constants out of line
hush-gtest failed to link with "undefined reference to
CHDChain::VERSION_HD_MNEMONIC". The version constants are static const int with
in-class initialisers and no definition anywhere, so any ODR use needs one --
and gtest's EXPECT_*/ASSERT_* macros take their arguments by const reference,
which is exactly that. test_mnemonic_compat.cpp:161 passes VERSION_HD_MNEMONIC
to EXPECT_LT.

dragonxd links either way, because nothing in the daemon binds these to a
reference; only the test target exposed it, and the test target was never built
on the branch that introduced the test.

Define all four rather than only the one that failed: VERSION_HD_BASE,
VERSION_HD_TRANSPARENT and CURRENT_VERSION carry the identical latent fault, and
the next EXPECT_EQ against any of them would hit the same wall. Fixing the test
instead would have hidden the problem rather than removed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 18:58:24 +02:00
733df964ec Merge autoshield-safety into dev: auto-shield coinbase + BIP39 by default
Brings the auto-shield-coinbase feature and the wallet seed work onto the release
line so both are exercised from dev rather than a side branch.

From upstream: auto-shield of matured coinbase into a wallet z-address, plus
fixes to two pre-existing scheduler wedges (consolidation dispatching every block
instead of once per interval, and a failing sweep stranding its running flag).

On top of that:
  * the auto-shield destination is chosen by re-deriving m/32'/coin'/i' from the
    seed and taking the lowest in-gap account the wallet holds a key for, rather
    than the first entry in std::set order. Key metadata is NOT evidence of
    provenance -- z_importwallet copies hdKeypath and seedFp verbatim from the
    import file -- so a crafted import could otherwise claim account 0 and
    capture every shielded reward;
  * HD seed/chain persistence is hardened: the chain record is written before the
    seed, a corrupt hdchain is loud rather than silently reverting derivation to
    the raw entropy, hdchain survives -salvagewallet, and the silent BIP39 ->
    random seed fallback is gone;
  * seed provenance is recorded, and -autoshield defaults ON only where that
    provenance says the seed is recoverable;
  * mnemonic wallets now store the EXPANDED 64-byte BIP39 seed with the 32-byte
    entropy in a separate display-only record. Derivation reads stored bytes
    directly on every binary, so key trees are identical and no CHDChain version
    bump or minversion fence is needed -- the format stays readable by earlier
    releases;
  * new wallets are created from a BIP39 phrase by default;
  * the plaintext hdseed record is erased when a wallet is encrypted. It was
    previously left behind, and CDB::Rewrite copies surviving records verbatim,
    so the unencrypted seed persisted on disk forever.

TWO DEFAULTS CHANGE for new wallets: auto-shielding (where the seed provenance is
known) and BIP39 seed generation. Existing wallets are untouched -- seed
generation is reachable only when a wallet has none, and all three key stores
refuse to replace an existing seed.

Testing state, stated plainly: every commit built clean and the branch was
verified on an isolated chain -- destination selection provably ignores an
imported foreign key, both provenance branches behave, a new-format wallet
reopened by a pre-change binary lists identical addresses, and restoring only the
24 words recovers the wallet. NOT yet tested: any of this against a funded wallet
on mainnet, or a soak of these defaults on a real node.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 11:53:32 -05:00
d12e7dc99d Merge origin/master into dev: restore the mingw -Wa,-mbig-obj flag
dev branched from the release line before 1c3523aac and so lost -Wa,-mbig-obj
from util/build-win.sh. Without it the Windows cross-compile fails at link: large
template/boost-heavy translation units exceed the PE/COFF ~32k-section limit, and
GNU ld emits "dangerous relocation" on .pdata and crashes. v1.1.0 therefore could
not produce a win64 binary at all.

The merge brings only that one file back. util/build-win.sh is the only file both
sides touched, and the two edits are ~20 lines apart, so it auto-merges keeping
both fixes: -Wa,-mbig-obj on the configure line and -DARCH=default on the cmake
line. Verified after merging that dev's own work is intact -- the extended
checkpoint table, the guarded RandomX dedup, the 1.1.0 version bump, and
ARCH=default in the other two build scripts.

No C++ changed; the only delta from the pre-merge dev is the shell script.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 10:42:29 -05:00
3 changed files with 82 additions and 0 deletions

View File

@@ -369,6 +369,7 @@ extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPub
extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_sweepstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_sweepstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_consolidationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_consolidationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp

View File

@@ -3345,6 +3345,77 @@ UniValue z_sweepstatus(const UniValue& params, bool fHelp, const CPubKey& mypk)
return ret; return ret;
} }
UniValue z_autoshieldstatus(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 0)
throw runtime_error(
"z_autoshieldstatus\n"
"\nReport the state of automatic coinbase shielding: whether it is on, where it sends,\n"
"and -- when it is off -- why.\n"
"\nResult:\n"
"{\n"
" \"autoshield\" : true|false, (boolean) whether auto-shielding is enabled\n"
" \"running\" : true|false, (boolean) whether a round is in flight\n"
" \"next_autoshield\" : n, (numeric) height of the next round\n"
" \"autoshieldinterval\" : n, (numeric) blocks between rounds\n"
" \"autoshieldaddress\" : \"zaddr\", (string) resolved destination; empty until first resolved\n"
" \"autoshieldfee\" : n, (numeric) fee in puposhis\n"
" \"autoshieldminutxos\" : n, (numeric) minimum matured coinbase utxos per round\n"
" \"hdseedorigin\" : n, (numeric) 0 unrecorded, 1 created, 2 restored, 3 retrofit, 4 unknown\n"
" \"hdseedorigin_desc\" : \"...\", (string) readable form of hdseedorigin\n"
" \"seed_recoverable\" : true|false, (boolean) whether a seed phrase can be exported\n"
" \"disabled_reason\" : \"...\" (string) why auto-shielding is not running, if it is not\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_autoshieldstatus", "")
+ HelpExampleRpc("z_autoshieldstatus", "")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("autoshield", pwalletMain->fAutoShieldEnabled));
ret.push_back(Pair("running", pwalletMain->fAutoShieldRunning));
ret.push_back(Pair("next_autoshield", pwalletMain->nextAutoShield));
ret.push_back(Pair("autoshieldinterval", pwalletMain->autoShieldInterval));
ret.push_back(Pair("autoshieldaddress", pwalletMain->autoShieldAddress));
ret.push_back(Pair("autoshieldfee", pwalletMain->autoShieldFee));
ret.push_back(Pair("autoshieldminutxos", pwalletMain->autoShieldMinUtxos));
int origin = pwalletMain->hdSeedOrigin;
std::string desc;
switch (origin) {
case CWallet::HDSEED_ORIGIN_CREATED: desc = "created on an empty wallet"; break;
case CWallet::HDSEED_ORIGIN_RESTORED: desc = "restored from -mnemonic/-hdseed"; break;
case CWallet::HDSEED_ORIGIN_RETROFIT: desc = "retrofitted onto a pre-existing wallet"; break;
case CWallet::HDSEED_ORIGIN_UNKNOWN: desc = "predates provenance recording"; break;
default: desc = "not yet recorded"; break;
}
ret.push_back(Pair("hdseedorigin", origin));
ret.push_back(Pair("hdseedorigin_desc", desc));
ret.push_back(Pair("seed_recoverable", pwalletMain->IsMnemonicSeed()));
// Say why it is off. A silent "false" is exactly what made the destination
// un-inspectable in the first place.
std::string why = "";
if (!pwalletMain->fAutoShieldEnabled) {
if (origin != CWallet::HDSEED_ORIGIN_CREATED && origin != CWallet::HDSEED_ORIGIN_RESTORED)
why = "HD seed origin is not known-recoverable; back the seed up and pass -autoshield=1";
else
why = "disabled by -autoshield=0";
} else if (pwalletMain->IsLocked()) {
why = "wallet is locked; rounds are skipped until it is unlocked";
} else if (pwalletMain->autoShieldAddress.empty()) {
why = "";
}
ret.push_back(Pair("disabled_reason", why));
return ret;
}
UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey&) UniValue z_listreceivedaddress(const UniValue& params, bool fHelp,const CPubKey&)
{ {
if (!EnsureWalletIsAvailable(fHelp)) if (!EnsureWalletIsAvailable(fHelp))
@@ -6349,6 +6420,7 @@ static const CRPCCommand commands[] =
{ "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, { "wallet", "z_gettotalbalance", &z_gettotalbalance, false },
{ "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, { "wallet", "z_mergetoaddress", &z_mergetoaddress, false },
{ "wallet", "z_sweepstatus", &z_sweepstatus, true }, { "wallet", "z_sweepstatus", &z_sweepstatus, true },
{ "wallet", "z_autoshieldstatus", &z_autoshieldstatus, true },
{ "wallet", "z_consolidationstatus", &z_consolidationstatus, true }, { "wallet", "z_consolidationstatus", &z_consolidationstatus, true },
{ "wallet", "z_sendmany", &z_sendmany, false }, { "wallet", "z_sendmany", &z_sendmany, false },
{ "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false }, { "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false },

View File

@@ -34,6 +34,15 @@
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
// Out-of-line definitions for CHDChain's in-class static constants. These are
// only initialised in the class body, so any ODR use -- binding one to a const
// reference, which is exactly what gtest's EXPECT_*/ASSERT_* macros do -- needs
// a definition or the link fails. hush-gtest hit this on VERSION_HD_MNEMONIC.
const int CHDChain::VERSION_HD_BASE;
const int CHDChain::VERSION_HD_TRANSPARENT;
const int CHDChain::VERSION_HD_MNEMONIC;
const int CHDChain::CURRENT_VERSION;
using namespace std; using namespace std;
static uint64_t nAccountingEntryNumber = 0; static uint64_t nAccountingEntryNumber = 0;