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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 (...) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user