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:
commit
c342e7d712
145
src/init.cpp
145
src/init.cpp
|
@ -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);
|
||||
// Spawn a thread that will wait for the chain state needed for
|
||||
// ThreadNotifyWallets to become available.
|
||||
threadGroup.create_thread(
|
||||
boost::bind(&TraceThread<boost::function<void()>>, "txnotify", threadnotifywallets)
|
||||
boost::bind(&TraceThread<void (*)()>, "txnotify", &ThreadStartWalletNotifier)
|
||||
);
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: data directory maintenance
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue