Merge pull request #5847 from nuttycom/bug/wallet_bestchain_init

Fix assertion in wallet initialization when wallet best block is ahead of the main chain.
This commit is contained in:
Kris Nuttycombe 2022-04-08 07:48:19 -06:00 committed by GitHub
commit c342e7d712
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 75 deletions

View File

@ -80,6 +80,9 @@ static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_DISABLE_SAFEMODE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
// The time that the wallet will wait for the block index to load
// during startup before timing out.
static const int64_t WALLET_INITIAL_SYNC_TIMEOUT = 1000 * 60 * 5;
#if ENABLE_ZMQ
static CZMQNotificationInterface* pzmqNotificationInterface = NULL;
@ -599,6 +602,112 @@ void CleanupBlockRevFiles()
}
}
void ThreadStartWalletNotifier()
{
CBlockIndex *pindexLastTip;
// If the wallet is compiled in and enabled, we want to start notifying
// from the block which corresponds with the wallet's view of the chain
// tip. In particular, we want to handle the case where the node shuts
// down uncleanly, and on restart the chain's tip is potentially up to
// an hour of chain sync older than the wallet's tip. We assume here
// that there is only a single wallet connected to the validation
// interface, which is currently true.
#ifdef ENABLE_WALLET
if (pwalletMain)
{
std::optional<uint256> walletBestBlockHash;
{
LOCK(pwalletMain->cs_wallet);
walletBestBlockHash = pwalletMain->GetPersistedBestBlock();
}
if (walletBestBlockHash.has_value()) {
int64_t slept;
auto timedOut = [&]() -> bool {
MilliSleep(50);
slept += 50;
if (slept > WALLET_INITIAL_SYNC_TIMEOUT) {
auto errmsg = strprintf(
"The wallet's best block hash %s was not detected in restored chain state. "
"Giving up; please restart with `-rescan`.",
walletBestBlockHash.value().GetHex());
LogPrintf("*** %s: %s", __func__, errmsg);
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal wallet synchronization error occurred, see debug.log for details"),
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return true;
}
return false;
};
// Wait until we've found the block that the wallet identifies as its
// best block.
while (true) {
boost::this_thread::interruption_point();
{
LOCK(cs_main);
BlockMap::iterator mi = mapBlockIndex.find(walletBestBlockHash.value());
if (mi != mapBlockIndex.end()) {
pindexLastTip = (*mi).second;
assert(pindexLastTip != nullptr);
break;
}
}
if (timedOut()) {
return;
}
}
// We cannot progress with wallet notification until the chain tip is no
// more than 100 blocks behind pindexLastTip. This can occur if the node
// shuts down abruptly without being able to write out chainActive; the
// node writes chain data out roughly hourly, while the wallet writes it
// every 10 minutes. We need to wait for ThreadImport to catch up, or any
// missing blocks to be fetched from peers.
while (true) {
boost::this_thread::interruption_point();
{
LOCK(cs_main);
const CBlockIndex *pindexFork = chainActive.FindFork(pindexLastTip);
// We know we have the genesis block.
assert(pindexFork != nullptr);
if (pindexLastTip->nHeight < pindexFork->nHeight ||
pindexLastTip->nHeight - pindexFork->nHeight < 100)
{
break;
}
}
if (timedOut()) {
return;
}
}
}
} else {
LOCK(cs_main);
pindexLastTip = chainActive.Tip();
}
#else
{
LOCK(cs_main);
pindexLastTip = chainActive.Tip();
}
#endif
// Become the thread that notifies listeners of transactions that have been
// recently added to the mempool, or have been added to or removed from the
// chain.
ThreadNotifyWallets(pindexLastTip);
}
void ThreadImport(std::vector<fs::path> vImportFiles, const CChainParams& chainparams)
{
RenameThread("zcash-loadblk");
@ -1706,41 +1815,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
}
#endif // ENABLE_MINING
// Start the thread that notifies listeners of transactions that have been
// recently added to the mempool, or have been added to or removed from the
// chain. We perform this before step 10 (import blocks) so that the
// original value of chainActive.Tip() can be passed to ThreadNotifyWallets
// before the chain tip changes again.
{
CBlockIndex *pindexLastTip;
{
LOCK(cs_main);
pindexLastTip = chainActive.Tip();
}
// However, if a wallet is enabled, we actually want to start notifying
// from the block which corresponds with the wallet's view of the chain
// tip. In particular, we want to handle the case where the node shuts
// down uncleanly, and on restart the chain's tip is potentially up to
// an hour of chain sync older than the wallet's tip. We assume here
// that there is only a single wallet connected to the validation
// interface, which is currently true.
#ifdef ENABLE_WALLET
if (pwalletMain)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
const auto walletBestBlock = pwalletMain->GetPersistedBestBlock();
if (walletBestBlock != nullptr) {
pindexLastTip = walletBestBlock;
}
}
#endif
boost::function<void()> threadnotifywallets = boost::bind(&ThreadNotifyWallets, pindexLastTip);
threadGroup.create_thread(
boost::bind(&TraceThread<boost::function<void()>>, "txnotify", threadnotifywallets)
);
}
// Spawn a thread that will wait for the chain state needed for
// ThreadNotifyWallets to become available.
threadGroup.create_thread(
boost::bind(&TraceThread<void (*)()>, "txnotify", &ThreadStartWalletNotifier)
);
// ********************************************************* Step 9: data directory maintenance

