mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-31 10:41:08 +00:00
Expose cluster information via rpc
Co-authored-by: glozow <gloriajzhao@gmail.com>
This commit is contained in:
parent
72e74e0d42
commit
21693f031a
@ -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},
|
||||
|
||||
@ -136,6 +136,8 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
||||
"getmempoolancestors",
|
||||
"getmempooldescendants",
|
||||
"getmempoolentry",
|
||||
"getmempoolfeeratediagram",
|
||||
"getmempoolcluster",
|
||||
"getmempoolinfo",
|
||||
"getmininginfo",
|
||||
"getnettotals",
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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'])
|
||||
|
||||
@ -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"])}})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user