Fix deadlocks in setaccount, sendfrom RPC calls

SendMoney*() now requires caller to acquire cs_main.
GetAccountAddress() now requires caller to acquire cs_main, cs_mapWallet.

Ordering is intended to match these two callchains[1]:

1. CRITICAL_BLOCK(cs_main)
    ProcessMessage(pfrom, strCommand, vMsg)
        AddToWalletIfMine()
              AddToWallet(wtx)
                  CRITICAL_BLOCK(cs_mapWallet)

2. CRITICAL_BLOCK(cs_main)
    ProcessMessage(pfrom, strCommand, vMsg)
        AddToWalletIfMine()
              AddToWallet(wtx)
                  CRITICAL_BLOCK(cs_mapWallet)
                      walletdb.WriteName(PubKeyToAddress(vchDefaultKey), "")
                          CRITICAL_BLOCK(cs_mapAddressBook)

Spotted by ArtForz.  Additional deadlock fixes by Gavin.

[1] http://www.bitcoin.org/smf/index.php?topic=4904.msg71897#msg71897
This commit is contained in:
Jeff Garzik 2011-04-04 22:24:35 -04:00 committed by Jeff Garzik
parent 454bc86479
commit f5f1878ba1
4 changed files with 86 additions and 69 deletions

2
db.cpp
View file

