rippled
Loading...
Searching...
No Matches
LedgerEntry_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2025 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/Oracle.h>
22#include <test/jtx/attester.h>
23#include <test/jtx/delegate.h>
24#include <test/jtx/multisign.h>
25#include <test/jtx/xchain_bridge.h>
26
27#include <xrpl/beast/unit_test.h>
28#include <xrpl/json/json_value.h>
29#include <xrpl/protocol/AccountID.h>
30#include <xrpl/protocol/ErrorCodes.h>
31#include <xrpl/protocol/STXChainBridge.h>
32#include <xrpl/protocol/jss.h>
33
34#if (defined(__clang_major__) && __clang_major__ < 15)
35#include <experimental/source_location>
37#else
38#include <source_location>
40#endif
41namespace ripple {
42
43namespace test {
44
58
60 {jss::account, FieldType::AccountField},
61 {jss::accounts, FieldType::TwoAccountArrayField},
62 {jss::authorize, FieldType::AccountField},
63 {jss::authorized, FieldType::AccountField},
64 {jss::credential_type, FieldType::BlobField},
65 {jss::currency, FieldType::CurrencyField},
66 {jss::issuer, FieldType::AccountField},
67 {jss::oracle_document_id, FieldType::UInt32Field},
68 {jss::owner, FieldType::AccountField},
69 {jss::seq, FieldType::UInt32Field},
70 {jss::subject, FieldType::AccountField},
71 {jss::ticket_seq, FieldType::UInt32Field},
72};
73
76{
77 auto it = std::ranges::find_if(mappings, [&fieldName](auto const& pair) {
78 return pair.first == fieldName;
79 });
80 if (it != mappings.end())
81 {
82 return it->second;
83 }
84 else
85 {
86 Throw<std::runtime_error>(
87 "`mappings` is missing field " + std::string(fieldName.c_str()));
88 }
89}
90
93{
94 switch (typeID)
95 {
97 return "number";
99 return "number";
101 return "hex string";
103 return "AccountID";
105 return "hex string";
107 return "Currency";
109 return "array";
111 return "hex string or object";
113 return "length-2 array of Accounts";
114 default:
115 Throw<std::runtime_error>(
116 "unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
117 }
118}
119
121{
122 void
124 Json::Value const& jv,
125 std::string const& err,
126 std::string const& msg,
127 source_location const location = source_location::current())
128 {
129 if (BEAST_EXPECT(jv.isMember(jss::status)))
130 BEAST_EXPECTS(
131 jv[jss::status] == "error", std::to_string(location.line()));
132 if (BEAST_EXPECT(jv.isMember(jss::error)))
133 BEAST_EXPECTS(
134 jv[jss::error] == err,
135 "Expected error " + err + ", received " +
136 jv[jss::error].asString() + ", at line " +
137 std::to_string(location.line()) + ", " +
138 jv.toStyledString());
139 if (msg.empty())
140 {
141 BEAST_EXPECTS(
142 jv[jss::error_message] == Json::nullValue ||
143 jv[jss::error_message] == "",
144 "Expected no error message, received \"" +
145 jv[jss::error_message].asString() + "\", at line " +
146 std::to_string(location.line()) + ", " +
147 jv.toStyledString());
148 }
149 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
150 BEAST_EXPECTS(
151 jv[jss::error_message] == msg,
152 "Expected error message \"" + msg + "\", received \"" +
153 jv[jss::error_message].asString() + "\", at line " +
154 std::to_string(location.line()) + ", " +
155 jv.toStyledString());
156 }
157
160 {
161 static Json::Value const injectObject = []() {
163 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
164 obj[jss::ledger_index] = "validated";
165 return obj;
166 }();
167 static Json::Value const injectArray = []() {
169 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
170 arr[1u] = "validated";
171 return arr;
172 }();
173 static std::array<Json::Value, 21> const allBadValues = {
174 "", // 0
175 true, // 1
176 1, // 2
177 "1", // 3
178 -1, // 4
179 1.1, // 5
180 "-1", // 6
181 "abcdef", // 7
182 "ABCDEF", // 8
183 "12KK", // 9
184 "0123456789ABCDEFGH", // 10
185 "rJxKV9e9p6wiPw!!!!xrJ4X1n98LosPL1sgcJW", // 11
186 "rPSTrR5yEr11uMkfsz1kHCp9jK4aoa3Avv", // 12
187 "n9K2isxwTxcSHJKxMkJznDoWXAUs7NNy49H9Fknz1pC7oHAH3kH9", // 13
188 "USD", // 14
189 "USDollars", // 15
190 "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6B01403D"
191 "6D", // 16
192 Json::arrayValue, // 17
193 Json::objectValue, // 18
194 injectObject, // 19
195 injectArray // 20
196 };
197
198 auto remove =
201 indices.begin(), indices.end());
203 values.reserve(allBadValues.size() - indexSet.size());
204 for (std::size_t i = 0; i < allBadValues.size(); ++i)
205 {
206 if (indexSet.find(i) == indexSet.end())
207 {
208 values.push_back(allBadValues[i]);
209 }
210 }
211 return values;
212 };
213
214 static auto const& badUInt32Values = remove({2, 3});
215 static auto const& badUInt64Values = remove({2, 3});
216 static auto const& badHashValues = remove({2, 3, 7, 8, 16});
217 static auto const& badAccountValues = remove({12});
218 static auto const& badBlobValues = remove({3, 7, 8, 16});
219 static auto const& badCurrencyValues = remove({14});
220 static auto const& badArrayValues = remove({17, 20});
221 static auto const& badIndexValues = remove({12, 16, 18, 19});
222
223 switch (fieldType)
224 {
226 return badUInt32Values;
228 return badUInt64Values;
230 return badHashValues;
232 return badAccountValues;
234 return badBlobValues;
236 return badCurrencyValues;
239 return badArrayValues;
241 return badIndexValues;
242 default:
243 Throw<std::runtime_error>(
244 "unknown type " +
245 std::to_string(static_cast<uint8_t>(fieldType)));
246 }
247 }
248
251 {
252 static Json::Value const twoAccountArray = []() {
254 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
255 arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
256 return arr;
257 }();
258
259 auto const typeID = getFieldType(fieldName);
260 switch (typeID)
261 {
263 return 1;
265 return 1;
267 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
268 "B01403D6D";
270 return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
272 return "ABCDEF";
274 return "USD";
276 return Json::arrayValue;
278 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
279 "B01403D6D";
281 return twoAccountArray;
282 default:
283 Throw<std::runtime_error>(
284 "unknown type " +
285 std::to_string(static_cast<uint8_t>(typeID)));
286 }
287 }
288
289 void
291 test::jtx::Env& env,
292 Json::Value correctRequest,
293 Json::StaticString const fieldName,
294 FieldType const typeID,
295 std::string const& expectedError,
296 bool required = true,
297 source_location const location = source_location::current())
298 {
299 forAllApiVersions([&, this](unsigned apiVersion) {
300 if (required)
301 {
302 correctRequest.removeMember(fieldName);
303 Json::Value const jrr = env.rpc(
304 apiVersion,
305 "json",
306 "ledger_entry",
307 to_string(correctRequest))[jss::result];
308 if (apiVersion < 2u)
309 checkErrorValue(jrr, "unknownOption", "", location);
310 else
312 jrr,
313 "invalidParams",
314 "No ledger_entry params provided.",
315 location);
316 }
317 auto tryField = [&](Json::Value fieldValue) -> void {
318 correctRequest[fieldName] = fieldValue;
319 Json::Value const jrr = env.rpc(
320 apiVersion,
321 "json",
322 "ledger_entry",
323 to_string(correctRequest))[jss::result];
324 auto const expectedErrMsg =
325 RPC::expected_field_message(fieldName, getTypeName(typeID));
326 checkErrorValue(jrr, expectedError, expectedErrMsg, location);
327 };
328
329 auto const& badValues = getBadValues(typeID);
330 for (auto const& value : badValues)
331 {
332 tryField(value);
333 }
334 if (required)
335 {
336 tryField(Json::nullValue);
337 }
338 });
339 }
340
341 void
343 test::jtx::Env& env,
344 Json::Value correctRequest,
345 Json::StaticString parentFieldName,
346 Json::StaticString fieldName,
347 FieldType typeID,
348 std::string const& expectedError,
349 bool required = true,
350 source_location const location = source_location::current())
351 {
352 forAllApiVersions([&, this](unsigned apiVersion) {
353 if (required)
354 {
355 correctRequest[parentFieldName].removeMember(fieldName);
356 Json::Value const jrr = env.rpc(
357 apiVersion,
358 "json",
359 "ledger_entry",
360 to_string(correctRequest))[jss::result];
362 jrr,
363 "malformedRequest",
365 location);
366
367 correctRequest[parentFieldName][fieldName] = Json::nullValue;
368 Json::Value const jrr2 = env.rpc(
369 apiVersion,
370 "json",
371 "ledger_entry",
372 to_string(correctRequest))[jss::result];
374 jrr2,
375 "malformedRequest",
377 location);
378 }
379 auto tryField = [&](Json::Value fieldValue) -> void {
380 correctRequest[parentFieldName][fieldName] = fieldValue;
381
382 Json::Value const jrr = env.rpc(
383 apiVersion,
384 "json",
385 "ledger_entry",
386 to_string(correctRequest))[jss::result];
388 jrr,
389 expectedError,
390 RPC::expected_field_message(fieldName, getTypeName(typeID)),
391 location);
392 };
393
394 auto const& badValues = getBadValues(typeID);
395 for (auto const& value : badValues)
396 {
397 tryField(value);
398 }
399 });
400 }
401
402 // No subfields
403 void
405 test::jtx::Env& env,
406 Json::StaticString const& parentField,
407 source_location const location = source_location::current())
408 {
410 env,
411 Json::Value{},
412 parentField,
414 "malformedRequest",
415 true,
416 location);
417 }
418
425
426 void
428 test::jtx::Env& env,
429 Json::StaticString const& parentField,
430 std::vector<Subfield> const& subfields,
431 source_location const location = source_location::current())
432 {
434 env,
435 Json::Value{},
436 parentField,
438 "malformedRequest",
439 true,
440 location);
441
442 Json::Value correctOutput;
443 correctOutput[parentField] = Json::objectValue;
444 for (auto const& subfield : subfields)
445 {
446 correctOutput[parentField][subfield.fieldName] =
447 getCorrectValue(subfield.fieldName);
448 }
449
450 for (auto const& subfield : subfields)
451 {
452 auto const fieldType = getFieldType(subfield.fieldName);
454 env,
455 correctOutput,
456 parentField,
457 subfield.fieldName,
458 fieldType,
459 subfield.malformedErrorMsg,
460 subfield.required,
461 location);
462 }
463 }
464
465 void
467 {
468 testcase("Invalid requests");
469 using namespace test::jtx;
470 Env env{*this};
471 Account const alice{"alice"};
472 env.fund(XRP(10000), alice);
473 env.close();
474 {
475 // Missing ledger_entry ledger_hash
476 Json::Value jvParams;
477 jvParams[jss::account_root] = alice.human();
478 jvParams[jss::ledger_hash] =
479 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
480 "AA";
481 auto const jrr = env.rpc(
482 "json", "ledger_entry", to_string(jvParams))[jss::result];
483 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
484 }
485 {
486 // Missing ledger_entry ledger_hash
487 Json::Value jvParams;
488 jvParams[jss::account_root] = alice.human();
489 auto const typeId = FieldType::HashField;
490
491 forAllApiVersions([&, this](unsigned apiVersion) {
492 auto tryField = [&](Json::Value fieldValue) -> void {
493 jvParams[jss::ledger_hash] = fieldValue;
494 Json::Value const jrr = env.rpc(
495 apiVersion,
496 "json",
497 "ledger_entry",
498 to_string(jvParams))[jss::result];
499 auto const expectedErrMsg = fieldValue.isString()
500 ? "ledgerHashMalformed"
501 : "ledgerHashNotString";
502 checkErrorValue(jrr, "invalidParams", expectedErrMsg);
503 };
504
505 auto const& badValues = getBadValues(typeId);
506 for (auto const& value : badValues)
507 {
508 tryField(value);
509 }
510 });
511 }
512
513 {
514 // ask for an zero index
515 Json::Value jvParams;
516 jvParams[jss::ledger_index] = "validated";
517 jvParams[jss::index] =
518 "00000000000000000000000000000000000000000000000000000000000000"
519 "00";
520 auto const jrr = env.rpc(
521 "json", "ledger_entry", to_string(jvParams))[jss::result];
522 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
523 }
524
525 forAllApiVersions([&, this](unsigned apiVersion) {
526 // "features" is not an option supported by ledger_entry.
527 {
529 jvParams[jss::features] =
530 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
531 "AAAAAAAAAA";
532 jvParams[jss::api_version] = apiVersion;
533 Json::Value const jrr = env.rpc(
534 "json", "ledger_entry", to_string(jvParams))[jss::result];
535
536 if (apiVersion < 2u)
537 checkErrorValue(jrr, "unknownOption", "");
538 else
540 jrr,
541 "invalidParams",
542 "No ledger_entry params provided.");
543 }
544 });
545 }
546
547 void
549 {
550 testcase("AccountRoot");
551 using namespace test::jtx;
552
553 auto cfg = envconfig();
554 cfg->FEES.reference_fee = 10;
555 Env env{*this, std::move(cfg)};
556
557 Account const alice{"alice"};
558 env.fund(XRP(10000), alice);
559 env.close();
560
561 std::string const ledgerHash{to_string(env.closed()->info().hash)};
562 {
563 // Exercise ledger_closed along the way.
564 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
565 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
566 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
567 }
568
569 std::string accountRootIndex;
570 {
571 // Request alice's account root.
572 Json::Value jvParams;
573 jvParams[jss::account_root] = alice.human();
574 jvParams[jss::ledger_hash] = ledgerHash;
575 Json::Value const jrr = env.rpc(
576 "json", "ledger_entry", to_string(jvParams))[jss::result];
577 BEAST_EXPECT(jrr.isMember(jss::node));
578 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
579 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
580 accountRootIndex = jrr[jss::index].asString();
581 }
582 {
583 constexpr char alicesAcctRootBinary[]{
584 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
585 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
586 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
587
588 // Request alice's account root, but with binary == true;
589 Json::Value jvParams;
590 jvParams[jss::account_root] = alice.human();
591 jvParams[jss::binary] = 1;
592 jvParams[jss::ledger_hash] = ledgerHash;
593 Json::Value const jrr = env.rpc(
594 "json", "ledger_entry", to_string(jvParams))[jss::result];
595 BEAST_EXPECT(jrr.isMember(jss::node_binary));
596 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
597 }
598 {
599 // Request alice's account root using the index.
600 Json::Value jvParams;
601 jvParams[jss::index] = accountRootIndex;
602 Json::Value const jrr = env.rpc(
603 "json", "ledger_entry", to_string(jvParams))[jss::result];
604 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
605 BEAST_EXPECT(jrr.isMember(jss::node));
606 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
607 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
608 }
609 {
610 // Request alice's account root by index, but with binary == false.
611 Json::Value jvParams;
612 jvParams[jss::index] = accountRootIndex;
613 jvParams[jss::binary] = 0;
614 Json::Value const jrr = env.rpc(
615 "json", "ledger_entry", to_string(jvParams))[jss::result];
616 BEAST_EXPECT(jrr.isMember(jss::node));
617 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
618 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
619 }
620 {
621 // Check alias
622 Json::Value jvParams;
623 jvParams[jss::account] = alice.human();
624 jvParams[jss::ledger_hash] = ledgerHash;
625 Json::Value const jrr = env.rpc(
626 "json", "ledger_entry", to_string(jvParams))[jss::result];
627 BEAST_EXPECT(jrr.isMember(jss::node));
628 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
629 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
630 accountRootIndex = jrr[jss::index].asString();
631 }
632 {
633 // Check malformed cases
634 Json::Value jvParams;
636 env,
637 jvParams,
638 jss::account_root,
640 "malformedAddress");
641 }
642 {
643 // Request an account that is not in the ledger.
644 Json::Value jvParams;
645 jvParams[jss::account_root] = Account("bob").human();
646 jvParams[jss::ledger_hash] = ledgerHash;
647 Json::Value const jrr = env.rpc(
648 "json", "ledger_entry", to_string(jvParams))[jss::result];
649 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
650 }
651 }
652
653 void
655 {
656 testcase("Check");
657 using namespace test::jtx;
658 Env env{*this};
659 Account const alice{"alice"};
660 env.fund(XRP(10000), alice);
661 env.close();
662
663 auto const checkId = keylet::check(env.master, env.seq(env.master));
664
665 env(check::create(env.master, alice, XRP(100)));
666 env.close();
667
668 std::string const ledgerHash{to_string(env.closed()->info().hash)};
669 {
670 // Request a check.
671 Json::Value jvParams;
672 jvParams[jss::check] = to_string(checkId.key);
673 jvParams[jss::ledger_hash] = ledgerHash;
674 Json::Value const jrr = env.rpc(
675 "json", "ledger_entry", to_string(jvParams))[jss::result];
676 BEAST_EXPECT(
677 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
678 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
679 }
680 {
681 // Request an index that is not a check. We'll use alice's
682 // account root index.
683 std::string accountRootIndex;
684 {
685 Json::Value jvParams;
686 jvParams[jss::account_root] = alice.human();
687 Json::Value const jrr = env.rpc(
688 "json", "ledger_entry", to_string(jvParams))[jss::result];
689 accountRootIndex = jrr[jss::index].asString();
690 }
691 Json::Value jvParams;
692 jvParams[jss::check] = accountRootIndex;
693 jvParams[jss::ledger_hash] = ledgerHash;
694 Json::Value const jrr = env.rpc(
695 "json", "ledger_entry", to_string(jvParams))[jss::result];
697 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
698 }
699 {
700 // Check malformed cases
701 runLedgerEntryTest(env, jss::check);
702 }
703 }
704
705 void
707 {
708 testcase("Credentials");
709
710 using namespace test::jtx;
711
712 Env env(*this);
713 Account const issuer{"issuer"};
714 Account const alice{"alice"};
715 Account const bob{"bob"};
716 char const credType[] = "abcde";
717
718 env.fund(XRP(5000), issuer, alice, bob);
719 env.close();
720
721 // Setup credentials with DepositAuth object for Alice and Bob
722 env(credentials::create(alice, issuer, credType));
723 env.close();
724
725 {
726 // Succeed
727 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
728 BEAST_EXPECT(
729 jv.isObject() && jv.isMember(jss::result) &&
730 !jv[jss::result].isMember(jss::error) &&
731 jv[jss::result].isMember(jss::node) &&
732 jv[jss::result][jss::node].isMember(
733 sfLedgerEntryType.jsonName) &&
734 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
735 jss::Credential);
736
737 std::string const credIdx = jv[jss::result][jss::index].asString();
738
739 jv = credentials::ledgerEntry(env, credIdx);
740 BEAST_EXPECT(
741 jv.isObject() && jv.isMember(jss::result) &&
742 !jv[jss::result].isMember(jss::error) &&
743 jv[jss::result].isMember(jss::node) &&
744 jv[jss::result][jss::node].isMember(
745 sfLedgerEntryType.jsonName) &&
746 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
747 jss::Credential);
748 }
749
750 {
751 // Fail, credential doesn't exist
752 auto const jv = credentials::ledgerEntry(
753 env,
754 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
755 "E4");
757 jv[jss::result], "entryNotFound", "Entry not found.");
758 }
759
760 {
761 // Check all malformed cases
763 env,
764 jss::credential,
765 {
766 {jss::subject, "malformedRequest"},
767 {jss::issuer, "malformedRequest"},
768 {jss::credential_type, "malformedRequest"},
769 });
770 }
771 }
772
773 void
775 {
776 testcase("Delegate");
777
778 using namespace test::jtx;
779
780 Env env{*this};
781 Account const alice{"alice"};
782 Account const bob{"bob"};
783 env.fund(XRP(10000), alice, bob);
784 env.close();
785 env(delegate::set(alice, bob, {"Payment", "CheckCreate"}));
786 env.close();
787 std::string const ledgerHash{to_string(env.closed()->info().hash)};
788 std::string delegateIndex;
789 {
790 // Request by account and authorize
791 Json::Value jvParams;
792 jvParams[jss::delegate][jss::account] = alice.human();
793 jvParams[jss::delegate][jss::authorize] = bob.human();
794 jvParams[jss::ledger_hash] = ledgerHash;
795 Json::Value const jrr = env.rpc(
796 "json", "ledger_entry", to_string(jvParams))[jss::result];
797 BEAST_EXPECT(
798 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
799 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
800 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
801 delegateIndex = jrr[jss::node][jss::index].asString();
802 }
803 {
804 // Request by index.
805 Json::Value jvParams;
806 jvParams[jss::delegate] = delegateIndex;
807 jvParams[jss::ledger_hash] = ledgerHash;
808 Json::Value const jrr = env.rpc(
809 "json", "ledger_entry", to_string(jvParams))[jss::result];
810 BEAST_EXPECT(
811 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
812 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
813 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
814 }
815
816 {
817 // Check all malformed cases
819 env,
820 jss::delegate,
821 {
822 {jss::account, "malformedAddress"},
823 {jss::authorize, "malformedAddress"},
824 });
825 }
826 }
827
828 void
830 {
831 testcase("Deposit Preauth");
832
833 using namespace test::jtx;
834
835 Env env{*this};
836 Account const alice{"alice"};
837 Account const becky{"becky"};
838
839 env.fund(XRP(10000), alice, becky);
840 env.close();
841
842 env(deposit::auth(alice, becky));
843 env.close();
844
845 std::string const ledgerHash{to_string(env.closed()->info().hash)};
846 std::string depositPreauthIndex;
847 {
848 // Request a depositPreauth by owner and authorized.
849 Json::Value jvParams;
850 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
851 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
852 jvParams[jss::ledger_hash] = ledgerHash;
853 Json::Value const jrr = env.rpc(
854 "json", "ledger_entry", to_string(jvParams))[jss::result];
855
856 BEAST_EXPECT(
857 jrr[jss::node][sfLedgerEntryType.jsonName] ==
858 jss::DepositPreauth);
859 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
860 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
861 depositPreauthIndex = jrr[jss::node][jss::index].asString();
862 }
863 {
864 // Request a depositPreauth by index.
865 Json::Value jvParams;
866 jvParams[jss::deposit_preauth] = depositPreauthIndex;
867 jvParams[jss::ledger_hash] = ledgerHash;
868 Json::Value const jrr = env.rpc(
869 "json", "ledger_entry", to_string(jvParams))[jss::result];
870
871 BEAST_EXPECT(
872 jrr[jss::node][sfLedgerEntryType.jsonName] ==
873 jss::DepositPreauth);
874 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
875 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
876 }
877 {
878 // test all missing/malformed field cases
880 env,
881 jss::deposit_preauth,
882 {
883 {jss::owner, "malformedOwner"},
884 {jss::authorized, "malformedAuthorized", false},
885 });
886 }
887 }
888
889 void
891 {
892 testcase("Deposit Preauth with credentials");
893
894 using namespace test::jtx;
895
896 Env env(*this);
897 Account const issuer{"issuer"};
898 Account const alice{"alice"};
899 Account const bob{"bob"};
900 char const credType[] = "abcde";
901
902 env.fund(XRP(5000), issuer, alice, bob);
903 env.close();
904
905 {
906 // Setup Bob with DepositAuth
907 env(fset(bob, asfDepositAuth));
908 env.close();
909 env(deposit::authCredentials(bob, {{issuer, credType}}));
910 env.close();
911 }
912
913 {
914 // Succeed
915 Json::Value jvParams;
916 jvParams[jss::ledger_index] = jss::validated;
917 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
918
919 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
921 auto& arr(
922 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
923
924 Json::Value jo;
925 jo[jss::issuer] = issuer.human();
926 jo[jss::credential_type] = strHex(std::string_view(credType));
927 arr.append(std::move(jo));
928 auto const jrr =
929 env.rpc("json", "ledger_entry", to_string(jvParams));
930
931 BEAST_EXPECT(
932 jrr.isObject() && jrr.isMember(jss::result) &&
933 !jrr[jss::result].isMember(jss::error) &&
934 jrr[jss::result].isMember(jss::node) &&
935 jrr[jss::result][jss::node].isMember(
936 sfLedgerEntryType.jsonName) &&
937 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
938 jss::DepositPreauth);
939 }
940
941 {
942 // Failed, invalid account
943 Json::Value jvParams;
944 jvParams[jss::ledger_index] = jss::validated;
945 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
946
947 auto tryField = [&](Json::Value fieldValue) -> void {
949 Json::Value jo;
950 jo[jss::issuer] = fieldValue;
951 jo[jss::credential_type] = strHex(std::string_view(credType));
952 arr.append(jo);
953 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
954 arr;
955
956 Json::Value const jrr = env.rpc(
957 "json", "ledger_entry", to_string(jvParams))[jss::result];
958 auto const expectedErrMsg = fieldValue.isNull()
959 ? RPC::missing_field_message(jss::issuer.c_str())
960 : RPC::expected_field_message(jss::issuer, "AccountID");
962 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
963 };
964
965 auto const& badValues = getBadValues(FieldType::AccountField);
966 for (auto const& value : badValues)
967 {
968 tryField(value);
969 }
970 tryField(Json::nullValue);
971 }
972
973 {
974 // Failed, duplicates in credentials
975 Json::Value jvParams;
976 jvParams[jss::ledger_index] = jss::validated;
977 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
978
979 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
981 auto& arr(
982 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
983
984 Json::Value jo;
985 jo[jss::issuer] = issuer.human();
986 jo[jss::credential_type] = strHex(std::string_view(credType));
987 arr.append(jo);
988 arr.append(std::move(jo));
989 auto const jrr =
990 env.rpc("json", "ledger_entry", to_string(jvParams));
992 jrr[jss::result],
993 "malformedAuthorizedCredentials",
995 jss::authorized_credentials, "array"));
996 }
997
998 {
999 // Failed, invalid credential_type
1000 Json::Value jvParams;
1001 jvParams[jss::ledger_index] = jss::validated;
1002 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1003
1004 auto tryField = [&](Json::Value fieldValue) -> void {
1006 Json::Value jo;
1007 jo[jss::issuer] = issuer.human();
1008 jo[jss::credential_type] = fieldValue;
1009 arr.append(jo);
1010 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1011 arr;
1012
1013 Json::Value const jrr = env.rpc(
1014 "json", "ledger_entry", to_string(jvParams))[jss::result];
1015 auto const expectedErrMsg = fieldValue.isNull()
1016 ? RPC::missing_field_message(jss::credential_type.c_str())
1018 jss::credential_type, "hex string");
1020 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
1021 };
1022
1023 auto const& badValues = getBadValues(FieldType::BlobField);
1024 for (auto const& value : badValues)
1025 {
1026 tryField(value);
1027 }
1028 tryField(Json::nullValue);
1029 }
1030
1031 {
1032 // Failed, authorized and authorized_credentials both present
1033 Json::Value jvParams;
1034 jvParams[jss::ledger_index] = jss::validated;
1035 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1036 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
1037
1038 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1040 auto& arr(
1041 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1042
1043 Json::Value jo;
1044 jo[jss::issuer] = issuer.human();
1045 jo[jss::credential_type] = strHex(std::string_view(credType));
1046 arr.append(std::move(jo));
1047
1048 auto const jrr =
1049 env.rpc("json", "ledger_entry", to_string(jvParams));
1051 jrr[jss::result],
1052 "malformedRequest",
1053 "Must have exactly one of `authorized` and "
1054 "`authorized_credentials`.");
1055 }
1056
1057 {
1058 // Failed, authorized_credentials is not an array
1059 Json::Value jvParams;
1060 jvParams[jss::ledger_index] = jss::validated;
1061 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1063 env,
1064 jvParams,
1065 jss::deposit_preauth,
1066 jss::authorized_credentials,
1068 "malformedAuthorizedCredentials",
1069 false);
1070 }
1071
1072 {
1073 // Failed, authorized_credentials contains string data
1074 Json::Value jvParams;
1075 jvParams[jss::ledger_index] = jss::validated;
1076 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1077 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1079 auto& arr(
1080 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1081 arr.append("foobar");
1082
1083 auto const jrr =
1084 env.rpc("json", "ledger_entry", to_string(jvParams));
1086 jrr[jss::result],
1087 "malformedAuthorizedCredentials",
1088 "Invalid field 'authorized_credentials', not array.");
1089 }
1090
1091 {
1092 // Failed, authorized_credentials contains arrays
1093 Json::Value jvParams;
1094 jvParams[jss::ledger_index] = jss::validated;
1095 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1096 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1098 auto& arr(
1099 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1100 Json::Value payload = Json::arrayValue;
1101 payload.append(42);
1102 arr.append(std::move(payload));
1103
1104 auto const jrr =
1105 env.rpc("json", "ledger_entry", to_string(jvParams));
1107 jrr[jss::result],
1108 "malformedAuthorizedCredentials",
1109 "Invalid field 'authorized_credentials', not array.");
1110 }
1111
1112 {
1113 // Failed, authorized_credentials is empty array
1114 Json::Value jvParams;
1115 jvParams[jss::ledger_index] = jss::validated;
1116 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1117 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1119
1120 auto const jrr =
1121 env.rpc("json", "ledger_entry", to_string(jvParams));
1123 jrr[jss::result],
1124 "malformedAuthorizedCredentials",
1125 "Invalid field 'authorized_credentials', not array.");
1126 }
1127
1128 {
1129 // Failed, authorized_credentials is too long
1130 static std::array<std::string_view, 9> const credTypes = {
1131 "cred1",
1132 "cred2",
1133 "cred3",
1134 "cred4",
1135 "cred5",
1136 "cred6",
1137 "cred7",
1138 "cred8",
1139 "cred9"};
1140 static_assert(
1141 sizeof(credTypes) / sizeof(credTypes[0]) >
1143
1144 Json::Value jvParams;
1145 jvParams[jss::ledger_index] = jss::validated;
1146 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1147 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1149
1150 auto& arr(
1151 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1152
1153 for (auto cred : credTypes)
1154 {
1155 Json::Value jo;
1156 jo[jss::issuer] = issuer.human();
1157 jo[jss::credential_type] = strHex(std::string_view(cred));
1158 arr.append(std::move(jo));
1159 }
1160
1161 auto const jrr =
1162 env.rpc("json", "ledger_entry", to_string(jvParams));
1164 jrr[jss::result],
1165 "malformedAuthorizedCredentials",
1166 "Invalid field 'authorized_credentials', not array.");
1167 }
1168 }
1169
1170 void
1172 {
1173 testcase("Directory");
1174 using namespace test::jtx;
1175 Env env{*this};
1176 Account const alice{"alice"};
1177 Account const gw{"gateway"};
1178 auto const USD = gw["USD"];
1179 env.fund(XRP(10000), alice, gw);
1180 env.close();
1181
1182 env.trust(USD(1000), alice);
1183 env.close();
1184
1185 // Run up the number of directory entries so alice has two
1186 // directory nodes.
1187 for (int d = 1'000'032; d >= 1'000'000; --d)
1188 {
1189 env(offer(alice, USD(1), drops(d)));
1190 }
1191 env.close();
1192
1193 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1194 {
1195 // Exercise ledger_closed along the way.
1196 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
1197 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
1198 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
1199 }
1200
1201 std::string const dirRootIndex =
1202 "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
1203 {
1204 // Locate directory by index.
1205 Json::Value jvParams;
1206 jvParams[jss::directory] = dirRootIndex;
1207 jvParams[jss::ledger_hash] = ledgerHash;
1208 Json::Value const jrr = env.rpc(
1209 "json", "ledger_entry", to_string(jvParams))[jss::result];
1210 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
1211 }
1212 {
1213 // Locate directory by directory root.
1214 Json::Value jvParams;
1215 jvParams[jss::directory] = Json::objectValue;
1216 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1217 Json::Value const jrr = env.rpc(
1218 "json", "ledger_entry", to_string(jvParams))[jss::result];
1219 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1220 }
1221 {
1222 // Locate directory by owner.
1223 Json::Value jvParams;
1224 jvParams[jss::directory] = Json::objectValue;
1225 jvParams[jss::directory][jss::owner] = alice.human();
1226 jvParams[jss::ledger_hash] = ledgerHash;
1227 Json::Value const jrr = env.rpc(
1228 "json", "ledger_entry", to_string(jvParams))[jss::result];
1229 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1230 }
1231 {
1232 // Locate directory by directory root and sub_index.
1233 Json::Value jvParams;
1234 jvParams[jss::directory] = Json::objectValue;
1235 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1236 jvParams[jss::directory][jss::sub_index] = 1;
1237 Json::Value const jrr = env.rpc(
1238 "json", "ledger_entry", to_string(jvParams))[jss::result];
1239 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1240 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1241 }
1242 {
1243 // Locate directory by owner and sub_index.
1244 Json::Value jvParams;
1245 jvParams[jss::directory] = Json::objectValue;
1246 jvParams[jss::directory][jss::owner] = alice.human();
1247 jvParams[jss::directory][jss::sub_index] = 1;
1248 jvParams[jss::ledger_hash] = ledgerHash;
1249 Json::Value const jrr = env.rpc(
1250 "json", "ledger_entry", to_string(jvParams))[jss::result];
1251 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1252 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1253 }
1254 {
1255 // Bad directory argument.
1256 Json::Value jvParams;
1257 jvParams[jss::ledger_hash] = ledgerHash;
1259 env,
1260 jvParams,
1261 jss::directory,
1263 "malformedRequest");
1264 }
1265 {
1266 // Non-integer sub_index.
1267 Json::Value jvParams;
1268 jvParams[jss::directory] = Json::objectValue;
1269 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1270 jvParams[jss::ledger_hash] = ledgerHash;
1272 env,
1273 jvParams,
1274 jss::directory,
1275 jss::sub_index,
1277 "malformedRequest",
1278 false);
1279 }
1280 {
1281 // Malformed owner entry.
1282 Json::Value jvParams;
1283 jvParams[jss::directory] = Json::objectValue;
1284
1285 jvParams[jss::ledger_hash] = ledgerHash;
1287 env,
1288 jvParams,
1289 jss::directory,
1290 jss::owner,
1292 "malformedAddress",
1293 false);
1294 }
1295 {
1296 // Malformed directory object. Specifies both dir_root and owner.
1297 Json::Value jvParams;
1298 jvParams[jss::directory] = Json::objectValue;
1299 jvParams[jss::directory][jss::owner] = alice.human();
1300 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1301 jvParams[jss::ledger_hash] = ledgerHash;
1302 Json::Value const jrr = env.rpc(
1303 "json", "ledger_entry", to_string(jvParams))[jss::result];
1305 jrr,
1306 "malformedRequest",
1307 "Must have exactly one of `owner` and `dir_root` fields.");
1308 }
1309 {
1310 // Incomplete directory object. Missing both dir_root and owner.
1311 Json::Value jvParams;
1312 jvParams[jss::directory] = Json::objectValue;
1313 jvParams[jss::directory][jss::sub_index] = 1;
1314 jvParams[jss::ledger_hash] = ledgerHash;
1315 Json::Value const jrr = env.rpc(
1316 "json", "ledger_entry", to_string(jvParams))[jss::result];
1318 jrr,
1319 "malformedRequest",
1320 "Must have exactly one of `owner` and `dir_root` fields.");
1321 }
1322 }
1323
1324 void
1326 {
1327 testcase("Escrow");
1328 using namespace test::jtx;
1329 Env env{*this};
1330 Account const alice{"alice"};
1331 env.fund(XRP(10000), alice);
1332 env.close();
1333
1334 // Lambda to create an escrow.
1335 auto escrowCreate = [](test::jtx::Account const& account,
1336 test::jtx::Account const& to,
1337 STAmount const& amount,
1338 NetClock::time_point const& cancelAfter) {
1339 Json::Value jv;
1340 jv[jss::TransactionType] = jss::EscrowCreate;
1341 jv[jss::Account] = account.human();
1342 jv[jss::Destination] = to.human();
1343 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1344 jv[sfFinishAfter.jsonName] =
1345 cancelAfter.time_since_epoch().count() + 2;
1346 return jv;
1347 };
1348
1349 using namespace std::chrono_literals;
1350 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
1351 env.close();
1352
1353 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1354 std::string escrowIndex;
1355 {
1356 // Request the escrow using owner and sequence.
1357 Json::Value jvParams;
1358 jvParams[jss::escrow] = Json::objectValue;
1359 jvParams[jss::escrow][jss::owner] = alice.human();
1360 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
1361 Json::Value const jrr = env.rpc(
1362 "json", "ledger_entry", to_string(jvParams))[jss::result];
1363 BEAST_EXPECT(
1364 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1365 escrowIndex = jrr[jss::index].asString();
1366 }
1367 {
1368 // Request the escrow by index.
1369 Json::Value jvParams;
1370 jvParams[jss::escrow] = escrowIndex;
1371 jvParams[jss::ledger_hash] = ledgerHash;
1372 Json::Value const jrr = env.rpc(
1373 "json", "ledger_entry", to_string(jvParams))[jss::result];
1374 BEAST_EXPECT(
1375 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1376 }
1377 {
1378 // Malformed escrow fields
1380 env,
1381 jss::escrow,
1382 {{jss::owner, "malformedOwner"}, {jss::seq, "malformedSeq"}});
1383 }
1384 }
1385
1386 void
1388 {
1389 testcase("Offer");
1390 using namespace test::jtx;
1391 Env env{*this};
1392 Account const alice{"alice"};
1393 Account const gw{"gateway"};
1394 auto const USD = gw["USD"];
1395 env.fund(XRP(10000), alice, gw);
1396 env.close();
1397
1398 env(offer(alice, USD(321), XRP(322)));
1399 env.close();
1400
1401 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1402 std::string offerIndex;
1403 {
1404 // Request the offer using owner and sequence.
1405 Json::Value jvParams;
1406 jvParams[jss::offer] = Json::objectValue;
1407 jvParams[jss::offer][jss::account] = alice.human();
1408 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
1409 jvParams[jss::ledger_hash] = ledgerHash;
1410 Json::Value const jrr = env.rpc(
1411 "json", "ledger_entry", to_string(jvParams))[jss::result];
1412 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1413 offerIndex = jrr[jss::index].asString();
1414 }
1415 {
1416 // Request the offer using its index.
1417 Json::Value jvParams;
1418 jvParams[jss::offer] = offerIndex;
1419 Json::Value const jrr = env.rpc(
1420 "json", "ledger_entry", to_string(jvParams))[jss::result];
1421 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1422 }
1423
1424 {
1425 // Malformed offer fields
1427 env,
1428 jss::offer,
1429 {{jss::account, "malformedAddress"},
1430 {jss::seq, "malformedRequest"}});
1431 }
1432 }
1433
1434 void
1436 {
1437 testcase("Pay Chan");
1438 using namespace test::jtx;
1439 using namespace std::literals::chrono_literals;
1440 Env env{*this};
1441 Account const alice{"alice"};
1442
1443 env.fund(XRP(10000), alice);
1444 env.close();
1445
1446 // Lambda to create a PayChan.
1447 auto payChanCreate = [](test::jtx::Account const& account,
1448 test::jtx::Account const& to,
1449 STAmount const& amount,
1450 NetClock::duration const& settleDelay,
1451 PublicKey const& pk) {
1452 Json::Value jv;
1453 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1454 jv[jss::Account] = account.human();
1455 jv[jss::Destination] = to.human();
1456 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1457 jv[sfSettleDelay.jsonName] = settleDelay.count();
1458 jv[sfPublicKey.jsonName] = strHex(pk.slice());
1459 return jv;
1460 };
1461
1462 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
1463 env.close();
1464
1465 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1466
1467 uint256 const payChanIndex{
1468 keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
1469 {
1470 // Request the payment channel using its index.
1471 Json::Value jvParams;
1472 jvParams[jss::payment_channel] = to_string(payChanIndex);
1473 jvParams[jss::ledger_hash] = ledgerHash;
1474 Json::Value const jrr = env.rpc(
1475 "json", "ledger_entry", to_string(jvParams))[jss::result];
1476 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
1477 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
1478 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
1479 }
1480 {
1481 // Request an index that is not a payment channel.
1482 Json::Value jvParams;
1483 jvParams[jss::payment_channel] = ledgerHash;
1484 jvParams[jss::ledger_hash] = ledgerHash;
1485 Json::Value const jrr = env.rpc(
1486 "json", "ledger_entry", to_string(jvParams))[jss::result];
1487 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1488 }
1489
1490 {
1491 // Malformed paychan field
1492 runLedgerEntryTest(env, jss::payment_channel);
1493 }
1494 }
1495
1496 void
1498 {
1499 testcase("RippleState");
1500 using namespace test::jtx;
1501 Env env{*this};
1502 Account const alice{"alice"};
1503 Account const gw{"gateway"};
1504 auto const USD = gw["USD"];
1505 env.fund(XRP(10000), alice, gw);
1506 env.close();
1507
1508 env.trust(USD(999), alice);
1509 env.close();
1510
1511 env(pay(gw, alice, USD(97)));
1512 env.close();
1513
1514 // check both aliases
1515 for (auto const& fieldName : {jss::ripple_state, jss::state})
1516 {
1517 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1518 {
1519 // Request the trust line using the accounts and currency.
1520 Json::Value jvParams;
1521 jvParams[fieldName] = Json::objectValue;
1522 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1523 jvParams[fieldName][jss::accounts][0u] = alice.human();
1524 jvParams[fieldName][jss::accounts][1u] = gw.human();
1525 jvParams[fieldName][jss::currency] = "USD";
1526 jvParams[jss::ledger_hash] = ledgerHash;
1527 Json::Value const jrr = env.rpc(
1528 "json", "ledger_entry", to_string(jvParams))[jss::result];
1529 BEAST_EXPECT(
1530 jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
1531 BEAST_EXPECT(
1532 jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
1533 }
1534 {
1535 // test basic malformed scenarios
1537 env,
1538 fieldName,
1539 {
1540 {jss::accounts, "malformedRequest"},
1541 {jss::currency, "malformedCurrency"},
1542 });
1543 }
1544 {
1545 // ripple_state one of the accounts is missing.
1546 Json::Value jvParams;
1547 jvParams[fieldName] = Json::objectValue;
1548 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1549 jvParams[fieldName][jss::accounts][0u] = alice.human();
1550 jvParams[fieldName][jss::currency] = "USD";
1551 jvParams[jss::ledger_hash] = ledgerHash;
1552 Json::Value const jrr = env.rpc(
1553 "json", "ledger_entry", to_string(jvParams))[jss::result];
1555 jrr,
1556 "malformedRequest",
1557 "Invalid field 'accounts', not length-2 array of "
1558 "Accounts.");
1559 }
1560 {
1561 // ripple_state more than 2 accounts.
1562 Json::Value jvParams;
1563 jvParams[fieldName] = Json::objectValue;
1564 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1565 jvParams[fieldName][jss::accounts][0u] = alice.human();
1566 jvParams[fieldName][jss::accounts][1u] = gw.human();
1567 jvParams[fieldName][jss::accounts][2u] = alice.human();
1568 jvParams[fieldName][jss::currency] = "USD";
1569 jvParams[jss::ledger_hash] = ledgerHash;
1570 Json::Value const jrr = env.rpc(
1571 "json", "ledger_entry", to_string(jvParams))[jss::result];
1573 jrr,
1574 "malformedRequest",
1575 "Invalid field 'accounts', not length-2 array of "
1576 "Accounts.");
1577 }
1578 {
1579 // ripple_state account[0] / account[1] is not an account.
1580 Json::Value jvParams;
1581 jvParams[fieldName] = Json::objectValue;
1582 auto tryField = [&](Json::Value badAccount) -> void {
1583 {
1584 // account[0]
1585 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1586 jvParams[fieldName][jss::accounts][0u] = badAccount;
1587 jvParams[fieldName][jss::accounts][1u] = gw.human();
1588 jvParams[fieldName][jss::currency] = "USD";
1589
1590 Json::Value const jrr = env.rpc(
1591 "json",
1592 "ledger_entry",
1593 to_string(jvParams))[jss::result];
1595 jrr,
1596 "malformedAddress",
1598 jss::accounts, "array of Accounts"));
1599 }
1600
1601 {
1602 // account[1]
1603 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1604 jvParams[fieldName][jss::accounts][0u] = alice.human();
1605 jvParams[fieldName][jss::accounts][1u] = badAccount;
1606 jvParams[fieldName][jss::currency] = "USD";
1607
1608 Json::Value const jrr = env.rpc(
1609 "json",
1610 "ledger_entry",
1611 to_string(jvParams))[jss::result];
1613 jrr,
1614 "malformedAddress",
1616 jss::accounts, "array of Accounts"));
1617 }
1618 };
1619
1620 auto const& badValues = getBadValues(FieldType::AccountField);
1621 for (auto const& value : badValues)
1622 {
1623 tryField(value);
1624 }
1625 tryField(Json::nullValue);
1626 }
1627 {
1628 // ripple_state account[0] == account[1].
1629 Json::Value jvParams;
1630 jvParams[fieldName] = Json::objectValue;
1631 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1632 jvParams[fieldName][jss::accounts][0u] = alice.human();
1633 jvParams[fieldName][jss::accounts][1u] = alice.human();
1634 jvParams[fieldName][jss::currency] = "USD";
1635 jvParams[jss::ledger_hash] = ledgerHash;
1636 Json::Value const jrr = env.rpc(
1637 "json", "ledger_entry", to_string(jvParams))[jss::result];
1639 jrr,
1640 "malformedRequest",
1641 "Cannot have a trustline to self.");
1642 }
1643 }
1644 }
1645
1646 void
1648 {
1649 testcase("Ticket");
1650 using namespace test::jtx;
1651 Env env{*this};
1652 env.close();
1653
1654 // Create two tickets.
1655 std::uint32_t const tkt1{env.seq(env.master) + 1};
1656 env(ticket::create(env.master, 2));
1657 env.close();
1658
1659 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1660 // Request four tickets: one before the first one we created, the
1661 // two created tickets, and the ticket that would come after the
1662 // last created ticket.
1663 {
1664 // Not a valid ticket requested by index.
1665 Json::Value jvParams;
1666 jvParams[jss::ticket] =
1667 to_string(getTicketIndex(env.master, tkt1 - 1));
1668 jvParams[jss::ledger_hash] = ledgerHash;
1669 Json::Value const jrr = env.rpc(
1670 "json", "ledger_entry", to_string(jvParams))[jss::result];
1671 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1672 }
1673 {
1674 // First real ticket requested by index.
1675 Json::Value jvParams;
1676 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
1677 jvParams[jss::ledger_hash] = ledgerHash;
1678 Json::Value const jrr = env.rpc(
1679 "json", "ledger_entry", to_string(jvParams))[jss::result];
1680 BEAST_EXPECT(
1681 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
1682 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
1683 }
1684 {
1685 // Second real ticket requested by account and sequence.
1686 Json::Value jvParams;
1687 jvParams[jss::ticket] = Json::objectValue;
1688 jvParams[jss::ticket][jss::account] = env.master.human();
1689 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
1690 jvParams[jss::ledger_hash] = ledgerHash;
1691 Json::Value const jrr = env.rpc(
1692 "json", "ledger_entry", to_string(jvParams))[jss::result];
1693 BEAST_EXPECT(
1694 jrr[jss::node][jss::index] ==
1695 to_string(getTicketIndex(env.master, tkt1 + 1)));
1696 }
1697 {
1698 // Not a valid ticket requested by account and sequence.
1699 Json::Value jvParams;
1700 jvParams[jss::ticket] = Json::objectValue;
1701 jvParams[jss::ticket][jss::account] = env.master.human();
1702 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
1703 jvParams[jss::ledger_hash] = ledgerHash;
1704 Json::Value const jrr = env.rpc(
1705 "json", "ledger_entry", to_string(jvParams))[jss::result];
1706 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1707 }
1708 {
1709 // Request a ticket using an account root entry.
1710 Json::Value jvParams;
1711 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
1712 jvParams[jss::ledger_hash] = ledgerHash;
1713 Json::Value const jrr = env.rpc(
1714 "json", "ledger_entry", to_string(jvParams))[jss::result];
1716 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
1717 }
1718
1719 {
1720 // test basic malformed scenarios
1722 env,
1723 jss::ticket,
1724 {
1725 {jss::account, "malformedAddress"},
1726 {jss::ticket_seq, "malformedRequest"},
1727 });
1728 }
1729 }
1730
1731 void
1733 {
1734 testcase("DID");
1735 using namespace test::jtx;
1736 using namespace std::literals::chrono_literals;
1737 Env env{*this};
1738 Account const alice{"alice"};
1739
1740 env.fund(XRP(10000), alice);
1741 env.close();
1742
1743 // Lambda to create a DID.
1744 auto didCreate = [](test::jtx::Account const& account) {
1745 Json::Value jv;
1746 jv[jss::TransactionType] = jss::DIDSet;
1747 jv[jss::Account] = account.human();
1748 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
1749 jv[sfURI.jsonName] = strHex(std::string{"uri"});
1750 return jv;
1751 };
1752
1753 env(didCreate(alice));
1754 env.close();
1755
1756 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1757
1758 {
1759 // Request the DID using its index.
1760 Json::Value jvParams;
1761 jvParams[jss::did] = alice.human();
1762 jvParams[jss::ledger_hash] = ledgerHash;
1763 Json::Value const jrr = env.rpc(
1764 "json", "ledger_entry", to_string(jvParams))[jss::result];
1765 BEAST_EXPECT(
1766 jrr[jss::node][sfDIDDocument.jsonName] ==
1767 strHex(std::string{"data"}));
1768 BEAST_EXPECT(
1769 jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
1770 }
1771 {
1772 // Request an index that is not a DID.
1773 Json::Value jvParams;
1774 jvParams[jss::did] = env.master.human();
1775 jvParams[jss::ledger_hash] = ledgerHash;
1776 Json::Value const jrr = env.rpc(
1777 "json", "ledger_entry", to_string(jvParams))[jss::result];
1778 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1779 }
1780 {
1781 // Malformed DID index
1782 Json::Value jvParams;
1784 env,
1785 jvParams,
1786 jss::did,
1788 "malformedAddress");
1789 }
1790 }
1791
1792 void
1794 {
1795 testcase("Invalid Oracle Ledger Entry");
1796 using namespace ripple::test::jtx;
1797 using namespace ripple::test::jtx::oracle;
1798
1799 Env env(*this);
1800 Account const owner("owner");
1801 env.fund(XRP(1'000), owner);
1802 Oracle oracle(
1803 env,
1804 {.owner = owner,
1805 .fee = static_cast<int>(env.current()->fees().base.drops())});
1806
1807 {
1808 // test basic malformed scenarios
1810 env,
1811 jss::oracle,
1812 {
1813 {jss::account, "malformedAccount"},
1814 {jss::oracle_document_id, "malformedDocumentID"},
1815 });
1816 }
1817 }
1818
1819 void
1821 {
1822 testcase("Oracle Ledger Entry");
1823 using namespace ripple::test::jtx;
1824 using namespace ripple::test::jtx::oracle;
1825
1826 Env env(*this);
1827 auto const baseFee =
1828 static_cast<int>(env.current()->fees().base.drops());
1829 std::vector<AccountID> accounts;
1831 for (int i = 0; i < 10; ++i)
1832 {
1833 Account const owner(std::string("owner") + std::to_string(i));
1834 env.fund(XRP(1'000), owner);
1835 // different accounts can have the same asset pair
1836 Oracle oracle(
1837 env, {.owner = owner, .documentID = i, .fee = baseFee});
1838 accounts.push_back(owner.id());
1839 oracles.push_back(oracle.documentID());
1840 // same account can have different asset pair
1841 Oracle oracle1(
1842 env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
1843 accounts.push_back(owner.id());
1844 oracles.push_back(oracle1.documentID());
1845 }
1846 for (int i = 0; i < accounts.size(); ++i)
1847 {
1848 auto const jv = [&]() {
1849 // document id is uint32
1850 if (i % 2)
1851 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
1852 // document id is string
1853 return Oracle::ledgerEntry(
1854 env, accounts[i], std::to_string(oracles[i]));
1855 }();
1856 try
1857 {
1858 BEAST_EXPECT(
1859 jv[jss::node][jss::Owner] == to_string(accounts[i]));
1860 }
1861 catch (...)
1862 {
1863 fail();
1864 }
1865 }
1866 }
1867
1868 void
1870 {
1871 testcase("MPT");
1872 using namespace test::jtx;
1873 using namespace std::literals::chrono_literals;
1874 Env env{*this};
1875 Account const alice{"alice"};
1876 Account const bob("bob");
1877
1878 MPTTester mptAlice(env, alice, {.holders = {bob}});
1879 mptAlice.create(
1880 {.transferFee = 10,
1881 .metadata = "123",
1882 .ownerCount = 1,
1885 mptAlice.authorize({.account = bob, .holderCount = 1});
1886
1887 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1888
1889 std::string const badMptID =
1890 "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
1891 {
1892 // Request the MPTIssuance using its MPTIssuanceID.
1893 Json::Value jvParams;
1894 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
1895 jvParams[jss::ledger_hash] = ledgerHash;
1896 Json::Value const jrr = env.rpc(
1897 "json", "ledger_entry", to_string(jvParams))[jss::result];
1898 BEAST_EXPECT(
1899 jrr[jss::node][sfMPTokenMetadata.jsonName] ==
1900 strHex(std::string{"123"}));
1901 BEAST_EXPECT(
1902 jrr[jss::node][jss::mpt_issuance_id] ==
1903 strHex(mptAlice.issuanceID()));
1904 }
1905 {
1906 // Request an index that is not a MPTIssuance.
1907 Json::Value jvParams;
1908 jvParams[jss::mpt_issuance] = badMptID;
1909 jvParams[jss::ledger_hash] = ledgerHash;
1910 Json::Value const jrr = env.rpc(
1911 "json", "ledger_entry", to_string(jvParams))[jss::result];
1912 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1913 }
1914 {
1915 // Request the MPToken using its owner + mptIssuanceID.
1916 Json::Value jvParams;
1917 jvParams[jss::mptoken] = Json::objectValue;
1918 jvParams[jss::mptoken][jss::account] = bob.human();
1919 jvParams[jss::mptoken][jss::mpt_issuance_id] =
1920 strHex(mptAlice.issuanceID());
1921 jvParams[jss::ledger_hash] = ledgerHash;
1922 Json::Value const jrr = env.rpc(
1923 "json", "ledger_entry", to_string(jvParams))[jss::result];
1924 BEAST_EXPECT(
1925 jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
1926 strHex(mptAlice.issuanceID()));
1927 }
1928 {
1929 // Request the MPToken using a bad mptIssuanceID.
1930 Json::Value jvParams;
1931 jvParams[jss::mptoken] = Json::objectValue;
1932 jvParams[jss::mptoken][jss::account] = bob.human();
1933 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
1934 jvParams[jss::ledger_hash] = ledgerHash;
1935 Json::Value const jrr = env.rpc(
1936 "json", "ledger_entry", to_string(jvParams))[jss::result];
1937 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1938 }
1939 {
1940 // Malformed MPTIssuance index
1941 Json::Value jvParams;
1943 env,
1944 jvParams,
1945 jss::mptoken,
1947 "malformedRequest");
1948 }
1949 }
1950
1951 void
1953 {
1954 testcase("PermissionedDomain");
1955
1956 using namespace test::jtx;
1957
1958 Env env(*this, testable_amendments() | featurePermissionedDomains);
1959 Account const issuer{"issuer"};
1960 Account const alice{"alice"};
1961 Account const bob{"bob"};
1962
1963 env.fund(XRP(5000), issuer, alice, bob);
1964 env.close();
1965
1966 auto const seq = env.seq(alice);
1967 env(pdomain::setTx(alice, {{alice, "first credential"}}));
1968 env.close();
1969 auto const objects = pdomain::getObjects(alice, env);
1970 if (!BEAST_EXPECT(objects.size() == 1))
1971 return;
1972
1973 {
1974 // Succeed
1975 Json::Value params;
1976 params[jss::ledger_index] = jss::validated;
1977 params[jss::permissioned_domain][jss::account] = alice.human();
1978 params[jss::permissioned_domain][jss::seq] = seq;
1979 auto jv = env.rpc("json", "ledger_entry", to_string(params));
1980 BEAST_EXPECT(
1981 jv.isObject() && jv.isMember(jss::result) &&
1982 !jv[jss::result].isMember(jss::error) &&
1983 jv[jss::result].isMember(jss::node) &&
1984 jv[jss::result][jss::node].isMember(
1985 sfLedgerEntryType.jsonName) &&
1986 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
1987 jss::PermissionedDomain);
1988
1989 std::string const pdIdx = jv[jss::result][jss::index].asString();
1990 BEAST_EXPECT(
1991 strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
1992
1993 params.clear();
1994 params[jss::ledger_index] = jss::validated;
1995 params[jss::permissioned_domain] = pdIdx;
1996 jv = env.rpc("json", "ledger_entry", to_string(params));
1997 BEAST_EXPECT(
1998 jv.isObject() && jv.isMember(jss::result) &&
1999 !jv[jss::result].isMember(jss::error) &&
2000 jv[jss::result].isMember(jss::node) &&
2001 jv[jss::result][jss::node].isMember(
2002 sfLedgerEntryType.jsonName) &&
2003 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
2004 jss::PermissionedDomain);
2005 }
2006
2007 {
2008 // Fail, invalid permissioned domain index
2009 Json::Value params;
2010 params[jss::ledger_index] = jss::validated;
2011 params[jss::permissioned_domain] =
2012 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
2013 "DE";
2014 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
2016 jrr[jss::result], "entryNotFound", "Entry not found.");
2017 }
2018 {
2019 // test basic malformed scenarios
2021 env,
2022 jss::permissioned_domain,
2023 {
2024 {jss::account, "malformedAddress"},
2025 {jss::seq, "malformedRequest"},
2026 });
2027 }
2028 }
2029
2030 void
2032 {
2033 testcase("command-line");
2034 using namespace test::jtx;
2035
2036 Env env{*this};
2037 Account const alice{"alice"};
2038 env.fund(XRP(10000), alice);
2039 env.close();
2040
2041 auto const checkId = keylet::check(env.master, env.seq(env.master));
2042
2043 env(check::create(env.master, alice, XRP(100)));
2044 env.close();
2045
2046 std::string const ledgerHash{to_string(env.closed()->info().hash)};
2047 {
2048 // Request a check.
2049 Json::Value const jrr =
2050 env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
2051 BEAST_EXPECT(
2052 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
2053 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
2054 }
2055 }
2056
2057public:
2058 void
2081};
2082
2085{
2086 void
2088 Json::Value const& jv,
2089 std::string const& err,
2090 std::string const& msg)
2091 {
2092 if (BEAST_EXPECT(jv.isMember(jss::status)))
2093 BEAST_EXPECT(jv[jss::status] == "error");
2094 if (BEAST_EXPECT(jv.isMember(jss::error)))
2095 BEAST_EXPECT(jv[jss::error] == err);
2096 if (msg.empty())
2097 {
2098 BEAST_EXPECT(
2099 jv[jss::error_message] == Json::nullValue ||
2100 jv[jss::error_message] == "");
2101 }
2102 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
2103 BEAST_EXPECT(jv[jss::error_message] == msg);
2104 }
2105
2106 void
2108 {
2109 testcase("ledger_entry: bridge");
2110 using namespace test::jtx;
2111
2112 Env mcEnv{*this, features};
2113 Env scEnv(*this, envconfig(), features);
2114
2115 createBridgeObjects(mcEnv, scEnv);
2116
2117 std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
2118 std::string bridge_index;
2119 Json::Value mcBridge;
2120 {
2121 // request the bridge via RPC
2122 Json::Value jvParams;
2123 jvParams[jss::bridge_account] = mcDoor.human();
2124 jvParams[jss::bridge] = jvb;
2125 Json::Value const jrr = mcEnv.rpc(
2126 "json", "ledger_entry", to_string(jvParams))[jss::result];
2127
2128 BEAST_EXPECT(jrr.isMember(jss::node));
2129 auto r = jrr[jss::node];
2130
2131 BEAST_EXPECT(r.isMember(jss::Account));
2132 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
2133
2134 BEAST_EXPECT(r.isMember(jss::Flags));
2135
2136 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
2137 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
2138
2139 // we not created an account yet
2140 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2141 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
2142
2143 // we have not claimed a locking chain tx yet
2144 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
2145 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
2146
2147 BEAST_EXPECT(r.isMember(jss::index));
2148 bridge_index = r[jss::index].asString();
2149 mcBridge = r;
2150 }
2151 {
2152 // request the bridge via RPC by index
2153 Json::Value jvParams;
2154 jvParams[jss::index] = bridge_index;
2155 Json::Value const jrr = mcEnv.rpc(
2156 "json", "ledger_entry", to_string(jvParams))[jss::result];
2157
2158 BEAST_EXPECT(jrr.isMember(jss::node));
2159 BEAST_EXPECT(jrr[jss::node] == mcBridge);
2160 }
2161 {
2162 // swap door accounts and make sure we get an error value
2163 Json::Value jvParams;
2164 // Sidechain door account is "master", not scDoor
2165 jvParams[jss::bridge_account] = Account::master.human();
2166 jvParams[jss::bridge] = jvb;
2167 jvParams[jss::ledger_hash] = ledgerHash;
2168 Json::Value const jrr = mcEnv.rpc(
2169 "json", "ledger_entry", to_string(jvParams))[jss::result];
2170
2171 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2172 }
2173 {
2174 // create two claim ids and verify that the bridge counter was
2175 // incremented
2177 mcEnv.close();
2179 mcEnv.close();
2180
2181 // request the bridge via RPC
2182 Json::Value jvParams;
2183 jvParams[jss::bridge_account] = mcDoor.human();
2184 jvParams[jss::bridge] = jvb;
2185 Json::Value const jrr = mcEnv.rpc(
2186 "json", "ledger_entry", to_string(jvParams))[jss::result];
2187
2188 BEAST_EXPECT(jrr.isMember(jss::node));
2189 auto r = jrr[jss::node];
2190
2191 // we executed two create claim id txs
2192 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
2193 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2194 }
2195 }
2196
2197 void
2199 {
2200 testcase("ledger_entry: xchain_claim_id");
2201 using namespace test::jtx;
2202
2203 Env mcEnv{*this, features};
2204 Env scEnv(*this, envconfig(), features);
2205
2206 createBridgeObjects(mcEnv, scEnv);
2207
2209 scEnv.close();
2211 scEnv.close();
2212
2213 std::string bridge_index;
2214 {
2215 // request the xchain_claim_id via RPC
2216 Json::Value jvParams;
2217 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2218 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2219 1;
2220 Json::Value const jrr = scEnv.rpc(
2221 "json", "ledger_entry", to_string(jvParams))[jss::result];
2222
2223 BEAST_EXPECT(jrr.isMember(jss::node));
2224 auto r = jrr[jss::node];
2225
2226 BEAST_EXPECT(r.isMember(jss::Account));
2227 BEAST_EXPECT(r[jss::Account] == scAlice.human());
2228 BEAST_EXPECT(
2229 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2230 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
2231 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2232 }
2233
2234 {
2235 // request the xchain_claim_id via RPC
2236 Json::Value jvParams;
2237 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2238 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2239 2;
2240 Json::Value const jrr = scEnv.rpc(
2241 "json", "ledger_entry", to_string(jvParams))[jss::result];
2242
2243 BEAST_EXPECT(jrr.isMember(jss::node));
2244 auto r = jrr[jss::node];
2245
2246 BEAST_EXPECT(r.isMember(jss::Account));
2247 BEAST_EXPECT(r[jss::Account] == scBob.human());
2248 BEAST_EXPECT(
2249 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2250 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2251 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2252 }
2253 }
2254
2255 void
2257 {
2258 testcase("ledger_entry: xchain_create_account_claim_id");
2259 using namespace test::jtx;
2260
2261 Env mcEnv{*this, features};
2262 Env scEnv(*this, envconfig(), features);
2263
2264 // note: signers.size() and quorum are both 5 in createBridgeObjects
2265 createBridgeObjects(mcEnv, scEnv);
2266
2267 auto scCarol =
2268 Account("scCarol"); // Don't fund it - it will be created with the
2269 // xchain transaction
2270 auto const amt = XRP(1000);
2272 mcAlice, jvb, scCarol, amt, reward));
2273 mcEnv.close();
2274
2275 // send less than quorum of attestations (otherwise funds are
2276 // immediately transferred and no "claim" object is created)
2277 size_t constexpr num_attest = 3;
2278 auto attestations = create_account_attestations(
2279 scAttester,
2280 jvb,
2281 mcAlice,
2282 amt,
2283 reward,
2284 payee,
2285 /*wasLockingChainSend*/ true,
2286 1,
2287 scCarol,
2288 signers,
2290 for (size_t i = 0; i < num_attest; ++i)
2291 {
2292 scEnv(attestations[i]);
2293 }
2294 scEnv.close();
2295
2296 {
2297 // request the create account claim_id via RPC
2298 Json::Value jvParams;
2299 jvParams[jss::xchain_owned_create_account_claim_id] =
2301 jvParams[jss::xchain_owned_create_account_claim_id]
2302 [jss::xchain_owned_create_account_claim_id] = 1;
2303 Json::Value const jrr = scEnv.rpc(
2304 "json", "ledger_entry", to_string(jvParams))[jss::result];
2305
2306 BEAST_EXPECT(jrr.isMember(jss::node));
2307 auto r = jrr[jss::node];
2308
2309 BEAST_EXPECT(r.isMember(jss::Account));
2310 BEAST_EXPECT(r[jss::Account] == Account::master.human());
2311
2312 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2313 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
2314
2315 BEAST_EXPECT(
2316 r.isMember(sfXChainCreateAccountAttestations.jsonName));
2317 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
2318 BEAST_EXPECT(attest.isArray());
2319 BEAST_EXPECT(attest.size() == 3);
2320 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
2321 sfXChainCreateAccountProofSig.jsonName));
2322 Json::Value a[num_attest];
2323 for (size_t i = 0; i < num_attest; ++i)
2324 {
2325 a[i] = attest[Json::Value::UInt(0)]
2326 [sfXChainCreateAccountProofSig.jsonName];
2327 BEAST_EXPECT(
2328 a[i].isMember(jss::Amount) &&
2329 a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
2330 BEAST_EXPECT(
2331 a[i].isMember(jss::Destination) &&
2332 a[i][jss::Destination] == scCarol.human());
2333 BEAST_EXPECT(
2334 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
2336 signers.begin(), signers.end(), [&](signer const& s) {
2337 return a[i][sfAttestationSignerAccount.jsonName] ==
2338 s.account.human();
2339 }));
2340 BEAST_EXPECT(
2341 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
2343 payee.begin(),
2344 payee.end(),
2345 [&](Account const& account) {
2346 return a[i][sfAttestationRewardAccount.jsonName] ==
2347 account.human();
2348 }));
2349 BEAST_EXPECT(
2350 a[i].isMember(sfWasLockingChainSend.jsonName) &&
2351 a[i][sfWasLockingChainSend.jsonName] == 1);
2352 BEAST_EXPECT(
2353 a[i].isMember(sfSignatureReward.jsonName) &&
2354 a[i][sfSignatureReward.jsonName].asInt() ==
2355 1 * drop_per_xrp);
2356 }
2357 }
2358
2359 // complete attestations quorum - CreateAccountClaimID should not be
2360 // present anymore
2361 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
2362 {
2363 scEnv(attestations[i]);
2364 }
2365 scEnv.close();
2366 {
2367 // request the create account claim_id via RPC
2368 Json::Value jvParams;
2369 jvParams[jss::xchain_owned_create_account_claim_id] =
2371 jvParams[jss::xchain_owned_create_account_claim_id]
2372 [jss::xchain_owned_create_account_claim_id] = 1;
2373 Json::Value const jrr = scEnv.rpc(
2374 "json", "ledger_entry", to_string(jvParams))[jss::result];
2375 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2376 }
2377 }
2378
2379public:
2380 void
2387};
2388
2389BEAST_DEFINE_TESTSUITE(LedgerEntry, rpc, ripple);
2390BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, rpc, ripple);
2391
2392} // namespace test
2393} // namespace ripple
T any_of(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:63
constexpr char const * c_str() const
Definition json_value.h:76
Represents a JSON value.
Definition json_value.h:149
Json::UInt UInt
Definition json_value.h:156
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string toStyledString() const
void clear()
Remove all object members and array elements.
Int asInt() const
bool isString() const
bool isObject() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isNull() const
isNull() tests to see if this field is null.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
A public key.
Definition PublicKey.h:61
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
void run() override
Runs the suite.
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, source_location const location=source_location::current())
std::vector< Json::Value > getBadValues(FieldType fieldType)
void testMalformedSubfield(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString parentFieldName, Json::StaticString fieldName, FieldType typeID, std::string const &expectedError, bool required=true, source_location const location=source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::vector< Subfield > const &subfields, source_location const location=source_location::current())
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg, source_location const location=source_location::current())
Json::Value getCorrectValue(Json::StaticString fieldName)
void run() override
Runs the suite.
void testMalformedField(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString const fieldName, FieldType const typeID, std::string const &expectedError, bool required=true, source_location const location=source_location::current())
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
static Account const master
The master account.
Definition Account.h:48
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
A transaction testing environment.
Definition Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:115
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:268
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
Account const & master
Definition Env.h:125
NetClock::time_point now()
Returns the current network time.
Definition Env.h:284
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:791
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:87
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:120
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:35
T empty(T... args)
T end(T... args)
T find_if(T... args)
@ nullValue
'null' value
Definition json_value.h:38
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
std::string missing_field_message(std::string const &name)
Definition ErrorCodes.h:277
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:337
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:570
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:336
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:395
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
Json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:31
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:54
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:31
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
JValueVec create_account_attestations(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, std::vector< jtx::Account > const &rewardAccounts, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, std::vector< jtx::signer > const &signers, std::size_t const numAtts, std::size_t const fromIdx)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:54
require_t required(Args const &... args)
Compose many condition functors into one.
Definition require.h:49
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
static uint256 ledgerHash(LedgerInfo const &info)
std::vector< std::pair< Json::StaticString, FieldType > > mappings
FieldType getFieldType(Json::StaticString fieldName)
std::string getTypeName(FieldType typeID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:85
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:152
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:151
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:106
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:101
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition Indexes.cpp:156
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:150
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:149
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:148
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:153
T push_back(T... args)
T reserve(T... args)
T size(T... args)
uint256 key
Definition Keylet.h:40
std::vector< Account > const payee
std::vector< signer > const signers
void createBridgeObjects(Env &mcEnv, Env &scEnv)
Set the sequence number on a JTx.
Definition seq.h:34
A signer in a SignerList.
Definition multisign.h:39
T to_string(T... args)