View File

@ -93,31 +93,6 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
MilliSleep(50);
}
// We cannot progress with wallet notification until the chain tip is no
// more than 100 blocks behind pindexLastTip. This can occur if the node
// shuts down abruptly without being able to write out chainActive; the
// node writes chain data out roughly hourly, while the wallet writes it
// every 10 minutes. We need to wait for ThreadImport to catch up.
while (true) {
boost::this_thread::interruption_point();
{
LOCK(cs_main);
const CBlockIndex *pindexFork = chainActive.FindFork(pindexLastTip);
// We know we have the genesis block.
assert(pindexFork != nullptr);
if (pindexLastTip->nHeight < pindexFork->nHeight ||
pindexLastTip->nHeight - pindexFork->nHeight < 100)
{
break;
}
}
MilliSleep(50);
}
while (true) {
// Run the notifier on an integer second in the steady clock.
auto now = std::chrono::steady_clock::now().time_since_epoch();
@ -214,11 +189,14 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, pindexLastTip, chainParams.GetConsensus())) {
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
LogPrintf(
"*** %s: Failed to read block %s while notifying wallets of block disconnects",
__func__, pindexLastTip->GetBlockHash().GetHex());
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for details"),
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
// Let wallets know transactions went from 1-confirmed to
@ -245,11 +223,14 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, blockData.pindex, chainParams.GetConsensus())) {
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block connects");
LogPrintf(
"*** %s: Failed to read block %s while notifying wallets of block connects",
__func__, blockData.pindex->GetBlockHash().GetHex());
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for details"),
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return;
}
// Tell wallet about transactions that went from mempool

View File

@ -1500,25 +1500,20 @@ void CWallet::SetBestChain(const CBlockLocator& loc)
SetBestChainINTERNAL(walletdb, loc);
}
CBlockIndex* CWallet::GetPersistedBestBlock()
std::optional<uint256> CWallet::GetPersistedBestBlock()
{
AssertLockHeld(cs_main);
AssertLockHeld(cs_wallet);
CWalletDB walletdb(strWalletFile);
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator)) {
if (!locator.vHave.empty()) {
BlockMap::iterator mi = mapBlockIndex.find(locator.vHave[0]);
if (mi != mapBlockIndex.end()) {
return (*mi).second;
}
return locator.vHave[0];
}
}
// The wallet's best block is not known to the node. This can occur when a
// wallet file is transplanted between disparate nodes.
return nullptr;
// The wallet has never persisted a best block to disk.
return std::nullopt;
}
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> CWallet::GetSproutNullifiers(

View File

@ -1810,15 +1810,16 @@ public:
/** Saves witness caches and best block locator to disk. */
void SetBestChain(const CBlockLocator& loc);
/**
* Returns the block index corresponding to the wallet's most recently
* Returns the block hash corresponding to the wallet's most recently
* persisted best block. This is the state to which the wallet will revert
* if restarted immediately, and does not necessarily match the current
* in-memory state.
*
* Returns nullptr if the wallet's best block is not known to this node
* (e.g. if the wallet was transplanted from another node).
* Returns std::nullopt if the wallet has never written a best block,
* i.e. this is a brand new wallet, or the node was shut down before
* SetBestChain was ever called to persist wallet state.
*/
CBlockIndex* GetPersistedBestBlock();
std::optional<uint256> GetPersistedBestBlock();
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> GetSproutNullifiers(
const std::set<libzcash::SproutPaymentAddress>& addresses);