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 <pasta@dashboost.org>
This commit is contained in:
Konstantin Akimov 2024-01-23 08:33:24 +07:00 committed by GitHub
parent 3d9dc207cf
commit d17665307b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 0 deletions

View File

@ -0,0 +1,4 @@
RPC changes
-----------
New RPC getrawtransactionmulti that can return batch up to 100 rawtransaction at one request.

View File

@ -107,6 +107,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettransaction", 1, "include_watchonly" }, { "gettransaction", 1, "include_watchonly" },
{ "gettransaction", 2, "verbose" }, { "gettransaction", 2, "verbose" },
{ "getrawtransaction", 1, "verbose" }, { "getrawtransaction", 1, "verbose" },
{ "getrawtransactionmulti", 0, "txid_map" },
{ "getrawtransactionmulti", 1, "verbose" },
{ "gettxchainlocks", 0, "txids" }, { "gettxchainlocks", 0, "txids" },
{ "createrawtransaction", 0, "inputs" }, { "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" }, { "createrawtransaction", 1, "outputs" },

View File

@ -265,6 +265,91 @@ static UniValue getrawtransaction(const JSONRPCRequest& request)
return result; 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) static UniValue gettxchainlocks(const JSONRPCRequest& request)
{ {
RPCHelpMan{ RPCHelpMan{
@ -1885,6 +1970,7 @@ static const CRPCCommand commands[] =
// --------------------- ------------------------ ----------------------- ---------- // --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} }, { "rawtransactions", "getassetunlockstatuses", &getassetunlockstatuses, {"indexes"} },
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "getrawtransactionmulti", &getrawtransactionmulti, {"txid_map","verbose"} },
{ "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} }, { "rawtransactions", "gettxchainlocks", &gettxchainlocks, {"txids"} },
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} }, { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} },
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} },

View File

@ -306,19 +306,24 @@ class RawTransactionsTest(BitcoinTestFramework):
# 1. valid parameters - only supply txid # 1. valid parameters - only supply txid
txId = rawTx["txid"] txId = rawTx["txid"]
assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex']) 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 # 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].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 # 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].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. # 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. # 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].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 # 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].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" # 6. invalid parameters - supply txid and string "Flase"
assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase") assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase")