Skip to content

Commit 5ea335a

Browse files
committed
Merge bitcoin/bitcoin#30793: rpc: add getorphantxs
98c1536 test: add getorphantxs tests (tdb3) 93f48fc test: add tx_in_orphanage() (tdb3) 34a9c10 rpc: add getorphantxs (tdb3) f511ff3 refactor: move verbosity parsing to rpc/util (tdb3) 532491f net: add GetOrphanTransactions() to PeerManager (tdb3) 91b65ad refactor: add OrphanTxBase for external use (tdb3) Pull request description: This PR adds a new hidden rpc, `getorphantxs`, that provides the caller with a list of orphan transactions. This rpc may be helpful when checking orphan behavior/scenarios (e.g. in tests like `p2p_orphan_handling`) or providing additional data for statistics/visualization. ``` getorphantxs ( verbosity ) Shows transactions in the tx orphanage. EXPERIMENTAL warning: this call may be changed in future releases. Arguments: 1. verbosity (numeric, optional, default=0) 0 for an array of txids (may contain duplicates), 1 for an array of objects with tx details, and 2 for details from (1) and tx hex Result (for verbose = 0): [ (json array) "hex", (string) The transaction hash in hex ... ] Result (for verbose = 1): [ (json array) { (json object) "txid" : "hex", (string) The transaction hash in hex "wtxid" : "hex", (string) The transaction witness hash in hex "bytes" : n, (numeric) The serialized transaction size in bytes "vsize" : n, (numeric) The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. "weight" : n, (numeric) The transaction weight as defined in BIP 141. "expiration" : xxx, (numeric) The orphan expiration time expressed in UNIX epoch time "from" : [ (json array) n, (numeric) Peer ID ... ] }, ... ] Result (for verbose = 2): [ (json array) { (json object) "txid" : "hex", (string) The transaction hash in hex "wtxid" : "hex", (string) The transaction witness hash in hex "bytes" : n, (numeric) The serialized transaction size in bytes "vsize" : n, (numeric) The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted. "weight" : n, (numeric) The transaction weight as defined in BIP 141. "expiration" : xxx, (numeric) The orphan expiration time expressed in UNIX epoch time "from" : [ (json array) n, (numeric) Peer ID ... ], "hex" : "hex" (string) The serialized, hex-encoded transaction data }, ... ] Examples: > bitcoin-cli getorphantxs 2 > curl --user myusername --data-binary '{"jsonrpc": "2.0", "id": "curltest", "method": "getorphantxs", "params": [2]}' -H 'content-type: application/json' http://127.0.0.1:8332/ ``` ``` $ build/src/bitcoin-cli getorphantxs 2 [ { "txid": "50128aac5deab548228d74d846675ad4def91cd92453d81a2daa778df12a63f2", "wtxid": "bb61659336f59fcf23acb47c05dc4bbea63ab533a98c412f3a12cb813308d52c", "bytes": 133, "vsize": 104, "weight": 415, "expiration": 1725663854, "from": [ 1 ], "hex": "020000000001010b992959eaa2018bbf31a4a3f9aa30896a8144dbd5cfaf263bf07c0845a3a6620000000000000000000140fe042a010000002251202913b252fe537830f843bfdc5fa7d20ba48639a87c86ff837b92d083c55ad7c102015121c0000000000000000000000000000000000000000000000000000000000000000100000000" }, { "txid": "330bb7f701604a40ade20aa129e9a3eb8a7bf024e599084ca1026d3222b9f8a1", "wtxid": "b7651f7d4c1a40c4d01f6a1e43a121967091fa0f56bb460146c1c5c068e824f6", "bytes": 133, "vsize": 104, "weight": 415, "expiration": 1725663854, "from": [ 2 ], "hex": "020000000001013600adfe41e0ebd2454838963d270916d2b47239c9eebb93a992b720d3589a080000000000000000000140fe042a010000002251202913b252fe537830f843bfdc5fa7d20ba48639a87c86ff837b92d083c55ad7c102015121c0000000000000000000000000000000000000000000000000000000000000000100000000" } ] ``` ACKs for top commit: glozow: reACK 98c1536 hodlinator: re-ACK 98c1536 danielabrozzoni: ACK 98c1536 pablomartin4btc: tACK 98c1536 itornaza: reACK 98c1536 Tree-SHA512: 66075f9faa83748350b87397302100d08af92cbef5fadb27f2b4903f028c08020bf34a23e17262b41abb3f379ca9f46cf6cd5459b8681f2b83bffbbaf3c03ff9
2 parents 76e2e8a + 98c1536 commit 5ea335a

File tree

14 files changed

+294
-20
lines changed

14 files changed

+294
-20
lines changed

src/net_processing.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ class PeerManagerImpl final : public PeerManager
515515
std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override
516516
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
517517
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
518+
std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
518519
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
519520
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
520521
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -1917,6 +1918,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
19171918
return true;
19181919
}
19191920

1921+
std::vector<TxOrphanage::OrphanTxBase> PeerManagerImpl::GetOrphanTransactions()
1922+
{
1923+
LOCK(m_tx_download_mutex);
1924+
return m_orphanage.GetOrphanTransactions();
1925+
}
1926+
19201927
PeerManagerInfo PeerManagerImpl::GetInfo() const
19211928
{
19221929
return PeerManagerInfo{

src/net_processing.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define BITCOIN_NET_PROCESSING_H
88

99
#include <net.h>
10+
#include <txorphanage.h>
1011
#include <validationinterface.h>
1112

1213
#include <chrono>
@@ -99,6 +100,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
99100
/** Get statistics from node state */
100101
virtual bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const = 0;
101102

103+
virtual std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() = 0;
104+
102105
/** Get peer manager info. */
103106
virtual PeerManagerInfo GetInfo() const = 0;
104107

src/rpc/blockchain.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -766,14 +766,7 @@ static RPCHelpMan getblock()
766766
{
767767
uint256 hash(ParseHashV(request.params[0], "blockhash"));
768768

769-
int verbosity = 1;
770-
if (!request.params[1].isNull()) {
771-
if (request.params[1].isBool()) {
772-
verbosity = request.params[1].get_bool() ? 1 : 0;
773-
} else {
774-
verbosity = request.params[1].getInt<int>();
775-
}
776-
}
769+
int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/1)};
777770

778771
const CBlockIndex* pblockindex;
779772
const CBlockIndex* tip;

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
254254
{ "keypoolrefill", 0, "newsize" },
255255
{ "getrawmempool", 0, "verbose" },
256256
{ "getrawmempool", 1, "mempool_sequence" },
257+
{ "getorphantxs", 0, "verbosity" },
258+
{ "getorphantxs", 0, "verbose" },
257259
{ "estimatesmartfee", 0, "conf_target" },
258260
{ "estimaterawfee", 0, "conf_target" },
259261
{ "estimaterawfee", 1, "threshold" },

src/rpc/mempool.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
#include <node/mempool_persist.h>
99

1010
#include <chainparams.h>
11+
#include <consensus/validation.h>
1112
#include <core_io.h>
1213
#include <kernel/mempool_entry.h>
14+
#include <net_processing.h>
1315
#include <node/mempool_persist_args.h>
1416
#include <node/types.h>
1517
#include <policy/rbf.h>
@@ -24,6 +26,7 @@
2426
#include <util/moneystr.h>
2527
#include <util/strencodings.h>
2628
#include <util/time.h>
29+
#include <util/vector.h>
2730

2831
#include <utility>
2932

@@ -812,6 +815,104 @@ static RPCHelpMan savemempool()
812815
};
813816
}
814817

