Skip to content

Commit 3eb80fd

Browse files
laanwjPastaPastaPasta
authored andcommitted
Merge bitcoin#10286: Call wallet notify callbacks in scheduler thread (without cs_main)
89f0312 Remove redundant pwallet nullptr check (Matt Corallo) c4784b5 Add a dev notes document describing the new wallet RPC blocking (Matt Corallo) 3ea8b75 Give ZMQ consistent order with UpdatedBlockTip on scheduler thread (Matt Corallo) cb06edf Fix wallet RPC race by waiting for callbacks in sendrawtransaction (Matt Corallo) e545ded Also call other wallet notify callbacks in scheduler thread (Matt Corallo) 17220d6 Use callbacks to cache whether wallet transactions are in mempool (Matt Corallo) 5d67a78 Add calls to CWallet::BlockUntilSyncedToCurrentChain() in RPCs (Matt Corallo) 5ee3172 Add CWallet::BlockUntilSyncedToCurrentChain() (Matt Corallo) 0b2f42d Add CallFunctionInQueue to wait on validation interface queue drain (Matt Corallo) 2b4b345 Add ability to assert a lock is not held in DEBUG_LOCKORDER (Matt Corallo) 0343676 Call TransactionRemovedFromMempool in the CScheduler thread (Matt Corallo) a7d3936 Add a CValidationInterface::TransactionRemovedFromMempool (Matt Corallo) Pull request description: Based on bitcoin#10179, this effectively reverts bitcoin#9583, regaining most of the original speedups of bitcoin#7946. This concludes the work of bitcoin#9725, bitcoin#10178, and bitcoin#10179. See individual commit messages for more information. Tree-SHA512: eead4809b0a75d1fb33b0765174ff52c972e45040635e38cf3686cef310859c1e6b3c00e7186cbd17374c6ae547bfbd6c1718fe36f26c76ba8a8b052d6ed7bc9
1 parent 4ec4e34 commit 3eb80fd

File tree

10 files changed

+336
-28
lines changed

10 files changed

+336
-28
lines changed

doc/developer-notes.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,3 +686,16 @@ A few guidelines for introducing and reviewing new RPC interfaces:
686686

687687
- *Rationale*: If a RPC response is not a JSON object then it is harder to avoid API breakage if
688688
new data in the response is needed.
689+
690+
- Wallet RPCs call BlockUntilSyncedToCurrentChain to maintain consistency with
691+
`getblockchaininfo`'s state immediately prior to the call's execution. Wallet
692+
RPCs whose behavior does *not* depend on the current chainstate may omit this
693+
call.
694+
695+
- *Rationale*: In previous versions of Bitcoin Core, the wallet was always
696+
in-sync with the chainstate (by virtue of them all being updated in the
697+
same cs_main lock). In order to maintain the behavior that wallet RPCs
698+
return results as of at least the highest best-known block an RPC
699+
client may be aware of prior to entering a wallet RPC call, we must block
700+
until the wallet is caught up to the chainstate as of the RPC call's entry.
701+
This also makes the API much easier for RPC clients to reason about.

src/init.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ void PrepareShutdown()
338338
#endif
339339
UnregisterAllValidationInterfaces();
340340
GetMainSignals().UnregisterBackgroundSignalScheduler();
341+
GetMainSignals().UnregisterWithMempoolSignals(mempool);
341342
}
342343