@ -668,8 +668,8 @@ bool CWalletDB::LoadWallet()
#endif #endif
//// todo: shouldn't we catch exceptions and try to recover and continue? //// todo: shouldn't we catch exceptions and try to recover and continue?
CRITICAL_BLOCK(cs_mapKeys)
CRITICAL_BLOCK(cs_mapWallet) CRITICAL_BLOCK(cs_mapWallet)
CRITICAL_BLOCK(cs_mapKeys)
{ {
// Get cursor // Get cursor
Dbc* pcursor = GetCursor(); Dbc* pcursor = GetCursor();

View file

@ -4046,35 +4046,35 @@ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
// requires cs_main lock
string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee)
{ {
CRITICAL_BLOCK(cs_main) CReserveKey reservekey;
int64 nFeeRequired;
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired))
{ {
CReserveKey reservekey; string strError;
int64 nFeeRequired; if (nValue + nFeeRequired > GetBalance())
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) strError = strprintf(_("Error: This is an oversized transaction that requires a transaction fee of %s "), FormatMoney(nFeeRequired).c_str());
{ else
string strError; strError = _("Error: Transaction creation failed ");
if (nValue + nFeeRequired > GetBalance()) printf("SendMoney() : %s", strError.c_str());
strError = strprintf(_("Error: This is an oversized transaction that requires a transaction fee of %s "), FormatMoney(nFeeRequired).c_str()); return strError;
else
strError = _("Error: Transaction creation failed ");
printf("SendMoney() : %s", strError.c_str());
return strError;
}
if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
} }
if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
MainFrameRepaint(); MainFrameRepaint();
return ""; return "";
} }
// requires cs_main lock
string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee) string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee)
{ {
// Check amount // Check amount

86
rpc.cpp
View file

@ -315,46 +315,45 @@ Value getnewaddress(const Array& params, bool fHelp)
} }
// requires cs_main, cs_mapWallet locks
string GetAccountAddress(string strAccount, bool bForceNew=false) string GetAccountAddress(string strAccount, bool bForceNew=false)
{ {
string strAddress; string strAddress;
CRITICAL_BLOCK(cs_mapWallet) CWalletDB walletdb;
walletdb.TxnBegin();
CAccount account;
walletdb.ReadAccount(strAccount, account);
// Check if the current key has been used
if (!account.vchPubKey.empty())
{ {
CWalletDB walletdb; CScript scriptPubKey;
walletdb.TxnBegin(); scriptPubKey.SetBitcoinAddress(account.vchPubKey);
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin();
CAccount account; it != mapWallet.end() && !account.vchPubKey.empty();
walletdb.ReadAccount(strAccount, account); ++it)
// Check if the current key has been used
if (!account.vchPubKey.empty())
{ {
CScript scriptPubKey; const CWalletTx& wtx = (*it).second;
scriptPubKey.SetBitcoinAddress(account.vchPubKey); foreach(const CTxOut& txout, wtx.vout)
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); if (txout.scriptPubKey == scriptPubKey)
it != mapWallet.end() && !account.vchPubKey.empty(); account.vchPubKey.clear();
++it)
{
const CWalletTx& wtx = (*it).second;
foreach(const CTxOut& txout, wtx.vout)
if (txout.scriptPubKey == scriptPubKey)
account.vchPubKey.clear();
}
} }
// Generate a new key
if (account.vchPubKey.empty() || bForceNew)
{
account.vchPubKey = GetKeyFromKeyPool();
string strAddress = PubKeyToAddress(account.vchPubKey);
SetAddressBookName(strAddress, strAccount);
walletdb.WriteAccount(strAccount, account);
}
walletdb.TxnCommit();
strAddress = PubKeyToAddress(account.vchPubKey);
} }
// Generate a new key
if (account.vchPubKey.empty() || bForceNew)
{
account.vchPubKey = GetKeyFromKeyPool();
string strAddress = PubKeyToAddress(account.vchPubKey);
SetAddressBookName(strAddress, strAccount);
walletdb.WriteAccount(strAccount, account);
}
walletdb.TxnCommit();
strAddress = PubKeyToAddress(account.vchPubKey);
return strAddress; return strAddress;
} }
@ -368,7 +367,15 @@ Value getaccountaddress(const Array& params, bool fHelp)
// Parse the account first so we don't generate a key if there's an error // Parse the account first so we don't generate a key if there's an error
string strAccount = AccountFromValue(params[0]); string strAccount = AccountFromValue(params[0]);
return GetAccountAddress(strAccount); Value ret;
CRITICAL_BLOCK(cs_main)
CRITICAL_BLOCK(cs_mapWallet)
{
ret = GetAccountAddress(strAccount);
}
return ret;
} }
@ -392,6 +399,8 @@ Value setaccount(const Array& params, bool fHelp)
strAccount = AccountFromValue(params[1]); strAccount = AccountFromValue(params[1]);
// Detect when changing the account of an address that is the 'unused current key' of another account: // Detect when changing the account of an address that is the 'unused current key' of another account:
CRITICAL_BLOCK(cs_main)
CRITICAL_BLOCK(cs_mapWallet)
CRITICAL_BLOCK(cs_mapAddressBook) CRITICAL_BLOCK(cs_mapAddressBook)
{ {
if (mapAddressBook.count(strAddress)) if (mapAddressBook.count(strAddress))
@ -475,9 +484,13 @@ Value sendtoaddress(const Array& params, bool fHelp)
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
wtx.mapValue["to"] = params[3].get_str(); wtx.mapValue["to"] = params[3].get_str();
string strError = SendMoneyToBitcoinAddress(strAddress, nAmount, wtx); CRITICAL_BLOCK(cs_main)
if (strError != "") {
throw JSONRPCError(-4, strError); string strError = SendMoneyToBitcoinAddress(strAddress, nAmount, wtx);
if (strError != "")
throw JSONRPCError(-4, strError);
}
return wtx.GetHash().GetHex(); return wtx.GetHash().GetHex();
} }
@ -752,6 +765,7 @@ Value sendfrom(const Array& params, bool fHelp)
if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty()) if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty())
wtx.mapValue["to"] = params[5].get_str(); wtx.mapValue["to"] = params[5].get_str();
CRITICAL_BLOCK(cs_main)
CRITICAL_BLOCK(cs_mapWallet) CRITICAL_BLOCK(cs_mapWallet)
{ {
// Check funds // Check funds

29
ui.cpp
View file

@ -1934,20 +1934,23 @@ void CSendDialog::OnButtonSend(wxCommandEvent& event)
if (fBitcoinAddress) if (fBitcoinAddress)
{ {
// Send to bitcoin address CRITICAL_BLOCK(cs_main)
CScript scriptPubKey; {
scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; // Send to bitcoin address
CScript scriptPubKey;
scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG;
string strError = SendMoney(scriptPubKey, nValue, wtx, true); string strError = SendMoney(scriptPubKey, nValue, wtx, true);
if (strError == "") if (strError == "")
wxMessageBox(_("Payment sent "), _("Sending...")); wxMessageBox(_("Payment sent "), _("Sending..."));
else if (strError == "ABORTED") else if (strError == "ABORTED")
return; // leave send dialog open return; // leave send dialog open
else else
{ {
wxMessageBox(strError + " ", _("Sending...")); wxMessageBox(strError + " ", _("Sending..."));
EndModal(false); EndModal(false);
} }
}
} }
else else
{ {