Merge #18487: rpc: Fix rpcRunLater race in walletpassphrase

7b8e15728d1ad058a4b7d7569fd5d5ba6806ca28 rpc: Fix rpcRunLater race in walletpassphrase (João Barbosa)

Pull request description:

  Release locks before calling `rpcRunLater`.

  Quick explanation: `rpcRunLater` leads to `event_free` which calls `event_del` which can wait for the event callback to finish if it's already running and that callback will try to lock wallet mutex - which is already locked in http thread.

  Fixes #14995 , fixes #18482. Best reviewed with whitespace changes hidden.

ACKs for top commit:
  MarcoFalke:
    ACK 7b8e15728d, only tested that this avoids the node freezing. Did not look at how libevent works or how the deadlock happens or if this breaks other stuff. 📞
  ryanofsky:
    Code review ACK 7b8e15728d1ad058a4b7d7569fd5d5ba6806ca28. Just updated comment since last review

Tree-SHA512: 17874a2fa7b0e164fb0d7ee4cb7d59650275b8c03476fb291d60af8b758495457660d3912623fb26259fefe84aeba21c0a9e0c6467982ba511f19344ed5413ab
This commit is contained in:
Wladimir J. van der Laan 2020-04-06 20:29:16 +02:00
commit 75021e80ee
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D

View File

@ -1912,6 +1912,8 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
}, },
}.Check(request); }.Check(request);
int64_t nSleepTime;
{
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
@ -1927,7 +1929,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
strWalletPass = request.params[0].get_str().c_str(); strWalletPass = request.params[0].get_str().c_str();
// Get the timeout // Get the timeout
int64_t nSleepTime = request.params[1].get_int64(); nSleepTime = request.params[1].get_int64();
// Timeout cannot be negative, otherwise it will relock immediately // Timeout cannot be negative, otherwise it will relock immediately
if (nSleepTime < 0) { if (nSleepTime < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
@ -1949,7 +1951,13 @@ static UniValue walletpassphrase(const JSONRPCRequest& request)
pwallet->TopUpKeyPool(); pwallet->TopUpKeyPool();
pwallet->nRelockTime = GetTime() + nSleepTime; pwallet->nRelockTime = GetTime() + nSleepTime;
}
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
// can occur. The deadlock would happen when RPCRunLater removes the
// previous timer (and waits for the callback to finish if already running)
// and the callback locks cs_wallet.
AssertLockNotHeld(wallet->cs_wallet);
// Keep a weak pointer to the wallet so that it is possible to unload the // Keep a weak pointer to the wallet so that it is possible to unload the
// wallet before the following callback is called. If a valid shared pointer // wallet before the following callback is called. If a valid shared pointer
// is acquired in the callback then the wallet is still loaded. // is acquired in the callback then the wallet is still loaded.