diff --git a/src/validationinterface.h b/src/validationinterface.h --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -7,6 +7,7 @@ #define BITCOIN_VALIDATIONINTERFACE_H #include "primitives/transaction.h" // CTransaction(Ref) +#include "sync.h" #include #include @@ -41,6 +42,17 @@ */ void CallFunctionInValidationInterfaceQueue(std::function func); +/** + * This is a synonym for the following, which asserts certain locks are not + * held: + * std::promise promise; + * CallFunctionInValidationInterfaceQueue([&promise] { + * promise.set_value(); + * }); + * promise.get_future().wait(); + */ +void SyncWithValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main); + class CValidationInterface { protected: /** diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -4,12 +4,15 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "validationinterface.h" + #include "init.h" #include "scheduler.h" #include "sync.h" #include "util.h" +#include "validation.h" #include +#include #include #include @@ -122,6 +125,14 @@ g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func)); } +void SyncWithValidationInterfaceQueue() { + AssertLockNotHeld(cs_main); + // Block until the validation queue drains + std::promise promise; + CallFunctionInValidationInterfaceQueue([&promise] { promise.set_value(); }); + promise.get_future().wait(); +} + void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -675,6 +675,18 @@ std::unique_ptr dbw; + /** + * The following is used to keep track of how far behind the wallet is + * from the chain sync, and to allow clients to block on us being caught up. + * + * Note that this is *not* how far we've processed, we may need some rescan + * to have seen all transactions in the chain, but is only used to track + * live BlockConnected callbacks. + * + * Protected by cs_main (see BlockUntilSyncedToCurrentChain) + */ + const CBlockIndex *m_last_block_processed; + public: const CChainParams &chainParams; /* @@ -1127,6 +1139,14 @@ */ bool SetHDMasterKey(const CPubKey &key, CHDChain *possibleOldChain = nullptr); + + /** + * Blocks until the wallet state is up-to-date to /at least/ the current + * chain at the time this function is entered + * Obviously holding cs_main/cs_wallet when going into this call may cause + * deadlock + */ + void BlockUntilSyncedToCurrentChain(); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -36,6 +36,7 @@ #include #include +#include std::vector vpwallets; @@ -1287,6 +1288,8 @@ for (size_t i = 0; i < pblock->vtx.size(); i++) { SyncTransaction(pblock->vtx[i], pindex, i); } + + m_last_block_processed = pindex; } void CWallet::BlockDisconnected(const std::shared_ptr &pblock) { @@ -1297,6 +1300,32 @@ } } +void CWallet::BlockUntilSyncedToCurrentChain() { + AssertLockNotHeld(cs_main); + AssertLockNotHeld(cs_wallet); + + { + // Skip the queue-draining stuff if we know we're caught up with + // chainActive.Tip()... + // We could also take cs_wallet here, and call m_last_block_processed + // protected by cs_wallet instead of cs_main, but as long as we need + // cs_main here anyway, it's easier to just call it cs_main-protected. + LOCK(cs_main); + const CBlockIndex *initialChainTip = chainActive.Tip(); + + if (m_last_block_processed && + m_last_block_processed->GetAncestor(initialChainTip->nHeight) == + initialChainTip) { + return; + } + } + + // ...otherwise put a callback in the validation interface queue and wait + // for the queue to drain enough to execute it (indicating we are caught up + // at least with the time we entered this function). + SyncWithValidationInterfaceQueue(); +} + isminetype CWallet::IsMine(const CTxIn &txin) const { LOCK(cs_wallet); std::map::const_iterator mi = @@ -4092,11 +4121,12 @@ LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); - RegisterValidationInterface(walletInstance); - // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); + /* chainActive requires cs_main */ + LOCK(cs_main); + CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) { CWalletDB walletdb(*walletInstance->dbw); @@ -4106,6 +4136,9 @@ } } + walletInstance->m_last_block_processed = chainActive.Tip(); + RegisterValidationInterface(walletInstance); + if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { // We can't rescan beyond non-pruned blocks, stop and throw an error. // This might happen if a user uses a old wallet within a pruned node or