Expose cluster information via rpc

Co-authored-by: glozow <gloriajzhao@gmail.com>
This commit is contained in:
Suhas Daftuar 2023-04-19 17:12:04 -04:00
parent 72e74e0d42
commit 21693f031a
6 changed files with 171 additions and 1 deletions

View File

@ -258,6 +258,23 @@ static RPCHelpMan testmempoolaccept()
};
}
static std::vector<RPCResult> ClusterDescription()
{
return {
RPCResult{RPCResult::Type::NUM, "weight", "total sigops-adjusted weight (as defined in BIP 141 and modified by '-bytespersigop'"},
RPCResult{RPCResult::Type::NUM, "txcount", "number of transactions"},
RPCResult{RPCResult::Type::ARR, "txs", "transactions in this cluster in mining order",
{RPCResult{RPCResult::Type::OBJ, "txentry", "",
{
RPCResult{RPCResult::Type::STR_HEX, "txid", "the transaction id"},
RPCResult{RPCResult::Type::NUM, "chunkfee", "fee of the chunk containing this tx"},
RPCResult{RPCResult::Type::NUM, "chunkweight", "sigops-adjusted weight of the chunk containing this transaction"}
}
}}
}
};
}
static std::vector<RPCResult> MempoolEntryDescription()
{
return {
@ -269,6 +286,7 @@ static std::vector<RPCResult> MempoolEntryDescription()
RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
RPCResult{RPCResult::Type::NUM, "chunkweight", "sigops-adjusted weight (as defined in BIP 141 and modified by '-bytespersigop') of this transaction's chunk"},
RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
RPCResult{RPCResult::Type::OBJ, "fees", "",
{
@ -276,6 +294,7 @@ static std::vector<RPCResult> MempoolEntryDescription()
RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
RPCResult{RPCResult::Type::STR_AMOUNT, "chunk", "transaction fees of chunk, denominated in " + CURRENCY_UNIT},
}},
RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
{RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
@ -286,6 +305,27 @@ static std::vector<RPCResult> MempoolEntryDescription()
};
}
static void clusterToJSON(const CTxMemPool& pool, UniValue& info, std::vector<const CTxMemPoolEntry *> cluster) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
{
AssertLockHeld(pool.cs);
int total_weight{0};
for (const auto& tx : cluster) {
total_weight += tx->GetAdjustedWeight();
}
info.pushKV("weight", total_weight);
info.pushKV("txcount", (int)cluster.size());
UniValue txs(UniValue::VARR);
for (const auto& tx : cluster) {
UniValue txentry(UniValue::VOBJ);
auto feerate = pool.GetMainChunkFeerate(*tx);
txentry.pushKV("txid", tx->GetTx().GetHash().ToString());
txentry.pushKV("chunkfee", ValueFromAmount((int)feerate.fee));
txentry.pushKV("chunkweight", feerate.size);
txs.push_back(txentry);
}
info.pushKV("txs", txs);
}
static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
{
AssertLockHeld(pool.cs);
@ -302,12 +342,15 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
info.pushKV("ancestorcount", ancestor_count);
info.pushKV("ancestorsize", ancestor_size);
info.pushKV("wtxid", e.GetTx().GetWitnessHash().ToString());
auto feerate = pool.GetMainChunkFeerate(e);
info.pushKV("chunkweight", feerate.size);
UniValue fees(UniValue::VOBJ);
fees.pushKV("base", ValueFromAmount(e.GetFee()));
fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
fees.pushKV("ancestor", ValueFromAmount(ancestor_fees));
fees.pushKV("descendant", ValueFromAmount(descendant_fees));
fees.pushKV("chunk", ValueFromAmount((int)feerate.fee));
info.pushKV("fees", std::move(fees));
const CTransaction& tx = e.GetTx();
@ -384,6 +427,49 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempoo
}
}
static RPCHelpMan getmempoolfeeratediagram()
{
return RPCHelpMan{"getmempoolfeeratediagram",
"Returns the feerate diagram for the whole mempool.",
{},
{
RPCResult{"mempool chunks",
RPCResult::Type::ARR, "", "",
{
{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "weight", "cumulative sigops-adjusted weight"},
{RPCResult::Type::NUM, "fee", "cumulative fee"}
}
}
}
}
},
RPCExamples{
HelpExampleCli("getmempoolfeeratediagram", "")
+ HelpExampleRpc("getmempoolfeeratediagram", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
UniValue result(UniValue::VARR);
auto diagram = mempool.GetFeerateDiagram();
for (auto f : diagram) {
UniValue o(UniValue::VOBJ);
o.pushKV("weight", f.size);
o.pushKV("fee", ValueFromAmount(f.fee));
result.push_back(o);
}
return result;
}
};
}
static RPCHelpMan getrawmempool()
{
return RPCHelpMan{
@ -561,6 +647,35 @@ static RPCHelpMan getmempooldescendants()
};
}
static RPCHelpMan getmempoolcluster()
{
return RPCHelpMan{"getmempoolcluster",
"Returns mempool data for given cluster\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid of a transaction in the cluster"},
},
RPCResult{
RPCResult::Type::OBJ, "", "", ClusterDescription()},
RPCExamples{
HelpExampleCli("getmempoolcluster", "txid")
+ HelpExampleRpc("getmempoolcluster", "txid")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
uint256 hash = ParseHashV(request.params[0], "parameter 1");
const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
LOCK(mempool.cs);
auto cluster = mempool.GetCluster(Txid::FromUint256(hash));
UniValue info(UniValue::VOBJ);
clusterToJSON(mempool, info, cluster);
return info;
},
};
}
static RPCHelpMan getmempoolentry()
{
return RPCHelpMan{
@ -694,6 +809,8 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool)
ret.pushKV("fullrbf", true);
ret.pushKV("permitbaremultisig", pool.m_opts.permit_bare_multisig);
ret.pushKV("maxdatacarriersize", pool.m_opts.max_datacarrier_bytes.value_or(0));
ret.pushKV("limitclustercount", pool.m_opts.limits.cluster_count);
ret.pushKV("limitclustersize", pool.m_opts.limits.cluster_size_vbytes);
return ret;
}
@ -718,6 +835,8 @@ static RPCHelpMan getmempoolinfo()
{RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection (DEPRECATED)"},
{RPCResult::Type::BOOL, "permitbaremultisig", "True if the mempool accepts transactions with bare multisig outputs"},
{RPCResult::Type::NUM, "maxdatacarriersize", "Maximum number of bytes that can be used by OP_RETURN outputs in the mempool"},
{RPCResult::Type::NUM, "limitclustercount", "Maximum number of transactions that can be in a cluster (configured by -limitclustercount)"},
{RPCResult::Type::NUM, "limitclustersize", "Maximum size of a cluster in virtual bytes (configured by -limitclustersize)"},
}},
RPCExamples{
HelpExampleCli("getmempoolinfo", "")
@ -1145,8 +1264,10 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
{"blockchain", &getmempoolancestors},
{"blockchain", &getmempooldescendants},
{"blockchain", &getmempoolentry},
{"blockchain", &getmempoolcluster},
{"blockchain", &gettxspendingprevout},
{"blockchain", &getmempoolinfo},
{"hidden", &getmempoolfeeratediagram},
{"blockchain", &getrawmempool},
{"blockchain", &importmempool},
{"blockchain", &savemempool},

View File

@ -136,6 +136,8 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"getmempoolancestors",
"getmempooldescendants",
"getmempoolentry",
"getmempoolfeeratediagram",
"getmempoolcluster",
"getmempoolinfo",
"getmininginfo",
"getnettotals",

View File

@ -1010,3 +1010,25 @@ bool CTxMemPool::ChangeSet::CheckMemPoolPolicyLimits()
return !m_pool->m_txgraph->IsOversized(TxGraph::Level::TOP);
}
std::vector<FeePerWeight> CTxMemPool::GetFeerateDiagram() const
{
FeePerWeight zero{};
std::vector<FeePerWeight> ret;
ret.emplace_back(zero);
StartBlockBuilding();
std::vector<CTxMemPoolEntry::CTxMemPoolEntryRef> dummy;
FeePerWeight last_selection = GetBlockBuilderChunk(dummy);
while (last_selection != FeePerWeight{}) {
last_selection += ret.back();
ret.emplace_back(last_selection);
IncludeBuilderChunk();
last_selection = GetBlockBuilderChunk(dummy);
}
StopBlockBuilding();
return ret;
}

