xrpld
Loading...
Searching...
No Matches
RPCCall.cpp
1#include <xrpld/rpc/RPCCall.h>
2
3#include <xrpld/core/Config.h>
4#include <xrpld/rpc/ServerHandler.h>
5
6#include <xrpl/basics/ByteUtilities.h>
7#include <xrpl/basics/Log.h>
8#include <xrpl/basics/Slice.h>
9#include <xrpl/basics/StringUtilities.h>
10#include <xrpl/basics/base64.h>
11#include <xrpl/basics/base_uint.h>
12#include <xrpl/basics/contract.h>
13#include <xrpl/beast/core/LexicalCast.h>
14#include <xrpl/beast/utility/Journal.h>
15#include <xrpl/beast/utility/Zero.h>
16#include <xrpl/beast/utility/instrumentation.h>
17#include <xrpl/json/json_forwards.h>
18#include <xrpl/json/json_reader.h>
19#include <xrpl/json/json_value.h>
20#include <xrpl/json/to_string.h>
21#include <xrpl/net/HTTPClient.h>
22#include <xrpl/protocol/AccountID.h>
23#include <xrpl/protocol/ApiVersion.h>
24#include <xrpl/protocol/ErrorCodes.h>
25#include <xrpl/protocol/KeyType.h>
26#include <xrpl/protocol/PublicKey.h>
27#include <xrpl/protocol/RPCErr.h>
28#include <xrpl/protocol/SystemParameters.h>
29#include <xrpl/protocol/jss.h>
30#include <xrpl/protocol/tokens.h>
31
32#include <boost/algorithm/string/predicate.hpp>
33#include <boost/asio/io_context.hpp>
34#include <boost/asio/streambuf.hpp>
35#include <boost/regex/v5/regex.hpp>
36#include <boost/regex/v5/regex_match.hpp>
37#include <boost/system/detail/error_code.hpp>
38
39#include <algorithm>
40#include <array>
41#include <chrono>
42#include <cstddef>
43#include <cstdint>
44#include <exception>
45#include <functional>
46#include <iostream>
47#include <optional>
48#include <sstream>
49#include <stdexcept>
50#include <string>
51#include <unordered_map>
52#include <utility>
53#include <vector>
54
55namespace xrpl {
56
57class RPCParser;
58
59//
60// HTTP protocol
61//
62// This ain't Apache. We're just using HTTP header for the length field
63// and to be compatible with other JSON-RPC implementations.
64//
65
66std::string
68 std::string const& strHost,
69 std::string const& strPath,
70 std::string const& strMsg,
71 std::unordered_map<std::string, std::string> const& mapRequestHeaders)
72{
74
75 // CHECKME this uses a different version than the replies below use. Is
76 // this by design or an accident or should it be using
77 // build_info::getFullVersionString () as well?
78
79 s << "POST " << (strPath.empty() ? "/" : strPath) << " HTTP/1.0\r\n"
80 << "User-Agent: " << systemName() << "-json-rpc/v1\r\n"
81 << "Host: " << strHost << "\r\n"
82 << "Content-Type: application/json\r\n"
83 << "Content-Length: " << strMsg.size() << "\r\n"
84 << "Accept: application/json\r\n";
85
86 for (auto const& [k, v] : mapRequestHeaders)
87 s << k << ": " << v << "\r\n";
88
89 s << "\r\n" << strMsg;
90
91 return s.str();
92}
93
95{
96private:
97 unsigned const apiVersion_;
99
100 // TODO New routine for parsing ledger parameters, other routines should
101 // standardize on this.
102 static bool
103 jvParseLedger(json::Value& jvRequest, std::string const& strLedger)
104 {
105 if (strLedger == "current" || strLedger == "closed" || strLedger == "validated")
106 {
107 jvRequest[jss::ledger_index] = strLedger;
108 }
109 else if (strLedger.length() == 64)
110 {
111 // YYY Could confirm this is a uint256.
112 jvRequest[jss::ledger_hash] = strLedger;
113 }
114 else
115 {
116 jvRequest[jss::ledger_index] = beast::lexicalCast<std::uint32_t>(strLedger);
117 }
118
119 return true;
120 }
121
122 // Build a object { "currency" : "XYZ", "issuer" : "rXYX" }
123 static json::Value
124 jvParseCurrencyIssuer(std::string const& strCurrencyIssuer)
125 {
126 // Matches a sequence of 3 characters from
127 // `xrpl::detail::isoCharSet` (the currency),
128 // optionally followed by a forward slash and some other characters
129 // (the issuer).
130 // https://www.boost.org/doc/libs/1_82_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html
131 static boost::regex const kReCurIss("\\`([][:alnum:]<>(){}[|?!@#$%^&*]{3})(?:/(.+))?\\'");
132
133 boost::smatch smMatch;
134
135 if (boost::regex_match(strCurrencyIssuer, smMatch, kReCurIss))
136 {
138 std::string const strCurrency = smMatch[1];
139 std::string const strIssuer = smMatch[2];
140
141 jvResult[jss::currency] = strCurrency;
142
143 if (!strIssuer.empty())
144 {
145 // Could confirm issuer is a valid XRPL address.
146 jvResult[jss::issuer] = strIssuer;
147 }
148
149 return jvResult;
150 }
151
152 return rpc::makeParamError(
153 std::string("Invalid currency/issuer '") + strCurrencyIssuer + "'");
154 }
155
156 static bool
158 {
159 if (parseBase58<xrpl::PublicKey>(type, strPk))
160 return true;
161
162 auto pkHex = strUnHex(strPk);
163 if (!pkHex)
164 return false;
165
166 if (!publicKeyType(makeSlice(*pkHex)))
167 return false;
168
169 return true;
170 }
171
172private:
173 using parseFuncPtr = json::Value (RPCParser::*)(json::Value const& jvParams);
174
176 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
177 parseAsIs(json::Value const& jvParams)
178 {
180
181 if (jvParams.isArray() && (jvParams.size() > 0))
182 v[jss::params] = jvParams;
183
184 return v;
185 }
186
188 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
189 parseInternal(json::Value const& jvParams)
190 {
192 v[jss::internal_command] = jvParams[0u];
193
195
196 for (unsigned i = 1; i < jvParams.size(); ++i)
197 params.append(jvParams[i]);
198
199 v[jss::params] = params;
200
201 return v;
202 }
203
205 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
206 parseManifest(json::Value const& jvParams)
207 {
208 if (jvParams.size() == 1)
209 {
211
212 std::string const strPk = jvParams[0u].asString();
215
216 jvRequest[jss::public_key] = strPk;
217
218 return jvRequest;
219 }
220
222 }
223
224 // fetch_info [clear]
226 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
228 {
230 unsigned int const iParams = jvParams.size();
231
232 if (iParams != 0)
233 jvRequest[jvParams[0u].asString()] = true;
234
235 return jvRequest;
236 }
237
238 // account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary]
239 // [count] [descending]
241 // NOLINTNEXTLINE(readability-make-member-function-const)
243 {
245 unsigned int iParams = jvParams.size();
246
247 auto const account = parseBase58<AccountID>(jvParams[0u].asString());
248 if (!account)
250
251 jvRequest[jss::account] = toBase58(*account);
252
253 bool bDone = false;
254
255 while (!bDone && iParams >= 2)
256 {
257 // VFALCO Why is json::StaticString appearing on the right side?
258 if (jvParams[iParams - 1].asString() == jss::binary)
259 {
260 jvRequest[jss::binary] = true;
261 --iParams;
262 }
263 else if (jvParams[iParams - 1].asString() == jss::count)
264 {
265 jvRequest[jss::count] = true;
266 --iParams;
267 }
268 else if (jvParams[iParams - 1].asString() == jss::descending)
269 {
270 jvRequest[jss::descending] = true;
271 --iParams;
272 }
273 else
274 {
275 bDone = true;
276 }
277 }
278
279 if (1 == iParams)
280 {
281 }
282 else if (2 == iParams)
283 {
284 if (!jvParseLedger(jvRequest, jvParams[1u].asString()))
285 return jvRequest;
286 }
287 else
288 {
289 std::int64_t const uLedgerMin = jvParams[1u].asInt();
290 std::int64_t const uLedgerMax = jvParams[2u].asInt();
291
292 if (uLedgerMax != -1 && uLedgerMax < uLedgerMin)
293 {
294 if (apiVersion_ == 1)
296 return rpcError(RpcNotSynced);
297 }
298
299 jvRequest[jss::ledger_index_min] = jvParams[1u].asInt();
300 jvRequest[jss::ledger_index_max] = jvParams[2u].asInt();
301
302 if (iParams >= 4)
303 jvRequest[jss::limit] = jvParams[3u].asInt();
304
305 if (iParams >= 5)
306 jvRequest[jss::offset] = jvParams[4u].asInt();
307 }
308
309 return jvRequest;
310 }
311
312 // book_offers <taker_pays> <taker_gets> [<taker> [<ledger> [<limit>
313 // [<proof> [<marker>]]]]] limit: 0 = no limit proof: 0 or 1
314 //
315 // Mnemonic: taker pays --> offer --> taker gets
317 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
319 {
321
322 json::Value jvTakerPays = jvParseCurrencyIssuer(jvParams[0u].asString());
323 json::Value jvTakerGets = jvParseCurrencyIssuer(jvParams[1u].asString());
324
325 if (isRpcError(jvTakerPays))
326 {
327 return jvTakerPays;
328 }
329
330 jvRequest[jss::taker_pays] = jvTakerPays;
331
332 if (isRpcError(jvTakerGets))
333 {
334 return jvTakerGets;
335 }
336
337 jvRequest[jss::taker_gets] = jvTakerGets;
338
339 if (jvParams.size() >= 3)
340 {
341 jvRequest[jss::issuer] = jvParams[2u].asString();
342 }
343
344 if (jvParams.size() >= 4 && !jvParseLedger(jvRequest, jvParams[3u].asString()))
345 return jvRequest;
346
347 if (jvParams.size() >= 5)
348 {
349 try
350 {
351 int const iLimit = jvParams[4u].asInt();
352
353 if (iLimit > 0)
354 jvRequest[jss::limit] = iLimit;
355 }
356 catch (std::exception const&)
357 {
358 return rpc::invalidFieldError(jss::limit);
359 }
360 }
361
362 if (jvParams.size() >= 6)
363 {
364 try
365 {
366 int const bProof = jvParams[5u].asInt();
367 if (bProof != 0)
368 jvRequest[jss::proof] = true;
369 }
370 catch (std::exception const&)
371 {
372 return rpc::invalidFieldError(jss::proof);
373 }
374 }
375
376 if (jvParams.size() == 7)
377 jvRequest[jss::marker] = jvParams[6u];
378
379 return jvRequest;
380 }
381
382 // can_delete [<ledgerid>|<ledgerhash>|now|always|never]
384 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
386 {
388
389 if (jvParams.size() == 0u)
390 return jvRequest;
391
392 std::string const input = jvParams[0u].asString();
393 if (input.find_first_not_of("0123456789") == std::string::npos)
394 {
395 jvRequest["can_delete"] = jvParams[0u].asUInt();
396 }
397 else
398 {
399 jvRequest["can_delete"] = input;
400 }
401
402 return jvRequest;
403 }
404
405 // connect <ip[:port]> [port]
407 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
408 parseConnect(json::Value const& jvParams)
409 {
411 std::string ip = jvParams[0u].asString();
412 if (jvParams.size() == 2)
413 {
414 jvRequest[jss::ip] = ip;
415 jvRequest[jss::port] = jvParams[1u].asUInt();
416 return jvRequest;
417 }
418
419 // handle case where there is one argument of the form ip:port
420 if (std::count(ip.begin(), ip.end(), ':') == 1)
421 {
422 std::size_t const colon = ip.find_last_of(':');
423 jvRequest[jss::ip] = std::string{ip, 0, colon};
424 jvRequest[jss::port] = json::Value{std::string{ip, colon + 1}}.asUInt();
425 return jvRequest;
426 }
427
428 // default case, no port
429 jvRequest[jss::ip] = ip;
430 return jvRequest;
431 }
432
433 // deposit_authorized <source_account> <destination_account>
434 // [<ledger> [<credentials>, ...]]
436 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
438 {
440 jvRequest[jss::source_account] = jvParams[0u].asString();
441 jvRequest[jss::destination_account] = jvParams[1u].asString();
442
443 if (jvParams.size() >= 3)
444 jvParseLedger(jvRequest, jvParams[2u].asString());
445
446 // 8 credentials max
447 if ((jvParams.size() >= 4) && (jvParams.size() <= 11))
448 {
449 jvRequest[jss::credentials] = json::Value(json::ValueType::Array);
450 for (uint32_t i = 3; i < jvParams.size(); ++i)
451 jvRequest[jss::credentials].append(jvParams[i].asString());
452 }
453
454 return jvRequest;
455 }
456
457 // Return an error for attempting to subscribe/unsubscribe via RPC.
459 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
460 parseEvented(json::Value const& jvParams)
461 {
462 return rpcError(RpcNoEvents);
463 }
464
465 // feature [<feature>] [accept|reject]
467 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
468 parseFeature(json::Value const& jvParams)
469 {
471
472 if (jvParams.size() > 0)
473 jvRequest[jss::feature] = jvParams[0u].asString();
474
475 if (jvParams.size() > 1)
476 {
477 auto const action = jvParams[1u].asString();
478
479 // This may look reversed, but it's intentional: jss::vetoed
480 // determines whether an amendment is vetoed - so "reject" means
481 // that jss::vetoed is true.
482 if (boost::iequals(action, "reject"))
483 {
484 jvRequest[jss::vetoed] = json::Value(true);
485 }
486 else if (boost::iequals(action, "accept"))
487 {
488 jvRequest[jss::vetoed] = json::Value(false);
489 }
490 else
491 {
493 }
494 }
495
496 return jvRequest;
497 }
498
499 // get_counts [<min_count>]
501 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
503 {
505
506 if (jvParams.size() != 0u)
507 jvRequest[jss::min_count] = jvParams[0u].asUInt();
508
509 return jvRequest;
510 }
511
512 // sign_for <account> <secret> <json> offline
513 // sign_for <account> <secret> <json>
515 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
516 parseSignFor(json::Value const& jvParams)
517 {
518 bool const bOffline = 4 == jvParams.size() && jvParams[3u].asString() == "offline";
519
520 if (3 == jvParams.size() || bOffline)
521 {
522 json::Value txJSON;
523 json::Reader reader;
524 if (reader.parse(jvParams[2u].asString(), txJSON))
525 {
526 // sign_for txJSON.
528
529 jvRequest[jss::account] = jvParams[0u].asString();
530 jvRequest[jss::secret] = jvParams[1u].asString();
531 jvRequest[jss::tx_json] = txJSON;
532
533 if (bOffline)
534 jvRequest[jss::offline] = true;
535
536 return jvRequest;
537 }
538 }
540 }
541
542 // json <command> <json>
544 parseJson(json::Value const& jvParams)
545 {
546 json::Reader reader;
547 json::Value jvRequest;
548
549 JLOG(j_.trace()) << "RPC method: " << jvParams[0u];
550 JLOG(j_.trace()) << "RPC json: " << jvParams[1u];
551
552 if (reader.parse(jvParams[1u].asString(), jvRequest))
553 {
554 if (!jvRequest.isObjectOrNull())
556
557 jvRequest[jss::method] = jvParams[0u];
558
559 return jvRequest;
560 }
561
563 }
564
565 bool
567 {
568 if (jv.isArray())
569 {
570 if (jv.size() == 0)
571 return false;
572 // json::Value is not a std::ranges range, so the iterator form is used.
573 // NOLINTNEXTLINE(modernize-use-ranges)
574 return std::all_of(
575 jv.begin(), jv.end(), [this](auto const& j) { return isValidJson2(j); });
576 }
577 if (jv.isObject())
578 {
579 if (jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0" &&
580 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0" &&
581 jv.isMember(jss::id) && jv.isMember(jss::method))
582 {
583 return !jv.isMember(jss::params) ||
584 (jv[jss::params].isNull() || jv[jss::params].isArray() ||
585 jv[jss::params].isObject());
586 }
587 }
588 return false;
589 }
590
592 parseJson2(json::Value const& jvParams)
593 {
594 json::Reader reader;
595 json::Value jv;
596 bool const validParse = reader.parse(jvParams[0u].asString(), jv);
597 if (validParse && isValidJson2(jv))
598 {
599 if (jv.isObject())
600 {
602 if (jv.isMember(jss::params))
603 {
604 auto const& params = jv[jss::params];
605 for (auto i = params.begin(); i != params.end(); ++i)
606 jv1[i.key().asString()] = *i;
607 }
608 jv1[jss::jsonrpc] = jv[jss::jsonrpc];
609 jv1[jss::ripplerpc] = jv[jss::ripplerpc];
610 jv1[jss::id] = jv[jss::id];
611 jv1[jss::method] = jv[jss::method];
612 return jv1;
613 }
614 // else jv.isArray()
616 for (json::UInt j = 0; j < jv.size(); ++j)
617 {
618 if (jv[j].isMember(jss::params))
619 {
620 auto const& params = jv[j][jss::params];
621 for (auto i = params.begin(); i != params.end(); ++i)
622 jv1[j][i.key().asString()] = *i;
623 }
624 jv1[j][jss::jsonrpc] = jv[j][jss::jsonrpc];
625 jv1[j][jss::ripplerpc] = jv[j][jss::ripplerpc];
626 jv1[j][jss::id] = jv[j][jss::id];
627 jv1[j][jss::method] = jv[j][jss::method];
628 }
629 return jv1;
630 }
631 auto jvError = rpcError(RpcInvalidParams);
632 if (jv.isMember(jss::jsonrpc))
633 jvError[jss::jsonrpc] = jv[jss::jsonrpc];
634 if (jv.isMember(jss::ripplerpc))
635 jvError[jss::ripplerpc] = jv[jss::ripplerpc];
636 if (jv.isMember(jss::id))
637 jvError[jss::id] = jv[jss::id];
638 return jvError;
639 }
640
641 // ledger [id|index|current|closed|validated] [full|tx]
643 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
644 parseLedger(json::Value const& jvParams)
645 {
647
648 if (jvParams.size() == 0u)
649 {
650 return jvRequest;
651 }
652
653 jvParseLedger(jvRequest, jvParams[0u].asString());
654
655 if (2 == jvParams.size())
656 {
657 if (jvParams[1u].asString() == "full")
658 {
659 jvRequest[jss::full] = true;
660 }
661 else if (jvParams[1u].asString() == "tx")
662 {
663 jvRequest[jss::transactions] = true;
664 jvRequest[jss::expand] = true;
665 }
666 }
667
668 return jvRequest;
669 }
670
671 // ledger_header <id>|<index>
673 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
674 parseLedgerId(json::Value const& jvParams)
675 {
677
678 std::string const strLedger = jvParams[0u].asString();
679
680 if (strLedger.length() == 64)
681 {
682 jvRequest[jss::ledger_hash] = strLedger;
683 }
684 else
685 {
686 jvRequest[jss::ledger_index] = beast::lexicalCast<std::uint32_t>(strLedger);
687 }
688
689 return jvRequest;
690 }
691
692 // ledger_entry [id] [<index>]
694 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
696 {
698
699 jvRequest[jss::index] = jvParams[0u].asString();
700
701 if (jvParams.size() == 2 && !jvParseLedger(jvRequest, jvParams[1u].asString()))
703
704 return jvRequest;
705 }
706
707 // log_level: Get log levels
708 // log_level <severity>: Set master log level to the
709 // specified severity log_level <partition> <severity>: Set specified
710 // partition to specified severity
712 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
713 parseLogLevel(json::Value const& jvParams)
714 {
716
717 if (jvParams.size() == 1)
718 {
719 jvRequest[jss::severity] = jvParams[0u].asString();
720 }
721 else if (jvParams.size() == 2)
722 {
723 jvRequest[jss::partition] = jvParams[0u].asString();
724 jvRequest[jss::severity] = jvParams[1u].asString();
725 }
726
727 return jvRequest;
728 }
729
730 // owner_info <account>
731 // account_info <account> [<ledger>]
732 // account_offers <account> [<ledger>]
735 {
736 return parseAccountRaw1(jvParams);
737 }
738
741 {
742 return parseAccountRaw1(jvParams);
743 }
744
745 // account_lines <account> <account>|"" [<ledger>]
748 {
749 return parseAccountRaw2(jvParams, jss::peer);
750 }
751
752 // account_channels <account> <account>|"" [<ledger>]
755 {
756 return parseAccountRaw2(jvParams, jss::destination_account);
757 }
758
759 // channel_authorize: <private_key> [<key_type>] <channel_id> <drops>
761 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
763 {
765
766 unsigned int index = 0;
767
768 if (jvParams.size() == 4)
769 {
770 jvRequest[jss::passphrase] = jvParams[index];
771 index++;
772
773 if (!keyTypeFromString(jvParams[index].asString()))
774 return rpcError(RpcBadKeyType);
775 jvRequest[jss::key_type] = jvParams[index];
776 index++;
777 }
778 else
779 {
780 jvRequest[jss::secret] = jvParams[index];
781 index++;
782 }
783
784 {
785 // verify the channel id is a valid 256 bit number
786 uint256 channelId;
787 if (!channelId.parseHex(jvParams[index].asString()))
789 jvRequest[jss::channel_id] = to_string(channelId);
790 index++;
791 }
792
793 if (!jvParams[index].isString() || !toUInt64(jvParams[index].asString()))
795 jvRequest[jss::amount] = jvParams[index];
796
797 // If additional parameters are appended, be sure to increment index
798 // here
799
800 return jvRequest;
801 }
802
803 // channel_verify <public_key> <channel_id> <drops> <signature>
805 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
807 {
808 std::string const strPk = jvParams[0u].asString();
809
810 if (!validPublicKey(strPk))
812
814
815 jvRequest[jss::public_key] = strPk;
816 {
817 // verify the channel id is a valid 256 bit number
818 uint256 channelId;
819 if (!channelId.parseHex(jvParams[1u].asString()))
821 }
822 jvRequest[jss::channel_id] = jvParams[1u].asString();
823
824 if (!jvParams[2u].isString() || !toUInt64(jvParams[2u].asString()))
826 jvRequest[jss::amount] = jvParams[2u];
827
828 jvRequest[jss::signature] = jvParams[3u].asString();
829
830 return jvRequest;
831 }
832
834 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
835 parseAccountRaw2(json::Value const& jvParams, char const* const acc2Field)
836 {
837 std::array<char const* const, 2> accFields{{jss::account, acc2Field}};
838 auto const nParams = jvParams.size();
840 for (auto i = 0; i < nParams; ++i)
841 {
842 // This was non-const. see comment below
843 std::string const strParam = jvParams[i].asString();
844
845 if (i == 1 && strParam.empty())
846 continue;
847
848 // Parameters 0 and 1 are accounts
849 if (i < 2)
850 {
851 if (parseBase58<AccountID>(strParam))
852 {
853 // TODO: this was std::move'd before but it does not work in practice.
854 // We would need a Value(std::string&&) for it to work.
855 // See https://github.com/XRPLF/rippled/issues/6677
856 jvRequest[accFields[i]] = strParam;
857 }
858 else
859 {
861 }
862 }
863 else
864 {
865 if (jvParseLedger(jvRequest, strParam))
866 return jvRequest;
868 }
869 }
870
871 return jvRequest;
872 }
873
874 // TODO: Get index from an alternate syntax: rXYZ:<index>
876 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
878 {
879 std::string const strIdent = jvParams[0u].asString();
880 unsigned int const iCursor = jvParams.size();
881
882 if (!parseBase58<AccountID>(strIdent))
884
885 // Get info on account.
887
888 jvRequest[jss::account] = strIdent;
889
890 if (iCursor == 2 && !jvParseLedger(jvRequest, jvParams[1u].asString()))
892
893 return jvRequest;
894 }
895
897 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
898 parseVault(json::Value const& jvParams)
899 {
900 std::string const strVaultID = jvParams[0u].asString();
902 if (!id.parseHex(strVaultID))
904
906 jvRequest[jss::vault_id] = strVaultID;
907
908 if (jvParams.size() > 1)
909 jvParseLedger(jvRequest, jvParams[1u].asString());
910
911 return jvRequest;
912 }
913
914 // peer_reservations_add <public_key> [<name>]
916 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
918 {
919 json::Value jvRequest;
920 jvRequest[jss::public_key] = jvParams[0u].asString();
921 if (jvParams.size() > 1)
922 {
923 jvRequest[jss::description] = jvParams[1u].asString();
924 }
925 return jvRequest;
926 }
927
928 // peer_reservations_del <public_key>
930 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
932 {
933 json::Value jvRequest;
934 jvRequest[jss::public_key] = jvParams[0u].asString();
935 return jvRequest;
936 }
937
938 // ripple_path_find <json> [<ledger>]
941 {
942 json::Reader reader;
944 bool const bLedger = 2 == jvParams.size();
945
946 JLOG(j_.trace()) << "RPC json: " << jvParams[0u];
947
948 if (reader.parse(jvParams[0u].asString(), jvRequest))
949 {
950 if (bLedger)
951 {
952 jvParseLedger(jvRequest, jvParams[1u].asString());
953 }
954
955 return jvRequest;
956 }
957
959 }
960
961 // simulate any transaction on the network
962 //
963 // simulate <tx_blob> [binary]
964 // simulate <tx_json> [binary]
966 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
967 parseSimulate(json::Value const& jvParams)
968 {
969 json::Value txJSON;
970 json::Reader reader;
972
973 if (reader.parse(jvParams[0u].asString(), txJSON))
974 {
975 jvRequest[jss::tx_json] = txJSON;
976 }
977 else
978 {
979 jvRequest[jss::tx_blob] = jvParams[0u].asString();
980 }
981
982 if (jvParams.size() == 2)
983 {
984 if (!jvParams[1u].isString() || jvParams[1u].asString() != "binary")
986 jvRequest[jss::binary] = true;
987 }
988
989 return jvRequest;
990 }
991
992 // sign/submit any transaction to the network
993 //
994 // sign <private_key> <json> offline
995 // submit <private_key> <json>
996 // submit <tx_blob>
998 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1000 {
1001 json::Value txJSON;
1002 json::Reader reader;
1003 bool const bOffline = jvParams.size() >= 3 && jvParams[2u].asString() == "offline";
1004 std::optional<std::string> const field = [&jvParams,
1005 bOffline]() -> std::optional<std::string> {
1006 if (jvParams.size() < 3)
1007 return std::nullopt;
1008 if (jvParams.size() < 4 && bOffline)
1009 return std::nullopt;
1010 json::UInt const index = bOffline ? 3u : 2u;
1011 return jvParams[index].asString();
1012 }();
1013
1014 if (1 == jvParams.size())
1015 {
1016 // Submitting tx_blob
1017
1019
1020 jvRequest[jss::tx_blob] = jvParams[0u].asString();
1021
1022 return jvRequest;
1023 }
1024 if ((jvParams.size() >= 2 || bOffline) && reader.parse(jvParams[1u].asString(), txJSON))
1025 {
1026 // Signing or submitting tx_json.
1028
1029 jvRequest[jss::secret] = jvParams[0u].asString();
1030 jvRequest[jss::tx_json] = txJSON;
1031
1032 if (bOffline)
1033 jvRequest[jss::offline] = true;
1034
1035 if (field)
1036 jvRequest[jss::signature_target] = *field;
1037
1038 return jvRequest;
1039 }
1040
1041 return rpcError(RpcInvalidParams);
1042 }
1043
1044 // submit any multisigned transaction to the network
1045 //
1046 // submit_multisigned <json>
1048 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1050 {
1051 if (1 == jvParams.size())
1052 {
1053 json::Value txJSON;
1054 json::Reader reader;
1055 if (reader.parse(jvParams[0u].asString(), txJSON))
1056 {
1058 jvRequest[jss::tx_json] = txJSON;
1059 return jvRequest;
1060 }
1061 }
1062
1063 return rpcError(RpcInvalidParams);
1064 }
1065
1066 // transaction_entry <tx_hash> <ledger_hash/ledger_index>
1068 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1070 {
1071 // Parameter count should have already been verified.
1072 XRPL_ASSERT(
1073 jvParams.size() == 2, "xrpl::RPCParser::parseTransactionEntry : valid parameter count");
1074
1075 std::string const txHash = jvParams[0u].asString();
1076 if (txHash.length() != 64)
1077 return rpcError(RpcInvalidParams);
1078
1080 jvRequest[jss::tx_hash] = txHash;
1081
1082 jvParseLedger(jvRequest, jvParams[1u].asString());
1083
1084 // jvParseLedger inserts a "ledger_index" of 0 if it doesn't
1085 // find a match.
1086 if (jvRequest.isMember(jss::ledger_index) && jvRequest[jss::ledger_index] == 0)
1087 return rpcError(RpcInvalidParams);
1088
1089 return jvRequest;
1090 }
1091
1092 // tx <transaction_id>
1094 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1095 parseTx(json::Value const& jvParams)
1096 {
1098
1099 if (jvParams.size() == 2 || jvParams.size() == 4)
1100 {
1101 if (jvParams[1u].asString() == jss::binary)
1102 jvRequest[jss::binary] = true;
1103 }
1104
1105 if (jvParams.size() >= 3)
1106 {
1107 auto const offset = jvParams.size() == 3 ? 0 : 1;
1108
1109 jvRequest[jss::min_ledger] = jvParams[1u + offset].asString();
1110 jvRequest[jss::max_ledger] = jvParams[2u + offset].asString();
1111 }
1112
1113 if (jvParams[0u].asString().length() == 16)
1114 {
1115 jvRequest[jss::ctid] = jvParams[0u].asString();
1116 }
1117 else
1118 {
1119 jvRequest[jss::transaction] = jvParams[0u].asString();
1120 }
1121
1122 return jvRequest;
1123 }
1124
1125 // tx_history <index>
1127 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1129 {
1131
1132 jvRequest[jss::start] = jvParams[0u].asUInt();
1133
1134 return jvRequest;
1135 }
1136
1137 // validation_create [<pass_phrase>|<seed>|<seed_key>]
1138 //
1139 // NOTE: It is poor security to specify secret information on the command
1140 // line. This information might be saved in the command shell history file
1141 // (e.g. .bash_history) and it may be leaked via the process status command
1142 // (i.e. ps).
1144 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1146 {
1148
1149 if (jvParams.size() != 0u)
1150 jvRequest[jss::secret] = jvParams[0u].asString();
1151
1152 return jvRequest;
1153 }
1154
1155 // wallet_propose [<passphrase>]
1156 // <passphrase> is only for testing. Master seeds should only be generated
1157 // randomly.
1159 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1161 {
1163
1164 if (jvParams.size() != 0u)
1165 jvRequest[jss::passphrase] = jvParams[0u].asString();
1166
1167 return jvRequest;
1168 }
1169
1170 // parse gateway balances
1171 // gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ <hotwallet>
1172 // ]]
1173
1175 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1177 {
1178 unsigned int index = 0;
1179 unsigned int const size = jvParams.size();
1180
1182
1183 std::string param = jvParams[index++].asString();
1184 if (param.empty())
1185 return rpc::makeParamError("Invalid first parameter");
1186
1187 if (param[0] != 'r')
1188 {
1189 if (param.size() == 64)
1190 {
1191 jvRequest[jss::ledger_hash] = param;
1192 }
1193 else
1194 {
1195 jvRequest[jss::ledger_index] = param;
1196 }
1197
1198 if (size <= index)
1199 return rpc::makeParamError("Invalid hotwallet");
1200
1201 param = jvParams[index++].asString();
1202 }
1203
1204 jvRequest[jss::account] = param;
1205
1206 if (index < size)
1207 {
1208 json::Value& hotWallets = (jvRequest["hotwallet"] = json::ValueType::Array);
1209 while (index < size)
1210 hotWallets.append(jvParams[index++].asString());
1211 }
1212
1213 return jvRequest;
1214 }
1215
1216 // server_definitions [hash]
1218 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1220 {
1222
1223 if (jvParams.size() == 1)
1224 {
1225 jvRequest[jss::hash] = jvParams[0u].asString();
1226 }
1227
1228 return jvRequest;
1229 }
1230
1231 // server_info [counters]
1233 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1235 {
1237 if (jvParams.size() == 1 && jvParams[0u].asString() == "counters")
1238 jvRequest[jss::counters] = true;
1239 return jvRequest;
1240 }
1241
1242public:
1243 //--------------------------------------------------------------------------
1244
1245 explicit RPCParser(unsigned apiVersion, beast::Journal j) : apiVersion_(apiVersion), j_(j)
1246 {
1247 }
1248
1249 //--------------------------------------------------------------------------
1250
1251 // Convert a rpc method and params to a request.
1252 // <-- { method: xyz, params: [... ] } or { error: ..., ... }
1254 parseCommand(std::string strMethod, json::Value jvParams, bool allowAnyCommand)
1255 {
1256 if (auto stream = j_.trace())
1257 {
1258 stream << "Method: '" << strMethod << "'";
1259 stream << "Params: " << jvParams;
1260 }
1261
1262 struct Command
1263 {
1264 char const* name;
1265 parseFuncPtr parse;
1266 int minParams;
1267 int maxParams;
1268 };
1269
1270 static constexpr Command kCommands[] = {
1271 // Request-response methods
1272 // - Returns an error, or the request.
1273 // - To modify the method, provide a new method in the request.
1274 {.name = "account_currencies",
1276 .minParams = 1,
1277 .maxParams = 3},
1278 {.name = "account_info",
1280 .minParams = 1,
1281 .maxParams = 3},
1282 {.name = "account_lines",
1284 .minParams = 1,
1285 .maxParams = 5},
1286 {.name = "account_channels",
1288 .minParams = 1,
1289 .maxParams = 3},
1290 {.name = "account_nfts",
1292 .minParams = 1,
1293 .maxParams = 5},
1294 {.name = "account_objects",
1296 .minParams = 1,
1297 .maxParams = 5},
1298 {.name = "account_offers",
1300 .minParams = 1,
1301 .maxParams = 4},
1302 {.name = "account_tx",
1304 .minParams = 1,
1305 .maxParams = 8},
1306 {.name = "amm_info", .parse = &RPCParser::parseAsIs, .minParams = 1, .maxParams = 2},
1307 {.name = "vault_info", .parse = &RPCParser::parseVault, .minParams = 1, .maxParams = 2},
1308 {.name = "book_changes",
1309 .parse = &RPCParser::parseLedgerId,
1310 .minParams = 1,
1311 .maxParams = 1},
1312 {.name = "book_offers",
1314 .minParams = 2,
1315 .maxParams = 7},
1316 {.name = "can_delete",
1317 .parse = &RPCParser::parseCanDelete,
1318 .minParams = 0,
1319 .maxParams = 1},
1320 {.name = "channel_authorize",
1322 .minParams = 3,
1323 .maxParams = 4},
1324 {.name = "channel_verify",
1326 .minParams = 4,
1327 .maxParams = 4},
1328 {.name = "connect", .parse = &RPCParser::parseConnect, .minParams = 1, .maxParams = 2},
1329 {.name = "consensus_info",
1330 .parse = &RPCParser::parseAsIs,
1331 .minParams = 0,
1332 .maxParams = 0},
1333 {.name = "deposit_authorized",
1335 .minParams = 2,
1336 .maxParams = 11},
1337 {.name = "feature", .parse = &RPCParser::parseFeature, .minParams = 0, .maxParams = 2},
1338 {.name = "fetch_info",
1339 .parse = &RPCParser::parseFetchInfo,
1340 .minParams = 0,
1341 .maxParams = 1},
1342 {.name = "gateway_balances",
1344 .minParams = 1,
1345 .maxParams = -1},
1346 {.name = "get_counts",
1347 .parse = &RPCParser::parseGetCounts,
1348 .minParams = 0,
1349 .maxParams = 1},
1350 {.name = "json", .parse = &RPCParser::parseJson, .minParams = 2, .maxParams = 2},
1351 {.name = "json2", .parse = &RPCParser::parseJson2, .minParams = 1, .maxParams = 1},
1352 {.name = "ledger", .parse = &RPCParser::parseLedger, .minParams = 0, .maxParams = 2},
1353 {.name = "ledger_accept",
1354 .parse = &RPCParser::parseAsIs,
1355 .minParams = 0,
1356 .maxParams = 0},
1357 {.name = "ledger_closed",
1358 .parse = &RPCParser::parseAsIs,
1359 .minParams = 0,
1360 .maxParams = 0},
1361 {.name = "ledger_current",
1362 .parse = &RPCParser::parseAsIs,
1363 .minParams = 0,
1364 .maxParams = 0},
1365 {.name = "ledger_entry",
1367 .minParams = 1,
1368 .maxParams = 2},
1369 {.name = "ledger_header",
1370 .parse = &RPCParser::parseLedgerId,
1371 .minParams = 1,
1372 .maxParams = 1},
1373 {.name = "ledger_request",
1374 .parse = &RPCParser::parseLedgerId,
1375 .minParams = 1,
1376 .maxParams = 1},
1377 {.name = "log_level",
1378 .parse = &RPCParser::parseLogLevel,
1379 .minParams = 0,
1380 .maxParams = 2},
1381 {.name = "logrotate", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1382 {.name = "manifest",
1383 .parse = &RPCParser::parseManifest,
1384 .minParams = 1,
1385 .maxParams = 1},
1386 {.name = "owner_info",
1388 .minParams = 1,
1389 .maxParams = 3},
1390 {.name = "peers", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1391 {.name = "ping", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1392 {.name = "print", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 1},
1393 // { "profile", &RPCParser::parseProfile, 1, 9
1394 // },
1395 {.name = "random", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1396 {.name = "peer_reservations_add",
1398 .minParams = 1,
1399 .maxParams = 2},
1400 {.name = "peer_reservations_del",
1402 .minParams = 1,
1403 .maxParams = 1},
1404 {.name = "peer_reservations_list",
1405 .parse = &RPCParser::parseAsIs,
1406 .minParams = 0,
1407 .maxParams = 0},
1408 {.name = "ripple_path_find",
1410 .minParams = 1,
1411 .maxParams = 2},
1412 {.name = "server_definitions",
1414 .minParams = 0,
1415 .maxParams = 1},
1416 {.name = "server_info",
1418 .minParams = 0,
1419 .maxParams = 1},
1420 {.name = "server_state",
1422 .minParams = 0,
1423 .maxParams = 1},
1424 {.name = "sign", .parse = &RPCParser::parseSignSubmit, .minParams = 2, .maxParams = 4},
1425 {.name = "sign_for", .parse = &RPCParser::parseSignFor, .minParams = 3, .maxParams = 4},
1426 {.name = "stop", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1427 {.name = "simulate",
1428 .parse = &RPCParser::parseSimulate,
1429 .minParams = 1,
1430 .maxParams = 2},
1431 {.name = "submit",
1433 .minParams = 1,
1434 .maxParams = 4},
1435 {.name = "submit_multisigned",
1437 .minParams = 1,
1438 .maxParams = 1},
1439 {.name = "transaction_entry",
1441 .minParams = 2,
1442 .maxParams = 2},
1443 {.name = "tx", .parse = &RPCParser::parseTx, .minParams = 1, .maxParams = 4},
1444 {.name = "tx_history",
1445 .parse = &RPCParser::parseTxHistory,
1446 .minParams = 1,
1447 .maxParams = 1},
1448 {.name = "unl_list", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1449 {.name = "validation_create",
1451 .minParams = 0,
1452 .maxParams = 1},
1453 {.name = "validator_info",
1454 .parse = &RPCParser::parseAsIs,
1455 .minParams = 0,
1456 .maxParams = 0},
1457 {.name = "version", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1458 {.name = "wallet_propose",
1460 .minParams = 0,
1461 .maxParams = 1},
1462 {.name = "internal",
1463 .parse = &RPCParser::parseInternal,
1464 .minParams = 1,
1465 .maxParams = -1},
1466
1467 // Event methods
1468 {.name = "path_find",
1469 .parse = &RPCParser::parseEvented,
1470 .minParams = -1,
1471 .maxParams = -1},
1472 {.name = "subscribe",
1473 .parse = &RPCParser::parseEvented,
1474 .minParams = -1,
1475 .maxParams = -1},
1476 {.name = "unsubscribe",
1477 .parse = &RPCParser::parseEvented,
1478 .minParams = -1,
1479 .maxParams = -1},
1480 };
1481
1482 auto const count = jvParams.size();
1483
1484 for (auto const& command : kCommands)
1485 {
1486 if (strMethod == command.name)
1487 {
1488 if ((command.minParams >= 0 && count < command.minParams) ||
1489 (command.maxParams >= 0 && count > command.maxParams))
1490 {
1491 JLOG(j_.debug()) << "Wrong number of parameters for " << command.name
1492 << " minimum=" << command.minParams
1493 << " maximum=" << command.maxParams << " actual=" << count;
1494
1495 return rpcError(RpcBadSyntax);
1496 }
1497
1498 return (this->*(command.parse))(jvParams);
1499 }
1500 }
1501
1502 // The command could not be found
1503 if (!allowAnyCommand)
1505
1506 return parseAsIs(jvParams);
1507 }
1508};
1509
1510//------------------------------------------------------------------------------
1511
1512//
1513// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
1514// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
1515// unspecified (HTTP errors and contents of 'error').
1516//
1517// 1.0 spec: http://json-rpc.org/wiki/specification
1518// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
1519//
1520
1522jsonrpcRequest(std::string const& strMethod, json::Value const& params, json::Value const& id)
1523{
1524 json::Value request;
1525 request[jss::method] = strMethod;
1526 request[jss::params] = params;
1527 request[jss::id] = id;
1528 return to_string(request) + "\n";
1529}
1530
1531namespace {
1532// Special local exception type thrown when request can't be parsed.
1533class RequestNotParsable : public std::runtime_error
1534{
1535 using std::runtime_error::runtime_error; // Inherit constructors
1536};
1537}; // namespace
1538
1540{
1541 explicit RPCCallImp() = default;
1542
1543 // VFALCO NOTE Is this a to-do comment or a doc comment?
1544 // Place the async result somewhere useful.
1545 static void
1546 callRPCHandler(json::Value* jvOutput, json::Value const& jvInput)
1547 {
1548 (*jvOutput) = jvInput;
1549 }
1550
1551 static bool
1553 std::function<void(json::Value const& jvInput)> callbackFuncP,
1554 boost::system::error_code const& ecResult,
1555 int iStatus,
1556 std::string const& strData,
1558 {
1559 if (callbackFuncP)
1560 {
1561 // Only care about the result, if we care to deliver it
1562 // callbackFuncP.
1563
1564 // Receive reply
1565 if (strData.empty())
1566 {
1568 "no response from server. Please "
1569 "ensure that the xrpld server is running in another "
1570 "process.");
1571 }
1572
1573 // Parse reply
1574 JLOG(j.debug()) << "RPC reply: " << strData << std::endl;
1575 if (strData.starts_with("Unable to parse request") ||
1576 strData.starts_with(jss::invalid_API_version.cStr()))
1578 json::Reader reader;
1579 json::Value jvReply;
1580 if (!reader.parse(strData, jvReply))
1581 Throw<std::runtime_error>("couldn't parse reply from server");
1582
1583 if (!jvReply)
1584 Throw<std::runtime_error>("expected reply to have result, error and id properties");
1585
1587
1588 jvResult["result"] = jvReply;
1589
1590 callbackFuncP(jvResult);
1591 }
1592
1593 return false;
1594 }
1595
1596 // Build the request.
1597 static void
1599 std::string const& strMethod,
1600 json::Value const& jvParams,
1602 std::string const& strPath,
1603 boost::asio::streambuf& sb,
1604 std::string const& strHost,
1606 {
1607 JLOG(j.debug()) << "requestRPC: strPath='" << strPath << "'";
1608
1609 std::ostream osRequest(&sb);
1610 osRequest << createHTTPPost(
1611 strHost, strPath, jsonrpcRequest(strMethod, jvParams, json::Value(1)), headers);
1612 }
1613};
1614
1615//------------------------------------------------------------------------------
1616
1617// Used internally by rpcClient.
1620 std::vector<std::string> const& args,
1621 json::Value& retParams,
1622 unsigned int apiVersion,
1624{
1626
1627 RPCParser rpParser(apiVersion, j);
1629
1630 for (int i = 1; i != args.size(); i++)
1631 jvRpcParams.append(args[i]);
1632
1634
1635 retParams[jss::method] = args[0];
1636 retParams[jss::params] = jvRpcParams;
1637
1638 jvRequest = rpParser.parseCommand(args[0], jvRpcParams, true);
1639
1640 auto insertApiVersion = [apiVersion](json::Value& jr) {
1641 if (jr.isObject() && !jr.isMember(jss::error) && !jr.isMember(jss::api_version))
1642 {
1643 jr[jss::api_version] = apiVersion;
1644 }
1645 };
1646
1647 if (jvRequest.isObject())
1648 {
1649 insertApiVersion(jvRequest);
1650 }
1651 else if (jvRequest.isArray())
1652 {
1653 // NOLINTNEXTLINE(modernize-use-ranges)
1654 std::for_each(jvRequest.begin(), jvRequest.end(), insertApiVersion);
1655 }
1656
1657 JLOG(j.trace()) << "RPC Request: " << jvRequest << std::endl;
1658 return jvRequest;
1659}
1660
1661//------------------------------------------------------------------------------
1662
1665 std::vector<std::string> const& args,
1666 Config const& config,
1667 Logs& logs,
1668 unsigned int apiVersion,
1670{
1671 static_assert(RpcBadSyntax == 1 && RpcSuccess == 0, "Expect specific rpc enum values.");
1672 if (args.empty())
1673 return {RpcBadSyntax, {}}; // rpcBAD_SYNTAX = print usage
1674
1675 int nRet = RpcSuccess;
1676 json::Value jvOutput;
1678
1679 try
1680 {
1682 jvRequest = rpcCmdToJson(args, jvRpc, apiVersion, logs.journal("RPCParser"));
1683
1684 if (jvRequest.isMember(jss::error))
1685 {
1686 jvOutput = jvRequest;
1687 jvOutput["rpc"] = jvRpc;
1688 }
1689 else
1690 {
1692 try
1693 {
1694 beast::logstream rpcCallLog{logs.journal("HTTPClient").warn()};
1695 setup = setupServerHandler(config, rpcCallLog);
1696 }
1697 catch (std::exception const&) // NOLINT(bugprone-empty-catch)
1698 {
1699 // ignore any exceptions, so the command
1700 // line client works without a config file
1701 }
1702
1703 if (config.rpcIp)
1704 {
1705 setup.client.ip = config.rpcIp->address().to_string();
1706 setup.client.port = config.rpcIp->port();
1707 }
1708
1710
1711 if (!setup.client.adminUser.empty())
1712 jvRequest["admin_user"] = setup.client.adminUser;
1713
1714 if (!setup.client.adminPassword.empty())
1715 jvRequest["admin_password"] = setup.client.adminPassword;
1716
1717 if (jvRequest.isObject())
1718 {
1719 jvParams.append(jvRequest);
1720 }
1721 else if (jvRequest.isArray())
1722 {
1723 for (json::UInt i = 0; i < jvRequest.size(); ++i)
1724 jvParams.append(jvRequest[i]);
1725 }
1726
1727 {
1728 boost::asio::io_context isService;
1730 isService,
1731 setup.client.ip,
1732 setup.client.port,
1733 setup.client.user,
1734 setup.client.password,
1735 "",
1736 // Allow parser to rewrite method.
1737 [&]() -> std::string {
1738 if (jvRequest.isMember(jss::method))
1739 return jvRequest[jss::method].asString();
1740 return jvRequest.isArray() ? "batch" : args[0];
1741 }(),
1742 jvParams, // Parsed, execute.
1743 static_cast<int>(setup.client.secure) != 0, // Use SSL
1744 config.quiet(),
1745 logs,
1746 [&jvOutput](json::Value const& jvInput) {
1747 RPCCallImp::callRPCHandler(&jvOutput, jvInput);
1748 },
1749 headers);
1750 isService.run(); // This blocks until there are no more
1751 // outstanding async calls.
1752 }
1753 if (jvOutput.isMember("result"))
1754 {
1755 // Had a successful JSON-RPC 2.0 call.
1756 jvOutput = jvOutput["result"];
1757
1758 // jvOutput may report a server side error.
1759 // It should report "status".
1760 }
1761 else
1762 {
1763 // Transport error.
1764 json::Value const jvRpcError = jvOutput;
1765
1766 jvOutput = rpcError(RpcJsonRpc);
1767 jvOutput["result"] = jvRpcError;
1768 }
1769
1770 // If had an error, supply invocation in result.
1771 if (jvOutput.isMember(jss::error))
1772 {
1773 jvOutput["rpc"] = jvRpc; // How the command was seen as method + params.
1774 jvOutput["request_sent"] = jvRequest; // How the command was translated.
1775 }
1776 }
1777
1778 if (jvOutput.isMember(jss::error))
1779 {
1780 jvOutput[jss::status] = "error";
1781 if (jvOutput.isMember(jss::error_code))
1782 {
1783 nRet = std::stoi(jvOutput[jss::error_code].asString());
1784 }
1785 else if (jvOutput[jss::error].isMember(jss::error_code))
1786 {
1787 nRet = std::stoi(jvOutput[jss::error][jss::error_code].asString());
1788 }
1789 else
1790 {
1791 nRet = RpcBadSyntax;
1792 }
1793 }
1794
1795 // YYY We could have a command line flag for single line output for
1796 // scripts. YYY We would intercept output here and simplify it.
1797 }
1798 catch (RequestNotParsable const& e)
1799 {
1800 jvOutput = rpcError(RpcInvalidParams);
1801 jvOutput["error_what"] = e.what();
1802 nRet = RpcInvalidParams;
1803 }
1804 catch (std::exception& e)
1805 {
1806 jvOutput = rpcError(RpcInternal);
1807 jvOutput["error_what"] = e.what();
1808 nRet = RpcInternal;
1809 }
1810
1811 return {nRet, std::move(jvOutput)};
1812}
1813
1814//------------------------------------------------------------------------------
1815
1816namespace rpc_call {
1817
1818int
1819fromCommandLine(Config const& config, std::vector<std::string> const& vCmd, Logs& logs)
1820{
1821 auto const result = rpcClient(vCmd, config, logs, rpc::kApiCommandLineVersion);
1822
1823 std::cout << result.second.toStyledString();
1824
1825 return result.first;
1826}
1827
1828//------------------------------------------------------------------------------
1829
1830void
1832 boost::asio::io_context& ioContext,
1833 std::string const& strIp,
1834 std::uint16_t const iPort,
1835 std::string const& strUsername,
1836 std::string const& strPassword,
1837 std::string const& strPath,
1838 std::string const& strMethod,
1839 json::Value const& jvParams,
1840 bool const bSSL,
1841 bool const quiet,
1842 Logs& logs,
1843 std::function<void(json::Value const& jvInput)> callbackFuncP,
1845{
1846 auto j = logs.journal("HTTPClient");
1847
1848 // Connect to localhost
1849 if (!quiet)
1850 {
1851 JLOG(j.info()) << (bSSL ? "Securely connecting to " : "Connecting to ") << strIp << ":"
1852 << iPort << std::endl;
1853 }
1854
1855 // HTTP basic authentication
1856 headers["Authorization"] =
1857 std::string("Basic ") + base64Encode(strUsername + ":" + strPassword);
1858
1859 // Send request
1860
1861 // Number of bytes to try to receive if no
1862 // Content-Length header received
1863 constexpr auto kRpcReplyMaxBytes = megabytes(256);
1864
1865 using namespace std::chrono_literals;
1866 static constexpr auto kRpcWebhookTimeout = 30s;
1867
1869 bSSL,
1870 ioContext,
1871 strIp,
1872 iPort,
1873 [strMethod, jvParams, headers, strPath, j](
1874 boost::asio::streambuf& sb, std::string const& strHost) {
1875 RPCCallImp::onRequest(strMethod, jvParams, headers, strPath, sb, strHost, j);
1876 },
1877 kRpcReplyMaxBytes,
1878 kRpcWebhookTimeout,
1879 [callbackFuncP, j](
1880 boost::system::error_code const& ecResult, int iStatus, std::string const& strData) {
1881 return RPCCallImp::onResponse(callbackFuncP, ecResult, iStatus, strData, j);
1882 },
1883 j);
1884}
1885
1886} // namespace rpc_call
1887
1888} // namespace xrpl
T all_of(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
Stream warn() const
Definition Journal.h:356
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:117
const_iterator begin() const
bool isNull() const
isNull() tests to see if this field is null.
bool isObject() const
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
const_iterator end() const
UInt asUInt() const
std::string asString() const
Returns the unquoted string value.
bool isObjectOrNull() const
bool isMember(char const *key) const
Return true if the object has a member named key.
Int asInt() const
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:525
std::optional< beast::ip::Endpoint > rpcIp
static void request(bool bSSL, boost::asio::io_context &ioContext, std::string strSite, unsigned short const port, std::function< void(boost::asio::streambuf &sb, std::string const &strHost)> build, std::size_t responseMax, std::chrono::seconds timeout, std::function< bool(boost::system::error_code const &ecResult, int iStatus, std::string const &strData)> complete, beast::Journal const &j)
Manages partitions for logging.
Definition Log.h:23
beast::Journal journal(std::string const &name)
Definition Log.cpp:137
json::Value parseManifest(json::Value const &jvParams)
Definition RPCCall.cpp:206
json::Value parseJson2(json::Value const &jvParams)
Definition RPCCall.cpp:592
json::Value parseLedgerId(json::Value const &jvParams)
Definition RPCCall.cpp:674
json::Value parseJson(json::Value const &jvParams)
Definition RPCCall.cpp:544
json::Value parseAccountTransactions(json::Value const &jvParams)
Definition RPCCall.cpp:242
json::Value parseSignFor(json::Value const &jvParams)
Definition RPCCall.cpp:516
RPCParser(unsigned apiVersion, beast::Journal j)
Definition RPCCall.cpp:1245
json::Value parseLedgerEntry(json::Value const &jvParams)
Definition RPCCall.cpp:695
json::Value parseAccountItems(json::Value const &jvParams)
Definition RPCCall.cpp:734
json::Value parseEvented(json::Value const &jvParams)
Definition RPCCall.cpp:460
json::Value parseCommand(std::string strMethod, json::Value jvParams, bool allowAnyCommand)
Definition RPCCall.cpp:1254
static bool validPublicKey(std::string const &strPk, TokenType type=TokenType::AccountPublic)
Definition RPCCall.cpp:157
json::Value parseChannelAuthorize(json::Value const &jvParams)
Definition RPCCall.cpp:762
json::Value parseFeature(json::Value const &jvParams)
Definition RPCCall.cpp:468
json::Value parseChannelVerify(json::Value const &jvParams)
Definition RPCCall.cpp:806
json::Value parseInternal(json::Value const &jvParams)
Definition RPCCall.cpp:189
json::Value parseAccountRaw1(json::Value const &jvParams)
Definition RPCCall.cpp:877
json::Value parseTxHistory(json::Value const &jvParams)
Definition RPCCall.cpp:1128
json::Value parseRipplePathFind(json::Value const &jvParams)
Definition RPCCall.cpp:940
json::Value parseValidationCreate(json::Value const &jvParams)
Definition RPCCall.cpp:1145
json::Value parseServerDefinitions(json::Value const &jvParams)
Definition RPCCall.cpp:1219
json::Value parseServerInfo(json::Value const &jvParams)
Definition RPCCall.cpp:1234
json::Value parseSubmitMultiSigned(json::Value const &jvParams)
Definition RPCCall.cpp:1049
json::Value parsePeerReservationsDel(json::Value const &jvParams)
Definition RPCCall.cpp:931
json::Value parseGetCounts(json::Value const &jvParams)
Definition RPCCall.cpp:502
json::Value parseFetchInfo(json::Value const &jvParams)
Definition RPCCall.cpp:227
json::Value parseLogLevel(json::Value const &jvParams)
Definition RPCCall.cpp:713
json::Value(RPCParser::*)(json::Value const &jvParams) parseFuncPtr
Definition RPCCall.cpp:173
static json::Value jvParseCurrencyIssuer(std::string const &strCurrencyIssuer)
Definition RPCCall.cpp:124
json::Value parseWalletPropose(json::Value const &jvParams)
Definition RPCCall.cpp:1160
json::Value parseAccountCurrencies(json::Value const &jvParams)
Definition RPCCall.cpp:740
json::Value parseVault(json::Value const &jvParams)
Definition RPCCall.cpp:898
json::Value parseGatewayBalances(json::Value const &jvParams)
Definition RPCCall.cpp:1176
json::Value parseTx(json::Value const &jvParams)
Definition RPCCall.cpp:1095
static bool jvParseLedger(json::Value &jvRequest, std::string const &strLedger)
Definition RPCCall.cpp:103
json::Value parseSimulate(json::Value const &jvParams)
Definition RPCCall.cpp:967
json::Value parseConnect(json::Value const &jvParams)
Definition RPCCall.cpp:408
json::Value parseSignSubmit(json::Value const &jvParams)
Definition RPCCall.cpp:999
json::Value parseBookOffers(json::Value const &jvParams)
Definition RPCCall.cpp:318
json::Value parseAccountLines(json::Value const &jvParams)
Definition RPCCall.cpp:747
json::Value parseAccountChannels(json::Value const &jvParams)
Definition RPCCall.cpp:754
json::Value parseTransactionEntry(json::Value const &jvParams)
Definition RPCCall.cpp:1069
unsigned const apiVersion_
Definition RPCCall.cpp:97
json::Value parseAsIs(json::Value const &jvParams)
Definition RPCCall.cpp:177
bool isValidJson2(json::Value const &jv)
Definition RPCCall.cpp:566
beast::Journal const j_
Definition RPCCall.cpp:98
json::Value parseCanDelete(json::Value const &jvParams)
Definition RPCCall.cpp:385
json::Value parseDepositAuthorized(json::Value const &jvParams)
Definition RPCCall.cpp:437
json::Value parsePeerReservationsAdd(json::Value const &jvParams)
Definition RPCCall.cpp:917
json::Value parseLedger(json::Value const &jvParams)
Definition RPCCall.cpp:644
json::Value parseAccountRaw2(json::Value const &jvParams, char const *const acc2Field)
Definition RPCCall.cpp:835
T count(T... args)
T empty(T... args)
T end(T... args)
T endl(T... args)
T find_first_not_of(T... args)
T find_last_of(T... args)
T for_each(T... args)
constexpr Out lexicalCast(In in, Out defaultValue=Out())
Convert from one type to another.
BasicLogstream< char > logstream
Definition Journal.h:474
constexpr Zero kZero
Definition Zero.h:30
unsigned int UInt
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
Processes XRPL RPC calls.
Definition RPCCall.cpp:1816
void fromNetwork(boost::asio::io_context &ioContext, std::string const &strIp, std::uint16_t const iPort, std::string const &strUsername, std::string const &strPassword, std::string const &strPath, std::string const &strMethod, json::Value const &jvParams, bool const bSSL, bool const quiet, Logs &logs, std::function< void(json::Value const &jvInput)> callbackFuncP, std::unordered_map< std::string, std::string > headers)
Definition RPCCall.cpp:1831
int fromCommandLine(Config const &config, std::vector< std::string > const &vCmd, Logs &logs)
Definition RPCCall.cpp:1819
json::Value makeParamError(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:231
json::Value invalidFieldError(std::string const &name)
Definition ErrorCodes.h:285
static constexpr auto kApiCommandLineVersion
Definition ApiVersion.h:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ RpcChannelAmtMalformed
Definition ErrorCodes.h:84
@ RpcInternal
Definition ErrorCodes.h:113
@ RpcBadKeyType
Definition ErrorCodes.h:116
@ RpcSuccess
Definition ErrorCodes.h:27
@ RpcActMalformed
Definition ErrorCodes.h:73
@ RpcNotSynced
Definition ErrorCodes.h:50
@ RpcLgrIdxsInvalid
Definition ErrorCodes.h:95
@ RpcPublicMalformed
Definition ErrorCodes.h:100
@ RpcJsonRpc
Definition ErrorCodes.h:30
@ RpcNoEvents
Definition ErrorCodes.h:37
@ RpcInvalidParams
Definition ErrorCodes.h:67
@ RpcBadSyntax
Definition ErrorCodes.h:29
@ RpcChannelMalformed
Definition ErrorCodes.h:83
@ RpcLgrIdxMalformed
Definition ErrorCodes.h:96
@ RpcUnknownCommand
Definition ErrorCodes.h:68
ServerHandler::Setup setupServerHandler(Config const &config, std::ostream &log)
std::optional< KeyType > keyTypeFromString(std::string const &s)
Definition KeyType.h:14
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::string jsonrpcRequest(std::string const &strMethod, json::Value const &params, json::Value const &id)
Definition RPCCall.cpp:1522
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
json::Value rpcError(ErrorCodeI iError)
Definition RPCErr.cpp:13
json::Value rpcCmdToJson(std::vector< std::string > const &args, json::Value &retParams, unsigned int apiVersion, beast::Journal j)
Definition RPCCall.cpp:1619
constexpr auto megabytes(T value) noexcept
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
static std::string const & systemName()
std::string base64Encode(std::uint8_t const *data, std::size_t len)
std::optional< std::uint64_t > toUInt64(std::string const &s)
TokenType
Definition tokens.h:19
std::pair< int, json::Value > rpcClient(std::vector< std::string > const &args, Config const &config, Logs &logs, unsigned int apiVersion, std::unordered_map< std::string, std::string > const &headers)
Internal invocation of RPC client.
Definition RPCCall.cpp:1664
bool isRpcError(json::Value jvResult)
Definition RPCErr.cpp:22
std::string createHTTPPost(std::string const &strHost, std::string const &strPath, std::string const &strMsg, std::unordered_map< std::string, std::string > const &mapRequestHeaders)
Definition RPCCall.cpp:67
BaseUInt< 256 > uint256
Definition base_uint.h:580
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T size(T... args)
T starts_with(T... args)
T stoi(T... args)
T str(T... args)
static bool onResponse(std::function< void(json::Value const &jvInput)> callbackFuncP, boost::system::error_code const &ecResult, int iStatus, std::string const &strData, beast::Journal j)
Definition RPCCall.cpp:1552
RPCCallImp()=default
static void callRPCHandler(json::Value *jvOutput, json::Value const &jvInput)
Definition RPCCall.cpp:1546
static void onRequest(std::string const &strMethod, json::Value const &jvParams, std::unordered_map< std::string, std::string > const &headers, std::string const &strPath, boost::asio::streambuf &sb, std::string const &strHost, beast::Journal j)
Definition RPCCall.cpp:1598