343344
/**
@@ -1580,6 +1581,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
15801581
threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));
15811582

15821583
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
1584+
GetMainSignals().RegisterWithMempoolSignals(mempool);
15831585

15841586
/* Register RPC commands regardless of -server setting so they will be
15851587
* available in the GUI RPC console even if external calls are disabled.

src/rpc/rawtransaction.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "init.h"
1313
#include "keystore.h"
1414
#include "validation.h"
15+
#include "validationinterface.h"
1516
#include "merkleblock.h"
1617
#include "net.h"
1718
#include "policy/policy.h"
@@ -38,6 +39,7 @@
3839
#include "llmq/quorums_commitment.h"
3940
#include "llmq/quorums_instantsend.h"
4041

42+
#include <future>
4143
#include <stdint.h>
4244

4345
#include <univalue.h>
@@ -956,7 +958,9 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
956958
);
957959

958960
ObserveSafeMode();
959-
LOCK(cs_main);
961+
962+
std::promise<void> promise;
963+
960964
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL});
961965

962966
// parse hex string from parameter
@@ -974,6 +978,8 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
974978
if (!request.params[3].isNull())
975979
fBypassLimits = request.params[3].get_bool();
976980

981+
{ // cs_main scope
982+
LOCK(cs_main);
977983
CCoinsViewCache &view = *pcoinsTip;
978984
bool fHaveChain = false;
979985
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
@@ -994,10 +1000,24 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
9941000
}
9951001
throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
9961002
}
1003+
} else {
1004+
// If wallet is enabled, ensure that the wallet has been made aware
1005+
// of the new transaction prior to returning. This prevents a race
1006+
// where a user might call sendrawtransaction with a transaction
1007+
// to/from their wallet, immediately call some wallet RPC, and get
1008+
// a stale result because callbacks have not yet been processed.
1009+
CallFunctionInValidationInterfaceQueue([&promise] {
1010+
promise.set_value();
1011+
});
9971012
}
9981013
} else if (fHaveChain) {
9991014
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
10001015
}
1016+
1017+
} // cs_main
1018+
1019+
promise.get_future().wait();
1020+
10011021
if(!g_connman)
10021022
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
10031023

src/txmempool.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ class CTxMemPool
529529
// to track size/count of descendant transactions. First version of
530530
// addUnchecked can be used to have it call CalculateMemPoolAncestors(), and
531531
// then invoke the second version.
532+
// Note that addUnchecked is ONLY called from ATMP outside of tests
533+
// and any other callers may break wallet's in-mempool tracking (due to
534+
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
532535
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool validFeeEstimate = true);
533536
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate = true);
534537

src/validation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2879,7 +2879,7 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams,
28792879

28802880
for (const PerBlockConnectTrace& trace : connectTrace.GetBlocksConnected()) {
28812881
assert(trace.pblock && trace.pindex);
2882-
GetMainSignals().BlockConnected(trace.pblock, trace.pindex, *trace.conflictedTxs);
2882+
GetMainSignals().BlockConnected(trace.pblock, trace.pindex, trace.conflictedTxs);
28832883
}
28842884
}
28852885
// When we reach this point, we switched to a new tip (stored in pindexNewTip).

src/validationinterface.cpp

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "primitives/block.h"
1010
#include "scheduler.h"
1111
#include "sync.h"
12+
#include "txmempool.h"
1213
#include "util.h"
1314

1415
#include <list>
@@ -21,6 +22,7 @@ struct MainSignalsInstance {
2122
boost::signals2::signal<void (const CTransactionRef &, int64_t)> TransactionAddedToMempool;
2223
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected;
2324
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex* pindexDisconnected)> BlockDisconnected;
25+
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
2426
boost::signals2::signal<void (const CBlockLocator &)> SetBestChain;
2527
boost::signals2::signal<void (const uint256 &)> Inventory;
2628
boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
@@ -59,6 +61,14 @@ void CMainSignals::FlushBackgroundCallbacks() {
5961
}
6062
}
6163

64+
void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) {
65+
pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
66+
}
67+
68+
void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool& pool) {
69+
pool.NotifyEntryRemoved.disconnect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
70+
}
71+
6272
CMainSignals& GetMainSignals()
6373
{
6474
return g_signals;
@@ -73,6 +83,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
7383
g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1, _2));
7484
g_signals.m_internals->NotifyTransactionLock.connect(boost::bind(&CValidationInterface::NotifyTransactionLock, pwalletIn, _1, _2));
7585
g_signals.m_internals->NotifyChainLock.connect(boost::bind(&CValidationInterface::NotifyChainLock, pwalletIn, _1, _2));
86+
g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
7687
g_signals.m_internals->SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
7788
g_signals.m_internals->Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
7889
g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2));
@@ -94,6 +105,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
94105
g_signals.m_internals->TransactionAddedToMempool.disconnect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1, _2));
95106
g_signals.m_internals->BlockConnected.disconnect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));
96107
g_signals.m_internals->BlockDisconnected.disconnect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1, _2));
108+
g_signals.m_internals->TransactionRemovedFromMempool.disconnect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
97109
g_signals.m_internals->UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
98110
g_signals.m_internals->NewPoWValidBlock.disconnect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2));
99111
g_signals.m_internals->NotifyHeaderTip.disconnect(boost::bind(&CValidationInterface::NotifyHeaderTip, pwalletIn, _1, _2));
@@ -117,6 +129,7 @@ void UnregisterAllValidationInterfaces() {
117129
g_signals.m_internals->TransactionAddedToMempool.disconnect_all_slots();
118130
g_signals.m_internals->BlockConnected.disconnect_all_slots();
119131
g_signals.m_internals->BlockDisconnected.disconnect_all_slots();
132+
g_signals.m_internals->TransactionRemovedFromMempool.disconnect_all_slots();
120133
g_signals.m_internals->UpdatedBlockTip.disconnect_all_slots();
121134
g_signals.m_internals->NewPoWValidBlock.disconnect_all_slots();
122135
g_signals.m_internals->NotifyHeaderTip.disconnect_all_slots();
@@ -127,28 +140,52 @@ void UnregisterAllValidationInterfaces() {
127140
g_signals.m_internals->NotifyMasternodeListChanged.disconnect_all_slots();
128141
}
129142

143+
void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) {
144+
g_signals.m_internals->m_schedulerClient.AddToProcessQueue(std::move(func));
145+
}
146+
147+
void CMainSignals::MempoolEntryRemoved(CTransactionRef ptx, MemPoolRemovalReason reason) {
148+
if (reason != MemPoolRemovalReason::BLOCK && reason != MemPoolRemovalReason::CONFLICT) {
149+
m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] {
150+
m_internals->TransactionRemovedFromMempool(ptx);
151+
});
152+
}
153+
}
154+
130155
void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {
131-
m_internals->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
156+
m_internals->m_schedulerClient.AddToProcessQueue([pindexNew, pindexFork, fInitialDownload, this] {
157+
m_internals->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
158+
});
132159
}
133160

134161
void CMainSignals::TransactionAddedToMempool(const CTransactionRef &ptx, int64_t nAcceptTime) {
135-
m_internals->TransactionAddedToMempool(ptx, nAcceptTime);
162+
m_internals->m_schedulerClient.AddToProcessQueue([ptx, this] {
163+
m_internals->TransactionAddedToMempool(ptx, nAcceptTime);
164+
});
136165
}
137166

138-
void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) {
139-
m_internals->BlockConnected(pblock, pindex, vtxConflicted);
167+
void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>>& pvtxConflicted) {
168+
m_internals->m_schedulerClient.AddToProcessQueue([pblock, pindex, pvtxConflicted, this] {
169+
m_internals->BlockConnected(pblock, pindex, *pvtxConflicted);
170+
});
140171
}
141172

142173
void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex* pindexDisconnected) {
143-
m_internals->BlockDisconnected(pblock, pindexDisconnected);
174+
m_internals->m_schedulerClient.AddToProcessQueue([pblock, this] {
175+
m_internals->BlockDisconnected(pblock, pindexDisconnected);
176+
});
144177
}
145178

146179
void CMainSignals::SetBestChain(const CBlockLocator &locator) {
147-
m_internals->SetBestChain(locator);
180+
m_internals->m_schedulerClient.AddToProcessQueue([locator, this] {
181+
m_internals->SetBestChain(locator);
182+
});
148183
}
149184

150185
void CMainSignals::Inventory(const uint256 &hash) {
151-
m_internals->Inventory(hash);
186+
m_internals->m_schedulerClient.AddToProcessQueue([hash, this] {
187+
m_internals->Inventory(hash);
188+
});
152189
}
153190

154191
void CMainSignals::Broadcast(int64_t nBestBlockTime, CConnman* connman) {

src/validationinterface.h

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
#ifndef BITCOIN_VALIDATIONINTERFACE_H
77
#define BITCOIN_VALIDATIONINTERFACE_H
88

9-
#include <memory>
10-
119
#include "primitives/transaction.h" // CTransaction(Ref)
1210

11+
#include <functional>
12+
#include <memory>
13+
1314
class CBlock;
1415
class CBlockIndex;
1516
struct CBlockLocator;
@@ -23,6 +24,8 @@ class CDeterministicMNList;
2324
class CDeterministicMNListDiff;
2425
class uint256;
2526
class CScheduler;
27+
class CTxMemPool;
28+
enum class MemPoolRemovalReason;
2629

2730
namespace llmq {
2831
class CChainLockSig;
@@ -37,31 +40,74 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn);
3740
void UnregisterValidationInterface(CValidationInterface* pwalletIn);
3841
/** Unregister all wallets from core */
3942
void UnregisterAllValidationInterfaces();
43+
/**
44+
* Pushes a function to callback onto the notification queue, guaranteeing any
45+
* callbacks generated prior to now are finished when the function is called.
46+
*
47+
* Be very careful blocking on func to be called if any locks are held -
48+
* validation interface clients may not be able to make progress as they often
49+
* wait for things like cs_main, so blocking until func is called with cs_main
50+
* will result in a deadlock (that DEBUG_LOCKORDER will miss).
51+
*/
52+
void CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
4053