View File

@ -406,6 +406,23 @@ public:
*/
void UpdateTransactionsFromBlock(const std::vector<Txid>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
std::vector<FeePerWeight> GetFeerateDiagram() const EXCLUSIVE_LOCKS_REQUIRED(cs);
FeePerWeight GetMainChunkFeerate(const CTxMemPoolEntry& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs) {
return m_txgraph->GetMainChunkFeerate(tx);
}
std::vector<const CTxMemPoolEntry*> GetCluster(Txid txid) const EXCLUSIVE_LOCKS_REQUIRED(cs) {
auto tx = GetIter(txid);
if (!tx) return {};
auto cluster = m_txgraph->GetCluster(**tx, TxGraph::Level::MAIN);
std::vector<const CTxMemPoolEntry*> ret;
ret.reserve(cluster.size());
for (const auto& tx : cluster) {
ret.emplace_back(static_cast<const CTxMemPoolEntry*>(tx));
}
return ret;
}
size_t GetUniqueClusterCount(const setEntries& iters_conflicting) const EXCLUSIVE_LOCKS_REQUIRED(cs) {
std::vector<const TxGraph::Ref *> entries;
entries.reserve(iters_conflicting.size());

View File

@ -128,7 +128,8 @@ class MempoolPersistTest(BitcoinTestFramework):
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
self.log.debug('Verify all fields are loaded correctly')
assert_equal(last_entry, self.nodes[0].getmempoolentry(txid=last_txid))
new_entry = self.nodes[0].getmempoolentry(txid=last_txid)
assert_equal({**last_entry, "clusterid": None}, {**new_entry, "clusterid": None})
self.nodes[0].sendrawtransaction(tx_prioritised_not_submitted['hex'])
entry_prioritised_before_restart = self.nodes[0].getmempoolentry(txid=tx_prioritised_not_submitted['txid'])
assert_equal(entry_prioritised_before_restart['fees']['base'] + Decimal('0.00009999'), entry_prioritised_before_restart['fees']['modified'])

View File

@ -101,14 +101,21 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))
self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))
raw_before[txid_a]["fees"]["descendant"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
# We expect tx_a to have a chunk fee that includes tx_b and tx_c.
raw_before[txid_a]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
raw_before[txid_b]["fees"]["modified"] += fee_delta_b
raw_before[txid_b]["fees"]["ancestor"] += fee_delta_b
raw_before[txid_b]["fees"]["descendant"] += fee_delta_b
# We also expect tx_b and tx_c to have their chunk fees modified too,
# since they chunk together.
raw_before[txid_b]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
raw_before[txid_c]["fees"]["modified"] += fee_delta_c_1 + fee_delta_c_2
raw_before[txid_c]["fees"]["ancestor"] += fee_delta_c_1 + fee_delta_c_2
raw_before[txid_c]["fees"]["descendant"] += fee_delta_c_1 + fee_delta_c_2
raw_before[txid_c]["fees"]["chunk"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
raw_before[txid_d]["fees"]["ancestor"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2
raw_after = self.nodes[0].getrawmempool(verbose=True)
# Don't bother comparing cluster ids, which are not meant to be stable.
assert_equal(raw_before[txid_a], raw_after[txid_a])
assert_equal(raw_before, raw_after)
assert_equal(self.nodes[0].getprioritisedtransactions(), {txid_b: {"fee_delta" : fee_delta_b*COIN, "in_mempool" : True, "modified_fee": int(fee_delta_b*COIN + COIN * tx_o_b["fee"])}, txid_c: {"fee_delta" : (fee_delta_c_1 + fee_delta_c_2)*COIN, "in_mempool" : True, "modified_fee": int((fee_delta_c_1 + fee_delta_c_2 ) * COIN + COIN * tx_o_c["fee"])}})