Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,11 @@ class PeerManagerImpl final : public PeerManager
ServiceFlags GetDesirableServiceFlags(ServiceFlags services) const override;
int GetNumberOfPeersWithValidatedDownloads() const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

size_t ExtraTxnForCompactCount() const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
size_t ExtraTxnForCompactBytes() const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
size_t ExtraTxnForCompactMemoryUsage() const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main);


private:
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_msgproc_mutex);
Expand Down Expand Up @@ -959,8 +964,8 @@ class PeerManagerImpl final : public PeerManager
std::vector<CTransactionRef> vExtraTxnForCompact GUARDED_BY(g_msgproc_mutex);
/** Offset into vExtraTxnForCompact to insert the next tx */
size_t vExtraTxnForCompactIt GUARDED_BY(g_msgproc_mutex) = 0;
size_t vExtraTxnForCompactCount GUARDED_BY(g_msgproc_mutex) = 0;
size_t blockreconstructionextratxn_memusage{0};

/** Check whether the last unknown block a peer advertised is not yet known. */
void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Update tracking information about which blocks a peer is assumed to have. */
Expand Down Expand Up @@ -1063,6 +1068,25 @@ CNodeState* PeerManagerImpl::State(NodeId pnode)
return const_cast<CNodeState*>(std::as_const(*this).State(pnode));
}

size_t PeerManagerImpl::ExtraTxnForCompactCount() const {
AssertLockHeld(::cs_main);
return vExtraTxnForCompactCount;
}
size_t PeerManagerImpl::ExtraTxnForCompactBytes() const {
AssertLockHeld(::cs_main);
size_t total_bytes = 0;
for (const auto& tx : vExtraTxnForCompact) {
if (tx) {
total_bytes += tx->GetTotalSize();
}
}
return total_bytes;
}
size_t PeerManagerImpl::ExtraTxnForCompactMemoryUsage() const {
AssertLockHeld(::cs_main);
return blockreconstructionextratxn_memusage;
}

/**
* Whether the peer supports the address. For example, a peer that does not
* implement BIP155 cannot receive Tor v3 addresses because it requires
Expand Down Expand Up @@ -1780,15 +1804,22 @@ void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx, c

{
auto& entry = vExtraTxnForCompact[vExtraTxnForCompactIt];
if (entry) blockreconstructionextratxn_memusage -= RecursiveDynamicUsage(*entry);
if (entry) {
blockreconstructionextratxn_memusage -= RecursiveDynamicUsage(*entry);
vExtraTxnForCompactCount--;
}
entry = tx;
blockreconstructionextratxn_memusage += tx_dynamic_usage;
vExtraTxnForCompactCount++;
}
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % m_opts.max_extra_txs;

while (blockreconstructionextratxn_memusage > m_opts.max_extra_txs_size) {
auto& entry = vExtraTxnForCompact[vExtraTxnForCompactIt];
if (entry) blockreconstructionextratxn_memusage -= RecursiveDynamicUsage(*entry);
if (entry) {
blockreconstructionextratxn_memusage -= RecursiveDynamicUsage(*entry);
vExtraTxnForCompactCount--;
}
entry.reset();
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % m_opts.max_extra_txs;
}
Expand Down Expand Up @@ -5989,4 +6020,4 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
} // release cs_main
MaybeSendFeefilter(*pto, *peer, current_time);
return true;
}
}
15 changes: 14 additions & 1 deletion src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
/** Get peer manager info. */
virtual PeerManagerInfo GetInfo() const = 0;

/**
* Get the number of transactions currently stored in the extra pool.
*/
virtual size_t ExtraTxnForCompactCount() const = 0;
/**
* Get the total size of transactions in extra pool.
*/
virtual size_t ExtraTxnForCompactBytes() const = 0;
/**
* Get the total memory usage of the extra pool.
*/
virtual size_t ExtraTxnForCompactMemoryUsage() const = 0;

