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)
This commit is contained in:
commit
7ce9ac5c83
4 changed files with 258 additions and 39 deletions
|
@ -80,6 +80,13 @@ The following outputs are affected by this change:
|
||||||
- REST `/rest/block/` (JSON format when including extended tx details)
|
- REST `/rest/block/` (JSON format when including extended tx details)
|
||||||
- `bitcoin-tx -json`
|
- `bitcoin-tx -json`
|
||||||
|
|
||||||
|
New mempool information RPC calls
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
RPC calls have been added to output detailed statistics for individual mempool
|
||||||
|
entries, as well as to calculate the in-mempool ancestors or descendants of a
|
||||||
|
transaction: see `getmempoolentry`, `getmempoolancestors`, `getmempooldescendants`.
|
||||||
|
|
||||||
### ZMQ
|
### ZMQ
|
||||||
|
|
||||||
Each ZMQ notification now contains an up-counting sequence number that allows
|
Each ZMQ notification now contains an up-counting sequence number that allows
|
||||||
|
|
|
@ -65,7 +65,14 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||||
descendant_fees = 0
|
descendant_fees = 0
|
||||||
descendant_size = 0
|
descendant_size = 0
|
||||||
|
|
||||||
|
descendants = []
|
||||||
|
ancestors = list(chain)
|
||||||
for x in reversed(chain):
|
for x in reversed(chain):
|
||||||
|
# Check that getmempoolentry is consistent with getrawmempool
|
||||||
|
entry = self.nodes[0].getmempoolentry(x)
|
||||||
|
assert_equal(entry, mempool[x])
|
||||||
|
|
||||||
|
# Check that the descendant calculations are correct
|
||||||
assert_equal(mempool[x]['descendantcount'], descendant_count)
|
assert_equal(mempool[x]['descendantcount'], descendant_count)
|
||||||
descendant_fees += mempool[x]['fee']
|
descendant_fees += mempool[x]['fee']
|
||||||
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
|
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
|
||||||
|
@ -74,6 +81,27 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||||
assert_equal(mempool[x]['descendantsize'], descendant_size)
|
assert_equal(mempool[x]['descendantsize'], descendant_size)
|
||||||
descendant_count += 1
|
descendant_count += 1
|
||||||
|
|
||||||
|
# Check that getmempooldescendants is correct
|
||||||
|
assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
|
||||||
|
descendants.append(x)
|
||||||
|
|
||||||
|
# Check that getmempoolancestors is correct
|
||||||
|
ancestors.remove(x)
|
||||||
|
assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))
|
||||||
|
|
||||||
|
# Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
|
||||||
|
v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
|
||||||
|
assert_equal(len(v_ancestors), len(chain)-1)
|
||||||
|
for x in v_ancestors.keys():
|
||||||
|
assert_equal(mempool[x], v_ancestors[x])
|
||||||
|
assert(chain[-1] not in v_ancestors.keys())
|
||||||
|
|
||||||
|
v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
|
||||||
|
assert_equal(len(v_descendants), len(chain)-1)
|
||||||
|
for x in v_descendants.keys():
|
||||||
|
assert_equal(mempool[x], v_descendants[x])
|
||||||
|
assert(chain[0] not in v_descendants.keys())
|
||||||
|
|
||||||
# Check that descendant modified fees includes fee deltas from
|
# Check that descendant modified fees includes fee deltas from
|
||||||
# prioritisetransaction
|
# prioritisetransaction
|
||||||
self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)
|
self.nodes[0].prioritisetransaction(chain[-1], 0, 1000)
|
||||||
|
|
|
@ -183,16 +183,30 @@ UniValue getdifficulty(const UniValue& params, bool fHelp)
|
||||||
return GetDifficulty();
|
return GetDifficulty();
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue mempoolToJSON(bool fVerbose = false)
|
std::string EntryDescriptionString()
|
||||||
{
|
{
|
||||||
if (fVerbose)
|
return " \"size\" : n, (numeric) transaction size in bytes\n"
|
||||||
{
|
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
|
||||||
LOCK(mempool.cs);
|
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
|
||||||
UniValue o(UniValue::VOBJ);
|
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
|
||||||
BOOST_FOREACH(const CTxMemPoolEntry& e, mempool.mapTx)
|
" \"height\" : n, (numeric) block height when transaction entered pool\n"
|
||||||
{
|
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
|
||||||
const uint256& hash = e.GetTx().GetHash();
|
" \"currentpriority\" : n, (numeric) transaction priority now\n"
|
||||||
UniValue info(UniValue::VOBJ);
|
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
|
||||||
|
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
|
||||||
|
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
|
||||||
|
" \"ancestorcount\" : n, (numeric) number of in-mempool ancestor transactions (including this one)\n"
|
||||||
|
" \"ancestorsize\" : n, (numeric) size of in-mempool ancestors (including this one)\n"
|
||||||
|
" \"ancestorfees\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one)\n"
|
||||||
|
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
|
||||||
|
" \"transactionid\", (string) parent transaction id\n"
|
||||||
|
" ... ]\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void entryToJSON(UniValue &info, const CTxMemPoolEntry &e)
|
||||||
|
{
|
||||||
|
AssertLockHeld(mempool.cs);
|
||||||
|
|
||||||
info.push_back(Pair("size", (int)e.GetTxSize()));
|
info.push_back(Pair("size", (int)e.GetTxSize()));
|
||||||
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
|
info.push_back(Pair("fee", ValueFromAmount(e.GetFee())));
|
||||||
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
|
info.push_back(Pair("modifiedfee", ValueFromAmount(e.GetModifiedFee())));
|
||||||
|
@ -203,6 +217,9 @@ UniValue mempoolToJSON(bool fVerbose = false)
|
||||||
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
|
info.push_back(Pair("descendantcount", e.GetCountWithDescendants()));
|
||||||
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
|
info.push_back(Pair("descendantsize", e.GetSizeWithDescendants()));
|
||||||
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
|
info.push_back(Pair("descendantfees", e.GetModFeesWithDescendants()));
|
||||||
|
info.push_back(Pair("ancestorcount", e.GetCountWithAncestors()));
|
||||||
|
info.push_back(Pair("ancestorsize", e.GetSizeWithAncestors()));
|
||||||
|
info.push_back(Pair("ancestorfees", e.GetModFeesWithAncestors()));
|
||||||
const CTransaction& tx = e.GetTx();
|
const CTransaction& tx = e.GetTx();
|
||||||
set<string> setDepends;
|
set<string> setDepends;
|
||||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||||
|
@ -218,6 +235,19 @@ UniValue mempoolToJSON(bool fVerbose = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
info.push_back(Pair("depends", depends));
|
info.push_back(Pair("depends", depends));
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue mempoolToJSON(bool fVerbose = false)
|
||||||
|
{
|
||||||
|
if (fVerbose)
|
||||||
|
{
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
UniValue o(UniValue::VOBJ);
|
||||||
|
BOOST_FOREACH(const CTxMemPoolEntry& e, mempool.mapTx)
|
||||||
|
{
|
||||||
|
const uint256& hash = e.GetTx().GetHash();
|
||||||
|
UniValue info(UniValue::VOBJ);
|
||||||
|
entryToJSON(info, e);
|
||||||
o.push_back(Pair(hash.ToString(), info));
|
o.push_back(Pair(hash.ToString(), info));
|
||||||
}
|
}
|
||||||
return o;
|
return o;
|
||||||
|
@ -251,20 +281,8 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
|
||||||
"\nResult: (for verbose = true):\n"
|
"\nResult: (for verbose = true):\n"
|
||||||
"{ (json object)\n"
|
"{ (json object)\n"
|
||||||
" \"transactionid\" : { (json object)\n"
|
" \"transactionid\" : { (json object)\n"
|
||||||
" \"size\" : n, (numeric) transaction size in bytes\n"
|
+ EntryDescriptionString()
|
||||||
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n"
|
+ " }, ...\n"
|
||||||
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority\n"
|
|
||||||
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
|
|
||||||
" \"height\" : n, (numeric) block height when transaction entered pool\n"
|
|
||||||
" \"startingpriority\" : n, (numeric) priority when transaction entered pool\n"
|
|
||||||
" \"currentpriority\" : n, (numeric) transaction priority now\n"
|
|
||||||
" \"descendantcount\" : n, (numeric) number of in-mempool descendant transactions (including this one)\n"
|
|
||||||
" \"descendantsize\" : n, (numeric) size of in-mempool descendants (including this one)\n"
|
|
||||||
" \"descendantfees\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one)\n"
|
|
||||||
" \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n"
|
|
||||||
" \"transactionid\", (string) parent transaction id\n"
|
|
||||||
" ... ]\n"
|
|
||||||
" }, ...\n"
|
|
||||||
"}\n"
|
"}\n"
|
||||||
"\nExamples\n"
|
"\nExamples\n"
|
||||||
+ HelpExampleCli("getrawmempool", "true")
|
+ HelpExampleCli("getrawmempool", "true")
|
||||||
|
@ -280,6 +298,167 @@ UniValue getrawmempool(const UniValue& params, bool fHelp)
|
||||||
return mempoolToJSON(fVerbose);
|
return mempoolToJSON(fVerbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue getmempoolancestors(const UniValue& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() < 1 || params.size() > 2) {
|
||||||
|
throw runtime_error(
|
||||||
|
"getmempoolancestors txid (verbose)\n"
|
||||||
|
"\nIf txid is in the mempool, returns all in-mempool ancestors.\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
|
||||||
|
"2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
|
||||||
|
"\nResult (for verbose=false):\n"
|
||||||
|
"[ (json array of strings)\n"
|
||||||
|
" \"transactionid\" (string) The transaction id of an in-mempool ancestor transaction\n"
|
||||||
|
" ,...\n"
|
||||||
|
"]\n"
|
||||||
|
"\nResult (for verbose=true):\n"
|
||||||
|
"{ (json object)\n"
|
||||||
|
" \"transactionid\" : { (json object)\n"
|
||||||
|
+ EntryDescriptionString()
|
||||||
|
+ " }, ...\n"
|
||||||
|
"}\n"
|
||||||
|
"\nExamples\n"
|
||||||
|
+ HelpExampleCli("getmempoolancestors", "\"mytxid\"")
|
||||||
|
+ HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fVerbose = false;
|
||||||
|
if (params.size() > 1)
|
||||||
|
fVerbose = params[1].get_bool();
|
||||||
|
|
||||||
|
uint256 hash = ParseHashV(params[0], "parameter 1");
|
||||||
|
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
|
||||||
|
CTxMemPool::txiter it = mempool.mapTx.find(hash);
|
||||||
|
if (it == mempool.mapTx.end()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
|
||||||
|
}
|
||||||
|
|
||||||
|
CTxMemPool::setEntries setAncestors;
|
||||||
|
uint64_t noLimit = std::numeric_limits<uint64_t>::max();
|
||||||
|
std::string dummy;
|
||||||
|
mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
|
||||||
|
|
||||||
|
if (!fVerbose) {
|
||||||
|
UniValue o(UniValue::VARR);
|
||||||
|
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors) {
|
||||||
|
o.push_back(ancestorIt->GetTx().GetHash().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
} else {
|
||||||
|
UniValue o(UniValue::VOBJ);
|
||||||
|
BOOST_FOREACH(CTxMemPool::txiter ancestorIt, setAncestors) {
|
||||||
|
const CTxMemPoolEntry &e = *ancestorIt;
|
||||||
|
const uint256& hash = e.GetTx().GetHash();
|
||||||
|
UniValue info(UniValue::VOBJ);
|
||||||
|
entryToJSON(info, e);
|
||||||
|
o.push_back(Pair(hash.ToString(), info));
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue getmempooldescendants(const UniValue& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() < 1 || params.size() > 2) {
|
||||||
|
throw runtime_error(
|
||||||
|
"getmempooldescendants txid (verbose)\n"
|
||||||
|
"\nIf txid is in the mempool, returns all in-mempool descendants.\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
|
||||||
|
"2. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n"
|
||||||
|
"\nResult (for verbose=false):\n"
|
||||||
|
"[ (json array of strings)\n"
|
||||||
|
" \"transactionid\" (string) The transaction id of an in-mempool descendant transaction\n"
|
||||||
|
" ,...\n"
|
||||||
|
"]\n"
|
||||||
|
"\nResult (for verbose=true):\n"
|
||||||
|
"{ (json object)\n"
|
||||||
|
" \"transactionid\" : { (json object)\n"
|
||||||
|
+ EntryDescriptionString()
|
||||||
|
+ " }, ...\n"
|
||||||
|
"}\n"
|
||||||
|
"\nExamples\n"
|
||||||
|
+ HelpExampleCli("getmempooldescendants", "\"mytxid\"")
|
||||||
|
+ HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fVerbose = false;
|
||||||
|
if (params.size() > 1)
|
||||||
|
fVerbose = params[1].get_bool();
|
||||||
|
|
||||||
|
uint256 hash = ParseHashV(params[0], "parameter 1");
|
||||||
|
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
|
||||||
|
CTxMemPool::txiter it = mempool.mapTx.find(hash);
|
||||||
|
if (it == mempool.mapTx.end()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
|
||||||
|
}
|
||||||
|
|
||||||
|
CTxMemPool::setEntries setDescendants;
|
||||||
|
mempool.CalculateDescendants(it, setDescendants);
|
||||||
|
// CTxMemPool::CalculateDescendants will include the given tx
|
||||||
|
setDescendants.erase(it);
|
||||||
|
|
||||||
|
if (!fVerbose) {
|
||||||
|
UniValue o(UniValue::VARR);
|
||||||
|
BOOST_FOREACH(CTxMemPool::txiter descendantIt, setDescendants) {
|
||||||
|
o.push_back(descendantIt->GetTx().GetHash().ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
} else {
|
||||||
|
UniValue o(UniValue::VOBJ);
|
||||||
|
BOOST_FOREACH(CTxMemPool::txiter descendantIt, setDescendants) {
|
||||||
|
const CTxMemPoolEntry &e = *descendantIt;
|
||||||
|
const uint256& hash = e.GetTx().GetHash();
|
||||||
|
UniValue info(UniValue::VOBJ);
|
||||||
|
entryToJSON(info, e);
|
||||||
|
o.push_back(Pair(hash.ToString(), info));
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue getmempoolentry(const UniValue& params, bool fHelp)
|
||||||
|
{
|
||||||
|
if (fHelp || params.size() != 1) {
|
||||||
|
throw runtime_error(
|
||||||
|
"getmempoolentry txid\n"
|
||||||
|
"\nReturns mempool data for given transaction\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"txid\" (string, required) The transaction id (must be in mempool)\n"
|
||||||
|
"\nResult:\n"
|
||||||
|
"{ (json object)\n"
|
||||||
|
+ EntryDescriptionString()
|
||||||
|
+ "}\n"
|
||||||
|
"\nExamples\n"
|
||||||
|
+ HelpExampleCli("getmempoolentry", "\"mytxid\"")
|
||||||
|
+ HelpExampleRpc("getmempoolentry", "\"mytxid\"")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 hash = ParseHashV(params[0], "parameter 1");
|
||||||
|
|
||||||
|
LOCK(mempool.cs);
|
||||||
|
|
||||||
|
CTxMemPool::txiter it = mempool.mapTx.find(hash);
|
||||||
|
if (it == mempool.mapTx.end()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
|
||||||
|
}
|
||||||
|
|
||||||
|
const CTxMemPoolEntry &e = *it;
|
||||||
|
UniValue info(UniValue::VOBJ);
|
||||||
|
entryToJSON(info, e);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
UniValue getblockhash(const UniValue& params, bool fHelp)
|
UniValue getblockhash(const UniValue& params, bool fHelp)
|
||||||
{
|
{
|
||||||
if (fHelp || params.size() != 1)
|
if (fHelp || params.size() != 1)
|
||||||
|
@ -1004,6 +1183,9 @@ static const CRPCCommand commands[] =
|
||||||
{ "blockchain", "getblockheader", &getblockheader, true },
|
{ "blockchain", "getblockheader", &getblockheader, true },
|
||||||
{ "blockchain", "getchaintips", &getchaintips, true },
|
{ "blockchain", "getchaintips", &getchaintips, true },
|
||||||
{ "blockchain", "getdifficulty", &getdifficulty, true },
|
{ "blockchain", "getdifficulty", &getdifficulty, true },
|
||||||
|
{ "blockchain", "getmempoolancestors", &getmempoolancestors, true },
|
||||||
|
{ "blockchain", "getmempooldescendants", &getmempooldescendants, true },
|
||||||
|
{ "blockchain", "getmempoolentry", &getmempoolentry, true },
|
||||||
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
|
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true },
|
||||||
{ "blockchain", "getrawmempool", &getrawmempool, true },
|
{ "blockchain", "getrawmempool", &getrawmempool, true },
|
||||||
{ "blockchain", "gettxout", &gettxout, true },
|
{ "blockchain", "gettxout", &gettxout, true },
|
||||||
|
|
|
@ -102,6 +102,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "prioritisetransaction", 2 },
|
{ "prioritisetransaction", 2 },
|
||||||
{ "setban", 2 },
|
{ "setban", 2 },
|
||||||
{ "setban", 3 },
|
{ "setban", 3 },
|
||||||
|
{ "getmempoolancestors", 1 },
|
||||||
|
{ "getmempooldescendants", 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
class CRPCConvertTable
|
class CRPCConvertTable
|
||||||
|
|
Loading…
Reference in a new issue