Skip to content

Commit d6a5dc4

Browse files
committed
add waitfornewblock/waitforblock/waitforblockheight rpcs and use them for tests
waitfornewblock waits until a new block is received, or the timeout expires, then returns the current block height/hash. waitforblock waits for a specific blockhash, or until the timeout expires, then returns the current block height/hash. If the target blockhash is the current tip, it will return immediately. waitforblockheight waits until the tip has reached a certain height or higher, then returns the current height and hash. waitforblockheight is used to avoid polling in the rpc tests.
1 parent 5b2ea29 commit d6a5dc4

File tree

5 files changed

+168
-2
lines changed

5 files changed

+168
-2
lines changed

qa/rpc-tests/test_framework/util.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,16 @@ def sync_blocks(rpc_connections, wait=1, timeout=60):
125125
"""
126126
Wait until everybody has the same tip
127127
"""
128+
maxheight = 0
128129
while timeout > 0:
129-
tips = [ x.getbestblockhash() for x in rpc_connections ]
130+
tips = [ x.waitforblockheight(maxheight, int(wait * 1000)) for x in rpc_connections ]
131+
heights = [ x["height"] for x in tips ]
130132
if tips == [ tips[0] ]*len(tips):
131133
return True
132-
time.sleep(wait)
134+
if heights == [ heights[0] ]*len(heights): #heights are the same but hashes are not
135+
raise AssertionError("Block sync failed")
133136
timeout -= wait
137+
maxheight = max(heights)
134138
raise AssertionError("Block sync failed")
135139

136140
def sync_mempools(rpc_connections, wait=1, timeout=60):

src/init.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,15 @@ bool static Bind(const CService &addr, unsigned int flags) {
280280
}
281281
return true;
282282
}
283+
void OnRPCStarted()
284+
{
285+
uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange);
286+
}
283287

284288
void OnRPCStopped()
285289
{
290+
uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange);
291+
RPCNotifyBlockChange(false, nullptr);
286292
cvBlockChange.notify_all();
287293
LogPrint("rpc", "RPC stopped.\n");
288294
}
@@ -666,6 +672,7 @@ bool InitSanityCheck(void)
666672

667673
bool AppInitServers(boost::thread_group& threadGroup)
668674
{
675+
RPCServer::OnStarted(&OnRPCStarted);
669676
RPCServer::OnStopped(&OnRPCStopped);
670677
RPCServer::OnPreCommand(&OnRPCPreCommand);
671678
if (!InitHTTPServer())
@@ -1357,6 +1364,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
13571364
{
13581365
LOCK(cs_main);
13591366
CBlockIndex* tip = chainActive.Tip();
1367+
RPCNotifyBlockChange(true, tip);
13601368
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
13611369
strLoadError = _("The block database contains a block which appears to be from the future. "
13621370
"This may be due to your computer's date and time being set incorrectly. "

src/rpc/blockchain.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,20 @@
2626

2727
#include <boost/thread/thread.hpp> // boost::thread::interrupt
2828

29+
#include <mutex>
30+
#include <condition_variable>
2931
using namespace std;
3032

33+
struct CUpdatedBlock
34+
{
35+
uint256 hash;
36+
int height;
37+
};
38+
39+
static std::mutex cs_blockchange;
40+
static std::condition_variable cond_blockchange;
41+
static CUpdatedBlock latestblock;
42+
3143
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
3244
void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
3345

@@ -168,6 +180,138 @@ UniValue getbestblockhash(const UniValue& params, bool fHelp)
168180
return chainActive.Tip()->GetBlockHash().GetHex();
169181
}
170182

183+
void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
184+
{
185+
if(pindex) {
186+
std::lock_guard<std::mutex> lock(cs_blockchange);
187+
latestblock.hash = pindex->GetBlockHash();
188+
latestblock.height = pindex->nHeight;
189+
}
190+
cond_blockchange.notify_all();
191+
}
192+
193+
UniValue waitfornewblock(const UniValue& params, bool fHelp)
194+
{
195+
if (fHelp || params.size() > 1)
196+
throw runtime_error(
197+
"waitfornewblock\n"
198+
"\nWaits for a specific new block and returns useful info about it.\n"
199+
"\nReturns the current block on timeout or exit.\n"
200+
"\nArguments:\n"
201+
"1. timeout (milliseconds) (int, optional, default=false)\n"
202+
"\nResult::\n"
203+
"{ (json object)\n"
204+
" \"hash\" : { (string) The blockhash\n"
205+
" \"height\" : { (int) Block height\n"
206+
"}\n"
207+
"\nExamples\n"
208+
+ HelpExampleCli("waitfornewblock", "1000")
209+
+ HelpExampleRpc("waitfornewblock", "1000")
210+
);
211+
int timeout = 0;
212+
if (params.size() > 0)
213+
timeout = params[0].get_int();
214+
215+
CUpdatedBlock block;
216+
{
217+
std::unique_lock<std::mutex> lock(cs_blockchange);
218+
block = latestblock;
219+
if(timeout)
220+
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]{return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
221+
else
222+
cond_blockchange.wait(lock, [&block]{return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
223+
block = latestblock;
224+
}
225+
UniValue ret(UniValue::VOBJ);
226+
ret.push_back(Pair("hash", block.hash.GetHex()));
227+
ret.push_back(Pair("height", block.height));
228+
return ret;
229+
}
230+
231+
UniValue waitforblock(const UniValue& params, bool fHelp)
232+
{
233+
if (fHelp || params.size() < 1 || params.size() > 2)
234+
throw runtime_error(
235+
"waitforblock\n"
236+
"\nWaits for a specific new block and returns useful info about it.\n"
237+
"\nReturns the current block on timeout or exit.\n"
238+
"\nArguments:\n"
239+
"1. blockhash to wait for (string)\n"
240+
"2. timeout (milliseconds) (int, optional, default=false)\n"
241+
"\nResult::\n"
242+
"{ (json object)\n"
243+
" \"hash\" : { (string) The blockhash\n"
244+
" \"height\" : { (int) Block height\n"
245+
"}\n"
246+
"\nExamples\n"
247+
+ HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000")
248+
+ HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000")
249+
);
250+
int timeout = 0;
251+
252+
uint256 hash = uint256S(params[0].get_str());
253+
254+
if (params.size() > 1)
255+
timeout = params[1].get_int();
256+
257+
CUpdatedBlock block;
258+
{
259+
std::unique_lock<std::mutex> lock(cs_blockchange);
260+
if(timeout)
261+
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]{return latestblock.hash == hash || !IsRPCRunning();});
262+
else
263+
cond_blockchange.wait(lock, [&hash]{return latestblock.hash == hash || !IsRPCRunning(); });
264+
block = latestblock;
265+
}
266+
267+
UniValue ret(UniValue::VOBJ);
268+
ret.push_back(Pair("hash", block.hash.GetHex()));
269+
ret.push_back(Pair("height", block.height));
270+
return ret;
271+
}
272+
273+
UniValue waitforblockheight(const UniValue& params, bool fHelp)
274+
{
275+
if (fHelp || params.size() < 1 || params.size() > 2)
276+
throw runtime_error(
277+
"waitforblock\n"
278+
"\nWaits for (at least) block height and returns the height and hash\n"
279+
"\nof the current tip.\n"
280+
"\nReturns the current block on timeout or exit.\n"
281+
"\nArguments:\n"
282+
"1. block height to wait for (int)\n"
283+
"2. timeout (milliseconds) (int, optional, default=false)\n"
284+
"\nResult::\n"
285+
"{ (json object)\n"
286+
" \"hash\" : { (string) The blockhash\n"
287+
" \"height\" : { (int) Block height\n"
288+
"}\n"
289+
"\nExamples\n"
290+
+ HelpExampleCli("waitforblockheight", "\"100\", 1000")
291+
+ HelpExampleRpc("waitforblockheight", "\"100\", 1000")
292+
);
293+
int timeout = 0;
294+
295+
int height = params[0].get_int();
296+
297+
if (params.size() > 1)
298+
timeout = params[1].get_int();
299+
300+
CUpdatedBlock block;
301+
{
302+
std::unique_lock<std::mutex> lock(cs_blockchange);
303+
if(timeout)
304+
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]{return latestblock.height >= height || !IsRPCRunning();});
305+
else
306+
cond_blockchange.wait(lock, [&height]{return latestblock.height >= height || !IsRPCRunning(); });
307+
block = latestblock;
308+
}
309+
UniValue ret(UniValue::VOBJ);
310+
ret.push_back(Pair("hash", block.hash.GetHex()));
311+
ret.push_back(Pair("height", block.height));
312+
return ret;
313+
}
314+
171315
UniValue getdifficulty(const UniValue& params, bool fHelp)
172316
{
173317
if (fHelp || params.size() != 0)
@@ -1203,6 +1347,9 @@ static const CRPCCommand commands[] =
12031347
/* Not shown in help */
12041348
{ "hidden", "invalidateblock", &invalidateblock, true },
12051349
{ "hidden", "reconsiderblock", &reconsiderblock, true },
1350+
{ "hidden", "waitfornewblock", &waitfornewblock, true },
1351+
{ "hidden", "waitforblock", &waitforblock, true },
1352+
{ "hidden", "waitforblockheight", &waitforblockheight, true },
12061353
};
12071354

12081355
void RegisterBlockchainRPCCommands(CRPCTable &t)

src/rpc/client.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
4646
{ "getbalance", 1 },
4747
{ "getbalance", 2 },
4848
{ "getblockhash", 0 },
49+
{ "waitforblockheight", 0 },
50+
{ "waitforblockheight", 1 },
51+
{ "waitforblock", 1 },
52+
{ "waitforblock", 2 },
53+
{ "waitfornewblock", 0 },
54+
{ "waitfornewblock", 1 },
4955
{ "move", 2 },
5056
{ "move", 3 },
5157
{ "sendfrom", 2 },

src/rpc/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,6 @@ bool StartRPC();
194194
void InterruptRPC();
195195
void StopRPC();
196196
std::string JSONRPCExecBatch(const UniValue& vReq);
197+
void RPCNotifyBlockChange(bool ibd, const CBlockIndex *);
197198

198199
#endif // BITCOIN_RPCSERVER_H

0 commit comments

Comments
 (0)