Skip to content

Commit a8bff38

Browse files
committed
Merge bitcoin/bitcoin#32862: rpc: use CScheduler for relocking wallet and remove RPCTimer
fcfd3db remove RPCTimerInterface and RPCRunLater (Matthew Zipkin) 8a17657 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 bitcoin/bitcoin#32796 (comment) ACKs for top commit: fjahr: Code Review ACK fcfd3db achow101: ACK fcfd3db furszy: ACK fcfd3db Tree-SHA512: 04f5e9c3f73f598c3d41d6e35bb59c64c7b93b03ad9fce3c40901733147ce7764f41f475fef1527d44af18f722759996a31ca83b48cb52153795d5022fecfd14
2 parents 21b42f3 + fcfd3db commit a8bff38

File tree

10 files changed

+14
-187
lines changed

10 files changed

+14
-187
lines changed

src/httprpc.cpp

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -32,45 +32,6 @@ using util::TrimStringView;
3232
/** WWW-Authenticate to present with 401 Unauthorized response */
3333
static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
3434

35-
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
36-
* re-lock the wallet.
37-
*/
38-
class HTTPRPCTimer : public RPCTimerBase
39-
{
40-
public:
41-
HTTPRPCTimer(struct event_base* eventBase, std::function<void()>& func, int64_t millis) :
42-
ev(eventBase, false, func)
43-
{
44-
struct timeval tv;
45-
tv.tv_sec = millis/1000;
46-
tv.tv_usec = (millis%1000)*1000;
47-
ev.trigger(&tv);
48-
}
49-
private:
50-
HTTPEvent ev;
51-
};
52-
53-
class HTTPRPCTimerInterface : public RPCTimerInterface
54-
{
55-
public:
56-
explicit HTTPRPCTimerInterface(struct event_base* _base) : base(_base)
57-
{
58-
}
59-
const char* Name() override
60-
{
61-
return "HTTP";
62-
}
63-
RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
64-
{
65-
return new HTTPRPCTimer(base, func, millis);
66-
}
67-
private:
68-
struct event_base* base;
69-
};
70-
71-
72-
/* Stored RPC timer interface (for unregistration) */
73-
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
7435
/* List of -rpcauth values */
7536
static std::vector<std::vector<std::string>> g_rpcauth;
7637
/* RPC Auth Whitelist */
@@ -380,8 +341,6 @@ bool StartHTTPRPC(const std::any& context)
380341
}
381342
struct event_base* eventBase = EventBase();
382343
assert(eventBase);
383-
httpRPCTimerInterface = std::make_unique<HTTPRPCTimerInterface>(eventBase);
384-
RPCSetTimerInterface(httpRPCTimerInterface.get());
385344
return true;
386345
}
387346

@@ -397,8 +356,4 @@ void StopHTTPRPC()
397356
if (g_wallet_init_interface.HasWalletSupport()) {
398357
UnregisterHTTPHandler("/wallet/", false);
399358
}
400-
if (httpRPCTimerInterface) {
401-
RPCUnsetTimerInterface(httpRPCTimerInterface.get());
402-
httpRPCTimerInterface.reset();
403-
}
404359
}

src/interfaces/chain.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,6 @@ class Chain
352352
//! Check if deprecated RPC is enabled.
353353
virtual bool rpcEnableDeprecated(const std::string& method) = 0;
354354

355-
//! Run function after given number of seconds. Cancel any previous calls with same name.
356-
virtual void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) = 0;
357-
358355
//! Get settings value.
359356
virtual common::SettingsValue getSetting(const std::string& arg) = 0;
360357

src/interfaces/node.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class BanMan;
2727
class CFeeRate;
2828
class CNodeStats;
2929
class Coin;
30-
class RPCTimerInterface;
3130
class UniValue;
3231
class Proxy;
3332
enum class SynchronizationState;
@@ -205,12 +204,6 @@ class Node
205204
//! List rpc commands.
206205
virtual std::vector<std::string> listRpcCommands() = 0;
207206

208-
//! Set RPC timer interface if unset.
209-
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) = 0;
210-
211-
//! Unset RPC timer interface.
212-
virtual void rpcUnsetTimerInterface(RPCTimerInterface* iface) = 0;
213-
214207
//! Get unspent output associated with a transaction.
215208
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;
216209

