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:
@@ -40,6 +40,7 @@
|
||||
#include "coins.h"
|
||||
#include "wallet/asyncrpcoperation_saplingconsolidation.h"
|
||||
#include "wallet/asyncrpcoperation_sweep.h"
|
||||
#include "wallet/asyncrpcoperation_autoshieldcoinbase.h"
|
||||
#include <random>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
@@ -555,6 +556,9 @@ void CWallet::ChainTip(const CBlockIndex *pindex,
|
||||
if (fSweepEnabled) {
|
||||
RunSaplingSweep(pindex->GetHeight());
|
||||
}
|
||||
if (fAutoShieldEnabled) {
|
||||
RunAutoShieldCoinbase(pindex->GetHeight());
|
||||
}
|
||||
if (fTxDeleteEnabled) {
|
||||
DeleteWalletTransactions(pindex);
|
||||
}
|
||||
@@ -604,6 +608,12 @@ void CWallet::RunSaplingSweep(int blockHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Don't Run While auto-shield is running.
|
||||
if (fAutoShieldRunning) {
|
||||
LogPrintf("%s: not sweeping since autoshield is currently running at height=%d\n", __func__, blockHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
fSweepRunning = true;
|
||||
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
@@ -641,6 +651,11 @@ void CWallet::RunSaplingConsolidation(int blockHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fAutoShieldRunning) {
|
||||
LogPrintf("%s: not consolidating since autoshield is currently running at height=%d\n", __func__, blockHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
LogPrintf("%s: creating consolidation operation at blockHeight=%d\n", __func__, blockHeight);
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingConsolidationOperationId);
|
||||
@@ -653,6 +668,63 @@ void CWallet::RunSaplingConsolidation(int blockHeight) {
|
||||
q->addOperation(operation);
|
||||
}
|
||||
|
||||
// Periodically drain matured transparent coinbase into a wallet-owned Sapling
|
||||
// z-address. Default-ON but conditional: this is enqueue-only (all gathering
|
||||
// happens on the async worker thread inside the op, which is why we must not
|
||||
// take cs_main here — ChainTip runs from the wallet-notify context). It is a
|
||||
// silent no-op wherever it cannot act (locked wallet, no owned coinbase,
|
||||
// external -mineraddress), so it is safe to run on every node.
|
||||
void CWallet::RunAutoShieldCoinbase(int blockHeight) {
|
||||
// Sapling is always active from height 1 on DragonX+HACs.
|
||||
if (blockHeight == 0)
|
||||
return;
|
||||
|
||||
LOCK(cs_wallet);
|
||||
|
||||
if (!fAutoShieldEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextAutoShield > blockHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Self-guard: an op is already in flight (nextAutoShield only advances when
|
||||
// it completes). Don't cancel + re-enqueue a fresh op every block.
|
||||
if (fAutoShieldRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mutual exclusion: sweep/consolidation share the single async worker and
|
||||
// cs_wallet; don't queue an autoshield in the same connected block.
|
||||
// NOTE: fConsolidationRunning is currently never set true (a pre-existing
|
||||
// consolidation-scheduler bug); this half of the guard only becomes
|
||||
// effective once that is fixed on the consolidation side.
|
||||
if (fSweepRunning || fConsolidationRunning) {
|
||||
LogPrintf("%s: not autoshielding since sweep/consolidation is running at height=%d\n", __func__, blockHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
// Silent no-op while locked: we can neither sign the shield nor derive a
|
||||
// destination z-addr. Advance the interval so we don't retry every block.
|
||||
if (IsLocked()) {
|
||||
LogPrintf("%s: wallet locked; matured coinbase will accumulate until unlocked (height=%d)\n", __func__, blockHeight);
|
||||
nextAutoShield = autoShieldInterval + blockHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
fAutoShieldRunning = true;
|
||||
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> lastOperation = q->getOperationForId(saplingAutoShieldOperationId);
|
||||
if (lastOperation != nullptr) {
|
||||
lastOperation->cancel();
|
||||
}
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_autoshieldcoinbase(blockHeight + 5));
|
||||
saplingAutoShieldOperationId = operation->getId();
|
||||
q->addOperation(operation);
|
||||
}
|
||||
|
||||
bool CWallet::CommitAutomatedTx(const CTransaction& tx) {
|
||||
CWalletTx wtx(this, tx);
|
||||
CReserveKey reservekey(pwalletMain);
|
||||
|
||||
Reference in New Issue
Block a user