From d17665307b426a74b64f8eba5d948bddca64c6a7 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 23 Jan 2024 08:33:24 +0700 Subject: [PATCH] feat: new rpc getrawtransactionmulti (#5839) ## Issue being fixed or feature implemented For platform needs `getrawtransactionmulti` will help to reduce amount of rpc calls for sake of performance improvement. ## What was done? Implemented new RPC, basic functional test, release note. ## How Has This Been Tested? On testnet: ``` > getrawtransactionmulti '{"000000abbe61a4d9b9356cb1d7deb1132d0b444a62869e71c2f3aa8ce2361359":["6e3ef19a3f955ac75a1f84dae60d42bbe11548ef54e37033ff2d91b3c4a09e9c", "415d5fafd5ee24ada8b99c36df339785a3066170c0dca6bb1aa6a5b96cf51e35"], "0":["ec7090f01c0e9b6e29d3be8810b12c780d2fb34372a53b231ce18bb7d2f1e8b0"]}' > getrawtransactionmulti '{"000000abbe61a4d9b9356cb1d7deb1132d0b444a62869e71c2f3aa8ce2361359":["6e3ef19a3f955ac75a1f84dae60d42bbe11548ef54e37033ff2d91b3c4a09e9c", "415d5fafd5ee24ada8b99c36df339785a3066170c0dca6bb1aa6a5b96cf51e35"], "0":["ec7090f01c0e9b6e29d3be8810b12c780d2fb34372a53b231ce18bb7d2f1e8b0"]}' true ``` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone --------- Co-authored-by: pasta --- doc/release-notes-5839.md | 4 ++ src/rpc/client.cpp | 2 + src/rpc/rawtransaction.cpp | 86 +++++++++++++++++++++++++++ test/functional/rpc_rawtransaction.py | 5 ++ 4 files changed, 97 insertions(+) create mode 100644 doc/release-notes-5839.md diff --git a/doc/release-notes-5839.md b/doc/release-notes-5839.md new file mode 100644 index 0000000000..5d52d469c3 --- /dev/null +++ b/doc/release-notes-5839.md @@ -0,0 +1,4 @@ +RPC changes +----------- + +New RPC getrawtransactionmulti that can return batch up to 100 rawtransaction at one request. diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 6b686d7ae1..e9050e41ca 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -107,6 +107,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "gettransaction", 1, "include_watchonly" }, { "gettransaction", 2, "verbose" }, { "getrawtransaction", 1, "verbose" }, + { "getrawtransactionmulti", 0, "txid_map" }, + { "getrawtransactionmulti", 1, "verbose" }, { "gettxchainlocks", 0, "txids" }, { "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 1, "outputs" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 647d5a0706..3ea596705e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -265,6 +265,91 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) return result; } +static UniValue getrawtransactionmulti(const JSONRPCRequest& request) { + RPCHelpMan{ + "getrawtransactionmulti", + "\nReturns the raw transaction data for multiple transactions.\n" + "\nThis call is an extension of getrawtransaction that supports multiple transactions.\n" + "It accepts a map of block hashes to a list of transaction hashes.\n" + "A block hash of 0 indicates transactions not yet mined or in the mempool.\n", + { + {"transactions", RPCArg::Type::OBJ, RPCArg::Optional::NO, + "A JSON object with block hashes as keys and lists of transaction hashes as values (no more than 100 in total)", + { + {"blockhash", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, + "The block hash and the list of transaction ids to fetch", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The transaction id"}, + }}, + }}, + {"verbose", RPCArg::Type::BOOL, /* default */ "false", + "If false, return a string, otherwise return a json object"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("getrawtransactionmulti", + R"('{"blockhash1":["txid1","txid2"], "0":["txid3"]}')") + + HelpExampleRpc("getrawtransactionmulti", + R"('{"blockhash1":["txid1","txid2"], "0":["txid3"]})") + }, + }.Check(request); + + // Parse arguments + UniValue transactions{request.params[0].get_obj()}; + // Accept either a bool (true) or a num (>=1) to indicate verbose output. + bool fVerbose{false}; + if (!request.params[1].isNull()) { + fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool(); + } + + const NodeContext& node{EnsureAnyNodeContext(request.context)}; + const ChainstateManager& chainman{EnsureChainman(node)}; + const LLMQContext& llmq_ctx{EnsureLLMQContext(node)}; + CTxMemPool& mempool{EnsureMemPool(node)}; + + if (transactions.size() > 100) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Up to 100 blocks and txids only"); + } + + size_t count{0}; + UniValue result(UniValue::VOBJ); + for (const std::string& blockhash_str : transactions.getKeys()) { + const uint256 blockhash{uint256S(blockhash_str)}; + const UniValue txids = transactions[blockhash_str].get_array(); + + CBlockIndex* blockindex{blockhash.IsNull() ? nullptr : WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(blockhash))}; + if (blockindex == nullptr && !blockhash.IsNull()) { + for (const auto idx : irange::range(txids.size())) { + result.pushKV(txids[idx].get_str(), "None"); + } + continue; + } + + count += txids.size(); + if (count > 100) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Up to 100 txids in total"); + } + for (const auto idx : irange::range(txids.size())) { + const std::string txid_str = txids[idx].get_str(); + const uint256 txid = ParseHashV(txid_str, "transaction id"); + + uint256 hash_block; + const CTransactionRef tx = GetTransaction(blockindex, &mempool, txid, Params().GetConsensus(), hash_block); + if (!tx) { + result.pushKV(txid_str, "None"); + } else if (fVerbose) { + UniValue tx_data{UniValue::VOBJ}; + TxToJSON(*tx, hash_block, mempool, chainman.ActiveChainstate(), *llmq_ctx.clhandler, *llmq_ctx.isman, tx_data); + result.pushKV(txid_str, tx_data); + } else { + result.pushKV(txid_str, EncodeHexTx(*tx)); + } + } + } + + return result; +} + static UniValue gettxchainlocks(const JSONRPCRequest& request) { RPCHelpMan{ @@ -1885,6 +1970,7 @@ static const CRPCCommand commands[] = // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} }, { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, + { "rawtransactions", "getrawtransactionmulti", &getrawtransactionmulti, {"txid_map","verbose"} }, { "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} }, { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} }, diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 0d054812f0..2d4f243bc0 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -306,19 +306,24 @@ class RawTransactionsTest(BitcoinTestFramework): # 1. valid parameters - only supply txid txId = rawTx["txid"] assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex']) + assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]})[txId], rawTxSigned['hex']) # 2. valid parameters - supply txid and 0 for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, 0), rawTxSigned['hex']) + assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]}, 0)[txId], rawTxSigned['hex']) # 3. valid parameters - supply txid and False for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, False), rawTxSigned['hex']) + assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]}, False)[txId], rawTxSigned['hex']) # 4. valid parameters - supply txid and 1 for verbose. # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. assert_equal(self.nodes[0].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]}, 1)[txId]['hex'], rawTxSigned['hex']) # 5. valid parameters - supply txid and True for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]}, True)[txId]['hex'], rawTxSigned['hex']) # 6. invalid parameters - supply txid and string "Flase" assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase")