Merge pull request #2625 from gavinandresen/walletlock_asio

Use boost::asio::deadline_timer for walletpassphrase timeout
This commit is contained in:
Jeff Garzik 2013-05-30 08:01:22 -07:00
commit 9c95a2e836
4 changed files with 43 additions and 63 deletions

View File

@ -11,17 +11,17 @@
#include "bitcoinrpc.h" #include "bitcoinrpc.h"
#include "db.h" #include "db.h"
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/ip/v6_only.hpp> #include <boost/asio/ip/v6_only.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/iostreams/concepts.hpp> #include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp> #include <boost/iostreams/stream.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <list> #include <list>
@ -38,6 +38,7 @@ static std::string strRPCUserColonPass;
// These are created by StartRPCThreads, destroyed in StopRPCThreads // These are created by StartRPCThreads, destroyed in StopRPCThreads
static asio::io_service* rpc_io_service = NULL; static asio::io_service* rpc_io_service = NULL;
static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers;
static ssl::context* rpc_ssl_context = NULL; static ssl::context* rpc_ssl_context = NULL;
static boost::thread_group* rpc_worker_group = NULL; static boost::thread_group* rpc_worker_group = NULL;
@ -852,6 +853,7 @@ void StopRPCThreads()
if (rpc_io_service == NULL) return; if (rpc_io_service == NULL) return;
deadlineTimers.clear();
rpc_io_service->stop(); rpc_io_service->stop();
rpc_worker_group->join_all(); rpc_worker_group->join_all();
delete rpc_worker_group; rpc_worker_group = NULL; delete rpc_worker_group; rpc_worker_group = NULL;
@ -859,6 +861,26 @@ void StopRPCThreads()
delete rpc_io_service; rpc_io_service = NULL; delete rpc_io_service; rpc_io_service = NULL;
} }
void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func)
{
if (!err)
func();
}
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64 nSeconds)
{
assert(rpc_io_service != NULL);
if (deadlineTimers.count(name) == 0)
{
deadlineTimers.insert(make_pair(name,
boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service))));
}
deadlineTimers[name]->expires_from_now(posix_time::seconds(nSeconds));
deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func));
}
class JSONRequest class JSONRequest
{ {
public: public:

View File

@ -89,6 +89,12 @@ void RPCTypeCheck(const json_spirit::Array& params,
void RPCTypeCheck(const json_spirit::Object& o, void RPCTypeCheck(const json_spirit::Object& o,
const std::map<std::string, json_spirit::Value_type>& typesExpected, bool fAllowNull=false); const std::map<std::string, json_spirit::Value_type>& typesExpected, bool fAllowNull=false);
/*
Run func nSeconds from now. Uses boost deadline timers.
Overrides previous timer <name> (if any).
*/
void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64 nSeconds);
typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp); typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp);
class CRPCCommand class CRPCCommand

View File

@ -84,7 +84,7 @@ Value getinfo(const Array& params, bool fHelp)
obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize()));
obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee)));
if (pwalletMain->IsCrypted()) if (pwalletMain->IsCrypted())
obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000)); obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime));
obj.push_back(Pair("errors", GetWarnings("statusbar"))); obj.push_back(Pair("errors", GetWarnings("statusbar")));
return obj; return obj;
} }
@ -1270,56 +1270,11 @@ Value keypoolrefill(const Array& params, bool fHelp)
} }
void ThreadTopUpKeyPool(void* parg) static void LockWallet(CWallet* pWallet)
{ {
// Make this thread recognisable as the key-topping-up thread LOCK(cs_nWalletUnlockTime);
RenameThread("bitcoin-key-top"); nWalletUnlockTime = 0;
pWallet->Lock();
pwalletMain->TopUpKeyPool();
}
void ThreadCleanWalletPassphrase(void* parg)
{
// Make this thread recognisable as the wallet relocking thread
RenameThread("bitcoin-lock-wa");
int64 nMyWakeTime = GetTimeMillis() + *((int64*)parg) * 1000;
ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime);
if (nWalletUnlockTime == 0)
{
nWalletUnlockTime = nMyWakeTime;
do
{
if (nWalletUnlockTime==0)
break;
int64 nToSleep = nWalletUnlockTime - GetTimeMillis();
if (nToSleep <= 0)
break;
LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime);
MilliSleep(nToSleep);
ENTER_CRITICAL_SECTION(cs_nWalletUnlockTime);
} while(1);
if (nWalletUnlockTime)
{
nWalletUnlockTime = 0;
pwalletMain->Lock();
}
}
else
{
if (nWalletUnlockTime < nMyWakeTime)
nWalletUnlockTime = nMyWakeTime;
}
LEAVE_CRITICAL_SECTION(cs_nWalletUnlockTime);
delete (int64*)parg;
} }
Value walletpassphrase(const Array& params, bool fHelp) Value walletpassphrase(const Array& params, bool fHelp)
@ -1333,9 +1288,6 @@ Value walletpassphrase(const Array& params, bool fHelp)
if (!pwalletMain->IsCrypted()) if (!pwalletMain->IsCrypted())
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
if (!pwalletMain->IsLocked())
throw JSONRPCError(RPC_WALLET_ALREADY_UNLOCKED, "Error: Wallet is already unlocked.");
// Note that the walletpassphrase is stored in params[0] which is not mlock()ed // Note that the walletpassphrase is stored in params[0] which is not mlock()ed
SecureString strWalletPass; SecureString strWalletPass;
strWalletPass.reserve(100); strWalletPass.reserve(100);
@ -1353,9 +1305,12 @@ Value walletpassphrase(const Array& params, bool fHelp)
"walletpassphrase <passphrase> <timeout>\n" "walletpassphrase <passphrase> <timeout>\n"
"Stores the wallet decryption key in memory for <timeout> seconds."); "Stores the wallet decryption key in memory for <timeout> seconds.");
NewThread(ThreadTopUpKeyPool, NULL); pwalletMain->TopUpKeyPool();
int64* pnSleepTime = new int64(params[1].get_int64());
NewThread(ThreadCleanWalletPassphrase, pnSleepTime); int64 nSleepTime = params[1].get_int64();
LOCK(cs_nWalletUnlockTime);
nWalletUnlockTime = GetTime() + nSleepTime;
RPCRunLater("lockwallet", boost::bind(LockWallet, pwalletMain), nSleepTime);
return Value::null; return Value::null;
} }

View File

@ -89,9 +89,6 @@ bool CWallet::AddCScript(const CScript& redeemScript)
bool CWallet::Unlock(const SecureString& strWalletPassphrase) bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{ {
if (!IsLocked())
return false;
CCrypter crypter; CCrypter crypter;
CKeyingMaterial vMasterKey; CKeyingMaterial vMasterKey;
@ -102,7 +99,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase)
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false; return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false; continue; // try another master key
if (CCryptoKeyStore::Unlock(vMasterKey)) if (CCryptoKeyStore::Unlock(vMasterKey))
return true; return true;
} }