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
This commit is contained in:
parent
9d053d170b
commit
92f2c1fe0f
4 changed files with 43 additions and 63 deletions
|
@ -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>
|
||||||
|
|
||||||
|
@ -34,6 +34,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;
|
||||||
|
|
||||||
|
@ -843,6 +844,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;
|
||||||
|
@ -850,6 +852,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:
|
||||||
|
|
|
@ -88,6 +88,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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
{
|
{
|
||||||
|
LOCK(cs_nWalletUnlockTime);
|
||||||
nWalletUnlockTime = 0;
|
nWalletUnlockTime = 0;
|
||||||
pwalletMain->Lock();
|
pWallet->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)
|
||||||
|
@ -1319,9 +1274,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);
|
||||||
|
@ -1339,9 +1291,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,9 +87,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;
|
||||||
|
|
||||||
|
@ -100,7 +97,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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue