Skip to content

Commit 7ce9ac5

Browse files
committed
Merge #7292: [RPC] Expose ancestor/descendant information over RPC
176e19b Mention new RPC's in release notes (Suhas Daftuar) 7f6eda8 Add ancestor statistics to mempool entry RPC output (Suhas Daftuar) a9b8390 Add test coverage for new RPC calls (Suhas Daftuar) b09b813 Add getmempoolentry RPC call (Suhas Daftuar) 0dfd869 Add getmempooldescendants RPC call (Suhas Daftuar) 8f7b5dc Add getmempoolancestors RPC call (Suhas Daftuar) 5ec0cde Refactor logic for converting mempool entries to JSON (Suhas Daftuar)
2 parents fd9881a + 176e19b commit 7ce9ac5

File tree

4 files changed

+258
-39
lines changed

4 files changed

+258
-39
lines changed

doc/release-notes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ The following outputs are affected by this change:
8080
- REST `/rest/block/` (JSON format when including extended tx details)
8181
- `bitcoin-tx -json`
8282

83+
New mempool information RPC calls
84+
---------------------------------
85+
86+
RPC calls have been added to output detailed statistics for individual mempool
87+
entries, as well as to calculate the in-mempool ancestors or descendants of a
88+
transaction: see `getmempoolentry`, `getmempoolancestors`, `getmempooldescendants`.
89+
8390
### ZMQ
8491

8592
Each ZMQ notification now contains an up-counting sequence number that allows

qa/rpc-tests/mempool_packages.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,14 @@ def run_test(self):
6565
descendant_fees = 0
6666
descendant_size = 0
6767

68+
descendants = []
69+
ancestors = list(chain)
6870
for x in reversed(chain):
71+
# Check that getmempoolentry is consistent with getrawmempool
72+
entry = self.nodes[0].getmempoolentry(x)
73+
assert_equal(entry, mempool[x])
74+
75+
# Check that the descendant calculations are correct
6976
assert_equal(mempool[x]['descendantcount'], descendant_count)
7077
descendant_fees += mempool[x]['fee']
7178
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
@@ -74,6 +81,27 @@ def run_test(self):
7481
assert_equal(mempool[x]['descendantsize'], descendant_size)
7582
descendant_count += 1
7683

84+
# Check that getmempooldescendants is correct
85+
assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
86+
descendants.append(x)
87+
88+
# Check that getmempoolancestors is correct
89+
ancestors.remove(x)
90+
assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))
91+
92+
# Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
93+
v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
94+
assert_equal(len(v_ancestors), len(chain)-1)
95+
for x in v_ancestors.keys():
96+
assert_equal(mempool[x], v_ancestors[x])
97+
assert(chain[-1] not in v_ancestors.keys())
98+
99+
v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
100+
assert_equal(len(v_descendants), len(chain)-1)
101+
for x in v_descendants.keys():
102+
assert_equal(mempool[x], v_descendants[x])
103+
assert(chain[0] not in v_descendants.keys())
104+
77105
# Check that descendant modified fees includes fee deltas from
78106
# prioritisetransaction
79107
self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)

src/rpc/blockchain.cpp

Lines changed: 221 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,60 @@ UniValue getdifficulty(const UniValue& params, bool fHelp)
183183
return GetDifficulty();
184184
}
185185

186+
std::string EntryDescriptionString()
187+
{
188+
return " \"size\" : n, (numeric) transaction size in bytes\n"
189+
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
190+
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
191+
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
192+
" \"height\" : n, (numeric) block height when transaction entered pool\n"
193+
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
194+
" \"currentpriority\" : n, (numeric) transaction priority now\n"
195+
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
196+
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
197+
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
198+
" \"ancestorcount\" : n, (numeric) number of in-mempool ancestor transactions (including this one)\n"
199+
" \"ancestorsize\" : n, (numeric) size of in-mempool ancestors (including this one)\n"
200+
" \"ancestorfees\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one)\n"
201+
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
202+
" \"transactionid\", (string) parent transaction id\n"
203+
" ... ]\n";
204+
}
205+
206+
void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
207+
{
208+
AssertLockHeld(mempool.cs);
209+
210+
info.push_back(Pair("size", (int)e.GetTxSize()));
211+
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
212+
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
213+
info.push_back(Pair("time", e.GetTime()));
214+
info.push_back(Pair("height", (int)e.GetHeight()));
215+
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
216+
info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height())));
217+
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
218+
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
219+
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
220+
info.push_back(Pair("ancestorcount", e.GetCountWithAncestors()));
221+
info.push_back(Pair("ancestorsize", e.GetSizeWithAncestors()));
222+
info.push_back(Pair("ancestorfees", e.GetModFeesWithAncestors()));
223+
const CTransaction& tx = e.GetTx();
224+
set<string> setDepends;
225+
BOOST_FOREACH(const CTxIn& txin, tx.vin)
226+
{
227+
if (mempool.exists(txin.prevout.hash))
228+
setDepends.insert(txin.prevout.hash.ToString());
229+
}
230+
231+
UniValue depends(UniValue::VARR);
232+
BOOST_FOREACH(const string& dep, setDepends)
233+
{
234+
depends.push_back(dep);
235+
}
236+
237+
info.push_back(Pair("depends", depends));
238+
}
239+
186240
UniValue mempoolToJSON(bool fVerbose = false)
187241
{
188242
if (fVerbose)
@@ -193,31 +247,7 @@ UniValue mempoolToJSON(bool fVerbose = false)
193247
{
194248
const uint256& hash = e.GetTx().GetHash();
195249
UniValue info(UniValue::VOBJ);
196-
info.push_back(Pair("size", (int)e.GetTxSize()));
197-
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
198-
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
199-
info.push_back(Pair("time", e.GetTime()));
200-
info.push_back(Pair("height", (int)e.GetHeight()));
201-
info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight())));
202-
info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height())));
203-
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
204-
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
205-
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
206-
const CTransaction& tx = e.GetTx();
207-
set<string> setDepends;
208-
BOOST_FOREACH(const CTxIn& txin, tx.vin)
209-
{
210-
if (mempool.exists(txin.prevout.hash))
211-
setDepends.insert(txin.prevout.hash.ToString());
212-
}
213-
214-
UniValue depends(UniValue::VARR);
215-
BOOST_FOREACH(const string& dep, setDepends)
216-
{
217-
depends.push_back(dep);
218-
}
219-
220-
info.push_back(Pair("depends", depends));
250+
entryToJSON(info, e);
221251
o.push_back(Pair(hash.ToString(), info));
222252
}
223253
return o;
@@ -251,20 +281,8 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
251281
"\nResult: (for verbose = true):\n"
252282
"{ (json object)\n"
253283
" \"transactionid\" : { (json object)\n"
254-
" \"size\" : n, (numeric) transaction size in bytes\n"
255-
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
256-
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
257-
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
258-
" \"height\" : n, (numeric) block height when transaction entered pool\n"
259-
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
260-
" \"currentpriority\" : n, (numeric) transaction priority now\n"
261-
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
262-
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
263-
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
264-
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
265-
" \"transactionid\", (string) parent transaction id\n"
266-
" ... ]\n"
267-
" }, ...\n"
284+
+ EntryDescriptionString()
285+
+ " }, ...\n"
268286
"}\n"
269287
"\nExamples\n"
270288
+ HelpExampleCli("getrawmempool", "true")
@@ -280,6 +298,167 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
280298
return mempoolToJSON(fVerbose);
281299
}
282300

301+
UniValue getmempoolancestors(const UniValue& params, bool fHelp)
302+
{
303+
if (fHelp || params.size() < 1 || params.size() > 2) {
304+
throw runtime_error(
305+
"getmempoolancestors txid (verbose)\n"
306+
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n"
307+
"\nArguments:\n"
308+
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
309+
"2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
310+
"\nResult (for verbose=false):\n"
311+
"[ (json array of strings)\n"
312+
" \"transactionid\" (string) The transaction id of an in-mempool ancestor transaction\n"
313+
" ,...\n"
314+
"]\n"
315+
"\nResult (for verbose=true):\n"
316+
"{ (json object)\n"
317+
" \"transactionid\" : { (json object)\n"
318+
+ EntryDescriptionString()
319+
+ " }, ...\n"
320+
"}\n"
321+
"\nExamples\n"
322+
+ HelpExampleCli("getmempoolancestors", "\"mytxid\"")
323+
+ HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
324+
);
325+
}
326+
327+
bool fVerbose = false;
328+
if (params.size() > 1)
329+
fVerbose = params[1].get_bool();
330+
331+
uint256 hash = ParseHashV(params[0], "parameter 1");
332+
333+
LOCK(mempool.cs);
334+
335+
CTxMemPool::txiter it = mempool.mapTx.find(hash);
336+
if (it == mempool.mapTx.end()) {
337+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
338+
}
339+
340+
CTxMemPool::setEntries setAncestors;
341+
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
342+
std::string dummy;
343+
mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
344+
345+
if (!fVerbose) {
346+
UniValue o(UniValue::VARR);
347+
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors) {
348+
o.push_back(ancestorIt->GetTx().GetHash().ToString());
349+
}
350+
351+
return o;
352+
} else {
353+
UniValue o(UniValue::VOBJ);
354+
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors) {
355+
const CTxMemPoolEntry &e = *ancestorIt;
356+
const uint256& hash = e.GetTx().GetHash();
357+
UniValue info(UniValue::VOBJ);
358+
entryToJSON(info, e);
359+
o.push_back(Pair(hash.ToString(), info));
360+
}
361+
return o;
362+
}
363+
}
364+
365+
UniValue getmempooldescendants(const UniValue& params, bool fHelp)
366+
{
367+
if (fHelp || params.size() < 1 || params.size() > 2) {
368+
throw runtime_error(
369+
"getmempooldescendants txid (verbose)\n"
370+
"\nIf txid is in the mempool, returns all in-mempool descendants.\n"
371+
"\nArguments:\n"
372+
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
373+
"2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
374+
"\nResult (for verbose=false):\n"
375+
"[ (json array of strings)\n"
376+
" \"transactionid\" (string) The transaction id of an in-mempool descendant transaction\n"
377+
" ,...\n"
378+
"]\n"
379+
"\nResult (for verbose=true):\n"
380+
"{ (json object)\n"
381+
" \"transactionid\" : { (json object)\n"
382+
+ EntryDescriptionString()
383+
+ " }, ...\n"
384+
"}\n"
385+
"\nExamples\n"
386+
+ HelpExampleCli("getmempooldescendants", "\"mytxid\"")
387+
+ HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
388+
);
389+
}
390+
391+
bool fVerbose = false;
392+
if (params.size() > 1)
393+
fVerbose = params[1].get_bool();
394+
395+
uint256 hash = ParseHashV(params[0], "parameter 1");
396+
397+
LOCK(mempool.cs);
398+
399+
CTxMemPool::txiter it = mempool.mapTx.find(hash);
400+
if (it == mempool.mapTx.end()) {
401+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
402+
}
403+
404+
CTxMemPool::setEntries setDescendants;
405+
mempool.CalculateDescendants(it, setDescendants);
406+
// CTxMemPool::CalculateDescendants will include the given tx
407+
setDescendants.erase(it);
408+
409+
if (!fVerbose) {
410+
UniValue o(UniValue::VARR);
411+
BOOST_FOREACH(CTxMemPool::txiter descendantIt, setDescendants) {
412+
o.push_back(descendantIt->GetTx().GetHash().ToString());
413+
}
414+
415+
return o;
416+
} else {
417+
UniValue o(UniValue::VOBJ);
418+
BOOST_FOREACH(CTxMemPool::txiter descendantIt, setDescendants) {
419+
const CTxMemPoolEntry &e = *descendantIt;
420+
const uint256& hash = e.GetTx().GetHash();
421+
UniValue info(UniValue::VOBJ);
422+
entryToJSON(info, e);
423+
o.push_back(Pair(hash.ToString(), info));
424+
}
425+
return o;
426+
}
427+
}
428+
429+
UniValue getmempoolentry(const UniValue& params, bool fHelp)
430+
{
431+
if (fHelp || params.size() != 1) {
432+
throw runtime_error(
433+
"getmempoolentry txid\n"
434+
"\nReturns mempool data for given transaction\n"
435+
"\nArguments:\n"
436+
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
437+
"\nResult:\n"
438+
"{ (json object)\n"
439+
+ EntryDescriptionString()
440+
+ "}\n"
441+
"\nExamples\n"
442+
+ HelpExampleCli("getmempoolentry", "\"mytxid\"")
443+
+ HelpExampleRpc("getmempoolentry", "\"mytxid\"")
444+
);
445+
}
446+
447+
uint256 hash = ParseHashV(params[0], "parameter 1");
448+
449+
LOCK(mempool.cs);
450+
451+
CTxMemPool::txiter it = mempool.mapTx.find(hash);
452+
if (it == mempool.mapTx.end()) {
453+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
454+
}
455+
456+
const CTxMemPoolEntry &e = *it;
457+
UniValue info(UniValue::VOBJ);
458+
entryToJSON(info, e);
459+
return info;
460+
}
461+
283462
UniValue getblockhash(const UniValue& params, bool fHelp)
284463
{
285464
if (fHelp || params.size() != 1)
@@ -1004,6 +1183,9 @@ static const CRPCCommand commands[] =
10041183
{ "blockchain", "getblockheader", &getblockheader, true },
10051184
{ "blockchain", "getchaintips", &getchaintips, true },
10061185
{ "blockchain", "getdifficulty", &getdifficulty, true },
1186+
{ "blockchain", "getmempoolancestors", &getmempoolancestors, true },
1187+
{ "blockchain", "getmempooldescendants", &getmempooldescendants, true },
1188+
{ "blockchain", "getmempoolentry", &getmempoolentry, true },
10071189
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
10081190
{ "blockchain", "getrawmempool", &getrawmempool, true },
10091191
{ "blockchain", "gettxout", &gettxout, true },

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
102102
{ "prioritisetransaction", 2 },
103103
{ "setban", 2 },
104104
{ "setban", 3 },
105+
{ "getmempoolancestors", 1 },
106+
{ "getmempooldescendants", 1 },
105107
};
106108

107109
class CRPCConvertTable

0 commit comments

Comments
 (0)