wallet: Add missing cs_wallet/cs_KeyStore locks to wallet#11634
Conversation
865b5d5 to
b229a8e
Compare
There was a problem hiding this comment.
Either:
- lock once before the loop;
- loop first with the lock to
mapWallet.find(hash)for allvWtxand then loop again without the lock towalletdb.WriteTx(); - lock only the mapWallet.find(hash).
b229a8e to
e921615
Compare
|
@promag Thanks for reviewing! Feedback addressed. Looks good? :-) |
There was a problem hiding this comment.
This should be before the line above. Anyway, there is no need to lock this wallet since it's a fresh instance, unknown to the remaining system. Unless you are adding the lock because of other asserts and if so please commit them to justify this change?
There was a problem hiding this comment.
Now moved before the line above. Without that lock we'll trigger Clang thread safety analysis warnings when #11634 is merged due to:
src/wallet/wallet.h: std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
As agreed upon in #11226 (comment) all guard/lock annotations are added in #11634 and all extra locking is submitted as separate PR:s (such as this one).
The annotations are compile-time only whereas the locks change run-time behaviour (and thus needs extra scrunity!).
There was a problem hiding this comment.
Yes, you're right - the annotations are added in #11226 :-)
e921615 to
007fcbf
Compare
|
utACK 007fcbf. |
* Reading the variables mapTxSpends and mapWallet (via IsSpent(...) call) require holding the mutex cs_wallet. * Reading the variables mapKeys and mapCryptedKeys require holding the mutex cs_KeyStore. * Reading the variable nTimeFirstKey requires holding the mutex cs_wallet. * Reading the variable mapWallet requires holding the mutex cs_wallet. Github-Pull: bitcoin#11634 Rebased-From: 007fcbff2fe5cd4798721963ca7ce5b5d3674626
007fcbf to
fba0830
Compare
|
Added another commit with two more missing locks:
|
|
@promag Would you mind re-reviewing? :-) |
There was a problem hiding this comment.
Given this is effectively just the wallet's constructor, might as well just take the lock further up and hold it the whole time.
There was a problem hiding this comment.
@TheBlueMatt Moving it further up will make the scope of the walletInstance->cs_wallet lock cover the ScanForWalletTransactions call. ScanForWalletTransactions locks cs_main giving us the lock order:
-
cs_wallet
-
cs_main
This is a potential deadlock given that the opposite lock order is used extensively:
$ git grep "LOCK2(cs_main,.*cs_wallet);" | wc -l
89
Please advice :-)
There was a problem hiding this comment.
A solution to that is to also lock cs_main.. but meh
There was a problem hiding this comment.
Same here. Just add a cs_wallet lock for the whole function.
8d97924 to
fca14f6
Compare
|
@TheBlueMatt Thanks for reviewing! Feedback addressed. Please re-review :-) |
fca14f6 to
8e64007
Compare
|
Fixed build issue. Please re-review :-) |
|
Given that https://github.com/bitcoin/bitcoin/pull/11226/files#diff-12635a58447c65585f51d32b7e04075bR857 is now closed, wouldn't it make sense to add the clang annotations within this commit? |
0af5880 to
491ec75
Compare
|
@MarcoFalke @TheBlueMatt @promag Thanks for reviewing. I've now addressed the feedback and added corresponding |
There was a problem hiding this comment.
Note that LoadWallet takes LOCK2(cs_main, cs_wallet), so taking cs_wallet before cs_main leads to a deadlock in case another thread takes cs_main and then cs_wallet, no?
There was a problem hiding this comment.
Thanks. Now fixed. See #11634 (comment).
f3cb135 to
e7eaf64
Compare
|
Please re-review :-) |
e7eaf64 to
0f8d1eb
Compare
…ax_keypool_index and nOrderPosNext * AddKeyPubKeyWithDB(...) reads encrypted_batch which potentially races with write in the same method. * IncOrderPosNext(...) reads nOrderPosNext which potentially races with write in BlockDisconnected(...). * LoadKeyPool(...) reads m_max_keypool_index which potentially races with write in BlockDisconnected(...). * LoadMinVersion(...) reads nWalletMaxVersion which potentially races with write in BlockDisconnected(...).
0bf4395 to
37b2538
Compare
|
Updated! Added Rationale:
Please review :-) |
|
Added Rationale: |
…cript_metadata and setLockedCoins
3431464 to
69e7ee2
Compare
|
@MarcoFalke @promag @TheBlueMatt Would you mind reviewing the updated version? :-) |
|
utACK 69e7ee2 |
…to wallet 69e7ee2 Add GUARDED_BY(cs_wallet) for setExternalKeyPool, mapKeyMetadata, m_script_metadata and setLockedCoins (practicalswift) 37b2538 Add GUARDED_BY(cs_wallet) for encrypted_batch, nWalletMaxVersion, m_max_keypool_index and nOrderPosNext (practicalswift) dee4292 wallet: Add Clang thread safety analysis annotations (practicalswift) 1c7e25d wallet: Add missing locks (practicalswift) Pull request description: Add missing wallet locks: * Calling the function `GetConflicts(...)` requires holding the mutex `cs_wallet` * Calling the function `IsSpent(...)` requires holding the mutex `cs_wallet` * Accessing the variables `mapKeys` and `mapCryptedKeys` requires holding the mutex `cs_KeyStore` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` * Accessing the variable `mapWallet` requires holding the mutex `cs_wallet` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` Tree-SHA512: 8a7b9a4e1f2147e77c04b817617a06304a2e2159148d3eb3514a3c09c41d77ef7e773df6e63880ad9acc026e00690f72d0c51f3f86279177f672d477423accca
| walletInstance->TopUpKeyPool(); | ||
|
|
||
| LOCK(cs_main); | ||
| LOCK2(cs_main, walletInstance->cs_wallet); |
There was a problem hiding this comment.
No annotation requires this lock right?
There was a problem hiding this comment.
Both cs_main and walletInstance->cs_wallet are required from what I can tell.
Removing that lock results in the following:
wallet/wallet.cpp:4104:28: error: calling function 'FindForkInGlobalIndex' requires holding mutex 'cs_main' exclusively [-Werror,-Wthread-safety-analysis]
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
^
wallet/wallet.cpp:4131:48: error: reading variable 'nTimeFirstKey' requires holding mutex 'walletInstance->cs_wallet' [-Werror,-Wthread-safety-analysis]
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
^
wallet/wallet.cpp:4131:114: error: reading variable 'nTimeFirstKey' requires holding mutex 'walletInstance->cs_wallet' [-Werror,-Wthread-safety-analysis]
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
^
wallet/wallet.cpp:4156:77: error: reading variable 'mapWallet' requires holding mutex 'walletInstance->cs_wallet' [-Werror,-Wthread-safety-analysis]
std::map<uint256, CWalletTx>::iterator mi = walletInstance->mapWallet.find(hash);
^
wallet/wallet.cpp:4157:43: error: reading variable 'mapWallet' requires holding mutex 'walletInstance->cs_wallet' [-Werror,-Wthread-safety-analysis]
if (mi != walletInstance->mapWallet.end())
^
wallet/wallet.cpp:4181:90: error: calling function 'GetKeyPoolSize' requires holding mutex 'walletInstance->cs_wallet' exclusively [-Werror,-Wthread-safety-analysis]
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
^
wallet/wallet.cpp:4182:90: error: reading variable 'mapWallet' requires holding mutex 'walletInstance->cs_wallet' [-Werror,-Wthread-safety-analysis]
walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
^
There was a problem hiding this comment.
Right, the new annotations..
It could make sense to avoid calling uiInterface.LoadWallet() with the lock held.
There was a problem hiding this comment.
@promag Ah, now I follow. Suggested fix below. What do you think?
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 29014790e..2deeb9c42 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -4093,7 +4093,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
- LOCK2(cs_main, walletInstance->cs_wallet);
+ LOCK(cs_main);
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!gArgs.GetBoolArg("-rescan", false))
@@ -4128,6 +4128,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
+ LOCK(walletInstance->cs_wallet);
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
pindexRescan = chainActive.Next(pindexRescan);
}
@@ -4178,6 +4179,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
{
+ LOCK(walletInstance->cs_wallet);
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size());There was a problem hiding this comment.
I think that currently there is no harm in calling uiInterface.LoadWallet() with the lock held but if it's not necessary then I wouldn't change that. The diff looks ok.
zcash: cherry picked from commit 1c7e25d zcash: bitcoin/bitcoin#11634
zcash: cherry picked from commit dee4292 zcash: bitcoin/bitcoin#11634
…ax_keypool_index and nOrderPosNext * AddKeyPubKeyWithDB(...) reads encrypted_batch which potentially races with write in the same method. * IncOrderPosNext(...) reads nOrderPosNext which potentially races with write in BlockDisconnected(...). * LoadKeyPool(...) reads m_max_keypool_index which potentially races with write in BlockDisconnected(...). * LoadMinVersion(...) reads nWalletMaxVersion which potentially races with write in BlockDisconnected(...). zcash: only nWalletMaxVersion, nOrderPosNext zcash: cherry picked from commit 37b2538 zcash: bitcoin/bitcoin#11634
…cript_metadata and setLockedCoins zcash: cherry picked from commit 69e7ee2 zcash: bitcoin/bitcoin#11634
zcash: cherry picked from commit 1c7e25d zcash: bitcoin/bitcoin#11634
zcash: cherry picked from commit dee4292 zcash: bitcoin/bitcoin#11634
…ax_keypool_index and nOrderPosNext * AddKeyPubKeyWithDB(...) reads encrypted_batch which potentially races with write in the same method. * IncOrderPosNext(...) reads nOrderPosNext which potentially races with write in BlockDisconnected(...). * LoadKeyPool(...) reads m_max_keypool_index which potentially races with write in BlockDisconnected(...). * LoadMinVersion(...) reads nWalletMaxVersion which potentially races with write in BlockDisconnected(...). zcash: only nWalletMaxVersion, nOrderPosNext zcash: cherry picked from commit 37b2538 zcash: bitcoin/bitcoin#11634
…cript_metadata and setLockedCoins zcash: cherry picked from commit 69e7ee2 zcash: bitcoin/bitcoin#11634
…to wallet 69e7ee2 Add GUARDED_BY(cs_wallet) for setExternalKeyPool, mapKeyMetadata, m_script_metadata and setLockedCoins (practicalswift) 37b2538 Add GUARDED_BY(cs_wallet) for encrypted_batch, nWalletMaxVersion, m_max_keypool_index and nOrderPosNext (practicalswift) dee4292 wallet: Add Clang thread safety analysis annotations (practicalswift) 1c7e25d wallet: Add missing locks (practicalswift) Pull request description: Add missing wallet locks: * Calling the function `GetConflicts(...)` requires holding the mutex `cs_wallet` * Calling the function `IsSpent(...)` requires holding the mutex `cs_wallet` * Accessing the variables `mapKeys` and `mapCryptedKeys` requires holding the mutex `cs_KeyStore` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` * Accessing the variable `mapWallet` requires holding the mutex `cs_wallet` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` Tree-SHA512: 8a7b9a4e1f2147e77c04b817617a06304a2e2159148d3eb3514a3c09c41d77ef7e773df6e63880ad9acc026e00690f72d0c51f3f86279177f672d477423accca
…to wallet 69e7ee2 Add GUARDED_BY(cs_wallet) for setExternalKeyPool, mapKeyMetadata, m_script_metadata and setLockedCoins (practicalswift) 37b2538 Add GUARDED_BY(cs_wallet) for encrypted_batch, nWalletMaxVersion, m_max_keypool_index and nOrderPosNext (practicalswift) dee4292 wallet: Add Clang thread safety analysis annotations (practicalswift) 1c7e25d wallet: Add missing locks (practicalswift) Pull request description: Add missing wallet locks: * Calling the function `GetConflicts(...)` requires holding the mutex `cs_wallet` * Calling the function `IsSpent(...)` requires holding the mutex `cs_wallet` * Accessing the variables `mapKeys` and `mapCryptedKeys` requires holding the mutex `cs_KeyStore` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` * Accessing the variable `mapWallet` requires holding the mutex `cs_wallet` * Accessing the variable `nTimeFirstKey` requires holding the mutex `cs_wallet` Tree-SHA512: 8a7b9a4e1f2147e77c04b817617a06304a2e2159148d3eb3514a3c09c41d77ef7e773df6e63880ad9acc026e00690f72d0c51f3f86279177f672d477423accca
Add missing wallet locks:
GetConflicts(...)requires holding the mutexcs_walletIsSpent(...)requires holding the mutexcs_walletmapKeysandmapCryptedKeysrequires holding the mutexcs_KeyStorenTimeFirstKeyrequires holding the mutexcs_walletmapWalletrequires holding the mutexcs_walletnTimeFirstKeyrequires holding the mutexcs_wallet