Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#30793: rpc: add getorphantxs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
glozow committed Oct 5, 2024
2 parents 76e2e8a + 98c1536 commit 5ea335a
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 20 deletions.
7 changes: 7 additions & 0 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ class PeerManagerImpl final : public PeerManager
std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
Expand Down Expand Up @@ -1917,6 +1918,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
return true;
}

std::vector<TxOrphanage::OrphanTxBase> PeerManagerImpl::GetOrphanTransactions()
{
LOCK(m_tx_download_mutex);
return m_orphanage.GetOrphanTransactions();
}

PeerManagerInfo PeerManagerImpl::GetInfo() const
{
return PeerManagerInfo{
Expand Down
3 changes: 3 additions & 0 deletions src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define BITCOIN_NET_PROCESSING_H

#include <net.h>
#include <txorphanage.h>
#include <validationinterface.h>

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

virtual std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() = 0;

/** Get peer manager info. */
virtual PeerManagerInfo GetInfo() const = 0;

Expand Down
9 changes: 1 addition & 8 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,14 +766,7 @@ static RPCHelpMan getblock()
{
uint256 hash(ParseHashV(request.params[0], "blockhash"));

int verbosity = 1;
if (!request.params[1].isNull()) {
if (request.params[1].isBool()) {
verbosity = request.params[1].get_bool() ? 1 : 0;
} else {
verbosity = request.params[1].getInt<int>();
}
}
int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/1)};

const CBlockIndex* pblockindex;
const CBlockIndex* tip;
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "keypoolrefill", 0, "newsize" },
{ "getrawmempool", 0, "verbose" },
{ "getrawmempool", 1, "mempool_sequence" },
{ "getorphantxs", 0, "verbosity" },
{ "getorphantxs", 0, "verbose" },
{ "estimatesmartfee", 0, "conf_target" },
{ "estimaterawfee", 0, "conf_target" },
{ "estimaterawfee", 1, "threshold" },
Expand Down
102 changes: 102 additions & 0 deletions src/rpc/mempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#include <node/mempool_persist.h>

#include <chainparams.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <kernel/mempool_entry.h>
#include <net_processing.h>
#include <node/mempool_persist_args.h>
#include <node/types.h>
#include <policy/rbf.h>
Expand All @@ -24,6 +26,7 @@
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/time.h>
#include <util/vector.h>

#include <utility>

Expand Down Expand Up @@ -812,6 +815,104 @@ static RPCHelpMan savemempool()
};
}

static std::vector<RPCResult> OrphanDescription()
{
return {
RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
RPCResult{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
RPCResult{RPCResult::Type::NUM, "bytes", "The serialized transaction size in bytes"},
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."},
RPCResult{RPCResult::Type::NUM, "weight", "The transaction weight as defined in BIP 141."},
RPCResult{RPCResult::Type::NUM_TIME, "expiration", "The orphan expiration time expressed in " + UNIX_EPOCH_TIME},
RPCResult{RPCResult::Type::ARR, "from", "",
{
RPCResult{RPCResult::Type::NUM, "peer_id", "Peer ID"},
}},
};
}

static UniValue OrphanToJSON(const TxOrphanage::OrphanTxBase& orphan)
{
UniValue o(UniValue::VOBJ);
o.pushKV("txid", orphan.tx->GetHash().ToString());
o.pushKV("wtxid", orphan.tx->GetWitnessHash().ToString());
o.pushKV("bytes", orphan.tx->GetTotalSize());
o.pushKV("vsize", GetVirtualTransactionSize(*orphan.tx));
o.pushKV("weight", GetTransactionWeight(*orphan.tx));
o.pushKV("expiration", int64_t{TicksSinceEpoch<std::chrono::seconds>(orphan.nTimeExpire)});
UniValue from(UniValue::VARR);
from.push_back(orphan.fromPeer); // only one fromPeer for now
o.pushKV("from", from);
return o;
}

static RPCHelpMan getorphantxs()
{
return RPCHelpMan{"getorphantxs",
"\nShows transactions in the tx orphanage.\n"
"\nEXPERIMENTAL warning: this call may be changed in future releases.\n",
{
{"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",
RPCArgOptions{.skip_type_check = true}},
},
{
RPCResult{"for verbose = 0",
RPCResult::Type::ARR, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
}},
RPCResult{"for verbose = 1",
RPCResult::Type::ARR, "", "",
{
{RPCResult::Type::OBJ, "", "", OrphanDescription()},
}},
RPCResult{"for verbose = 2",
RPCResult::Type::ARR, "", "",
{
{RPCResult::Type::OBJ, "", "",
Cat<std::vector<RPCResult>>(
OrphanDescription(),
{{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded transaction data"}}
)
},
}},
},
RPCExamples{
HelpExampleCli("getorphantxs", "2")
+ HelpExampleRpc("getorphantxs", "2")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const NodeContext& node = EnsureAnyNodeContext(request.context);
PeerManager& peerman = EnsurePeerman(node);
std::vector<TxOrphanage::OrphanTxBase> orphanage = peerman.GetOrphanTransactions();

int verbosity{ParseVerbosity(request.params[0], /*default_verbosity=*/0)};

UniValue ret(UniValue::VARR);

if (verbosity <= 0) {
for (auto const& orphan : orphanage) {
ret.push_back(orphan.tx->GetHash().ToString());
}
} else if (verbosity == 1) {
for (auto const& orphan : orphanage) {
ret.push_back(OrphanToJSON(orphan));
}
} else {
// >= 2
for (auto const& orphan : orphanage) {
UniValue o{OrphanToJSON(orphan)};
o.pushKV("hex", EncodeHexTx(*orphan.tx));
ret.push_back(o);
}
}

return ret;
},
};
}

static RPCHelpMan submitpackage()
{
return RPCHelpMan{"submitpackage",
Expand Down Expand Up @@ -1027,6 +1128,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
{"blockchain", &getrawmempool},
{"blockchain", &importmempool},
{"blockchain", &savemempool},
{"hidden", &getorphantxs},
{"rawtransactions", &submitpackage},
};
for (const auto& c : commands) {
Expand Down
10 changes: 1 addition & 9 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,15 +338,7 @@ static RPCHelpMan getrawtransaction()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved");
}

// Accept either a bool (true) or a num (>=0) to indicate verbosity.
int verbosity{0};
if (!request.params[1].isNull()) {
if (request.params[1].isBool()) {
verbosity = request.params[1].get_bool();
} else {
verbosity = request.params[1].getInt<int>();
}
}
int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/0)};

if (!request.params[2].isNull()) {
LOCK(cs_main);
Expand Down
12 changes: 12 additions & 0 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ void RPCTypeCheckObj(const UniValue& o,
}
}

int ParseVerbosity(const UniValue& arg, int default_verbosity)
{
if (!arg.isNull()) {
if (arg.isBool()) {
return arg.get_bool(); // true = 1
} else {
return arg.getInt<int>();
}
}
return default_verbosity;
}

CAmount AmountFromValue(const UniValue& value, int decimals)
{
if (!value.isNum() && !value.isStr())
Expand Down
9 changes: 9 additions & 0 deletions src/rpc/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ uint256 ParseHashO(const UniValue& o, std::string_view strKey);
std::vector<unsigned char> ParseHexV(const UniValue& v, std::string_view name);
std::vector<unsigned char> ParseHexO(const UniValue& o, std::string_view strKey);

/**
* Parses verbosity from provided UniValue.
*
* @param[in] arg The verbosity argument as a bool (true) or int (0, 1, 2,...)
* @param[in] default_verbosity The value to return if verbosity argument is null
* @returns An integer describing the verbosity level (e.g. 0, 1, 2, etc.)
*/
int ParseVerbosity(const UniValue& arg, int default_verbosity);

/**
* Validate and return a CAmount from a UniValue number or string.
*
Expand Down
1 change: 1 addition & 0 deletions src/test/fuzz/rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"getnetworkhashps",
"getnetworkinfo",
"getnodeaddresses",
"getorphantxs",
"getpeerinfo",
"getprioritisedtransactions",
"getrawaddrman",
Expand Down
12 changes: 11 additions & 1 deletion src/txorphanage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
return false;
}

auto ret = m_orphans.emplace(wtxid, OrphanTx{tx, peer, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()});
auto ret = m_orphans.emplace(wtxid, OrphanTx{{tx, peer, Now<NodeSeconds>() + ORPHAN_TX_EXPIRE_TIME}, m_orphan_list.size()});
assert(ret.second);
m_orphan_list.push_back(ret.first);
for (const CTxIn& txin : tx->vin) {
Expand Down Expand Up @@ -277,3 +277,13 @@ std::vector<std::pair<CTransactionRef, NodeId>> TxOrphanage::GetChildrenFromDiff
}
return children_found;
}

std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() const
{
std::vector<OrphanTxBase> ret;
ret.reserve(m_orphans.size());
for (auto const& o : m_orphans) {
ret.push_back({o.second.tx, o.second.fromPeer, o.second.nTimeExpire});
}
return ret;
}
10 changes: 8 additions & 2 deletions src/txorphanage.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,17 @@ class TxOrphanage {
return m_orphans.size();
}

protected:
struct OrphanTx {
/** Allows providing orphan information externally */
struct OrphanTxBase {
CTransactionRef tx;
NodeId fromPeer;
NodeSeconds nTimeExpire;
};

std::vector<OrphanTxBase> GetOrphanTransactions() const;

protected:
struct OrphanTx : public OrphanTxBase {
size_t list_pos;
};

Expand Down
Loading

0 comments on commit 5ea335a

Please sign in to comment.