pwalletMain->autoShieldAddress was only ever written by a round running in the
current process, so on any node without an explicit -autoshieldaddress,
z_autoshieldstatus reported an empty destination from startup until the first
round fired. disabled_reason was empty on that path too (rpcwallet.cpp), so the
RPC showed autoshield true, running false, no address and no explanation -- the
exact silent state z_autoshieldstatus was added to eliminate. On a restored
wallet, which pre-derives the whole -mnemonicsaplinggap window and therefore
always has an in-gap account to pick, the answer was known at startup and simply
not computed.
Split the read-only half of resolveDestination into a free function shared with
init: the configured override if set, else the lowest in-gap account
m/32'/coin'/i' the wallet already holds. init calls it once when autoshield is
enabled and no explicit address was given.
It deliberately does not generate a key. Deriving a fresh sapling account as a
side effect of populating a status field would mutate the wallet to make an RPC
prettier, so step 3 of resolveDestination -- the generation path, which must stay
inside the operation where an unlocked wallet is already established -- is left
where it was. A brand-new wallet holds nothing in-gap, so the field stays empty
there and disabled_reason now says why instead of being blank.
Behaviour is otherwise unchanged: same derivation, same lowest-index-wins rule,
same refusal to trust CKeyMetadata, same caching for the life of the process.
Verified on an isolated regtest chain, 12/12:
fresh wallet -> address "", reason "no destination resolved yet; one will
be derived from the HD seed on the first round"
after one round -> address set, reason empty
after RESTART -> address visible with NO block mined since (height 12 both
sides), and z_listaddresses still holds exactly 1 address,
so init derived nothing
-autoshieldaddress-> still overrides the derived destination, and the round
shields into it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
3.6 KiB
C++
79 lines
3.6 KiB
C++
// Copyright (c) 2016-2024 The Hush developers
|
|
// Copyright (c) 2024-2026 The DragonX developers
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
#ifndef ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H
|
|
#define ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H
|
|
|
|
#include "amount.h"
|
|
#include "asyncrpcoperation.h"
|
|
#include "univalue.h"
|
|
#include "zcash/Address.hpp"
|
|
#include "zcash/zip32.h"
|
|
|
|
// Default fee for automatic coinbase-shielding transactions
|
|
static const CAmount DEFAULT_AUTOSHIELD_FEE = 10000;
|
|
|
|
// Sentinel for "not a derived account" (i.e. the configured -autoshieldaddress).
|
|
static const uint32_t AUTOSHIELD_ACCOUNT_NONE = UINT32_MAX;
|
|
|
|
enum class AutoShieldDestStatus {
|
|
Resolved, // destOut/destStrOut are set
|
|
NotFound, // no in-gap account held yet; the operation will derive one
|
|
InvalidOverride, // -autoshieldaddress is set but is not a Sapling address
|
|
NoSeed, // no HD seed available (e.g. locked wallet)
|
|
};
|
|
|
|
// Resolve the auto-shield destination WITHOUT mutating the wallet: the configured
|
|
// -autoshieldaddress if set, else the lowest in-gap seed-derived account the wallet
|
|
// already holds. It deliberately does NOT generate a key, so init can call it purely
|
|
// to answer "where will this send?" -- deriving a fresh account as a side effect of
|
|
// populating a status field would be wrong. The operation's own resolveDestination
|
|
// falls through to generation when this returns NotFound.
|
|
// Caller must hold cs_wallet.
|
|
AutoShieldDestStatus ResolveAutoShieldDestinationReadOnly(
|
|
libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut, uint32_t& accountOut);
|
|
|
|
// A periodic, wallet-local operation that drains matured *transparent* coinbase
|
|
// UTXOs into a wallet-owned Sapling z-address in size-bounded batches. It is the
|
|
// automatic sibling of the manual z_shieldcoinbase RPC and mirrors the dispatch
|
|
// model of AsyncRPCOperation_sweep (self-gathers on the async worker thread,
|
|
// commits via CWallet::CommitAutomatedTx). It never mints a transparent output,
|
|
// so it respects the ac_private=1 transparent-output ban, and it deliberately
|
|
// does NOT toggle mining (unlike z_shieldcoinbase) so it can run every interval
|
|
// on a mining node without thrashing the miner.
|
|
class AsyncRPCOperation_autoshieldcoinbase : public AsyncRPCOperation
|
|
{
|
|
public:
|
|
AsyncRPCOperation_autoshieldcoinbase(int targetHeight);
|
|
virtual ~AsyncRPCOperation_autoshieldcoinbase();
|
|
|
|
// We don't want to be copied or moved around
|
|
AsyncRPCOperation_autoshieldcoinbase(AsyncRPCOperation_autoshieldcoinbase const&) = delete;
|
|
AsyncRPCOperation_autoshieldcoinbase(AsyncRPCOperation_autoshieldcoinbase&&) = delete;
|
|
AsyncRPCOperation_autoshieldcoinbase& operator=(AsyncRPCOperation_autoshieldcoinbase const&) = delete;
|
|
AsyncRPCOperation_autoshieldcoinbase& operator=(AsyncRPCOperation_autoshieldcoinbase&&) = delete;
|
|
|
|
virtual void main();
|
|
virtual void cancel();
|
|
virtual UniValue getStatus() const;
|
|
|
|
private:
|
|
int targetHeight_;
|
|
int numTxCreated_ = 0;
|
|
CAmount amountShielded_ = 0;
|
|
std::vector<std::string> shieldTxIds_;
|
|
|
|
bool main_impl();
|
|
|
|
// Resolve a spendable, wallet-owned Sapling destination: the configured
|
|
// -autoshieldaddress if set, else the first spendable z-addr the wallet
|
|
// holds, else a freshly generated one (requires an unlocked wallet).
|
|
// Returns false if none is available (e.g. locked wallet with no z-addr).
|
|
bool resolveDestination(libzcash::SaplingPaymentAddress& destOut, std::string& destStrOut);
|
|
|
|
void setResult();
|
|
};
|
|
|
|
#endif /* ASYNCRPCOPERATION_AUTOSHIELDCOINBASE_H */
|