/** Relay transaction to all peers. */
virtual void RelayTransaction(const uint256& txid, const uint256& wtxid) = 0;

Expand Down Expand Up @@ -165,4 +178,4 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
virtual int GetNumberOfPeersWithValidatedDownloads() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) = 0;
};

#endif // BITCOIN_NET_PROCESSING_H
#endif // BITCOIN_NET_PROCESSING_H
36 changes: 35 additions & 1 deletion src/rpc/mempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,39 @@ static RPCHelpMan getmempoolinfo()
};
}

static RPCHelpMan getextrapoolinfo()
{
return RPCHelpMan{"getextrapoolinfo",
"Returns details about the extra pool used for compact block reconstruction.\n"
"This pool stores recent transactions to help reconstruct compact blocks without requesting missing transactions from peers.\n",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "size", "Number of transactions in the extra pool"},
{RPCResult::Type::NUM, "bytes", "Sum of virtual transaction size for all transactions in the extra pool"},
{RPCResult::Type::NUM, "usage", "Total memory usage in bytes for the extra pool"},
}},
RPCExamples{
HelpExampleCli("getextrapoolinfo", "") +
HelpExampleRpc("getextrapoolinfo", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const NodeContext& node = EnsureAnyNodeContext(request.context);
PeerManager& peerman = EnsurePeerman(node);
LOCK(::cs_main);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holding cs_main lock while calling the potentially slow count function could block other threads unnecessarily.


UniValue ret(UniValue::VOBJ);
ret.pushKV("size", (int64_t)peerman.ExtraTxnForCompactCount());
ret.pushKV("bytes", (int64_t)peerman.ExtraTxnForCompactBytes());
ret.pushKV("usage", (int64_t)peerman.ExtraTxnForCompactMemoryUsage());

return ret;
},
};
}

static RPCHelpMan importmempool()
{
return RPCHelpMan{
Expand Down Expand Up @@ -1480,6 +1513,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
{"blockchain", &getmempoolentry},
{"blockchain", &gettxspendingprevout},
{"blockchain", &getmempoolinfo},
{"blockchain", &getextrapoolinfo},
{"blockchain", &getrawmempool},
{"blockchain", &importmempool},
{"blockchain", &savemempool},
Expand All @@ -1491,4 +1525,4 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
}
}
56 changes: 56 additions & 0 deletions test/functional/rpc_getextrapoolinfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
# Copyright (c) 2025 Luke Dashjr
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the getextrapoolinfo RPC command."""

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
from test_framework.messages import msg_tx, tx_from_hex
from test_framework.p2p import P2PInterface
from test_framework.script import CScript, OP_TRUE
from test_framework.messages import CTxOut


class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()


class RPCExtraPoolInfoTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2

def run_test(self):
self.wallet0 = MiniWallet(self.nodes[0])

self.connect_nodes(0, 1)

self.p2p_conn = self.nodes[1].add_p2p_connection(TestP2PConn())

info = self.nodes[1].getextrapoolinfo()
assert_equal(info['count'], 0)
assert_equal(info['bytes'], 0)
assert_equal(info['memory_usage'], 0)

dust_tx = self.wallet0.create_self_transfer()
dust_amount = 100 # Below dust threshold
dust_script = CScript([OP_TRUE])
dust_tx['tx'].vout.append(CTxOut(dust_amount, dust_script))
dust_tx['tx'].vout[0].nValue -= dust_amount
dust_tx['tx'].rehash()

tx_obj = tx_from_hex(dust_tx['tx'].serialize().hex())
self.p2p_conn.send_message(msg_tx(tx_obj))
self.p2p_conn.sync_with_ping()

info = self.nodes[1].getextrapoolinfo()
print(info)
assert_equal(info['count'] > 0, True)
assert_equal(info['bytes'] > 0, True)
assert_equal(info['memory_usage'] > 0, True)


if __name__ == '__main__':
RPCExtraPoolInfoTest(__file__).main()