wallet: Initialise ThreadNotifyWallets with wallet's best block

The previous code assumed that the last chain tip notified to the wallet
was equal to the node's chain tip at startup. However, this assumption
fails if the node shuts down uncleanly, or if a wallet file is moved
from one node to another.

We now try to start notifying from the wallet's best block, and if the
node doesn't have that block we fall back to the node's chain tip like
before.

Closes zcash/zcash#5805.
This commit is contained in:
Jack Grigg 2022-04-02 00:38:26 +00:00
parent 8e15446c17
commit 3a1261efda
4 changed files with 73 additions and 3 deletions

View File

@ -1707,15 +1707,33 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
// 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(), which corresponds with the wallet's
// view of the chaintip, is passed to ThreadNotifyWallets before the chain
// tip changes again.
// 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->GetBestBlock();
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)

View File

@ -93,6 +93,31 @@ 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();

View File

@ -1501,6 +1501,27 @@ void CWallet::SetBestChain(const CBlockLocator& loc)
SetBestChainINTERNAL(walletdb, loc);
}
CBlockIndex* CWallet::GetBestBlock()
{
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;
}
}
}
// 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;
}
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> CWallet::GetSproutNullifiers(
const std::set<libzcash::SproutPaymentAddress>& addresses) {
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> nullifierSet;

View File

@ -1805,6 +1805,12 @@ public:
void AddPendingSaplingMigrationTx(const CTransaction& tx);
/** Saves witness caches and best block locator to disk. */
void SetBestChain(const CBlockLocator& loc);
/**
* Returns the block index corresponding to the wallet's best block, or
* nullptr if the wallet's best block is not known to this node (e.g. if the
* wallet was transplanted from another node).
*/
CBlockIndex* GetBestBlock();
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> GetSproutNullifiers(
const std::set<libzcash::SproutPaymentAddress>& addresses);