Bitcoin Core 31.99.0
P2P Digital Currency
rpc.cpp
Go to the documentation of this file.
1// Copyright (c) 2021-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <base58.h>
6#include <key.h>
7#include <key_io.h>
8#include <primitives/block.h>
10#include <psbt.h>
11#include <rpc/client.h>
12#include <rpc/request.h>
13#include <rpc/server.h>
14#include <span.h>
15#include <streams.h>
17#include <test/fuzz/fuzz.h>
18#include <test/fuzz/util.h>
20#include <test/util/time.h>
21#include <tinyformat.h>
22#include <uint256.h>
23#include <univalue.h>
24#include <util/strencodings.h>
25#include <util/string.h>
26#include <util/time.h>
27
28#include <algorithm>
29#include <cassert>
30#include <cstdint>
31#include <cstdlib>
32#include <exception>
33#include <iostream>
34#include <memory>
35#include <optional>
36#include <stdexcept>
37#include <vector>
38enum class ChainType;
39
40using util::Join;
41using util::ToString;
42
43namespace {
44struct RPCFuzzTestingSetup : public TestingSetup {
45 RPCFuzzTestingSetup(const ChainType chain_type, TestOpts opts) : TestingSetup{chain_type, opts}
46 {
47 }
48
49 void CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments)
50 {
51 JSONRPCRequest request;
52 request.context = &m_node;
53 request.strMethod = rpc_method;
54 try {
55 request.params = RPCConvertValues(rpc_method, arguments);
56 } catch (const std::runtime_error&) {
57 return;
58 }
59 tableRPC.execute(request);
60 }
61
62 std::vector<std::string> GetRPCCommands() const
63 {
64 return tableRPC.listCommands();
65 }
66};
67
68RPCFuzzTestingSetup* rpc_testing_setup = nullptr;
69std::string g_limit_to_rpc_command;
70
71// RPC commands which are not appropriate for fuzzing: such as RPC commands
72// reading or writing to a filename passed as an RPC parameter, RPC commands
73// resulting in network activity, etc.
74const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
75 "addconnection", // avoid DNS lookups
76 "addnode", // avoid DNS lookups
77 "addpeeraddress", // avoid DNS lookups
78 "dumptxoutset", // avoid writing to disk
79 "enumeratesigners",
80 "echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.)
81 "generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large)
82 "generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large)
83 "gettxoutproof", // avoid prohibitively slow execution
84 "importmempool", // avoid reading from disk
85 "loadtxoutset", // avoid reading from disk
86 "loadwallet", // avoid reading from disk
87 "savemempool", // disabled as a precautionary measure: may take a file path argument in the future
88 "setban", // avoid DNS lookups
89 "stop", // avoid shutdown state
90};
91
92// RPC commands which are safe for fuzzing.
93const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
94 "abortprivatebroadcast",
95 "analyzepsbt",
96 "clearbanned",
97 "combinepsbt",
98 "combinerawtransaction",
99 "converttopsbt",
100 "createmultisig",
101 "createpsbt",
102 "createrawtransaction",
103 "decodepsbt",
104 "decoderawtransaction",
105 "decodescript",
106 "deriveaddresses",
107 "descriptorprocesspsbt",
108 "disconnectnode",
109 "echo",
110 "echojson",
111 "estimaterawfee",
112 "estimatesmartfee",
113 "finalizepsbt",
114 "generate",
115 "generateblock",
116 "getaddednodeinfo",
117 "getaddrmaninfo",
118 "getbestblockhash",
119 "getblock",
120 "getblockchaininfo",
121 "getblockcount",
122 "getblockfilter",
123 "getblockfrompeer", // when no peers are connected, no p2p message is sent
124 "getblockhash",
125 "getblockheader",
126 "getblockstats",
127 "getblocktemplate",
128 "getchaintips",
129 "getchainstates",
130 "getchaintxstats",
131 "getconnectioncount",
132 "getdeploymentinfo",
133 "getdescriptoractivity",
134 "getdescriptorinfo",
135 "getdifficulty",
136 "getindexinfo",
137 "getmemoryinfo",
138 "getmempoolancestors",
139 "getmempooldescendants",
140 "getmempoolentry",
141 "getmempoolfeeratediagram",
142 "getmempoolcluster",
143 "getmempoolinfo",
144 "getmininginfo",
145 "getnettotals",
146 "getnetworkhashps",
147 "getnetworkinfo",
148 "getnodeaddresses",
149 "getorphantxs",
150 "getpeerinfo",
151 "getprioritisedtransactions",
152 "getprivatebroadcastinfo",
153 "getrawaddrman",
154 "getrawmempool",
155 "getrawtransaction",
156 "getrpcinfo",
157 "gettxout",
158 "gettxoutsetinfo",
159 "gettxspendingprevout",
160 "help",
161 "invalidateblock",
162 "joinpsbts",
163 "listbanned",
164 "logging",
165 "mockscheduler",
166 "ping",
167 "preciousblock",
168 "prioritisetransaction",
169 "pruneblockchain",
170 "reconsiderblock",
171 "scanblocks",
172 "scantxoutset",
173 "sendmsgtopeer", // when no peers are connected, no p2p message is sent
174 "sendrawtransaction",
175 "setmocktime",
176 "setnetworkactive",
177 "signmessagewithprivkey",
178 "signrawtransactionwithkey",
179 "submitblock",
180 "submitheader",
181 "submitpackage",
182 "syncwithvalidationinterfacequeue",
183 "testmempoolaccept",
184 "uptime",
185 "utxoupdatepsbt",
186 "validateaddress",
187 "verifychain",
188 "verifymessage",
189 "verifytxoutproof",
190 "waitforblock",
191 "waitforblockheight",
192 "waitfornewblock",
193};
194
195std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
196{
197 const size_t max_string_length = 4096;
198 const size_t max_base58_bytes_length{64};
199 std::string r;
200 CallOneOf(
202 [&] {
203 // string argument
204 r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length);
205 },
206 [&] {
207 // base64 argument
209 },
210 [&] {
211 // hex argument
213 },
214 [&] {
215 // bool argument
216 r = fuzzed_data_provider.ConsumeBool() ? "true" : "false";
217 },
218 [&] {
219 // range argument
220 r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]";
221 },
222 [&] {
223 // integral argument (int64_t)
225 },
226 [&] {
227 // integral argument (uint64_t)
229 },
230 [&] {
231 // floating point argument
233 },
234 [&] {
235 // tx destination argument
237 },
238 [&] {
239 // uint160 argument
241 },
242 [&] {
243 // uint256 argument
245 },
246 [&] {
247 // base32 argument
249 },
250 [&] {
251 // base58 argument
253 },
254 [&] {
255 // base58 argument with checksum
257 },
258 [&] {
259 // hex encoded block
260 std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS);
261 if (!opt_block) {
262 good_data = false;
263 return;
264 }
265 DataStream data_stream{};
266 data_stream << TX_WITH_WITNESS(*opt_block);
267 r = HexStr(data_stream);
268 },
269 [&] {
270 // hex encoded block header
271 std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
272 if (!opt_block_header) {
273 good_data = false;
274 return;
275 }
276 DataStream data_stream{};
277 data_stream << *opt_block_header;
278 r = HexStr(data_stream);
279 },
280 [&] {
281 // hex encoded tx
282 std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
283 if (!opt_tx) {
284 good_data = false;
285 return;
286 }
287 DataStream data_stream;
289 data_stream << allow_witness(*opt_tx);
290 r = HexStr(data_stream);
291 },
292 [&] {
293 // base64 encoded psbt
294 std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
295 if (!opt_psbt) {
296 good_data = false;
297 return;
298 }
299 DataStream data_stream{};
300 data_stream << *opt_psbt;
301 r = EncodeBase64(data_stream);
302 },
303 [&] {
304 // base58 encoded key
306 if (!key.IsValid()) {
307 good_data = false;
308 return;
309 }
310 r = EncodeSecret(key);
311 },
312 [&] {
313 // hex encoded pubkey
315 if (!key.IsValid()) {
316 good_data = false;
317 return;
318 }
319 r = HexStr(key.GetPubKey());
320 });
321 return r;
322}
323
324std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
325{
326 std::vector<std::string> scalar_arguments;
328 {
329 scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider, good_data));
330 }
331 return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
332}
333
334std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
335{
336 return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider, good_data) : ConsumeArrayRPCArgument(fuzzed_data_provider, good_data);
337}
338
339RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
340{
341 static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>();
343 return setup.get();
344}
345}; // namespace
346
348{
349 rpc_testing_setup = InitializeRPCFuzzTestingSetup();
350 const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands();
351 for (const std::string& rpc_command : supported_rpc_commands) {
352 const bool safe_for_fuzzing = std::find(RPC_COMMANDS_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_SAFE_FOR_FUZZING.end();
353 const bool not_safe_for_fuzzing = std::find(RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.end();
354 if (!(safe_for_fuzzing || not_safe_for_fuzzing)) {
355 std::cerr << "Error: RPC command \"" << rpc_command << "\" not found in RPC_COMMANDS_SAFE_FOR_FUZZING or RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n";
356 std::terminate();
357 }
358 if (safe_for_fuzzing && not_safe_for_fuzzing) {
359 std::cerr << "Error: RPC command \"" << rpc_command << "\" found in *both* RPC_COMMANDS_SAFE_FOR_FUZZING and RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n";
360 std::terminate();
361 }
362 }
363 const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND");
364 if (limit_to_rpc_command_env != nullptr) {
365 g_limit_to_rpc_command = std::string{limit_to_rpc_command_env};
366 }
367}
368
370{
372 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
373 bool good_data{true};
375 const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
376 if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
377 return;
378 }
379 const bool safe_for_fuzzing = std::find(RPC_COMMANDS_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_SAFE_FOR_FUZZING.end();
380 if (!safe_for_fuzzing) {
381 return;
382 }
383 std::vector<std::string> arguments;
385 {
386 arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider, good_data));
387 }
388 try {
389 std::optional<test_only_CheckFailuresAreExceptionsNotAborts> maybe_mock{};
390 if (rpc_command == "echo") {
391 // Avoid aborting fuzzing for this specific test-only RPC with an
392 // intentional trigger_internal_bug
393 maybe_mock.emplace();
394 }
395 rpc_testing_setup->CallRPC(rpc_command, arguments);
396 } catch (const UniValue& json_rpc_error) {
397 const std::string error_msg{json_rpc_error.find_value("message").get_str()};
398 if (error_msg.starts_with("Internal bug detected")) {
399 // Only allow the intentional internal bug
400 assert(error_msg.find("trigger_internal_bug") != std::string::npos);
401 }
402 }
403}
std::string EncodeBase58(std::span< const unsigned char > input)
Why base-58 instead of standard base-64 encoding?
Definition: base58.cpp:89
std::string EncodeBase58Check(std::span< const unsigned char > input)
Encode a byte span into a base58-encoded string, including checksum.
Definition: base58.cpp:137
static UniValue CallRPC(BaseRequestHandler *rh, const std::string &strMethod, const std::vector< std::string > &args, const std::string &endpoint, const std::string &username)
ChainType
Definition: chaintype.h:12
An encapsulated private key.
Definition: key.h:36
bool IsValid() const
Check whether this private key is valid.
Definition: key.h:124
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:183
std::vector< std::string > listCommands() const
Returns a list of registered commands.
Definition: server.cpp:519
UniValue execute(const JSONRPCRequest &request) const
Execute a method.
Definition: server.cpp:482
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:133
std::string ConsumeRandomLengthString(size_t max_length)
UniValue params
Definition: request.h:57
std::string strMethod
Definition: request.h:56
std::any context
Definition: request.h:62
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition: time.h:40
const std::string & get_str() const
const UniValue & find_value(std::string_view key) const
Definition: univalue.cpp:232
std::string ToString() const
Definition: uint256.cpp:21
UniValue RPCConvertValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert command lines arguments to params object when -named is disabled.
Definition: client.cpp:435
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: hex_base.cpp:30
std::string EncodeSecret(const CKey &key)
Definition: key_io.cpp:232
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:295
Definition: basic.cpp:8
std::string ToString(const T &t)
Locale-independent version of std::to_string.
Definition: string.h:247
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition: string.h:206
static constexpr TransactionSerParams TX_NO_WITNESS
Definition: transaction.h:181
static constexpr TransactionSerParams TX_WITH_WITNESS
Definition: transaction.h:180
void initialize_rpc()
Definition: rpc.cpp:347
FUZZ_TARGET(rpc,.init=initialize_rpc)
Definition: rpc.cpp:369
void SetRPCWarmupFinished()
Definition: server.cpp:324
CRPCTable tableRPC
Definition: server.cpp:544
constexpr auto MakeUCharSpan(const V &v) -> decltype(UCharSpanCast(std::span{v}))
Like the std::span constructor, but for (const) unsigned char member types only.
Definition: span.h:111
node::NodeContext m_node
Definition: setup_common.h:63
Testing setup that configures a complete environment.
Definition: setup_common.h:118
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition: util.cpp:34
CKey ConsumePrivateKey(FuzzedDataProvider &fuzzed_data_provider, std::optional< bool > compressed) noexcept
Definition: util.cpp:230
CTxDestination ConsumeTxDestination(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition: util.cpp:184
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition: util.h:176
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
uint160 ConsumeUInt160(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition: util.h:167
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
@ ZEROS
Seed with a compile time constant of zeros.
static int setup(void)
Definition: tests.c:8040
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
std::string EncodeBase32(std::span< const unsigned char > input, bool pad)
Base32 encode.
std::string EncodeBase64(std::span< const unsigned char > input)
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:39