Fix staking crash with large wallets + add -zapwallettxes

- ThreadStakeMiner: catch-and-retry instead of crash on exception
  (boost::bad_weak_ptr no longer kills the daemon)
- GetStakeWeight: take wallet lock once instead of per-coin to
  reduce lock contention with 20K+ transaction wallets
- StakeMiner: continue instead of exit when CreateNewBlock fails
- Wrap all NotifyTransactionChanged/NotifyAddressBookChanged signal
  emissions in try/catch to absorb stale slot exceptions
- Add -zapwallettxes flag: strips all tx records from wallet.dat
  keeping only keys, then rescans blockchain to rebuild history

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 23:12:21 -07:00
parent 2c4c8370df
commit ca8baab501
7 changed files with 163 additions and 30 deletions
+13
View File
@@ -424,6 +424,7 @@ std::string HelpMessage()
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
" -postibdrescan " + _("Run the wallet rescan after initial sync in a background thread (default: 1)") + "\n" +
" -zapwallettxes " + _("Delete all wallet transactions and only recover from blockchain on startup") + "\n" +
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" +
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
@@ -578,6 +579,11 @@ bool AppInit2()
SoftSetBoolArg("-rescan", true);
}
if (GetBoolArg("-zapwallettxes")) {
// Zap all tx from wallet: rescan to rebuild from blockchain
SoftSetBoolArg("-rescan", true);
}
// ********************************************************* Step 3: parameter-to-internal-flags
fDebug = GetBoolArg("-debug");
@@ -721,6 +727,13 @@ bool AppInit2()
return false;
}
if (GetBoolArg("-zapwallettxes") && fs::exists(GetDataDir() / strWalletFileName))
{
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
if (!CWalletDB::ZapWalletTx(strWalletFileName))
return InitError(_("Error: could not zap wallet transactions"));
}
if (fs::exists(GetDataDir() / strWalletFileName))
{
CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, CWalletDB::Recover);
+4 -1
View File
@@ -434,7 +434,10 @@ void StakeMiner(CWallet *pwallet)
int64_t nFees;
unique_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
if (!pblock.get())
return;
{
MilliSleep(5000);
continue;
}
// Try to sign the block
if (pblock->SignBlock(*pwallet, nFees))
+28 -11
View File
@@ -1635,18 +1635,35 @@ void static ThreadStakeMiner(void* parg)
{
printf("ThreadStakeMiner started\n");
CWallet* pwallet = (CWallet*)parg;
try
int nConsecutiveErrors = 0;
while (!fShutdown)
{
vnThreadsRunning[THREAD_STAKE_MINER]++;
StakeMiner(pwallet);
vnThreadsRunning[THREAD_STAKE_MINER]--;
}
catch (std::exception& e) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
PrintException(&e, "ThreadStakeMiner()");
} catch (...) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
PrintException(NULL, "ThreadStakeMiner()");
try
{
vnThreadsRunning[THREAD_STAKE_MINER]++;
StakeMiner(pwallet);
vnThreadsRunning[THREAD_STAKE_MINER]--;
break; // normal exit
}
catch (std::exception& e) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
nConsecutiveErrors++;
printf("ThreadStakeMiner() exception: %s (attempt %d)\n", e.what(), nConsecutiveErrors);
if (nConsecutiveErrors >= 10) {
printf("ThreadStakeMiner() too many consecutive errors, giving up\n");
break;
}
MilliSleep(5000); // wait 5 seconds before retrying
} catch (...) {
vnThreadsRunning[THREAD_STAKE_MINER]--;
nConsecutiveErrors++;
printf("ThreadStakeMiner() unknown exception (attempt %d)\n", nConsecutiveErrors);
if (nConsecutiveErrors >= 10) {
printf("ThreadStakeMiner() too many consecutive errors, giving up\n");
break;
}
MilliSleep(5000);
}
}
printf("ThreadStakeMiner exiting, %d threads remaining\n", vnThreadsRunning[THREAD_STAKE_MINER]);
}
+2 -1
View File
@@ -1911,7 +1911,8 @@ Value clearwallettransactions(const Array& params, bool fHelp)
};
pwalletMain->mapWallet.erase(hash);
pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED);
try { pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); }
catch (...) { }
nTransactions++;
};
+54 -17
View File
@@ -487,7 +487,10 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
wtx.MarkSpent(txin.prevout.n);
wtx.WriteToDisk();
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED);
{
try { NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); }
catch (...) { }
}
}
}
}
@@ -505,7 +508,10 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
wtx.MarkUnspent(&txout - &tx.vout[0]);
wtx.WriteToDisk();
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, hash, CT_UPDATED);
{
try { NotifyTransactionChanged(this, hash, CT_UPDATED); }
catch (...) { }
}
}
}
}
@@ -638,7 +644,13 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
// Notify UI of new or updated transaction (skip during IBD to avoid flooding the event loop)
if (!IsInitialBlockDownload())
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
{
try {
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
} catch (...) {
// Absorb boost::bad_weak_ptr or other signal exceptions from stale slots
}
}
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
@@ -1855,18 +1867,37 @@ bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, ui
if (setCoins.empty())
return false;
// Collect coin hashes under lock, then read DB outside the per-coin lock
// to avoid acquiring/releasing LOCK2 thousands of times for large wallets
struct StakeCoin {
uint256 hash;
int64_t nValue;
unsigned int nTime;
};
vector<StakeCoin> vStakeCoins;
{
LOCK(cs_wallet);
vStakeCoins.reserve(setCoins.size());
for (auto& pcoin : setCoins)
{
StakeCoin sc;
sc.hash = pcoin.first->GetHash();
sc.nValue = pcoin.first->vout[pcoin.second].nValue;
sc.nTime = pcoin.first->nTime;
vStakeCoins.push_back(sc);
}
}
CTxDB txdb("r");
for (auto pcoin : setCoins)
int64_t nNow = GetTime();
for (auto& sc : vStakeCoins)
{
CTxIndex txindex;
{
LOCK2(cs_main, cs_wallet);
if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
continue;
}
if (!txdb.ReadTxIndex(sc.hash, txindex))
continue;
int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)GetTime());
CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60);
int64_t nTimeWeight = GetWeight((int64_t)sc.nTime, nNow);
CBigNum bnCoinDayWeight = CBigNum(sc.nValue) * nTimeWeight / COIN / (24 * 60 * 60);
// Weight is greater than zero
if (nTimeWeight > 0)
@@ -2139,7 +2170,8 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
coin.BindWallet(this);
coin.MarkSpent(txin.prevout.n);
coin.WriteToDisk();
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
try { NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); }
catch (...) { }
}
if (fFileBacked)
@@ -2268,8 +2300,9 @@ bool CWallet::SetAddressBookName(const CTxDestination& address, const string& st
const CTrianglesAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), strName, nMode);
}
NotifyAddressBookChanged(this, address, strName, fOwned, nMode);
try { NotifyAddressBookChanged(this, address, strName, fOwned, nMode); }
catch (...) { }
if (!fFileBacked)
return false;
return CWalletDB(strWalletFile).WriteName(CTrianglesAddress(address).ToString(), strName);
@@ -2282,7 +2315,7 @@ bool CWallet::DelAddressBookName(const CTxDestination& address)
mapAddressBook.erase(address);
}
bool fOwned = ::IsMine(*this, address);
string sName = "";
if (fOwned)
@@ -2290,7 +2323,8 @@ bool CWallet::DelAddressBookName(const CTxDestination& address)
const CTrianglesAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), sName, CT_DELETED);
}
NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED);
try { NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED); }
catch (...) { }
if (!fFileBacked)
return false;
@@ -2768,7 +2802,10 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx)
// Only notify UI if this transaction is in this wallet
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end() && !IsInitialBlockDownload())
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
{
try { NotifyTransactionChanged(this, hashTx, CT_UPDATED); }
catch (...) { }
}
}
}
+61
View File
@@ -718,3 +718,64 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename)
{
return CWalletDB::Recover(dbenv, filename, false);
}
bool CWalletDB::ZapWalletTx(const std::string& strWalletFile)
{
// Open the wallet database directly and delete all "tx" entries,
// keeping keys and other metadata intact. This strips transaction
// history while preserving private keys. A rescan will rebuild
// the transaction list from the blockchain.
printf("ZapWalletTx: erasing transaction records from %s\n", strWalletFile.c_str());
CWalletDB walletdb(strWalletFile, "r+");
if (!walletdb.pdb)
{
printf("ZapWalletTx: failed to open wallet database\n");
return false;
}
Dbc* pcursor = walletdb.GetCursor();
if (!pcursor)
{
printf("ZapWalletTx: failed to get cursor\n");
return false;
}
// First pass: collect all tx hashes to erase
std::vector<uint256> vTxHash;
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
while (true)
{
int ret = walletdb.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
if (ret == DB_NOTFOUND)
break;
if (ret != 0)
{
printf("ZapWalletTx: cursor read error %d\n", ret);
pcursor->close();
return false;
}
std::string strType;
ssKey >> strType;
if (strType == "tx")
{
uint256 hash;
ssKey >> hash;
vTxHash.push_back(hash);
}
}
pcursor->close();
// Second pass: erase all collected tx entries
int nErased = 0;
for (const uint256& hash : vTxHash)
{
if (walletdb.EraseTx(hash))
nErased++;
}
printf("ZapWalletTx: erased %d of %d transaction records\n", nErased, (int)vTxHash.size());
return true;
}
+1
View File
@@ -223,6 +223,7 @@ public:
DBErrors LoadWallet(CWallet* pwallet);
static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, std::string filename);
static bool ZapWalletTx(const std::string& strWalletFile);
};
#endif // TRIANGLES_WALLETDB_H