4154
class CValidationInterface {
4255
protected:
4356
virtual void AcceptedBlockHeader(const CBlockIndex *pindexNew) {}
4457
virtual void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload) {}
45-
/** Notifies listeners of updated block chain tip */
58+
/**
59+
* Notifies listeners of updated block chain tip
60+
*
61+
* Called on a background thread.
62+
*/
4663
virtual void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {}
47-
/** Notifies listeners of a transaction having been added to mempool. */
64+
/**
65+
* Notifies listeners of a transaction having been added to mempool.
66+
*
67+
* Called on a background thread.
68+
*/
4869
virtual void TransactionAddedToMempool(const CTransactionRef &ptxn, int64_t nAcceptTime) {}
70+
/**
71+
* Notifies listeners of a transaction leaving mempool.
72+
*
73+
* This only fires for transactions which leave mempool because of expiry,
74+
* size limiting, reorg (changes in lock times/coinbase maturity), or
75+
* replacement. This does not include any transactions which are included
76+
* in BlockConnectedDisconnected either in block->vtx or in txnConflicted.
77+
*
78+
* Called on a background thread.
79+
*/
80+
virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) {}
4981
/**
5082
* Notifies listeners of a block being connected.
5183
* Provides a vector of transactions evicted from the mempool as a result.
84+
*
85+
* Called on a background thread.
5286
*/
5387
virtual void BlockConnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex *pindex, const std::vector<CTransactionRef> &txnConflicted) {}
54-
/** Notifies listeners of a block being disconnected */
88+
/**
89+
* Notifies listeners of a block being disconnected
90+
*
91+
* Called on a background thread.
92+
*/
5593
virtual void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex *pindexDisconnected) {}
5694
virtual void NotifyTransactionLock(const CTransaction &tx, const llmq::CInstantSendLock& islock) {}
5795
virtual void NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLockSig& clsig) {}
5896
virtual void NotifyGovernanceVote(const CGovernanceVote &vote) {}
5997
virtual void NotifyGovernanceObject(const CGovernanceObject &object) {}
6098
virtual void NotifyInstantSendDoubleSpendAttempt(const CTransaction &currentTx, const CTransaction &previousTx) {}
6199
virtual void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) {}
62-
/** Notifies listeners of the new active block chain on-disk. */
100+
/**
101+
* Notifies listeners of the new active block chain on-disk.
102+
*
103+
* Called on a background thread.
104+
*/
63105
virtual void SetBestChain(const CBlockLocator &locator) {}
64-
/** Notifies listeners about an inventory item being seen on the network. */
106+
/**
107+
* Notifies listeners about an inventory item being seen on the network.
108+
*
109+
* Called on a background thread.
110+
*/
65111
virtual void Inventory(const uint256 &hash) {}
66112
/** Tells listeners to broadcast their data. */
67113
virtual void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) {}
@@ -89,6 +135,9 @@ class CMainSignals {
89135
friend void ::RegisterValidationInterface(CValidationInterface*);
90136
friend void ::UnregisterValidationInterface(CValidationInterface*);
91137
friend void ::UnregisterAllValidationInterfaces();
138+
friend void ::CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
139+
140+
void MempoolEntryRemoved(CTransactionRef tx, MemPoolRemovalReason reason);
92141

93142
public:
94143
/** Register a CScheduler to give callbacks which should run in the background (may only be called once) */
@@ -98,11 +147,16 @@ class CMainSignals {
98147
/** Call any remaining callbacks on the calling thread */
99148
void FlushBackgroundCallbacks();
100149

150+
/** Register with mempool to call TransactionRemovedFromMempool callbacks */
151+
void RegisterWithMempoolSignals(CTxMemPool& pool);
152+
/** Unregister with mempool */
153+
void UnregisterWithMempoolSignals(CTxMemPool& pool);
154+
101155
void AcceptedBlockHeader(const CBlockIndex *pindexNew);
102156
void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload);
103157
void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
104158
void TransactionAddedToMempool(const CTransactionRef &, int64_t);
105-
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef> &);
159+
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &);
106160
void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindexDisconnected);
107161
void NotifyTransactionLock(const CTransaction &tx, const llmq::CInstantSendLock& islock);
108162
void NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLockSig& clsig);

0 commit comments

Comments
 (0)