diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -681,6 +681,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; /* @@ -1132,6 +1144,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,34 @@ } } +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, its easier to just call it cs_main-protected. + LOCK(cs_main); + const CBlockIndex *initialChainTip = chainActive.Tip(); + + if (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). + + std::promise promise; + CallFunctionInValidationInterfaceQueue([&promise] { promise.set_value(); }); + promise.get_future().wait(); +} + isminetype CWallet::IsMine(const CTxIn &txin) const { LOCK(cs_wallet); std::map::const_iterator mi = @@ -4087,8 +4118,6 @@ LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); - RegisterValidationInterface(walletInstance); - // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); @@ -4101,6 +4130,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