818+
static std::vector<RPCResult> OrphanDescription()
819+
{
820+
return {
821+
RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
822+
RPCResult{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
823+
RPCResult{RPCResult::Type::NUM, "bytes", "The serialized transaction size in bytes"},
824+
RPCResult{RPCResult::Type::NUM, "vsize", "The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
825+
RPCResult{RPCResult::Type::NUM, "weight", "The transaction weight as defined in BIP 141."},
826+
RPCResult{RPCResult::Type::NUM_TIME, "expiration", "The orphan expiration time expressed in " + UNIX_EPOCH_TIME},
827+
RPCResult{RPCResult::Type::ARR, "from", "",
828+
{
829+
RPCResult{RPCResult::Type::NUM, "peer_id", "Peer ID"},
830+
}},
831+
};
832+
}
833+
834+
static UniValue OrphanToJSON(const TxOrphanage::OrphanTxBase& orphan)
835+
{
836+
UniValue o(UniValue::VOBJ);
837+
o.pushKV("txid", orphan.tx->GetHash().ToString());
838+
o.pushKV("wtxid", orphan.tx->GetWitnessHash().ToString());
839+
o.pushKV("bytes", orphan.tx->GetTotalSize());
840+
o.pushKV("vsize", GetVirtualTransactionSize(*orphan.tx));
841+
o.pushKV("weight", GetTransactionWeight(*orphan.tx));
842+
o.pushKV("expiration", int64_t{TicksSinceEpoch<std::chrono::seconds>(orphan.nTimeExpire)});
843+
UniValue from(UniValue::VARR);
844+
from.push_back(orphan.fromPeer); // only one fromPeer for now
845+
o.pushKV("from", from);
846+
return o;
847+
}
848+
849+
static RPCHelpMan getorphantxs()
850+
{
851+
return RPCHelpMan{"getorphantxs",
852+
"\nShows transactions in the tx orphanage.\n"
853+
"\nEXPERIMENTAL warning: this call may be changed in future releases.\n",
854+
{
855+
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for an array of txids (may contain duplicates), 1 for an array of objects with tx details, and 2 for details from (1) and tx hex",
856+
RPCArgOptions{.skip_type_check = true}},
857+
},
858+
{
859+
RPCResult{"for verbose = 0",
860+
RPCResult::Type::ARR, "", "",
861+
{
862+
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
863+
}},
864+
RPCResult{"for verbose = 1",
865+
RPCResult::Type::ARR, "", "",
866+
{
867+
{RPCResult::Type::OBJ, "", "", OrphanDescription()},
868+
}},
869+
RPCResult{"for verbose = 2",
870+
RPCResult::Type::ARR, "", "",
871+
{
872+
{RPCResult::Type::OBJ, "", "",
873+
Cat<std::vector<RPCResult>>(
874+
OrphanDescription(),
875+
{{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded transaction data"}}
876+
)
877+
},
878+
}},
879+
},
880+
RPCExamples{
881+
HelpExampleCli("getorphantxs", "2")
882+
+ HelpExampleRpc("getorphantxs", "2")
883+
},
884+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
885+
{
886+
const NodeContext& node = EnsureAnyNodeContext(request.context);
887+
PeerManager& peerman = EnsurePeerman(node);
888+
std::vector<TxOrphanage::OrphanTxBase> orphanage = peerman.GetOrphanTransactions();
889+
890+
int verbosity{ParseVerbosity(request.params[0], /*default_verbosity=*/0)};
891+
892+
UniValue ret(UniValue::VARR);
893+
894+
if (verbosity <= 0) {
895+
for (auto const& orphan : orphanage) {
896+
ret.push_back(orphan.tx->GetHash().ToString());
897+
}
898+
} else if (verbosity == 1) {
899+
for (auto const& orphan : orphanage) {
900+
ret.push_back(OrphanToJSON(orphan));
901+
}
902+
} else {
903+
// >= 2
904+
for (auto const& orphan : orphanage) {
905+
UniValue o{OrphanToJSON(orphan)};
906+
o.pushKV("hex", EncodeHexTx(*orphan.tx));
907+
ret.push_back(o);
908+
}
909+
}
910+
911+
return ret;
912+
},
913+
};
914+
}
915+
815916
static RPCHelpMan submitpackage()
816917
{
817918
return RPCHelpMan{"submitpackage",
@@ -1027,6 +1128,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
10271128
{"blockchain", &getrawmempool},
10281129
{"blockchain", &importmempool},
10291130
{"blockchain", &savemempool},
1131+
{"hidden", &getorphantxs},
10301132
{"rawtransactions", &submitpackage},
10311133
};
10321134
for (const auto& c : commands) {

src/rpc/rawtransaction.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,7 @@ static RPCHelpMan getrawtransaction()
338338
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved");
339339
}
340340

341-
// Accept either a bool (true) or a num (>=0) to indicate verbosity.
342-
int verbosity{0};
343-
if (!request.params[1].isNull()) {
344-
if (request.params[1].isBool()) {
345-
verbosity = request.params[1].get_bool();
346-
} else {
347-
verbosity = request.params[1].getInt<int>();
348-
}
349-
}
341+
int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/0)};
350342

351343
if (!request.params[2].isNull()) {
352344
LOCK(cs_main);

src/rpc/util.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ void RPCTypeCheckObj(const UniValue& o,
8181
}
8282
}
8383

84+
int ParseVerbosity(const UniValue& arg, int default_verbosity)
85+
{
86+
if (!arg.isNull()) {
87+
if (arg.isBool()) {
88+
return arg.get_bool(); // true = 1
89+
} else {
90+
return arg.getInt<int>();
91+
}
92+
}
93+
return default_verbosity;
94+
}
95+
8496
CAmount AmountFromValue(const UniValue& value, int decimals)
8597
{
8698
if (!value.isNum() && !value.isStr())

src/rpc/util.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ uint256 ParseHashO(const UniValue& o, std::string_view strKey);
100100
std::vector<unsigned char> ParseHexV(const UniValue& v, std::string_view name);
101101
std::vector<unsigned char> ParseHexO(const UniValue& o, std::string_view strKey);
102102

103+
/**
104+
* Parses verbosity from provided UniValue.
105+
*
106+
* @param[in] arg The verbosity argument as a bool (true) or int (0, 1, 2,...)
107+
* @param[in] default_verbosity The value to return if verbosity argument is null
108+
* @returns An integer describing the verbosity level (e.g. 0, 1, 2, etc.)
109+
*/
110+
int ParseVerbosity(const UniValue& arg, int default_verbosity);
111+
103112
/**
104113
* Validate and return a CAmount from a UniValue number or string.
105114
*

src/test/fuzz/rpc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
143143
"getnetworkhashps",
144144
"getnetworkinfo",
145145
"getnodeaddresses",
146+
"getorphantxs",
146147
"getpeerinfo",
147148
"getprioritisedtransactions",
148149
"getrawaddrman",

src/txorphanage.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
3333
return false;
3434
}
3535

36-
auto ret = m_orphans.emplace(wtxid, OrphanTx{tx, peer, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
36+
auto ret = m_orphans.emplace(wtxid, OrphanTx{{tx, peer, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME}, m_orphan_list.size()});
3737
assert(ret.second);
3838
m_orphan_list.push_back(ret.first);
3939
for (const CTxIn& txin : tx->vin) {
@@ -277,3 +277,13 @@ std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDiff
277277
}
278278
return children_found;
279279
}
280+
281+
std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() const
282+
{
283+
std::vector<OrphanTxBase> ret;
284+
ret.reserve(m_orphans.size());
285+
for (auto const& o : m_orphans) {
286+
ret.push_back({o.second.tx, o.second.fromPeer, o.second.nTimeExpire});
287+
}
288+
return ret;
289+
}

0 commit comments

Comments
 (0)