-
Notifications
You must be signed in to change notification settings - Fork 35.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wallet: Add missing cs_wallet/cs_KeyStore locks to wallet #11634
Conversation
865b5d5
to
b229a8e
Compare
src/wallet/wallet.cpp
Outdated
@@ -3953,6 +3960,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) | |||
for (const CWalletTx& wtxOld : vWtx) | |||
{ | |||
uint256 hash = wtxOld.GetHash(); | |||
LOCK(walletInstance->cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either:
- lock once before the loop;
- loop first with the lock to
mapWallet.find(hash)
for allvWtx
and 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? :-) |
src/wallet/wallet.cpp
Outdated
@@ -3950,6 +3957,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) | |||
{ | |||
CWalletDB walletdb(*walletInstance->dbw); | |||
|
|||
LOCK(walletInstance->cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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? :-) |
src/wallet/wallet.cpp
Outdated
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { | ||
pindexRescan = chainActive.Next(pindexRescan); | ||
{ | ||
LOCK(walletInstance->cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A solution to that is to also lock cs_main.. but meh
src/wallet/wallet.h
Outdated
nWalletVersion = FEATURE_BASE; | ||
nWalletMaxVersion = FEATURE_BASE; | ||
{ | ||
LOCK(cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
src/wallet/wallet.cpp
Outdated
@@ -3930,6 +3938,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& | |||
int64_t nStart = GetTimeMillis(); | |||
bool fFirstRun = true; | |||
CWallet *walletInstance = new CWallet(name, CWalletDBWrapper::Create(path)); | |||
LOCK(walletInstance->cs_wallet); | |||
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Now fixed. See #11634 (comment).
f3cb135
to
e7eaf64
Compare
Please re-review :-) |
e7eaf64
to
0f8d1eb
Compare
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 69e7ee2.
@@ -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(); | |||
|
|||
LOCK(cs_main); | |||
LOCK2(cs_main, walletInstance->cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No annotation requires this lock right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the new annotations..
It could make sense to avoid calling uiInterface.LoadWallet()
with the lock held.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_wallet
IsSpent(...)
requires holding the mutexcs_wallet
mapKeys
andmapCryptedKeys
requires holding the mutexcs_KeyStore
nTimeFirstKey
requires holding the mutexcs_wallet
mapWallet
requires holding the mutexcs_wallet
nTimeFirstKey
requires holding the mutexcs_wallet