src/node/interfaces.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,6 @@ class NodeImpl : public Node
356356
return ::tableRPC.execute(req);
357357
}
358358
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
359-
void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); }
360-
void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); }
361359
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
362360
{
363361
LOCK(::cs_main);
@@ -804,10 +802,6 @@ class ChainImpl : public Chain
804802
return std::make_unique<RpcHandlerImpl>(command);
805803
}
806804
bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); }
807-
void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) override
808-
{
809-
RPCRunLater(name, std::move(fn), seconds);
810-
}
811805
common::SettingsValue getSetting(const std::string& name) override
812806
{
813807
return args().GetSetting(name);

src/qt/rpcconsole.cpp

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -98,37 +98,6 @@ public Q_SLOTS:
9898
interfaces::Node& m_node;
9999
};
100100

101-
/** Class for handling RPC timers
102-
* (used for e.g. re-locking the wallet after a timeout)
103-
*/
104-
class QtRPCTimerBase: public QObject, public RPCTimerBase
105-
{
106-
Q_OBJECT
107-
public:
108-
QtRPCTimerBase(std::function<void()>& _func, int64_t millis):
109-
func(_func)
110-
{
111-
timer.setSingleShot(true);
112-
connect(&timer, &QTimer::timeout, [this]{ func(); });
113-
timer.start(millis);
114-
}
115-
~QtRPCTimerBase() = default;
116-
private:
117-
QTimer timer;
118-
std::function<void()> func;
119-
};
120-
121-
class QtRPCTimerInterface: public RPCTimerInterface
122-
{
123-
public:
124-
~QtRPCTimerInterface() = default;
125-
const char *Name() override { return "Qt"; }
126-
RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override
127-
{
128-
return new QtRPCTimerBase(func, millis);
129-
}
130-
};
131-
132101
class PeerIdViewDelegate : public QStyledItemDelegate
133102
{
134103
Q_OBJECT
@@ -567,12 +536,6 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
567536
ui->WalletSelector->setVisible(false);
568537
ui->WalletSelectorLabel->setVisible(false);
569538

570-
// Register RPC timer interface
571-
rpcTimerInterface = new QtRPCTimerInterface();
572-
// avoid accidentally overwriting an existing, non QTThread
573-
// based timer interface
574-
m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface);
575-
576539
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
577540
updateDetailWidget();
578541

@@ -602,8 +565,6 @@ RPCConsole::~RPCConsole()
602565
settings.setValue("PeersTabPeerHeaderState", m_peer_widget_header_state);
603566
settings.setValue("PeersTabBanlistHeaderState", m_banlist_widget_header_state);
604567

605-
m_node.rpcUnsetTimerInterface(rpcTimerInterface);
606-
delete rpcTimerInterface;
607568
delete ui;
608569
}
609570

src/qt/rpcconsole.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
class PlatformStyle;
2222
class RPCExecutor;
23-
class RPCTimerInterface;
2423
class WalletModel;
2524

2625
namespace interfaces {
@@ -166,7 +165,6 @@ public Q_SLOTS:
166165
QString cmdBeforeBrowsing;
167166
QList<NodeId> cachedNodeids;
168167
const PlatformStyle* const platformStyle;
169-
RPCTimerInterface *rpcTimerInterface = nullptr;
170168
QMenu *peersTableContextMenu = nullptr;
171169
QMenu *banTableContextMenu = nullptr;
172170
int consoleFontSize = 0;

src/rpc/server.cpp

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ static GlobalMutex g_rpc_warmup_mutex;
3434
static std::atomic<bool> g_rpc_running{false};
3535
static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true;
3636
static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started";
37-
/* Timer-creating functions */
38-
static RPCTimerInterface* timerInterface = nullptr;
39-
/* Map of name to timer. */
40-
static GlobalMutex g_deadline_timers_mutex;
41-
static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex);
4237
static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler);
4338

4439
struct RPCCommandExecutionInfo
@@ -301,7 +296,6 @@ void StopRPC()
301296
assert(!g_rpc_running);
302297
std::call_once(g_rpc_stop_flag, [&]() {
303298
LogDebug(BCLog::RPC, "Stopping RPC\n");
304-
WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
305299
DeleteAuthCookie();
306300
LogDebug(BCLog::RPC, "RPC stopped.\n");
307301
});
@@ -543,31 +537,4 @@ UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const
543537
return ret;
544538
}
545539

546-
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
547-
{
548-
if (!timerInterface)
549-
timerInterface = iface;
550-
}
551-
552-
void RPCSetTimerInterface(RPCTimerInterface *iface)
553-
{
554-
timerInterface = iface;
555-
}
556-
557-
void RPCUnsetTimerInterface(RPCTimerInterface *iface)
558-
{
559-
if (timerInterface == iface)
560-
timerInterface = nullptr;
561-
}
562-
563-
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds)
564-
{
565-
if (!timerInterface)
566-
throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC");
567-
LOCK(g_deadline_timers_mutex);
568-
deadlineTimers.erase(name);
569-
LogDebug(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name());
570-
deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000)));
571-
}
572-
573540
CRPCTable tableRPC;

