mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-02 23:53:02 +02:00
Merge bitcoin/bitcoin#32862: rpc: use CScheduler for relocking wallet and remove RPCTimer
fcfd3db563
remove RPCTimerInterface and RPCRunLater (Matthew Zipkin)8a1765795f
use WalletContext scheduler for walletpassphrase callback (Matthew Zipkin) Pull request description: This removes the dependency on libevent for events scheduled by RPC commands, like re-locking a wallet some time after decryption with walletpassphrase. Since walletpassphrase is currently the only RPC that does this, `RPCRunLater`, `RPCTimerInterface` and all related methods are left unused, and deleted in the second commit. Any future RPC that needs to execute a callback in the future can follow the pattern in this PR and just use a scheduler from node or wallet context. This is an alternative approach to #32796, described in https://github.com/bitcoin/bitcoin/pull/32796#issuecomment-3014309449 ACKs for top commit: fjahr: Code Review ACKfcfd3db563
achow101: ACKfcfd3db563
furszy: ACKfcfd3db563
Tree-SHA512: 04f5e9c3f73f598c3d41d6e35bb59c64c7b93b03ad9fce3c40901733147ce7764f41f475fef1527d44af18f722759996a31ca83b48cb52153795d5022fecfd14
This commit is contained in:
@@ -32,45 +32,6 @@ using util::TrimStringView;
|
|||||||
/** WWW-Authenticate to present with 401 Unauthorized response */
|
/** WWW-Authenticate to present with 401 Unauthorized response */
|
||||||
static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
|
static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
|
||||||
|
|
||||||
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
|
|
||||||
* re-lock the wallet.
|
|
||||||
*/
|
|
||||||
class HTTPRPCTimer : public RPCTimerBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HTTPRPCTimer(struct event_base* eventBase, std::function<void()>& func, int64_t millis) :
|
|
||||||
ev(eventBase, false, func)
|
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
tv.tv_sec = millis/1000;
|
|
||||||
tv.tv_usec = (millis%1000)*1000;
|
|
||||||
ev.trigger(&tv);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
HTTPEvent ev;
|
|
||||||
};
|
|
||||||
|
|
||||||
class HTTPRPCTimerInterface : public RPCTimerInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
const char* Name() override
|
|
||||||
{
|
|
||||||
return "HTTP";
|
|
||||||
}
|
|
||||||
RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
|
|
||||||
{
|
|
||||||
return new HTTPRPCTimer(base, func, millis);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
struct event_base* base;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* Stored RPC timer interface (for unregistration) */
|
|
||||||
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
|
|
||||||
/* List of -rpcauth values */
|
/* List of -rpcauth values */
|
||||||
static std::vector<std::vector<std::string>> g_rpcauth;
|
static std::vector<std::vector<std::string>> g_rpcauth;
|
||||||
/* RPC Auth Whitelist */
|
/* RPC Auth Whitelist */
|
||||||
@@ -380,8 +341,6 @@ bool StartHTTPRPC(const std::any& context)
|
|||||||
}
|
}
|
||||||
struct event_base* eventBase = EventBase();
|
struct event_base* eventBase = EventBase();
|
||||||
assert(eventBase);
|
assert(eventBase);
|
||||||
httpRPCTimerInterface = std::make_unique<HTTPRPCTimerInterface>(eventBase);
|
|
||||||
RPCSetTimerInterface(httpRPCTimerInterface.get());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,8 +356,4 @@ void StopHTTPRPC()
|
|||||||
if (g_wallet_init_interface.HasWalletSupport()) {
|
if (g_wallet_init_interface.HasWalletSupport()) {
|
||||||
UnregisterHTTPHandler("/wallet/", false);
|
UnregisterHTTPHandler("/wallet/", false);
|
||||||
}
|
}
|
||||||
if (httpRPCTimerInterface) {
|
|
||||||
RPCUnsetTimerInterface(httpRPCTimerInterface.get());
|
|
||||||
httpRPCTimerInterface.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -352,9 +352,6 @@ public:
|
|||||||
//! Check if deprecated RPC is enabled.
|
//! Check if deprecated RPC is enabled.
|
||||||
virtual bool rpcEnableDeprecated(const std::string& method) = 0;
|
virtual bool rpcEnableDeprecated(const std::string& method) = 0;
|
||||||
|
|
||||||
//! Run function after given number of seconds. Cancel any previous calls with same name.
|
|
||||||
virtual void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) = 0;
|
|
||||||
|
|
||||||
//! Get settings value.
|
//! Get settings value.
|
||||||
virtual common::SettingsValue getSetting(const std::string& arg) = 0;
|
virtual common::SettingsValue getSetting(const std::string& arg) = 0;
|
||||||
|
|
||||||
|
@@ -27,7 +27,6 @@ class BanMan;
|
|||||||
class CFeeRate;
|
class CFeeRate;
|
||||||
class CNodeStats;
|
class CNodeStats;
|
||||||
class Coin;
|
class Coin;
|
||||||
class RPCTimerInterface;
|
|
||||||
class UniValue;
|
class UniValue;
|
||||||
class Proxy;
|
class Proxy;
|
||||||
enum class SynchronizationState;
|
enum class SynchronizationState;
|
||||||
@@ -205,12 +204,6 @@ public:
|
|||||||
//! List rpc commands.
|
//! List rpc commands.
|
||||||
virtual std::vector<std::string> listRpcCommands() = 0;
|
virtual std::vector<std::string> listRpcCommands() = 0;
|
||||||
|
|
||||||
//! Set RPC timer interface if unset.
|
|
||||||
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;
|
|
||||||
|
|
||||||
//! Unset RPC timer interface.
|
|
||||||
virtual void rpcUnsetTimerInterface(RPCTimerInterface* iface) = 0;
|
|
||||||
|
|
||||||
//! Get unspent output associated with a transaction.
|
//! Get unspent output associated with a transaction.
|
||||||
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;
|
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;
|
||||||
|
|
||||||
|
@@ -356,8 +356,6 @@ public:
|
|||||||
return ::tableRPC.execute(req);
|
return ::tableRPC.execute(req);
|
||||||
}
|
}
|
||||||
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
|
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
|
||||||
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
|
|
||||||
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
|
|
||||||
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
|
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
LOCK(::cs_main);
|
LOCK(::cs_main);
|
||||||
@@ -804,10 +802,6 @@ public:
|
|||||||
return std::make_unique<RpcHandlerImpl>(command);
|
return std::make_unique<RpcHandlerImpl>(command);
|
||||||
}
|
}
|
||||||
bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); }
|
bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); }
|
||||||
void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) override
|
|
||||||
{
|
|
||||||
RPCRunLater(name, std::move(fn), seconds);
|
|
||||||
}
|
|
||||||
common::SettingsValue getSetting(const std::string& name) override
|
common::SettingsValue getSetting(const std::string& name) override
|
||||||
{
|
{
|
||||||
return args().GetSetting(name);
|
return args().GetSetting(name);
|
||||||
|
@@ -98,37 +98,6 @@ private:
|
|||||||
interfaces::Node& m_node;
|
interfaces::Node& m_node;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Class for handling RPC timers
|
|
||||||
* (used for e.g. re-locking the wallet after a timeout)
|
|
||||||
*/
|
|
||||||
class QtRPCTimerBase: public QObject, public RPCTimerBase
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
QtRPCTimerBase(std::function<void()>& _func, int64_t millis):
|
|
||||||
func(_func)
|
|
||||||
{
|
|
||||||
timer.setSingleShot(true);
|
|
||||||
connect(&timer, &QTimer::timeout, [this]{ func(); });
|
|
||||||
timer.start(millis);
|
|
||||||
}
|
|
||||||
~QtRPCTimerBase() = default;
|
|
||||||
private:
|
|
||||||
QTimer timer;
|
|
||||||
std::function<void()> func;
|
|
||||||
};
|
|
||||||
|
|
||||||
class QtRPCTimerInterface: public RPCTimerInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
~QtRPCTimerInterface() = default;
|
|
||||||
const char *Name() override { return "Qt"; }
|
|
||||||
RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
|
|
||||||
{
|
|
||||||
return new QtRPCTimerBase(func, millis);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PeerIdViewDelegate : public QStyledItemDelegate
|
class PeerIdViewDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -567,12 +536,6 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
|
|||||||
ui->WalletSelector->setVisible(false);
|
ui->WalletSelector->setVisible(false);
|
||||||
ui->WalletSelectorLabel->setVisible(false);
|
ui->WalletSelectorLabel->setVisible(false);
|
||||||
|
|
||||||
// Register RPC timer interface
|
|
||||||
rpcTimerInterface = new QtRPCTimerInterface();
|
|
||||||
// avoid accidentally overwriting an existing, non QTThread
|
|
||||||
// based timer interface
|
|
||||||
m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface);
|
|
||||||
|
|
||||||
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
|
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
|
||||||
updateDetailWidget();
|
updateDetailWidget();
|
||||||
|
|
||||||
@@ -602,8 +565,6 @@ RPCConsole::~RPCConsole()
|
|||||||
settings.setValue("PeersTabPeerHeaderState", m_peer_widget_header_state);
|
settings.setValue("PeersTabPeerHeaderState", m_peer_widget_header_state);
|
||||||
settings.setValue("PeersTabBanlistHeaderState", m_banlist_widget_header_state);
|
settings.setValue("PeersTabBanlistHeaderState", m_banlist_widget_header_state);
|
||||||
|
|
||||||
m_node.rpcUnsetTimerInterface(rpcTimerInterface);
|
|
||||||
delete rpcTimerInterface;
|
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
class PlatformStyle;
|
class PlatformStyle;
|
||||||
class RPCExecutor;
|
class RPCExecutor;
|
||||||
class RPCTimerInterface;
|
|
||||||
class WalletModel;
|
class WalletModel;
|
||||||
|
|
||||||
namespace interfaces {
|
namespace interfaces {
|
||||||
@@ -166,7 +165,6 @@ private:
|
|||||||
QString cmdBeforeBrowsing;
|
QString cmdBeforeBrowsing;
|
||||||
QList<NodeId> cachedNodeids;
|
QList<NodeId> cachedNodeids;
|
||||||
const PlatformStyle* const platformStyle;
|
const PlatformStyle* const platformStyle;
|
||||||
RPCTimerInterface *rpcTimerInterface = nullptr;
|
|
||||||
QMenu *peersTableContextMenu = nullptr;
|
QMenu *peersTableContextMenu = nullptr;
|
||||||
QMenu *banTableContextMenu = nullptr;
|
QMenu *banTableContextMenu = nullptr;
|
||||||
int consoleFontSize = 0;
|
int consoleFontSize = 0;
|
||||||
|
@@ -34,11 +34,6 @@ static GlobalMutex g_rpc_warmup_mutex;
|
|||||||
static std::atomic<bool> g_rpc_running{false};
|
static std::atomic<bool> g_rpc_running{false};
|
||||||
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
|
||||||
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
|
||||||
/* Timer-creating functions */
|
|
||||||
static RPCTimerInterface* timerInterface = nullptr;
|
|
||||||
/* Map of name to timer. */
|
|
||||||
static GlobalMutex g_deadline_timers_mutex;
|
|
||||||
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
|
|
||||||
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
|
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
|
||||||
|
|
||||||
struct RPCCommandExecutionInfo
|
struct RPCCommandExecutionInfo
|
||||||
@@ -301,7 +296,6 @@ void StopRPC()
|
|||||||
assert(!g_rpc_running);
|
assert(!g_rpc_running);
|
||||||
std::call_once(g_rpc_stop_flag, [&]() {
|
std::call_once(g_rpc_stop_flag, [&]() {
|
||||||
LogDebug(BCLog::RPC, "Stopping RPC\n");
|
LogDebug(BCLog::RPC, "Stopping RPC\n");
|
||||||
WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
|
|
||||||
DeleteAuthCookie();
|
DeleteAuthCookie();
|
||||||
LogDebug(BCLog::RPC, "RPC stopped.\n");
|
LogDebug(BCLog::RPC, "RPC stopped.\n");
|
||||||
});
|
});
|
||||||
@@ -543,31 +537,4 @@ UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
|
|
||||||
{
|
|
||||||
if (!timerInterface)
|
|
||||||
timerInterface = iface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPCSetTimerInterface(RPCTimerInterface *iface)
|
|
||||||
{
|
|
||||||
timerInterface = iface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPCUnsetTimerInterface(RPCTimerInterface *iface)
|
|
||||||
{
|
|
||||||
if (timerInterface == iface)
|
|
||||||
timerInterface = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds)
|
|
||||||
{
|
|
||||||
if (!timerInterface)
|
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
|
|
||||||
LOCK(g_deadline_timers_mutex);
|
|
||||||
deadlineTimers.erase(name);
|
|
||||||
LogDebug(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
|
|
||||||
deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000)));
|
|
||||||
}
|
|
||||||
|
|
||||||
CRPCTable tableRPC;
|
CRPCTable tableRPC;
|
||||||
|
@@ -35,47 +35,6 @@ void SetRPCWarmupFinished();
|
|||||||
/* returns the current warmup state. */
|
/* returns the current warmup state. */
|
||||||
bool RPCIsInWarmup(std::string *outStatus);
|
bool RPCIsInWarmup(std::string *outStatus);
|
||||||
|
|
||||||
/** Opaque base class for timers returned by NewTimerFunc.
|
|
||||||
* This provides no methods at the moment, but makes sure that delete
|
|
||||||
* cleans up the whole state.
|
|
||||||
*/
|
|
||||||
class RPCTimerBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~RPCTimerBase() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RPC timer "driver".
|
|
||||||
*/
|
|
||||||
class RPCTimerInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~RPCTimerInterface() = default;
|
|
||||||
/** Implementation name */
|
|
||||||
virtual const char *Name() = 0;
|
|
||||||
/** Factory function for timers.
|
|
||||||
* RPC will call the function to create a timer that will call func in *millis* milliseconds.
|
|
||||||
* @note As the RPC mechanism is backend-neutral, it can use different implementations of timers.
|
|
||||||
* This is needed to cope with the case in which there is no HTTP server, but
|
|
||||||
* only GUI RPC console, and to break the dependency of pcserver on httprpc.
|
|
||||||
*/
|
|
||||||
virtual RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Set the factory function for timers */
|
|
||||||
void RPCSetTimerInterface(RPCTimerInterface *iface);
|
|
||||||
/** Set the factory function for timer, but only, if unset */
|
|
||||||
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface);
|
|
||||||
/** Unset factory function for timers */
|
|
||||||
void RPCUnsetTimerInterface(RPCTimerInterface *iface);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run func nSeconds from now.
|
|
||||||
* Overrides previous timer <name> (if any).
|
|
||||||
*/
|
|
||||||
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds);
|
|
||||||
|
|
||||||
typedef RPCHelpMan (*RpcMethodFnType)();
|
typedef RPCHelpMan (*RpcMethodFnType)();
|
||||||
|
|
||||||
class CRPCCommand
|
class CRPCCommand
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
|
#include <scheduler.h>
|
||||||
|
#include <wallet/context.h>
|
||||||
#include <wallet/rpc/util.h>
|
#include <wallet/rpc/util.h>
|
||||||
#include <wallet/wallet.h>
|
#include <wallet/wallet.h>
|
||||||
|
|
||||||
@@ -88,24 +90,24 @@ RPCHelpMan walletpassphrase()
|
|||||||
relock_time = pwallet->nRelockTime;
|
relock_time = pwallet->nRelockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
|
// Get wallet scheduler to queue up the relock callback in the future.
|
||||||
// can occur. The deadlock would happen when RPCRunLater removes the
|
// Scheduled events don't get destructed until they are executed,
|
||||||
// previous timer (and waits for the callback to finish if already running)
|
// and they are executed in series in a single scheduler thread so
|
||||||
// and the callback locks cs_wallet.
|
// no cs_wallet lock is needed.
|
||||||
AssertLockNotHeld(wallet->cs_wallet);
|
WalletContext& context = EnsureWalletContext(request.context);
|
||||||
// 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.
|
||||||
std::weak_ptr<CWallet> weak_wallet = wallet;
|
std::weak_ptr<CWallet> weak_wallet = wallet;
|
||||||
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
|
context.scheduler->scheduleFromNow([weak_wallet, relock_time] {
|
||||||
if (auto shared_wallet = weak_wallet.lock()) {
|
if (auto shared_wallet = weak_wallet.lock()) {
|
||||||
LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
|
LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
|
||||||
// Skip if this is not the most recent rpcRunLater callback.
|
// Skip if this is not the most recent relock callback.
|
||||||
if (shared_wallet->nRelockTime != relock_time) return;
|
if (shared_wallet->nRelockTime != relock_time) return;
|
||||||
shared_wallet->Lock();
|
shared_wallet->Lock();
|
||||||
shared_wallet->nRelockTime = 0;
|
shared_wallet->nRelockTime = 0;
|
||||||
}
|
}
|
||||||
}, nSleepTime);
|
}, std::chrono::seconds(nSleepTime));
|
||||||
|
|
||||||
return UniValue::VNULL;
|
return UniValue::VNULL;
|
||||||
},
|
},
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Test the wallet keypool and interaction with wallet encryption/locking."""
|
"""Test the wallet keypool and interaction with wallet encryption/locking."""
|
||||||
|
|
||||||
import time
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
@@ -127,8 +126,10 @@ class KeyPoolTest(BitcoinTestFramework):
|
|||||||
nodes[0].keypoolrefill(3)
|
nodes[0].keypoolrefill(3)
|
||||||
|
|
||||||
# test walletpassphrase timeout
|
# test walletpassphrase timeout
|
||||||
time.sleep(1.1)
|
# CScheduler relies on condition_variable::wait_until() which does not
|
||||||
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
|
# guarantee accurate timing. We'll wait up to 5 seconds to execute a 1
|
||||||
|
# second scheduled event.
|
||||||
|
nodes[0].wait_until(lambda: nodes[0].getwalletinfo()["unlocked_until"] == 0, timeout=5)
|
||||||
|
|
||||||
# drain the keypool
|
# drain the keypool
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
|
Reference in New Issue
Block a user