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 // BuildInfo::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 for (auto const& j : jv)
573 {
574 if (!isValidJson2(j))
575 return false;
576 }
577 return true;
578 }
579 if (jv.isObject())
580 {
581 if (jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0" &&
582 jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0" &&
583 jv.isMember(jss::id) && jv.isMember(jss::method))
584 {
585 return !jv.isMember(jss::params) ||
586 (jv[jss::params].isNull() || jv[jss::params].isArray() ||
587 jv[jss::params].isObject());
588 }
589 }
590 return false;
591 }
592
594 parseJson2(json::Value const& jvParams)
595 {
596 json::Reader reader;
597 json::Value jv;
598 bool const validParse = reader.parse(jvParams[0u].asString(), jv);
599 if (validParse && isValidJson2(jv))
600 {
601 if (jv.isObject())
602 {
604 if (jv.isMember(jss::params))
605 {
606 auto const& params = jv[jss::params];
607 for (auto i = params.begin(); i != params.end(); ++i)
608 jv1[i.key().asString()] = *i;
609 }
610 jv1[jss::jsonrpc] = jv[jss::jsonrpc];
611 jv1[jss::ripplerpc] = jv[jss::ripplerpc];
612 jv1[jss::id] = jv[jss::id];
613 jv1[jss::method] = jv[jss::method];
614 return jv1;
615 }
616 // else jv.isArray()
618 for (json::UInt j = 0; j < jv.size(); ++j)
619 {
620 if (jv[j].isMember(jss::params))
621 {
622 auto const& params = jv[j][jss::params];
623 for (auto i = params.begin(); i != params.end(); ++i)
624 jv1[j][i.key().asString()] = *i;
625 }
626 jv1[j][jss::jsonrpc] = jv[j][jss::jsonrpc];
627 jv1[j][jss::ripplerpc] = jv[j][jss::ripplerpc];
628 jv1[j][jss::id] = jv[j][jss::id];
629 jv1[j][jss::method] = jv[j][jss::method];
630 }
631 return jv1;
632 }
633 auto jvError = rpcError(RpcInvalidParams);
634 if (jv.isMember(jss::jsonrpc))
635 jvError[jss::jsonrpc] = jv[jss::jsonrpc];
636 if (jv.isMember(jss::ripplerpc))
637 jvError[jss::ripplerpc] = jv[jss::ripplerpc];
638 if (jv.isMember(jss::id))
639 jvError[jss::id] = jv[jss::id];
640 return jvError;
641 }
642
643 // ledger [id|index|current|closed|validated] [full|tx]
645 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
646 parseLedger(json::Value const& jvParams)
647 {
649
650 if (jvParams.size() == 0u)
651 {
652 return jvRequest;
653 }
654
655 jvParseLedger(jvRequest, jvParams[0u].asString());
656
657 if (2 == jvParams.size())
658 {
659 if (jvParams[1u].asString() == "full")
660 {
661 jvRequest[jss::full] = true;
662 }
663 else if (jvParams[1u].asString() == "tx")
664 {
665 jvRequest[jss::transactions] = true;
666 jvRequest[jss::expand] = true;
667 }
668 }
669
670 return jvRequest;
671 }
672
673 // ledger_header <id>|<index>
675 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
676 parseLedgerId(json::Value const& jvParams)
677 {
679
680 std::string const strLedger = jvParams[0u].asString();
681
682 if (strLedger.length() == 64)
683 {
684 jvRequest[jss::ledger_hash] = strLedger;
685 }
686 else
687 {
688 jvRequest[jss::ledger_index] = beast::lexicalCast<std::uint32_t>(strLedger);
689 }
690
691 return jvRequest;
692 }
693
694 // ledger_entry [id] [<index>]
696 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
698 {
700
701 jvRequest[jss::index] = jvParams[0u].asString();
702
703 if (jvParams.size() == 2 && !jvParseLedger(jvRequest, jvParams[1u].asString()))
705
706 return jvRequest;
707 }
708
709 // log_level: Get log levels
710 // log_level <severity>: Set master log level to the
711 // specified severity log_level <partition> <severity>: Set specified
712 // partition to specified severity
714 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
715 parseLogLevel(json::Value const& jvParams)
716 {
718
719 if (jvParams.size() == 1)
720 {
721 jvRequest[jss::severity] = jvParams[0u].asString();
722 }
723 else if (jvParams.size() == 2)
724 {
725 jvRequest[jss::partition] = jvParams[0u].asString();
726 jvRequest[jss::severity] = jvParams[1u].asString();
727 }
728
729 return jvRequest;
730 }
731
732 // owner_info <account>
733 // account_info <account> [<ledger>]
734 // account_offers <account> [<ledger>]
737 {
738 return parseAccountRaw1(jvParams);
739 }
740
743 {
744 return parseAccountRaw1(jvParams);
745 }
746
747 // account_lines <account> <account>|"" [<ledger>]
750 {
751 return parseAccountRaw2(jvParams, jss::peer);
752 }
753
754 // account_channels <account> <account>|"" [<ledger>]
757 {
758 return parseAccountRaw2(jvParams, jss::destination_account);
759 }
760
761 // channel_authorize: <private_key> [<key_type>] <channel_id> <drops>
763 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
765 {
767
768 unsigned int index = 0;
769
770 if (jvParams.size() == 4)
771 {
772 jvRequest[jss::passphrase] = jvParams[index];
773 index++;
774
775 if (!keyTypeFromString(jvParams[index].asString()))
776 return rpcError(RpcBadKeyType);
777 jvRequest[jss::key_type] = jvParams[index];
778 index++;
779 }
780 else
781 {
782 jvRequest[jss::secret] = jvParams[index];
783 index++;
784 }
785
786 {
787 // verify the channel id is a valid 256 bit number
788 uint256 channelId;
789 if (!channelId.parseHex(jvParams[index].asString()))
791 jvRequest[jss::channel_id] = to_string(channelId);
792 index++;
793 }
794
795 if (!jvParams[index].isString() || !toUInt64(jvParams[index].asString()))
797 jvRequest[jss::amount] = jvParams[index];
798
799 // If additional parameters are appended, be sure to increment index
800 // here
801
802 return jvRequest;
803 }
804
805 // channel_verify <public_key> <channel_id> <drops> <signature>
807 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
809 {
810 std::string const strPk = jvParams[0u].asString();
811
812 if (!validPublicKey(strPk))
814
816
817 jvRequest[jss::public_key] = strPk;
818 {
819 // verify the channel id is a valid 256 bit number
820 uint256 channelId;
821 if (!channelId.parseHex(jvParams[1u].asString()))
823 }
824 jvRequest[jss::channel_id] = jvParams[1u].asString();
825
826 if (!jvParams[2u].isString() || !toUInt64(jvParams[2u].asString()))
828 jvRequest[jss::amount] = jvParams[2u];
829
830 jvRequest[jss::signature] = jvParams[3u].asString();
831
832 return jvRequest;
833 }
834
836 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
837 parseAccountRaw2(json::Value const& jvParams, char const* const acc2Field)
838 {
839 std::array<char const* const, 2> accFields{{jss::account, acc2Field}};
840 auto const nParams = jvParams.size();
842 for (auto i = 0; i < nParams; ++i)
843 {
844 // This was non-const. see comment below
845 std::string const strParam = jvParams[i].asString();
846
847 if (i == 1 && strParam.empty())
848 continue;
849
850 // Parameters 0 and 1 are accounts
851 if (i < 2)
852 {
853 if (parseBase58<AccountID>(strParam))
854 {
855 // TODO: this was std::move'd before but it does not work in practice.
856 // We would need a Value(std::string&&) for it to work.
857 // See https://github.com/XRPLF/rippled/issues/6677
858 jvRequest[accFields[i]] = strParam;
859 }
860 else
861 {
863 }
864 }
865 else
866 {
867 if (jvParseLedger(jvRequest, strParam))
868 return jvRequest;
870 }
871 }
872
873 return jvRequest;
874 }
875
876 // TODO: Get index from an alternate syntax: rXYZ:<index>
878 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
880 {
881 std::string const strIdent = jvParams[0u].asString();
882 unsigned int const iCursor = jvParams.size();
883
884 if (!parseBase58<AccountID>(strIdent))
886
887 // Get info on account.
889
890 jvRequest[jss::account] = strIdent;
891
892 if (iCursor == 2 && !jvParseLedger(jvRequest, jvParams[1u].asString()))
894
895 return jvRequest;
896 }
897
899 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
900 parseVault(json::Value const& jvParams)
901 {
902 std::string const strVaultID = jvParams[0u].asString();
903 uint256 id = beast::kZero;
904 if (!id.parseHex(strVaultID))
906
908 jvRequest[jss::vault_id] = strVaultID;
909
910 if (jvParams.size() > 1)
911 jvParseLedger(jvRequest, jvParams[1u].asString());
912
913 return jvRequest;
914 }
915
916 // peer_reservations_add <public_key> [<name>]
918 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
920 {
921 json::Value jvRequest;
922 jvRequest[jss::public_key] = jvParams[0u].asString();
923 if (jvParams.size() > 1)
924 {
925 jvRequest[jss::description] = jvParams[1u].asString();
926 }
927 return jvRequest;
928 }
929
930 // peer_reservations_del <public_key>
932 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
934 {
935 json::Value jvRequest;
936 jvRequest[jss::public_key] = jvParams[0u].asString();
937 return jvRequest;
938 }
939
940 // ripple_path_find <json> [<ledger>]
943 {
944 json::Reader reader;
946 bool const bLedger = 2 == jvParams.size();
947
948 JLOG(j_.trace()) << "RPC json: " << jvParams[0u];
949
950 if (reader.parse(jvParams[0u].asString(), jvRequest))
951 {
952 if (bLedger)
953 {
954 jvParseLedger(jvRequest, jvParams[1u].asString());
955 }
956
957 return jvRequest;
958 }
959
961 }
962
963 // simulate any transaction on the network
964 //
965 // simulate <tx_blob> [binary]
966 // simulate <tx_json> [binary]
968 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
969 parseSimulate(json::Value const& jvParams)
970 {
971 json::Value txJSON;
972 json::Reader reader;
974
975 if (reader.parse(jvParams[0u].asString(), txJSON))
976 {
977 jvRequest[jss::tx_json] = txJSON;
978 }
979 else
980 {
981 jvRequest[jss::tx_blob] = jvParams[0u].asString();
982 }
983
984 if (jvParams.size() == 2)
985 {
986 if (!jvParams[1u].isString() || jvParams[1u].asString() != "binary")
988 jvRequest[jss::binary] = true;
989 }
990
991 return jvRequest;
992 }
993
994 // sign/submit any transaction to the network
995 //
996 // sign <private_key> <json> offline
997 // submit <private_key> <json>
998 // submit <tx_blob>
1000 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1002 {
1003 json::Value txJSON;
1004 json::Reader reader;
1005 bool const bOffline = jvParams.size() >= 3 && jvParams[2u].asString() == "offline";
1006 std::optional<std::string> const field = [&jvParams,
1007 bOffline]() -> std::optional<std::string> {
1008 if (jvParams.size() < 3)
1009 return std::nullopt;
1010 if (jvParams.size() < 4 && bOffline)
1011 return std::nullopt;
1012 json::UInt const index = bOffline ? 3u : 2u;
1013 return jvParams[index].asString();
1014 }();
1015
1016 if (1 == jvParams.size())
1017 {
1018 // Submitting tx_blob
1019
1021
1022 jvRequest[jss::tx_blob] = jvParams[0u].asString();
1023
1024 return jvRequest;
1025 }
1026 if ((jvParams.size() >= 2 || bOffline) && reader.parse(jvParams[1u].asString(), txJSON))
1027 {
1028 // Signing or submitting tx_json.
1030
1031 jvRequest[jss::secret] = jvParams[0u].asString();
1032 jvRequest[jss::tx_json] = txJSON;
1033
1034 if (bOffline)
1035 jvRequest[jss::offline] = true;
1036
1037 if (field)
1038 jvRequest[jss::signature_target] = *field;
1039
1040 return jvRequest;
1041 }
1042
1043 return rpcError(RpcInvalidParams);
1044 }
1045
1046 // submit any multisigned transaction to the network
1047 //
1048 // submit_multisigned <json>
1050 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1052 {
1053 if (1 == jvParams.size())
1054 {
1055 json::Value txJSON;
1056 json::Reader reader;
1057 if (reader.parse(jvParams[0u].asString(), txJSON))
1058 {
1060 jvRequest[jss::tx_json] = txJSON;
1061 return jvRequest;
1062 }
1063 }
1064
1065 return rpcError(RpcInvalidParams);
1066 }
1067
1068 // transaction_entry <tx_hash> <ledger_hash/ledger_index>
1070 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1072 {
1073 // Parameter count should have already been verified.
1074 XRPL_ASSERT(
1075 jvParams.size() == 2, "xrpl::RPCParser::parseTransactionEntry : valid parameter count");
1076
1077 std::string const txHash = jvParams[0u].asString();
1078 if (txHash.length() != 64)
1079 return rpcError(RpcInvalidParams);
1080
1082 jvRequest[jss::tx_hash] = txHash;
1083
1084 jvParseLedger(jvRequest, jvParams[1u].asString());
1085
1086 // jvParseLedger inserts a "ledger_index" of 0 if it doesn't
1087 // find a match.
1088 if (jvRequest.isMember(jss::ledger_index) && jvRequest[jss::ledger_index] == 0)
1089 return rpcError(RpcInvalidParams);
1090
1091 return jvRequest;
1092 }
1093
1094 // tx <transaction_id>
1096 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1097 parseTx(json::Value const& jvParams)
1098 {
1100
1101 if (jvParams.size() == 2 || jvParams.size() == 4)
1102 {
1103 if (jvParams[1u].asString() == jss::binary)
1104 jvRequest[jss::binary] = true;
1105 }
1106
1107 if (jvParams.size() >= 3)
1108 {
1109 auto const offset = jvParams.size() == 3 ? 0 : 1;
1110
1111 jvRequest[jss::min_ledger] = jvParams[1u + offset].asString();
1112 jvRequest[jss::max_ledger] = jvParams[2u + offset].asString();
1113 }
1114
1115 if (jvParams[0u].asString().length() == 16)
1116 {
1117 jvRequest[jss::ctid] = jvParams[0u].asString();
1118 }
1119 else
1120 {
1121 jvRequest[jss::transaction] = jvParams[0u].asString();
1122 }
1123
1124 return jvRequest;
1125 }
1126
1127 // tx_history <index>
1129 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1131 {
1133
1134 jvRequest[jss::start] = jvParams[0u].asUInt();
1135
1136 return jvRequest;
1137 }
1138
1139 // validation_create [<pass_phrase>|<seed>|<seed_key>]
1140 //
1141 // NOTE: It is poor security to specify secret information on the command
1142 // line. This information might be saved in the command shell history file
1143 // (e.g. .bash_history) and it may be leaked via the process status command
1144 // (i.e. ps).
1146 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1148 {
1150
1151 if (jvParams.size() != 0u)
1152 jvRequest[jss::secret] = jvParams[0u].asString();
1153
1154 return jvRequest;
1155 }
1156
1157 // wallet_propose [<passphrase>]
1158 // <passphrase> is only for testing. Master seeds should only be generated
1159 // randomly.
1161 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1163 {
1165
1166 if (jvParams.size() != 0u)
1167 jvRequest[jss::passphrase] = jvParams[0u].asString();
1168
1169 return jvRequest;
1170 }
1171
1172 // parse gateway balances
1173 // gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ <hotwallet>
1174 // ]]
1175
1177 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1179 {
1180 unsigned int index = 0;
1181 unsigned int const size = jvParams.size();
1182
1184
1185 std::string param = jvParams[index++].asString();
1186 if (param.empty())
1187 return RPC::makeParamError("Invalid first parameter");
1188
1189 if (param[0] != 'r')
1190 {
1191 if (param.size() == 64)
1192 {
1193 jvRequest[jss::ledger_hash] = param;
1194 }
1195 else
1196 {
1197 jvRequest[jss::ledger_index] = param;
1198 }
1199
1200 if (size <= index)
1201 return RPC::makeParamError("Invalid hotwallet");
1202
1203 param = jvParams[index++].asString();
1204 }
1205
1206 jvRequest[jss::account] = param;
1207
1208 if (index < size)
1209 {
1210 json::Value& hotWallets = (jvRequest["hotwallet"] = json::ValueType::Array);
1211 while (index < size)
1212 hotWallets.append(jvParams[index++].asString());
1213 }
1214
1215 return jvRequest;
1216 }
1217
1218 // server_definitions [hash]
1220 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1222 {
1224
1225 if (jvParams.size() == 1)
1226 {
1227 jvRequest[jss::hash] = jvParams[0u].asString();
1228 }
1229
1230 return jvRequest;
1231 }
1232
1233 // server_info [counters]
1235 // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
1237 {
1239 if (jvParams.size() == 1 && jvParams[0u].asString() == "counters")
1240 jvRequest[jss::counters] = true;
1241 return jvRequest;
1242 }
1243
1244public:
1245 //--------------------------------------------------------------------------
1246
1247 explicit RPCParser(unsigned apiVersion, beast::Journal j) : apiVersion_(apiVersion), j_(j)
1248 {
1249 }
1250
1251 //--------------------------------------------------------------------------
1252
1253 // Convert a rpc method and params to a request.
1254 // <-- { method: xyz, params: [... ] } or { error: ..., ... }
1256 parseCommand(std::string strMethod, json::Value jvParams, bool allowAnyCommand)
1257 {
1258 if (auto stream = j_.trace())
1259 {
1260 stream << "Method: '" << strMethod << "'";
1261 stream << "Params: " << jvParams;
1262 }
1263
1264 struct Command
1265 {
1266 char const* name;
1267 parseFuncPtr parse;
1268 int minParams;
1269 int maxParams;
1270 };
1271
1272 static constexpr Command kCommands[] = {
1273 // Request-response methods
1274 // - Returns an error, or the request.
1275 // - To modify the method, provide a new method in the request.
1276 {.name = "account_currencies",
1278 .minParams = 1,
1279 .maxParams = 3},
1280 {.name = "account_info",
1282 .minParams = 1,
1283 .maxParams = 3},
1284 {.name = "account_lines",
1286 .minParams = 1,
1287 .maxParams = 5},
1288 {.name = "account_channels",
1290 .minParams = 1,
1291 .maxParams = 3},
1292 {.name = "account_nfts",
1294 .minParams = 1,
1295 .maxParams = 5},
1296 {.name = "account_objects",
1298 .minParams = 1,
1299 .maxParams = 5},
1300 {.name = "account_offers",
1302 .minParams = 1,
1303 .maxParams = 4},
1304 {.name = "account_tx",
1306 .minParams = 1,
1307 .maxParams = 8},
1308 {.name = "amm_info", .parse = &RPCParser::parseAsIs, .minParams = 1, .maxParams = 2},
1309 {.name = "vault_info", .parse = &RPCParser::parseVault, .minParams = 1, .maxParams = 2},
1310 {.name = "book_changes",
1311 .parse = &RPCParser::parseLedgerId,
1312 .minParams = 1,
1313 .maxParams = 1},
1314 {.name = "book_offers",
1316 .minParams = 2,
1317 .maxParams = 7},
1318 {.name = "can_delete",
1319 .parse = &RPCParser::parseCanDelete,
1320 .minParams = 0,
1321 .maxParams = 1},
1322 {.name = "channel_authorize",
1324 .minParams = 3,
1325 .maxParams = 4},
1326 {.name = "channel_verify",
1328 .minParams = 4,
1329 .maxParams = 4},
1330 {.name = "connect", .parse = &RPCParser::parseConnect, .minParams = 1, .maxParams = 2},
1331 {.name = "consensus_info",
1332 .parse = &RPCParser::parseAsIs,
1333 .minParams = 0,
1334 .maxParams = 0},
1335 {.name = "deposit_authorized",
1337 .minParams = 2,
1338 .maxParams = 11},
1339 {.name = "feature", .parse = &RPCParser::parseFeature, .minParams = 0, .maxParams = 2},
1340 {.name = "fetch_info",
1341 .parse = &RPCParser::parseFetchInfo,
1342 .minParams = 0,
1343 .maxParams = 1},
1344 {.name = "gateway_balances",
1346 .minParams = 1,
1347 .maxParams = -1},
1348 {.name = "get_counts",
1349 .parse = &RPCParser::parseGetCounts,
1350 .minParams = 0,
1351 .maxParams = 1},
1352 {.name = "json", .parse = &RPCParser::parseJson, .minParams = 2, .maxParams = 2},
1353 {.name = "json2", .parse = &RPCParser::parseJson2, .minParams = 1, .maxParams = 1},
1354 {.name = "ledger", .parse = &RPCParser::parseLedger, .minParams = 0, .maxParams = 2},
1355 {.name = "ledger_accept",
1356 .parse = &RPCParser::parseAsIs,
1357 .minParams = 0,
1358 .maxParams = 0},
1359 {.name = "ledger_closed",
1360 .parse = &RPCParser::parseAsIs,
1361 .minParams = 0,
1362 .maxParams = 0},
1363 {.name = "ledger_current",
1364 .parse = &RPCParser::parseAsIs,
1365 .minParams = 0,
1366 .maxParams = 0},
1367 {.name = "ledger_entry",
1369 .minParams = 1,
1370 .maxParams = 2},
1371 {.name = "ledger_header",
1372 .parse = &RPCParser::parseLedgerId,
1373 .minParams = 1,
1374 .maxParams = 1},
1375 {.name = "ledger_request",
1376 .parse = &RPCParser::parseLedgerId,
1377 .minParams = 1,
1378 .maxParams = 1},
1379 {.name = "log_level",
1380 .parse = &RPCParser::parseLogLevel,
1381 .minParams = 0,
1382 .maxParams = 2},
1383 {.name = "logrotate", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1384 {.name = "manifest",
1385 .parse = &RPCParser::parseManifest,
1386 .minParams = 1,
1387 .maxParams = 1},
1388 {.name = "owner_info",
1390 .minParams = 1,
1391 .maxParams = 3},
1392 {.name = "peers", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1393 {.name = "ping", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1394 {.name = "print", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 1},
1395 // { "profile", &RPCParser::parseProfile, 1, 9
1396 // },
1397 {.name = "random", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1398 {.name = "peer_reservations_add",
1400 .minParams = 1,
1401 .maxParams = 2},
1402 {.name = "peer_reservations_del",
1404 .minParams = 1,
1405 .maxParams = 1},
1406 {.name = "peer_reservations_list",
1407 .parse = &RPCParser::parseAsIs,
1408 .minParams = 0,
1409 .maxParams = 0},
1410 {.name = "ripple_path_find",
1412 .minParams = 1,
1413 .maxParams = 2},
1414 {.name = "server_definitions",
1416 .minParams = 0,
1417 .maxParams = 1},
1418 {.name = "server_info",
1420 .minParams = 0,
1421 .maxParams = 1},
1422 {.name = "server_state",
1424 .minParams = 0,
1425 .maxParams = 1},
1426 {.name = "sign", .parse = &RPCParser::parseSignSubmit, .minParams = 2, .maxParams = 4},
1427 {.name = "sign_for", .parse = &RPCParser::parseSignFor, .minParams = 3, .maxParams = 4},
1428 {.name = "stop", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1429 {.name = "simulate",
1430 .parse = &RPCParser::parseSimulate,
1431 .minParams = 1,
1432 .maxParams = 2},
1433 {.name = "submit",
1435 .minParams = 1,
1436 .maxParams = 4},
1437 {.name = "submit_multisigned",
1439 .minParams = 1,
1440 .maxParams = 1},
1441 {.name = "transaction_entry",
1443 .minParams = 2,
1444 .maxParams = 2},
1445 {.name = "tx", .parse = &RPCParser::parseTx, .minParams = 1, .maxParams = 4},
1446 {.name = "tx_history",
1447 .parse = &RPCParser::parseTxHistory,
1448 .minParams = 1,
1449 .maxParams = 1},
1450 {.name = "unl_list", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1451 {.name = "validation_create",
1453 .minParams = 0,
1454 .maxParams = 1},
1455 {.name = "validator_info",
1456 .parse = &RPCParser::parseAsIs,
1457 .minParams = 0,
1458 .maxParams = 0},
1459 {.name = "version", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0},
1460 {.name = "wallet_propose",
1462 .minParams = 0,
1463 .maxParams = 1},
1464 {.name = "internal",
1465 .parse = &RPCParser::parseInternal,
1466 .minParams = 1,
1467 .maxParams = -1},
1468
1469 // Event methods
1470 {.name = "path_find",
1471 .parse = &RPCParser::parseEvented,
1472 .minParams = -1,
1473 .maxParams = -1},
1474 {.name = "subscribe",
1475 .parse = &RPCParser::parseEvented,
1476 .minParams = -1,
1477 .maxParams = -1},
1478 {.name = "unsubscribe",
1479 .parse = &RPCParser::parseEvented,
1480 .minParams = -1,
1481 .maxParams = -1},
1482 };
1483
1484 auto const count = jvParams.size();
1485
1486 for (auto const& command : kCommands)
1487 {
1488 if (strMethod == command.name)
1489 {
1490 if ((command.minParams >= 0 && count < command.minParams) ||
1491 (command.maxParams >= 0 && count > command.maxParams))
1492 {
1493 JLOG(j_.debug()) << "Wrong number of parameters for " << command.name
1494 << " minimum=" << command.minParams
1495 << " maximum=" << command.maxParams << " actual=" << count;
1496
1497 return rpcError(RpcBadSyntax);
1498 }
1499
1500 return (this->*(command.parse))(jvParams);
1501 }
1502 }
1503
1504 // The command could not be found
1505 if (!allowAnyCommand)
1507
1508 return parseAsIs(jvParams);
1509 }
1510};
1511
1512//------------------------------------------------------------------------------
1513
1514//
1515// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
1516// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
1517// unspecified (HTTP errors and contents of 'error').
1518//
1519// 1.0 spec: http://json-rpc.org/wiki/specification
1520// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
1521//
1522
1524jsonrpcRequest(std::string const& strMethod, json::Value const& params, json::Value const& id)
1525{
1526 json::Value request;
1527 request[jss::method] = strMethod;
1528 request[jss::params] = params;
1529 request[jss::id] = id;
1530 return to_string(request) + "\n";
1531}
1532
1533namespace {
1534// Special local exception type thrown when request can't be parsed.
1535class RequestNotParsable : public std::runtime_error
1536{
1537 using std::runtime_error::runtime_error; // Inherit constructors
1538};
1539}; // namespace
1540
1542{
1543 explicit RPCCallImp() = default;
1544
1545 // VFALCO NOTE Is this a to-do comment or a doc comment?
1546 // Place the async result somewhere useful.
1547 static void
1548 callRPCHandler(json::Value* jvOutput, json::Value const& jvInput)
1549 {
1550 (*jvOutput) = jvInput;
1551 }
1552
1553 static bool
1555 std::function<void(json::Value const& jvInput)> callbackFuncP,
1556 boost::system::error_code const& ecResult,
1557 int iStatus,
1558 std::string const& strData,
1560 {
1561 if (callbackFuncP)
1562 {
1563 // Only care about the result, if we care to deliver it
1564 // callbackFuncP.
1565
1566 // Receive reply
1567 if (strData.empty())
1568 {
1570 "no response from server. Please "
1571 "ensure that the xrpld server is running in another "
1572 "process.");
1573 }
1574
1575 // Parse reply
1576 JLOG(j.debug()) << "RPC reply: " << strData << std::endl;
1577 if (strData.starts_with("Unable to parse request") ||
1578 strData.starts_with(jss::invalid_API_version.cStr()))
1580 json::Reader reader;
1581 json::Value jvReply;
1582 if (!reader.parse(strData, jvReply))
1583 Throw<std::runtime_error>("couldn't parse reply from server");
1584
1585 if (!jvReply)
1586 Throw<std::runtime_error>("expected reply to have result, error and id properties");
1587
1589
1590 jvResult["result"] = jvReply;
1591
1592 callbackFuncP(jvResult);
1593 }
1594
1595 return false;
1596 }
1597
1598 // Build the request.
1599 static void
1601 std::string const& strMethod,
1602 json::Value const& jvParams,
1604 std::string const& strPath,
1605 boost::asio::streambuf& sb,
1606 std::string const& strHost,
1608 {
1609 JLOG(j.debug()) << "requestRPC: strPath='" << strPath << "'";
1610
1611 std::ostream osRequest(&sb);
1612 osRequest << createHTTPPost(
1613 strHost, strPath, jsonrpcRequest(strMethod, jvParams, json::Value(1)), headers);
1614 }
1615};
1616
1617//------------------------------------------------------------------------------
1618
1619// Used internally by rpcClient.
1622 std::vector<std::string> const& args,
1623 json::Value& retParams,
1624 unsigned int apiVersion,
1626{
1628
1629 RPCParser rpParser(apiVersion, j);
1631
1632 for (int i = 1; i != args.size(); i++)
1633 jvRpcParams.append(args[i]);
1634
1636
1637 retParams[jss::method] = args[0];
1638 retParams[jss::params] = jvRpcParams;
1639
1640 jvRequest = rpParser.parseCommand(args[0], jvRpcParams, true);
1641
1642 auto insertApiVersion = [apiVersion](json::Value& jr) {
1643 if (jr.isObject() && !jr.isMember(jss::error) && !jr.isMember(jss::api_version))
1644 {
1645 jr[jss::api_version] = apiVersion;
1646 }
1647 };
1648
1649 if (jvRequest.isObject())
1650 {
1651 insertApiVersion(jvRequest);
1652 }
1653 else if (jvRequest.isArray())
1654 {
1655 // NOLINTNEXTLINE(modernize-use-ranges)
1656 std::for_each(jvRequest.begin(), jvRequest.end(), insertApiVersion);
1657 }
1658
1659 JLOG(j.trace()) << "RPC Request: " << jvRequest << std::endl;
1660 return jvRequest;
1661}
1662
1663//------------------------------------------------------------------------------
1664
1667 std::vector<std::string> const& args,
1668 Config const& config,
1669 Logs& logs,
1670 unsigned int apiVersion,
1672{
1673 static_assert(RpcBadSyntax == 1 && RpcSuccess == 0, "Expect specific rpc enum values.");
1674 if (args.empty())
1675 return {RpcBadSyntax, {}}; // rpcBAD_SYNTAX = print usage
1676
1677 int nRet = RpcSuccess;
1678 json::Value jvOutput;
1680
1681 try
1682 {
1684 jvRequest = rpcCmdToJson(args, jvRpc, apiVersion, logs.journal("RPCParser"));
1685
1686 if (jvRequest.isMember(jss::error))
1687 {
1688 jvOutput = jvRequest;
1689 jvOutput["rpc"] = jvRpc;
1690 }
1691 else
1692 {
1694 try
1695 {
1696 beast::logstream rpcCallLog{logs.journal("HTTPClient").warn()};
1697 setup = setupServerHandler(config, rpcCallLog);
1698 }
1699 catch (std::exception const&) // NOLINT(bugprone-empty-catch)
1700 {
1701 // ignore any exceptions, so the command
1702 // line client works without a config file
1703 }
1704
1705 if (config.rpcIp)
1706 {
1707 setup.client.ip = config.rpcIp->address().to_string();
1708 setup.client.port = config.rpcIp->port();
1709 }
1710
1712
1713 if (!setup.client.adminUser.empty())
1714 jvRequest["admin_user"] = setup.client.adminUser;
1715
1716 if (!setup.client.adminPassword.empty())
1717 jvRequest["admin_password"] = setup.client.adminPassword;
1718
1719 if (jvRequest.isObject())
1720 {
1721 jvParams.append(jvRequest);
1722 }
1723 else if (jvRequest.isArray())
1724 {
1725 for (json::UInt i = 0; i < jvRequest.size(); ++i)
1726 jvParams.append(jvRequest[i]);
1727 }
1728
1729 {
1730 boost::asio::io_context isService;
1732 isService,
1733 setup.client.ip,
1734 setup.client.port,
1735 setup.client.user,
1736 setup.client.password,
1737 "",
1738 // Allow parser to rewrite method.
1739 [&]() -> std::string {
1740 if (jvRequest.isMember(jss::method))
1741 return jvRequest[jss::method].asString();
1742 return jvRequest.isArray() ? "batch" : args[0];
1743 }(),
1744 jvParams, // Parsed, execute.
1745 static_cast<int>(setup.client.secure) != 0, // Use SSL
1746 config.quiet(),
1747 logs,
1748 std::bind(RPCCallImp::callRPCHandler, &jvOutput, std::placeholders::_1),
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 RPCCall {
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 std::bind(
1875 strMethod,
1876 jvParams,
1877 headers,
1878 strPath,
1879 std::placeholders::_1,
1880 std::placeholders::_2,
1881 j),
1882 kRpcReplyMaxBytes,
1883 kRpcWebhookTimeout,
1884 std::bind(
1886 callbackFuncP,
1887 std::placeholders::_1,
1888 std::placeholders::_2,
1889 std::placeholders::_3,
1890 j),
1891 j);
1892}
1893
1894} // namespace RPCCall
1895
1896} // namespace xrpl
T begin(T... args)
T bind(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
Stream warn() const
Definition Journal.h:309
Unserialize a JSON document into a Value.
Definition json_reader.h:17
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
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:507
bool quiet() const
Definition Config.h:306
std::optional< beast::IP::Endpoint > rpcIp
Definition Config.h:259
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:20
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:594
json::Value parseLedgerId(json::Value const &jvParams)
Definition RPCCall.cpp:676
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:1247
json::Value parseLedgerEntry(json::Value const &jvParams)
Definition RPCCall.cpp:697
json::Value parseAccountItems(json::Value const &jvParams)
Definition RPCCall.cpp:736
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:1256
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:764
json::Value parseFeature(json::Value const &jvParams)
Definition RPCCall.cpp:468
json::Value parseChannelVerify(json::Value const &jvParams)
Definition RPCCall.cpp:808
json::Value parseInternal(json::Value const &jvParams)
Definition RPCCall.cpp:189
json::Value parseAccountRaw1(json::Value const &jvParams)
Definition RPCCall.cpp:879
json::Value parseTxHistory(json::Value const &jvParams)
Definition RPCCall.cpp:1130
json::Value parseRipplePathFind(json::Value const &jvParams)
Definition RPCCall.cpp:942
json::Value parseValidationCreate(json::Value const &jvParams)
Definition RPCCall.cpp:1147
json::Value parseServerDefinitions(json::Value const &jvParams)
Definition RPCCall.cpp:1221
json::Value parseServerInfo(json::Value const &jvParams)
Definition RPCCall.cpp:1236
json::Value parseSubmitMultiSigned(json::Value const &jvParams)
Definition RPCCall.cpp:1051
json::Value parsePeerReservationsDel(json::Value const &jvParams)
Definition RPCCall.cpp:933
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:715
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:1162
json::Value parseAccountCurrencies(json::Value const &jvParams)
Definition RPCCall.cpp:742
json::Value parseVault(json::Value const &jvParams)
Definition RPCCall.cpp:900
json::Value parseGatewayBalances(json::Value const &jvParams)
Definition RPCCall.cpp:1178
json::Value parseTx(json::Value const &jvParams)
Definition RPCCall.cpp:1097
static bool jvParseLedger(json::Value &jvRequest, std::string const &strLedger)
Definition RPCCall.cpp:103
json::Value parseSimulate(json::Value const &jvParams)
Definition RPCCall.cpp:969
json::Value parseConnect(json::Value const &jvParams)
Definition RPCCall.cpp:408
json::Value parseSignSubmit(json::Value const &jvParams)
Definition RPCCall.cpp:1001
json::Value parseBookOffers(json::Value const &jvParams)
Definition RPCCall.cpp:318
json::Value parseAccountLines(json::Value const &jvParams)
Definition RPCCall.cpp:749
json::Value parseAccountChannels(json::Value const &jvParams)
Definition RPCCall.cpp:756
json::Value parseTransactionEntry(json::Value const &jvParams)
Definition RPCCall.cpp:1071
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:919
json::Value parseLedger(json::Value const &jvParams)
Definition RPCCall.cpp:646
json::Value parseAccountRaw2(json::Value const &jvParams, char const *const acc2Field)
Definition RPCCall.cpp:837
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)
Out lexicalCast(In in, Out defaultValue=Out())
Convert from one type to another.
BasicLogstream< char > logstream
Definition Journal.h:427
unsigned int UInt
@ Array
array value (ordered list)
Definition json_value.h:25
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
Processes XRPL RPC calls.
Definition RPCCall.cpp:1816
int fromCommandLine(Config const &config, std::vector< std::string > const &vCmd, Logs &logs)
Definition RPCCall.cpp:1819
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
static constexpr auto kApiCommandLineVersion
Definition ApiVersion.h:44
json::Value invalidFieldError(std::string const &name)
Definition ErrorCodes.h:273
json::Value makeParamError(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:219
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ RpcChannelAmtMalformed
Definition ErrorCodes.h:83
@ RpcInternal
Definition ErrorCodes.h:112
@ RpcBadKeyType
Definition ErrorCodes.h:115
@ RpcSuccess
Definition ErrorCodes.h:26
@ RpcActMalformed
Definition ErrorCodes.h:72
@ RpcNotSynced
Definition ErrorCodes.h:49
@ RpcLgrIdxsInvalid
Definition ErrorCodes.h:94
@ RpcPublicMalformed
Definition ErrorCodes.h:99
@ RpcJsonRpc
Definition ErrorCodes.h:29
@ RpcNoEvents
Definition ErrorCodes.h:36
@ RpcInvalidParams
Definition ErrorCodes.h:66
@ RpcBadSyntax
Definition ErrorCodes.h:28
@ RpcChannelMalformed
Definition ErrorCodes.h:82
@ RpcLgrIdxMalformed
Definition ErrorCodes.h:95
@ RpcUnknownCommand
Definition ErrorCodes.h:67
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:93
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
std::string jsonrpcRequest(std::string const &strMethod, json::Value const &params, json::Value const &id)
Definition RPCCall.cpp:1524
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
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:1621
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:18
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:1666
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:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
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:1554
RPCCallImp()=default
static void callRPCHandler(json::Value *jvOutput, json::Value const &jvInput)
Definition RPCCall.cpp:1548
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:1600