src/rpc/server.h

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,47 +35,6 @@ void SetRPCWarmupFinished();
3535
/* returns the current warmup state. */
3636
bool RPCIsInWarmup(std::string *outStatus);
3737

38-
/** Opaque base class for timers returned by NewTimerFunc.
39-
* This provides no methods at the moment, but makes sure that delete
40-
* cleans up the whole state.
41-
*/
42-
class RPCTimerBase
43-
{
44-
public:
45-
virtual ~RPCTimerBase() = default;
46-
};
47-
48-
/**
49-
* RPC timer "driver".
50-
*/
51-
class RPCTimerInterface
52-
{
53-
public:
54-
virtual ~RPCTimerInterface() = default;
55-
/** Implementation name */
56-
virtual const char *Name() = 0;
57-
/** Factory function for timers.
58-
* RPC will call the function to create a timer that will call func in *millis* milliseconds.
59-
* @note As the RPC mechanism is backend-neutral, it can use different implementations of timers.
60-
* This is needed to cope with the case in which there is no HTTP server, but
61-
* only GUI RPC console, and to break the dependency of pcserver on httprpc.
62-
*/
63-
virtual RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) = 0;
64-
};
65-
66-
/** Set the factory function for timers */
67-
void RPCSetTimerInterface(RPCTimerInterface *iface);
68-
/** Set the factory function for timer, but only, if unset */
69-
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface);
70-
/** Unset factory function for timers */
71-
void RPCUnsetTimerInterface(RPCTimerInterface *iface);
72-
73-
/**
74-
* Run func nSeconds from now.
75-
* Overrides previous timer <name> (if any).
76-
*/
77-
void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nSeconds);
78-
7938
typedef RPCHelpMan (*RpcMethodFnType)();
8039

8140
class CRPCCommand

src/wallet/rpc/encrypt.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <rpc/util.h>
6+
#include <scheduler.h>
7+
#include <wallet/context.h>
68
#include <wallet/rpc/util.h>
79
#include <wallet/wallet.h>
810

@@ -88,24 +90,24 @@ RPCHelpMan walletpassphrase()
8890
relock_time = pwallet->nRelockTime;
8991
}
9092

91-
// rpcRunLater must be called without cs_wallet held otherwise a deadlock
92-
// can occur. The deadlock would happen when RPCRunLater removes the
93-
// previous timer (and waits for the callback to finish if already running)
94-
// and the callback locks cs_wallet.
95-
AssertLockNotHeld(wallet->cs_wallet);
93+
// Get wallet scheduler to queue up the relock callback in the future.
94+
// Scheduled events don't get destructed until they are executed,
95+
// and they are executed in series in a single scheduler thread so
96+
// no cs_wallet lock is needed.
97+
WalletContext& context = EnsureWalletContext(request.context);
9698
// Keep a weak pointer to the wallet so that it is possible to unload the
9799
// wallet before the following callback is called. If a valid shared pointer
98100
// is acquired in the callback then the wallet is still loaded.
99101
std::weak_ptr<CWallet> weak_wallet = wallet;
100-
pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
102+
context.scheduler->scheduleFromNow([weak_wallet, relock_time] {
101103
if (auto shared_wallet = weak_wallet.lock()) {
102104
LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
103-
// Skip if this is not the most recent rpcRunLater callback.
105+
// Skip if this is not the most recent relock callback.
104106
if (shared_wallet->nRelockTime != relock_time) return;
105107
shared_wallet->Lock();
106108
shared_wallet->nRelockTime = 0;
107109
}
108-
}, nSleepTime);
110+
}, std::chrono::seconds(nSleepTime));
109111

110112
return UniValue::VNULL;
111113
},

test/functional/wallet_keypool.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the wallet keypool and interaction with wallet encryption/locking."""
66

7-
import time
87
from decimal import Decimal
98

109
from test_framework.test_framework import BitcoinTestFramework
@@ -127,8 +126,10 @@ def run_test(self):
127126
nodes[0].keypoolrefill(3)
128127

129128
# test walletpassphrase timeout
130-
time.sleep(1.1)
131-
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
129+
# CScheduler relies on condition_variable::wait_until() which does not
130+
# guarantee accurate timing. We'll wait up to 5 seconds to execute a 1
131+
# second scheduled event.
132+
nodes[0].wait_until(lambda: nodes[0].getwalletinfo()["unlocked_until"] == 0, timeout=5)
132133

133134
# drain the keypool
134135
for _ in range(3):

0 commit comments

Comments
 (0)