rpc: avoid quadratic output lookups - #36032
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36032. ReviewsSee the guideline and AI policy for information on the review process.
If your review is incorrectly listed, please copy-paste |
|
Is the test needed? I don't think it adds any new coverage, so it may be better to just drop it in a GitHub comment, or in the pull description. This way, reviewers can use it, if they want, or ignore it, if they want. |
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
| const auto& values{outputs.getValues()}; | ||
| for (size_t i{0}; i < keys.size(); ++i) { | ||
| const std::string& name_{keys[i]}; | ||
| const UniValue& value{values[i]}; |
There was a problem hiding this comment.
unrelated comment: wen c++23?
src/univalue/include/univalue.h
@@ -12,0 +13 @@
+#include <ranges>
@@ -119,0 +120,5 @@ public:
+ auto getObjectItems() const
+ {
+ checkType(VOBJ);
+ return std::views::zip(keys, values);
+ }There was a problem hiding this comment.
Yeah, was also wondering about the same :)
`ParseOutputs` iterates a `UniValue` object's keys and looks up each value by key. Each lookup scans the key vector from the beginning, making the lookup work quadratic. Walk the parallel key and value vectors together to avoid repeated scans. This preserves output order and validation behavior.
abdcbd0 to
747cff8
Compare
|
Concept ACK Is this test placement what you suggest / did? I had to add a couple of header includes to have it compile. test diff
diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp
index d574e1e2d3a..e3a64177080 100644
--- a/src/test/rpc_tests.cpp
+++ b/src/test/rpc_tests.cpp
@@ -4,9 +4,11 @@
#include <core_io.h>
#include <interfaces/chain.h>
+#include <key_io.h>
#include <node/context.h>
#include <rpc/blockchain.h>
#include <rpc/client.h>
+#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <test/util/common.h>
@@ -676,4 +678,21 @@ BOOST_AUTO_TEST_CASE(rpc_arg_helper)
CheckRpc(params, UniValue{JSON(R"([5, "hello", 4, "test", true, 1.23, "world"])")}, check_positional);
}
+BOOST_AUTO_TEST_CASE(parse_outputs)
+{
+ constexpr size_t OUTPUT_COUNT{10'000};
+ UniValue outputs{UniValue::VOBJ};
+ for (size_t i{0}; i < OUTPUT_COUNT; ++i) {
+ auto destination{EncodeDestination(WitnessV0ScriptHash{CScript{} << i})};
+ outputs.pushKVEnd(destination, ValueFromAmount(i + 1));
+ }
+
+ const auto parsed_outputs{ParseOutputs(outputs)};
+ BOOST_REQUIRE_EQUAL(parsed_outputs.size(), OUTPUT_COUNT);
+ for (size_t i{OUTPUT_COUNT}; i > 0; --i) {
+ std::pair expected{CTxDestination{WitnessV0ScriptHash{CScript{} << (i - 1)}}, static_cast<CAmount>(i)};
+ BOOST_CHECK(parsed_outputs[i - 1] == expected);
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()Edit: Test results with the above diff on an M1 Max master this branch @ 747cff8 |
|
Thanks for the review and reproducer! |
Problem: Transaction-creation RPCs currently take quadratic time to parse outputs.
An authenticated RPC client can therefore tie up a worker with a large request.
sendmanyalso holds the wallet lock while parsing, delaying other operations on the same wallet.Fix: Parse transaction outputs in linear time by reading corresponding keys and values by index instead of looking up each value by key.
Reproducer: Run
time build/bin/test_bitcoin --run_test=rpc_tests/parse_outputsbefore and after the fix:parse_outputs test
Related to #35889