wallet: add default-on auto-shield-coinbase; drain miner coinbase into a z-addr

On this ac_private=1 chain miners accumulate one transparent coinbase UTXO per
block (the only transparent output the chain permits). It had to be shielded
manually via z_shieldcoinbase, and left unshielded it is the sole persistent
metadata leak on the chain and the source of miner UTXO-fragmentation send
failures.

Add AsyncRPCOperation_autoshieldcoinbase: a periodic, default-on wallet op
driven from CWallet::ChainTip alongside sweep/consolidation, draining matured
coinbase into a wallet-owned Sapling z-address in size-bounded batches.

- Enqueue-only driver (RunAutoShieldCoinbase): takes only cs_wallet in the
  notify context; all gathering runs on the async worker under LOCK2(cs_main,
  cs_wallet).
- Dedicated op (not a reuse of z_shieldcoinbase) so it never toggles mining.
- Default-ON but conditional: silent no-op on -disablewallet, external
  -mineraddress, non-mining, or locked wallets (explicit IsLocked() guard).
- Destination is reuse-then-create; -autoshieldaddress override is
  spend-key-validated at init so funds cannot be stranded.
- Sweep-model bookkeeping: advances nextAutoShield and clears the running flag
  on every terminal state; an op-id guard stops a stale op clobbering scheduler
  state; a self-guard stops cancel/re-enqueue churn.
- Sietch-padded output shape matches manual z_shieldcoinbase txns.

Config: -autoshield (default true), -autoshieldinterval, -autoshieldaddress,
-autoshieldfee (range-validated), -autoshieldminutxos.

Incorporates fixes from an 8-angle code review: CAmount fee type with init-time
range validation, op-id-guarded flag bookkeeping, driver self-guard, and a
tipHeight-consistent NU-activation guard.

Note: the fConsolidationRunning / nextConsolidation consolidation-scheduler
wedge is a pre-existing bug, left for a separate change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-21 02:06:09 -05:00
co-authored by Claude Opus 4.8
parent 1c3523aac1
commit 2ebbbc777c
6 changed files with 522 additions and 0 deletions
+20
View File
@@ -809,6 +809,12 @@ public:
bool fSweepExternalEnabled = false;
bool fSweepRunning = false;
// Automatic coinbase shielding (t->z). Default ON but conditional: it is a
// silent no-op on nodes where it cannot act (no wallet, external
// -mineraddress, non-mining, or locked wallet). See RunAutoShieldCoinbase.
bool fAutoShieldEnabled = true;
bool fAutoShieldRunning = false;
std::atomic<bool> fAbortRescan{false};
// abort current rescan
void AbortRescan() { fAbortRescan = true; }
@@ -833,6 +839,19 @@ public:
std::vector<std::string> sweepExcludeAddresses;
std::string consolidationAddress = "";
int nextAutoShield = 0;
int autoShieldInterval = 25;
CAmount autoShieldFee = 10000;
// Minimum matured coinbase UTXOs before a round fires, to avoid per-interval
// fee churn on a single freshly-matured reward.
int autoShieldMinUtxos = 1;
// Configured destination z-addr override; also used to cache the resolved
// wallet-owned destination so we keep reusing one address.
std::string autoShieldAddress = "";
// Id of the in-flight autoshield op; read by the op to confirm it is still
// the current one before mutating scheduler state.
AsyncRPCOperationId saplingAutoShieldOperationId;
void ClearNoteWitnessCache();
int64_t NullifierCount();
@@ -1224,6 +1243,7 @@ public:
const CBlock *pblock,
boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added);
void RunSaplingConsolidation(int blockHeight);
void RunAutoShieldCoinbase(int blockHeight);
bool CommitAutomatedTx(const CTransaction& tx);
/** Saves witness caches and best block locator to disk. */
void SetBestChain(const CBlockLocator& loc);