From 92f2c1fe0fe2905540b0435188988851145f92be Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Tue, 7 May 2013 10:47:00 -0400 Subject: [PATCH] Use boost::asio::deadline_timer for walletpassphrase timeout New method in bitcoinrpc: RunLater, that uses a map of deadline timers to run a function later. Behavior of walletpassphrase is changed; before, calling walletpassphrase again before the lock timeout passed would result in: Error: Wallet is already unlocked. You would have to call lockwallet before walletpassphrase. Now: the last walletpassphrase with correct password wins, and overrides any previous timeout. Fixes issue# 1961 which was caused by spawning too many threads. Test plan: Start with encrypted wallet, password 'foo' NOTE: python -c 'import time; print("%d"%time.time())' ... will tell you current unix timestamp. Try: walletpassphrase foo 600 getinfo EXPECT: unlocked_until is about 10 minutes in the future walletpassphrase foo 1 sleep 2 sendtoaddress mun74Bvba3B1PF2YkrF4NsgcJwHXXh12LF 11 EXPECT: Error: Please enter the wallet passphrase with walletpassphrase first. walletpassphrase foo 600 walletpassphrase foo 0 getinfo EXPECT: wallet is locked (unlocked_until is 0) walletpassphrase foo 10 walletpassphrase foo 600 getinfo EXPECT: wallet is unlocked until 10 minutes in future walletpassphrase foo 60 walletpassphrase bar 600 EXPECT: Error, incorrect passphrase getinfo EXPECT: wallet still scheduled to lock 60 seconds from first (successful) walletpassphrase --- src/bitcoinrpc.cpp | 28 ++++++++++++++++--- src/bitcoinrpc.h | 6 +++++ src/rpcwallet.cpp | 67 ++++++++-------------------------------------- src/wallet.cpp | 5 +--- 4 files changed, 43 insertions(+), 63 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index a9b73fd5a..a1d76e181 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -11,17 +11,17 @@ #include "bitcoinrpc.h" #include "db.h" +#include #include #include +#include #include #include +#include #include #include #include -#include #include -#include -#include #include #include @@ -34,6 +34,7 @@ static std::string strRPCUserColonPass; // These are created by StartRPCThreads, destroyed in StopRPCThreads static asio::io_service* rpc_io_service = NULL; +static map > deadlineTimers; static ssl::context* rpc_ssl_context = NULL; static boost::thread_group* rpc_worker_group = NULL; @@ -843,6 +844,7 @@ void StopRPCThreads() { if (rpc_io_service == NULL) return; + deadlineTimers.clear(); rpc_io_service->stop(); rpc_worker_group->join_all(); delete rpc_worker_group; rpc_worker_group = NULL; @@ -850,6 +852,26 @@ void StopRPCThreads() delete rpc_io_service; rpc_io_service = NULL; } +void RPCRunHandler(const boost::system::error_code& err, boost::function func) +{ + if (!err) + func(); +} + +void RPCRunLater(const std::string& name, boost::function func, int64 nSeconds) +{ + assert(rpc_io_service != NULL); + + if (deadlineTimers.count(name) == 0) + { + deadlineTimers.insert(make_pair(name, + boost::shared_ptr(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 { public: diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 315fd9238..270c2a009 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -88,6 +88,12 @@ void RPCTypeCheck(const json_spirit::Array& params, void RPCTypeCheck(const json_spirit::Object& o, const std::map& typesExpected, bool fAllowNull=false); +/* + Run func nSeconds from now. Uses boost deadline timers. + Overrides previous timer (if any). + */ +void RPCRunLater(const std::string& name, boost::function func, int64 nSeconds); + typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp); class CRPCCommand diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 5fd400c6b..f304f7026 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -84,7 +84,7 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); 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"))); return obj; } @@ -1256,56 +1256,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 - RenameThread("bitcoin-key-top"); - - 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; + LOCK(cs_nWalletUnlockTime); + nWalletUnlockTime = 0; + pWallet->Lock(); } Value walletpassphrase(const Array& params, bool fHelp) @@ -1319,9 +1274,6 @@ Value walletpassphrase(const Array& params, bool fHelp) if (!pwalletMain->IsCrypted()) 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 SecureString strWalletPass; strWalletPass.reserve(100); @@ -1339,9 +1291,12 @@ Value walletpassphrase(const Array& params, bool fHelp) "walletpassphrase \n" "Stores the wallet decryption key in memory for seconds."); - NewThread(ThreadTopUpKeyPool, NULL); - int64* pnSleepTime = new int64(params[1].get_int64()); - NewThread(ThreadCleanWalletPassphrase, pnSleepTime); + pwalletMain->TopUpKeyPool(); + + int64 nSleepTime = params[1].get_int64(); + LOCK(cs_nWalletUnlockTime); + nWalletUnlockTime = GetTime() + nSleepTime; + RPCRunLater("lockwallet", boost::bind(LockWallet, pwalletMain), nSleepTime); return Value::null; } diff --git a/src/wallet.cpp b/src/wallet.cpp index c70ea20e8..eebd8ac18 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -87,9 +87,6 @@ bool CWallet::AddCScript(const CScript& redeemScript) bool CWallet::Unlock(const SecureString& strWalletPassphrase) { - if (!IsLocked()) - return false; - CCrypter crypter; CKeyingMaterial vMasterKey; @@ -100,7 +97,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) - return false; + continue; // try another master key if (CCryptoKeyStore::Unlock(vMasterKey)) return true; }