rippled
Loading...
Searching...
No Matches
LedgerEntry_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/Oracle.h>
3#include <test/jtx/attester.h>
4#include <test/jtx/delegate.h>
5#include <test/jtx/multisign.h>
6#include <test/jtx/xchain_bridge.h>
7
8#include <xrpld/app/tx/apply.h>
9
10#include <xrpl/beast/unit_test.h>
11#include <xrpl/json/json_value.h>
12#include <xrpl/protocol/AccountID.h>
13#include <xrpl/protocol/ErrorCodes.h>
14#include <xrpl/protocol/STXChainBridge.h>
15#include <xrpl/protocol/jss.h>
16
17#include <source_location>
18namespace xrpl {
19
20namespace test {
21
37
39 {jss::account, FieldType::AccountField},
40 {jss::accounts, FieldType::TwoAccountArrayField},
41 {jss::asset, FieldType::IssueField},
42 {jss::asset2, FieldType::IssueField},
43 {jss::authorize, FieldType::AccountField},
44 {jss::authorized, FieldType::AccountField},
45 {jss::credential_type, FieldType::BlobField},
46 {jss::currency, FieldType::CurrencyField},
47 {jss::issuer, FieldType::AccountField},
48 {jss::oracle_document_id, FieldType::UInt32Field},
49 {jss::owner, FieldType::AccountField},
50 {jss::seq, FieldType::UInt32Field},
51 {jss::subject, FieldType::AccountField},
52 {jss::ticket_seq, FieldType::UInt32Field},
53};
54
57{
58 auto it = std::ranges::find_if(mappings, [&fieldName](auto const& pair) { return pair.first == fieldName; });
59 if (it != mappings.end())
60 {
61 return it->second;
62 }
63 else
64 {
65 Throw<std::runtime_error>("`mappings` is missing field " + std::string(fieldName.c_str()));
66 }
67}
68
71{
72 switch (typeID)
73 {
75 return "AccountID";
77 return "array";
79 return "hex string";
81 return "Currency";
84 return "hex string";
86 return "hex string or object";
88 return "Issue";
90 return "length-2 array of Accounts";
92 return "number";
94 return "number";
95 default:
96 Throw<std::runtime_error>("unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
97 }
98}
99
101{
102 void
104 Json::Value const& jv,
105 std::string const& err,
106 std::string const& msg,
108 {
109 if (BEAST_EXPECT(jv.isMember(jss::status)))
110 BEAST_EXPECTS(jv[jss::status] == "error", std::to_string(location.line()));
111 if (BEAST_EXPECT(jv.isMember(jss::error)))
112 BEAST_EXPECTS(
113 jv[jss::error] == err,
114 "Expected error " + err + ", received " + jv[jss::error].asString() + ", at line " +
115 std::to_string(location.line()) + ", " + jv.toStyledString());
116 if (msg.empty())
117 {
118 BEAST_EXPECTS(
119 jv[jss::error_message] == Json::nullValue || jv[jss::error_message] == "",
120 "Expected no error message, received \"" + jv[jss::error_message].asString() + "\", at line " +
121 std::to_string(location.line()) + ", " + jv.toStyledString());
122 }
123 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
124 BEAST_EXPECTS(
125 jv[jss::error_message] == msg,
126 "Expected error message \"" + msg + "\", received \"" + jv[jss::error_message].asString() +
127 "\", at line " + std::to_string(location.line()) + ", " + jv.toStyledString());
128 }
129
132 {
133 static Json::Value const injectObject = []() {
135 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
136 obj[jss::ledger_index] = "validated";
137 return obj;
138 }();
139 static Json::Value const injectArray = []() {
141 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
142 arr[1u] = "validated";
143 return arr;
144 }();
145 static std::array<Json::Value, 21> const allBadValues = {
146 "", // 0
147 true, // 1
148 1, // 2
149 "1", // 3
150 -1, // 4
151 1.1, // 5
152 "-1", // 6
153 "abcdef", // 7
154 "ABCDEF", // 8
155 "12KK", // 9
156 "0123456789ABCDEFGH", // 10
157 "rJxKV9e9p6wiPw!!!!xrJ4X1n98LosPL1sgcJW", // 11
158 "rPSTrR5yEr11uMkfsz1kHCp9jK4aoa3Avv", // 12
159 "n9K2isxwTxcSHJKxMkJznDoWXAUs7NNy49H9Fknz1pC7oHAH3kH9", // 13
160 "USD", // 14
161 "USDollars", // 15
162 "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6B01403D"
163 "6D", // 16
164 Json::arrayValue, // 17
165 Json::objectValue, // 18
166 injectObject, // 19
167 injectArray // 20
168 };
169
170 auto remove = [&](std::vector<std::uint8_t> indices) -> std::vector<Json::Value> {
171 std::unordered_set<std::uint8_t> indexSet(indices.begin(), indices.end());
173 values.reserve(allBadValues.size() - indexSet.size());
174 for (std::size_t i = 0; i < allBadValues.size(); ++i)
175 {
176 if (indexSet.find(i) == indexSet.end())
177 {
178 values.push_back(allBadValues[i]);
179 }
180 }
181 return values;
182 };
183
184 static auto const& badAccountValues = remove({12});
185 static auto const& badArrayValues = remove({17, 20});
186 static auto const& badBlobValues = remove({3, 7, 8, 16});
187 static auto const& badCurrencyValues = remove({14});
188 static auto const& badHashValues = remove({2, 3, 7, 8, 16});
189 static auto const& badFixedHashValues = remove({1, 2, 3, 4, 7, 8, 16});
190 static auto const& badIndexValues = remove({12, 16, 18, 19});
191 static auto const& badUInt32Values = remove({2, 3});
192 static auto const& badUInt64Values = remove({2, 3});
193 static auto const& badIssueValues = remove({});
194
195 switch (fieldType)
196 {
198 return badAccountValues;
201 return badArrayValues;
203 return badBlobValues;
205 return badCurrencyValues;
207 return badHashValues;
209 return badIndexValues;
211 return badFixedHashValues;
213 return badIssueValues;
215 return badUInt32Values;
217 return badUInt64Values;
218 default:
219 Throw<std::runtime_error>("unknown type " + std::to_string(static_cast<uint8_t>(fieldType)));
220 }
221 }
222
225 {
226 static Json::Value const twoAccountArray = []() {
228 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
229 arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
230 return arr;
231 }();
232 static Json::Value const issueObject = []() {
234 arr[jss::currency] = "XRP";
235 return arr;
236 }();
237
238 auto const typeID = getFieldType(fieldName);
239 switch (typeID)
240 {
242 return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
244 return Json::arrayValue;
246 return "ABCDEF";
248 return "USD";
250 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
251 "B01403D6D";
253 return issueObject;
255 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
256 "B01403D6D";
258 return twoAccountArray;
260 return 1;
262 return 1;
263 default:
264 Throw<std::runtime_error>("unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
265 }
266 }
267
268 void
270 test::jtx::Env& env,
271 Json::Value correctRequest,
272 Json::StaticString const fieldName,
273 FieldType const typeID,
274 std::string const& expectedError,
275 bool required = true,
277 {
278 forAllApiVersions([&, this](unsigned apiVersion) {
279 if (required)
280 {
281 correctRequest.removeMember(fieldName);
282 Json::Value const jrr =
283 env.rpc(apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
284 if (apiVersion < 2u)
285 checkErrorValue(jrr, "unknownOption", "", location);
286 else
287 checkErrorValue(jrr, "invalidParams", "No ledger_entry params provided.", location);
288 }
289 auto tryField = [&](Json::Value fieldValue) -> void {
290 correctRequest[fieldName] = fieldValue;
291 Json::Value const jrr =
292 env.rpc(apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
293 auto const expectedErrMsg = RPC::expected_field_message(fieldName, getTypeName(typeID));
294 checkErrorValue(jrr, expectedError, expectedErrMsg, location);
295 };
296
297 auto const& badValues = getBadValues(typeID);
298 for (auto const& value : badValues)
299 {
300 tryField(value);
301 }
302 if (required)
303 {
304 tryField(Json::nullValue);
305 }
306 });
307 }
308
309 void
311 test::jtx::Env& env,
312 Json::Value correctRequest,
313 Json::StaticString parentFieldName,
314 Json::StaticString fieldName,
315 FieldType typeID,
316 std::string const& expectedError,
317 bool required = true,
319 {
320 forAllApiVersions([&, this](unsigned apiVersion) {
321 if (required)
322 {
323 correctRequest[parentFieldName].removeMember(fieldName);
324 Json::Value const jrr =
325 env.rpc(apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
326 checkErrorValue(jrr, "malformedRequest", RPC::missing_field_message(fieldName.c_str()), location);
327
328 correctRequest[parentFieldName][fieldName] = Json::nullValue;
329 Json::Value const jrr2 =
330 env.rpc(apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
331 checkErrorValue(jrr2, "malformedRequest", RPC::missing_field_message(fieldName.c_str()), location);
332 }
333 auto tryField = [&](Json::Value fieldValue) -> void {
334 correctRequest[parentFieldName][fieldName] = fieldValue;
335
336 Json::Value const jrr =
337 env.rpc(apiVersion, "json", "ledger_entry", to_string(correctRequest))[jss::result];
339 jrr, expectedError, RPC::expected_field_message(fieldName, getTypeName(typeID)), location);
340 };
341
342 auto const& badValues = getBadValues(typeID);
343 for (auto const& value : badValues)
344 {
345 tryField(value);
346 }
347 });
348 }
349
350 // No subfields
351 void
353 test::jtx::Env& env,
354 Json::StaticString const& parentField,
356 {
357 testMalformedField(env, Json::Value{}, parentField, FieldType::HashField, "malformedRequest", true, location);
358 }
359
366
367 void
369 test::jtx::Env& env,
370 Json::StaticString const& parentField,
371 std::vector<Subfield> const& subfields,
373 {
375 env, Json::Value{}, parentField, FieldType::HashOrObjectField, "malformedRequest", true, location);
376
377 Json::Value correctOutput;
378 correctOutput[parentField] = Json::objectValue;
379 for (auto const& subfield : subfields)
380 {
381 correctOutput[parentField][subfield.fieldName] = getCorrectValue(subfield.fieldName);
382 }
383
384 for (auto const& subfield : subfields)
385 {
386 auto const fieldType = getFieldType(subfield.fieldName);
388 env,
389 correctOutput,
390 parentField,
391 subfield.fieldName,
392 fieldType,
393 subfield.malformedErrorMsg,
394 subfield.required,
395 location);
396 }
397 }
398
399 void
401 {
402 testcase("Invalid requests");
403 using namespace test::jtx;
404 Env env{*this};
405 Account const alice{"alice"};
406 env.fund(XRP(10000), alice);
407 env.close();
408 {
409 // Missing ledger_entry ledger_hash
410 Json::Value jvParams;
411 jvParams[jss::account_root] = alice.human();
412 jvParams[jss::ledger_hash] =
413 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
414 "AA";
415 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
416 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
417 }
418 {
419 // Missing ledger_entry ledger_hash
420 Json::Value jvParams;
421 jvParams[jss::account_root] = alice.human();
422 auto const typeId = FieldType::HashField;
423
424 forAllApiVersions([&, this](unsigned apiVersion) {
425 auto tryField = [&](Json::Value fieldValue) -> void {
426 jvParams[jss::ledger_hash] = fieldValue;
427 Json::Value const jrr =
428 env.rpc(apiVersion, "json", "ledger_entry", to_string(jvParams))[jss::result];
429 checkErrorValue(jrr, "invalidParams", "Invalid field 'ledger_hash', not hex string.");
430 };
431
432 auto const& badValues = getBadValues(typeId);
433 for (auto const& value : badValues)
434 {
435 tryField(value);
436 }
437 });
438 }
439
440 {
441 // ask for an zero index
442 Json::Value jvParams;
443 jvParams[jss::ledger_index] = "validated";
444 jvParams[jss::index] =
445 "00000000000000000000000000000000000000000000000000000000000000"
446 "00";
447 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
448 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
449 }
450
451 forAllApiVersions([&, this](unsigned apiVersion) {
452 // "features" is not an option supported by ledger_entry.
453 {
455 jvParams[jss::features] =
456 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
457 "AAAAAAAAAA";
458 jvParams[jss::api_version] = apiVersion;
459 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
460
461 if (apiVersion < 2u)
462 checkErrorValue(jrr, "unknownOption", "");
463 else
464 checkErrorValue(jrr, "invalidParams", "No ledger_entry params provided.");
465 }
466 });
467 }
468
469 void
471 {
472 testcase("AccountRoot");
473 using namespace test::jtx;
474
475 auto cfg = envconfig();
476 cfg->FEES.reference_fee = 10;
477 Env env{*this, std::move(cfg)};
478
479 Account const alice{"alice"};
480 env.fund(XRP(10000), alice);
481 env.close();
482
483 std::string const ledgerHash{to_string(env.closed()->header().hash)};
484 {
485 // Exercise ledger_closed along the way.
486 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
487 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
488 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
489 }
490
491 std::string accountRootIndex;
492 {
493 // Request alice's account root.
494 Json::Value jvParams;
495 jvParams[jss::account_root] = alice.human();
496 jvParams[jss::ledger_hash] = ledgerHash;
497 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
498 BEAST_EXPECT(jrr.isMember(jss::node));
499 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
500 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
501 accountRootIndex = jrr[jss::index].asString();
502 }
503 {
504 constexpr char alicesAcctRootBinary[]{
505 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
506 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
507 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
508
509 // Request alice's account root, but with binary == true;
510 Json::Value jvParams;
511 jvParams[jss::account_root] = alice.human();
512 jvParams[jss::binary] = 1;
513 jvParams[jss::ledger_hash] = ledgerHash;
514 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
515 BEAST_EXPECT(jrr.isMember(jss::node_binary));
516 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
517 }
518 {
519 // Request alice's account root using the index.
520 Json::Value jvParams;
521 jvParams[jss::index] = accountRootIndex;
522 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
523 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
524 BEAST_EXPECT(jrr.isMember(jss::node));
525 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
526 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
527 }
528 {
529 // Request alice's account root by index, but with binary == false.
530 Json::Value jvParams;
531 jvParams[jss::index] = accountRootIndex;
532 jvParams[jss::binary] = 0;
533 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
534 BEAST_EXPECT(jrr.isMember(jss::node));
535 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
536 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
537 }
538 {
539 // Check alias
540 Json::Value jvParams;
541 jvParams[jss::account] = alice.human();
542 jvParams[jss::ledger_hash] = ledgerHash;
543 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
544 BEAST_EXPECT(jrr.isMember(jss::node));
545 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
546 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
547 accountRootIndex = jrr[jss::index].asString();
548 }
549 {
550 // Check malformed cases
551 Json::Value jvParams;
552 testMalformedField(env, jvParams, jss::account_root, FieldType::AccountField, "malformedAddress");
553 }
554 {
555 // Request an account that is not in the ledger.
556 Json::Value jvParams;
557 jvParams[jss::account_root] = Account("bob").human();
558 jvParams[jss::ledger_hash] = ledgerHash;
559 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
560 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
561 }
562 }
563
564 void
566 {
567 testcase("Amendments");
568 using namespace test::jtx;
569 Env env{*this};
570
571 // positive test
572 {
573 Keylet const keylet = keylet::amendments();
574
575 // easier to hack an object into the ledger than generate it
576 // legitimately
577 {
578 auto const amendments = [&](OpenView& view, beast::Journal) -> bool {
579 auto const sle = std::make_shared<SLE>(keylet);
580
581 // Create Amendments vector (enabled amendments)
582 std::vector<uint256> enabledAmendments;
583 enabledAmendments.push_back(
584 uint256::fromVoid("42426C4D4F1009EE67080A9B7965B44656D7"
585 "714D104A72F9B4369F97ABF044EE"));
586 enabledAmendments.push_back(
587 uint256::fromVoid("4C97EBA926031A7CF7D7B36FDE3ED66DDA54"
588 "21192D63DE53FFB46E43B9DC8373"));
589 enabledAmendments.push_back(
590 uint256::fromVoid("03BDC0099C4E14163ADA272C1B6F6FABB448"
591 "CC3E51F522F978041E4B57D9158C"));
592 enabledAmendments.push_back(
593 uint256::fromVoid("35291ADD2D79EB6991343BDA0912269C817D"
594 "0F094B02226C1C14AD2858962ED4"));
595 sle->setFieldV256(sfAmendments, STVector256(enabledAmendments));
596
597 // Create Majorities array
598 STArray majorities;
599
600 auto majority1 = STObject::makeInnerObject(sfMajority);
601 majority1.setFieldH256(
602 sfAmendment,
603 uint256::fromVoid("7BB62DC13EC72B775091E9C71BF8CF97E122"
604 "647693B50C5E87A80DFD6FCFAC50"));
605 majority1.setFieldU32(sfCloseTime, 779561310);
606 majorities.push_back(std::move(majority1));
607
608 auto majority2 = STObject::makeInnerObject(sfMajority);
609 majority2.setFieldH256(
610 sfAmendment,
611 uint256::fromVoid("755C971C29971C9F20C6F080F2ED96F87884"
612 "E40AD19554A5EBECDCEC8A1F77FE"));
613 majority2.setFieldU32(sfCloseTime, 779561310);
614 majorities.push_back(std::move(majority2));
615
616 sle->setFieldArray(sfMajorities, majorities);
617
618 view.rawInsert(sle);
619 return true;
620 };
621 env.app().openLedger().modify(amendments);
622 }
623
624 Json::Value jvParams;
625 jvParams[jss::amendments] = to_string(keylet.key);
626 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
627 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Amendments);
628 }
629
630 // negative tests
631 testMalformedField(env, Json::Value{}, jss::amendments, FieldType::FixedHashField, "malformedRequest");
632 }
633
634 void
636 {
637 testcase("AMM");
638 using namespace test::jtx;
639 Env env{*this};
640
641 // positive test
642 Account const alice{"alice"};
643 env.fund(XRP(10000), alice);
644 env.close();
645 AMM amm(env, alice, XRP(10), alice["USD"](1000));
646 env.close();
647
648 {
649 Json::Value jvParams;
650 jvParams[jss::amm] = to_string(amm.ammID());
651 auto const result = env.rpc("json", "ledger_entry", to_string(jvParams));
652 BEAST_EXPECT(
653 result.isObject() && result.isMember(jss::result) && !result[jss::result].isMember(jss::error) &&
654 result[jss::result].isMember(jss::node) &&
655 result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
656 result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM);
657 }
658
659 {
660 Json::Value jvParams;
662 {
664 obj[jss::currency] = "XRP";
665 ammParams[jss::asset] = obj;
666 }
667 {
669 obj[jss::currency] = "USD";
670 obj[jss::issuer] = alice.human();
671 ammParams[jss::asset2] = obj;
672 }
673 jvParams[jss::amm] = ammParams;
674 auto const result = env.rpc("json", "ledger_entry", to_string(jvParams));
675 BEAST_EXPECT(
676 result.isObject() && result.isMember(jss::result) && !result[jss::result].isMember(jss::error) &&
677 result[jss::result].isMember(jss::node) &&
678 result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
679 result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM);
680 }
681
682 // negative tests
684 env,
685 jss::amm,
686 {
687 {jss::asset, "malformedRequest"},
688 {jss::asset2, "malformedRequest"},
689 });
690 }
691
692 void
694 {
695 testcase("Check");
696 using namespace test::jtx;
697 Env env{*this};
698 Account const alice{"alice"};
699 env.fund(XRP(10000), alice);
700 env.close();
701
702 auto const checkId = keylet::check(env.master, env.seq(env.master));
703
704 env(check::create(env.master, alice, XRP(100)));
705 env.close();
706
707 std::string const ledgerHash{to_string(env.closed()->header().hash)};
708 {
709 // Request a check.
710 Json::Value jvParams;
711 jvParams[jss::check] = to_string(checkId.key);
712 jvParams[jss::ledger_hash] = ledgerHash;
713 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
714 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
715 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
716 }
717 {
718 // Request an index that is not a check. We'll use alice's
719 // account root index.
720 std::string accountRootIndex;
721 {
722 Json::Value jvParams;
723 jvParams[jss::account_root] = alice.human();
724 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
725 accountRootIndex = jrr[jss::index].asString();
726 }
727 Json::Value jvParams;
728 jvParams[jss::check] = accountRootIndex;
729 jvParams[jss::ledger_hash] = ledgerHash;
730 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
731 checkErrorValue(jrr, "unexpectedLedgerType", "Unexpected ledger type.");
732 }
733 {
734 // Check malformed cases
735 runLedgerEntryTest(env, jss::check);
736 }
737 }
738
739 void
741 {
742 testcase("Credentials");
743
744 using namespace test::jtx;
745
746 Env env(*this);
747 Account const issuer{"issuer"};
748 Account const alice{"alice"};
749 Account const bob{"bob"};
750 char const credType[] = "abcde";
751
752 env.fund(XRP(5000), issuer, alice, bob);
753 env.close();
754
755 // Setup credentials with DepositAuth object for Alice and Bob
756 env(credentials::create(alice, issuer, credType));
757 env.close();
758
759 {
760 // Succeed
761 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
762 BEAST_EXPECT(
763 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
764 jv[jss::result].isMember(jss::node) &&
765 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
766 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::Credential);
767
768 std::string const credIdx = jv[jss::result][jss::index].asString();
769
770 jv = credentials::ledgerEntry(env, credIdx);
771 BEAST_EXPECT(
772 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
773 jv[jss::result].isMember(jss::node) &&
774 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
775 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::Credential);
776 }
777
778 {
779 // Fail, credential doesn't exist
780 auto const jv = credentials::ledgerEntry(
781 env,
782 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
783 "E4");
784 checkErrorValue(jv[jss::result], "entryNotFound", "Entry not found.");
785 }
786
787 {
788 // Check all malformed cases
790 env,
791 jss::credential,
792 {
793 {jss::subject, "malformedRequest"},
794 {jss::issuer, "malformedRequest"},
795 {jss::credential_type, "malformedRequest"},
796 });
797 }
798 }
799
800 void
802 {
803 testcase("Delegate");
804
805 using namespace test::jtx;
806
807 Env env{*this};
808 Account const alice{"alice"};
809 Account const bob{"bob"};
810 env.fund(XRP(10000), alice, bob);
811 env.close();
812 env(delegate::set(alice, bob, {"Payment", "CheckCreate"}));
813 env.close();
814 std::string const ledgerHash{to_string(env.closed()->header().hash)};
815 std::string delegateIndex;
816 {
817 // Request by account and authorize
818 Json::Value jvParams;
819 jvParams[jss::delegate][jss::account] = alice.human();
820 jvParams[jss::delegate][jss::authorize] = bob.human();
821 jvParams[jss::ledger_hash] = ledgerHash;
822 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
823 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
824 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
825 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
826 delegateIndex = jrr[jss::node][jss::index].asString();
827 }
828 {
829 // Request by index.
830 Json::Value jvParams;
831 jvParams[jss::delegate] = delegateIndex;
832 jvParams[jss::ledger_hash] = ledgerHash;
833 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
834 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
835 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
836 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
837 }
838
839 {
840 // Check all malformed cases
842 env,
843 jss::delegate,
844 {
845 {jss::account, "malformedAddress"},
846 {jss::authorize, "malformedAddress"},
847 });
848 }
849 }
850
851 void
853 {
854 testcase("Deposit Preauth");
855
856 using namespace test::jtx;
857
858 Env env{*this};
859 Account const alice{"alice"};
860 Account const becky{"becky"};
861
862 env.fund(XRP(10000), alice, becky);
863 env.close();
864
865 env(deposit::auth(alice, becky));
866 env.close();
867
868 std::string const ledgerHash{to_string(env.closed()->header().hash)};
869 std::string depositPreauthIndex;
870 {
871 // Request a depositPreauth by owner and authorized.
872 Json::Value jvParams;
873 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
874 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
875 jvParams[jss::ledger_hash] = ledgerHash;
876 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
877
878 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::DepositPreauth);
879 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
880 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
881 depositPreauthIndex = jrr[jss::node][jss::index].asString();
882 }
883 {
884 // Request a depositPreauth by index.
885 Json::Value jvParams;
886 jvParams[jss::deposit_preauth] = depositPreauthIndex;
887 jvParams[jss::ledger_hash] = ledgerHash;
888 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
889
890 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::DepositPreauth);
891 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
892 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
893 }
894 {
895 // test all missing/malformed field cases
897 env,
898 jss::deposit_preauth,
899 {
900 {jss::owner, "malformedOwner"},
901 {jss::authorized, "malformedAuthorized", false},
902 });
903 }
904 }
905
906 void
908 {
909 testcase("Deposit Preauth with credentials");
910
911 using namespace test::jtx;
912
913 Env env(*this);
914 Account const issuer{"issuer"};
915 Account const alice{"alice"};
916 Account const bob{"bob"};
917 char const credType[] = "abcde";
918
919 env.fund(XRP(5000), issuer, alice, bob);
920 env.close();
921
922 {
923 // Setup Bob with DepositAuth
924 env(fset(bob, asfDepositAuth));
925 env.close();
926 env(deposit::authCredentials(bob, {{issuer, credType}}));
927 env.close();
928 }
929
930 {
931 // Succeed
932 Json::Value jvParams;
933 jvParams[jss::ledger_index] = jss::validated;
934 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
935
936 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
937 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
938
939 Json::Value jo;
940 jo[jss::issuer] = issuer.human();
941 jo[jss::credential_type] = strHex(std::string_view(credType));
942 arr.append(std::move(jo));
943 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
944
945 BEAST_EXPECT(
946 jrr.isObject() && jrr.isMember(jss::result) && !jrr[jss::result].isMember(jss::error) &&
947 jrr[jss::result].isMember(jss::node) &&
948 jrr[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
949 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::DepositPreauth);
950 }
951
952 {
953 // Failed, invalid account
954 Json::Value jvParams;
955 jvParams[jss::ledger_index] = jss::validated;
956 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
957
958 auto tryField = [&](Json::Value fieldValue) -> void {
960 Json::Value jo;
961 jo[jss::issuer] = fieldValue;
962 jo[jss::credential_type] = strHex(std::string_view(credType));
963 arr.append(jo);
964 jvParams[jss::deposit_preauth][jss::authorized_credentials] = arr;
965
966 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
967 auto const expectedErrMsg = fieldValue.isNull() ? RPC::missing_field_message(jss::issuer.c_str())
968 : RPC::expected_field_message(jss::issuer, "AccountID");
969 checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg);
970 };
971
972 auto const& badValues = getBadValues(FieldType::AccountField);
973 for (auto const& value : badValues)
974 {
975 tryField(value);
976 }
977 tryField(Json::nullValue);
978 }
979
980 {
981 // Failed, duplicates in credentials
982 Json::Value jvParams;
983 jvParams[jss::ledger_index] = jss::validated;
984 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
985
986 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
987 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
988
989 Json::Value jo;
990 jo[jss::issuer] = issuer.human();
991 jo[jss::credential_type] = strHex(std::string_view(credType));
992 arr.append(jo);
993 arr.append(std::move(jo));
994 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
996 jrr[jss::result],
997 "malformedAuthorizedCredentials",
998 RPC::expected_field_message(jss::authorized_credentials, "array"));
999 }
1000
1001 {
1002 // Failed, invalid credential_type
1003 Json::Value jvParams;
1004 jvParams[jss::ledger_index] = jss::validated;
1005 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1006
1007 auto tryField = [&](Json::Value fieldValue) -> void {
1009 Json::Value jo;
1010 jo[jss::issuer] = issuer.human();
1011 jo[jss::credential_type] = fieldValue;
1012 arr.append(jo);
1013 jvParams[jss::deposit_preauth][jss::authorized_credentials] = arr;
1014
1015 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1016 auto const expectedErrMsg = fieldValue.isNull()
1017 ? RPC::missing_field_message(jss::credential_type.c_str())
1018 : RPC::expected_field_message(jss::credential_type, "hex string");
1019 checkErrorValue(jrr, "malformedAuthorizedCredentials", expectedErrMsg);
1020 };
1021
1022 auto const& badValues = getBadValues(FieldType::BlobField);
1023 for (auto const& value : badValues)
1024 {
1025 tryField(value);
1026 }
1027 tryField(Json::nullValue);
1028 }
1029
1030 {
1031 // Failed, authorized and authorized_credentials both present
1032 Json::Value jvParams;
1033 jvParams[jss::ledger_index] = jss::validated;
1034 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1035 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
1036
1037 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
1038 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1039
1040 Json::Value jo;
1041 jo[jss::issuer] = issuer.human();
1042 jo[jss::credential_type] = strHex(std::string_view(credType));
1043 arr.append(std::move(jo));
1044
1045 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
1047 jrr[jss::result],
1048 "malformedRequest",
1049 "Must have exactly one of `authorized` and "
1050 "`authorized_credentials`.");
1051 }
1052
1053 {
1054 // Failed, authorized_credentials is not an array
1055 Json::Value jvParams;
1056 jvParams[jss::ledger_index] = jss::validated;
1057 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1059 env,
1060 jvParams,
1061 jss::deposit_preauth,
1062 jss::authorized_credentials,
1064 "malformedAuthorizedCredentials",
1065 false);
1066 }
1067
1068 {
1069 // Failed, authorized_credentials contains string data
1070 Json::Value jvParams;
1071 jvParams[jss::ledger_index] = jss::validated;
1072 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1073 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
1074 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1075 arr.append("foobar");
1076
1077 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
1079 jrr[jss::result],
1080 "malformedAuthorizedCredentials",
1081 "Invalid field 'authorized_credentials', not array.");
1082 }
1083
1084 {
1085 // Failed, authorized_credentials contains arrays
1086 Json::Value jvParams;
1087 jvParams[jss::ledger_index] = jss::validated;
1088 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1089 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
1090 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1091 Json::Value payload = Json::arrayValue;
1092 payload.append(42);
1093 arr.append(std::move(payload));
1094
1095 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
1097 jrr[jss::result],
1098 "malformedAuthorizedCredentials",
1099 "Invalid field 'authorized_credentials', not array.");
1100 }
1101
1102 {
1103 // Failed, authorized_credentials is empty array
1104 Json::Value jvParams;
1105 jvParams[jss::ledger_index] = jss::validated;
1106 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1107 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
1108
1109 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
1111 jrr[jss::result],
1112 "malformedAuthorizedCredentials",
1113 "Invalid field 'authorized_credentials', array empty.");
1114 }
1115
1116 {
1117 // Failed, authorized_credentials is too long
1118 static std::array<std::string_view, 9> const credTypes = {
1119 "cred1", "cred2", "cred3", "cred4", "cred5", "cred6", "cred7", "cred8", "cred9"};
1120 static_assert(sizeof(credTypes) / sizeof(credTypes[0]) > maxCredentialsArraySize);
1121
1122 Json::Value jvParams;
1123 jvParams[jss::ledger_index] = jss::validated;
1124 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1125 jvParams[jss::deposit_preauth][jss::authorized_credentials] = Json::arrayValue;
1126
1127 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1128
1129 for (auto cred : credTypes)
1130 {
1131 Json::Value jo;
1132 jo[jss::issuer] = issuer.human();
1133 jo[jss::credential_type] = strHex(std::string_view(cred));
1134 arr.append(std::move(jo));
1135 }
1136
1137 auto const jrr = env.rpc("json", "ledger_entry", to_string(jvParams));
1139 jrr[jss::result],
1140 "malformedAuthorizedCredentials",
1141 "Invalid field 'authorized_credentials', array too long.");
1142 }
1143 }
1144
1145 void
1147 {
1148 testcase("Directory");
1149 using namespace test::jtx;
1150 Env env{*this};
1151 Account const alice{"alice"};
1152 Account const gw{"gateway"};
1153 auto const USD = gw["USD"];
1154 env.fund(XRP(10000), alice, gw);
1155 env.close();
1156
1157 env.trust(USD(1000), alice);
1158 env.close();
1159
1160 // Run up the number of directory entries so alice has two
1161 // directory nodes.
1162 for (int d = 1'000'032; d >= 1'000'000; --d)
1163 {
1164 env(offer(alice, USD(1), drops(d)));
1165 }
1166 env.close();
1167
1168 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1169 {
1170 // Exercise ledger_closed along the way.
1171 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
1172 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
1173 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
1174 }
1175
1176 std::string const dirRootIndex = "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
1177 {
1178 // Locate directory by index.
1179 Json::Value jvParams;
1180 jvParams[jss::directory] = dirRootIndex;
1181 jvParams[jss::ledger_hash] = ledgerHash;
1182 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1183 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
1184 }
1185 {
1186 // Locate directory by directory root.
1187 Json::Value jvParams;
1188 jvParams[jss::directory] = Json::objectValue;
1189 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1190 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1191 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1192 }
1193 {
1194 // Locate directory by owner.
1195 Json::Value jvParams;
1196 jvParams[jss::directory] = Json::objectValue;
1197 jvParams[jss::directory][jss::owner] = alice.human();
1198 jvParams[jss::ledger_hash] = ledgerHash;
1199 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1200 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1201 }
1202 {
1203 // Locate directory by directory root and sub_index.
1204 Json::Value jvParams;
1205 jvParams[jss::directory] = Json::objectValue;
1206 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1207 jvParams[jss::directory][jss::sub_index] = 1;
1208 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1209 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1210 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1211 }
1212 {
1213 // Locate directory by owner and sub_index.
1214 Json::Value jvParams;
1215 jvParams[jss::directory] = Json::objectValue;
1216 jvParams[jss::directory][jss::owner] = alice.human();
1217 jvParams[jss::directory][jss::sub_index] = 1;
1218 jvParams[jss::ledger_hash] = ledgerHash;
1219 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1220 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1221 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1222 }
1223 {
1224 // Bad directory argument.
1225 Json::Value jvParams;
1226 jvParams[jss::ledger_hash] = ledgerHash;
1227 testMalformedField(env, jvParams, jss::directory, FieldType::HashOrObjectField, "malformedRequest");
1228 }
1229 {
1230 // Non-integer sub_index.
1231 Json::Value jvParams;
1232 jvParams[jss::directory] = Json::objectValue;
1233 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1234 jvParams[jss::ledger_hash] = ledgerHash;
1236 env, jvParams, jss::directory, jss::sub_index, FieldType::UInt64Field, "malformedRequest", false);
1237 }
1238 {
1239 // Malformed owner entry.
1240 Json::Value jvParams;
1241 jvParams[jss::directory] = Json::objectValue;
1242
1243 jvParams[jss::ledger_hash] = ledgerHash;
1245 env, jvParams, jss::directory, jss::owner, FieldType::AccountField, "malformedAddress", false);
1246 }
1247 {
1248 // Malformed directory object. Specifies both dir_root and owner.
1249 Json::Value jvParams;
1250 jvParams[jss::directory] = Json::objectValue;
1251 jvParams[jss::directory][jss::owner] = alice.human();
1252 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1253 jvParams[jss::ledger_hash] = ledgerHash;
1254 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1255 checkErrorValue(jrr, "malformedRequest", "Must have exactly one of `owner` and `dir_root` fields.");
1256 }
1257 {
1258 // Incomplete directory object. Missing both dir_root and owner.
1259 Json::Value jvParams;
1260 jvParams[jss::directory] = Json::objectValue;
1261 jvParams[jss::directory][jss::sub_index] = 1;
1262 jvParams[jss::ledger_hash] = ledgerHash;
1263 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1264 checkErrorValue(jrr, "malformedRequest", "Must have exactly one of `owner` and `dir_root` fields.");
1265 }
1266 }
1267
1268 void
1270 {
1271 testcase("Escrow");
1272 using namespace test::jtx;
1273 Env env{*this};
1274 Account const alice{"alice"};
1275 env.fund(XRP(10000), alice);
1276 env.close();
1277
1278 // Lambda to create an escrow.
1279 auto escrowCreate = [](test::jtx::Account const& account,
1280 test::jtx::Account const& to,
1281 STAmount const& amount,
1282 NetClock::time_point const& cancelAfter) {
1283 Json::Value jv;
1284 jv[jss::TransactionType] = jss::EscrowCreate;
1285 jv[jss::Account] = account.human();
1286 jv[jss::Destination] = to.human();
1287 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1288 jv[sfFinishAfter.jsonName] = cancelAfter.time_since_epoch().count() + 2;
1289 return jv;
1290 };
1291
1292 using namespace std::chrono_literals;
1293 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
1294 env.close();
1295
1296 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1297 std::string escrowIndex;
1298 {
1299 // Request the escrow using owner and sequence.
1300 Json::Value jvParams;
1301 jvParams[jss::escrow] = Json::objectValue;
1302 jvParams[jss::escrow][jss::owner] = alice.human();
1303 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
1304 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1305 BEAST_EXPECT(jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1306 escrowIndex = jrr[jss::index].asString();
1307 }
1308 {
1309 // Request the escrow by index.
1310 Json::Value jvParams;
1311 jvParams[jss::escrow] = escrowIndex;
1312 jvParams[jss::ledger_hash] = ledgerHash;
1313 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1314 BEAST_EXPECT(jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1315 }
1316 {
1317 // Malformed escrow fields
1318 runLedgerEntryTest(env, jss::escrow, {{jss::owner, "malformedOwner"}, {jss::seq, "malformedSeq"}});
1319 }
1320 }
1321
1322 void
1324 {
1325 testcase("Fee Settings");
1326 using namespace test::jtx;
1327 Env env{*this};
1328
1329 // positive test
1330 {
1331 Keylet const keylet = keylet::fees();
1332 Json::Value jvParams;
1333 jvParams[jss::fee] = to_string(keylet.key);
1334 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1335 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::FeeSettings);
1336 }
1337
1338 // negative tests
1339 testMalformedField(env, Json::Value{}, jss::fee, FieldType::FixedHashField, "malformedRequest");
1340 }
1341
1342 void
1344 {
1345 testcase("Ledger Hashes");
1346 using namespace test::jtx;
1347 Env env{*this};
1348
1349 // positive test
1350 {
1351 Keylet const keylet = keylet::skip();
1352 Json::Value jvParams;
1353 jvParams[jss::hashes] = to_string(keylet.key);
1354 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1355 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::LedgerHashes);
1356 }
1357
1358 // negative tests
1359 testMalformedField(env, Json::Value{}, jss::hashes, FieldType::FixedHashField, "malformedRequest");
1360 }
1361
1362 void
1364 {
1365 testcase("NFT Offer");
1366 using namespace test::jtx;
1367 Env env{*this};
1368
1369 // positive test
1370 Account const issuer{"issuer"};
1371 Account const buyer{"buyer"};
1372 env.fund(XRP(1000), issuer, buyer);
1373
1374 uint256 const nftokenID0 = token::getNextID(env, issuer, 0, tfTransferable);
1375 env(token::mint(issuer, 0), txflags(tfTransferable));
1376 env.close();
1377 uint256 const offerID = keylet::nftoffer(issuer, env.seq(issuer)).key;
1378 env(token::createOffer(issuer, nftokenID0, drops(1)), token::destination(buyer), txflags(tfSellNFToken));
1379
1380 {
1381 Json::Value jvParams;
1382 jvParams[jss::nft_offer] = to_string(offerID);
1383 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1384 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenOffer);
1385 BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == issuer.human());
1386 BEAST_EXPECT(jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0));
1387 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1");
1388 }
1389
1390 // negative tests
1391 runLedgerEntryTest(env, jss::nft_offer);
1392 }
1393
1394 void
1396 {
1397 testcase("NFT Page");
1398 using namespace test::jtx;
1399 Env env{*this};
1400
1401 // positive test
1402 Account const issuer{"issuer"};
1403 env.fund(XRP(1000), issuer);
1404
1405 env(token::mint(issuer, 0), txflags(tfTransferable));
1406 env.close();
1407
1408 auto const nftpage = keylet::nftpage_max(issuer);
1409 BEAST_EXPECT(env.le(nftpage) != nullptr);
1410
1411 {
1412 Json::Value jvParams;
1413 jvParams[jss::nft_page] = to_string(nftpage.key);
1414 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1415 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
1416 }
1417
1418 // negative tests
1419 runLedgerEntryTest(env, jss::nft_page);
1420 }
1421
1422 void
1424 {
1425 testcase("Negative UNL");
1426 using namespace test::jtx;
1427 Env env{*this};
1428
1429 // positive test
1430 {
1431 Keylet const keylet = keylet::negativeUNL();
1432
1433 // easier to hack an object into the ledger than generate it
1434 // legitimately
1435 {
1436 auto const nUNL = [&](OpenView& view, beast::Journal) -> bool {
1437 auto const sle = std::make_shared<SLE>(keylet);
1438
1439 // Create DisabledValidators array
1440 STArray disabledValidators;
1441 auto disabledValidator = STObject::makeInnerObject(sfDisabledValidator);
1442 auto pubKeyBlob = strUnHex(
1443 "ED58F6770DB5DD77E59D28CB650EC3816E2FC95021BB56E720C9A1"
1444 "2DA79C58A3AB");
1445 disabledValidator.setFieldVL(sfPublicKey, *pubKeyBlob);
1446 disabledValidator.setFieldU32(sfFirstLedgerSequence, 91371264);
1447 disabledValidators.push_back(std::move(disabledValidator));
1448
1449 sle->setFieldArray(sfDisabledValidators, disabledValidators);
1450 sle->setFieldH256(
1451 sfPreviousTxnID,
1452 uint256::fromVoid("8D47FFE664BE6C335108DF689537625855A6"
1453 "A95160CC6D351341B9"
1454 "2624D9C5E3"));
1455 sle->setFieldU32(sfPreviousTxnLgrSeq, 91442944);
1456
1457 view.rawInsert(sle);
1458 return true;
1459 };
1460 env.app().openLedger().modify(nUNL);
1461 }
1462
1463 Json::Value jvParams;
1464 jvParams[jss::nunl] = to_string(keylet.key);
1465 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1466 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NegativeUNL);
1467 }
1468
1469 // negative tests
1470 testMalformedField(env, Json::Value{}, jss::nunl, FieldType::FixedHashField, "malformedRequest");
1471 }
1472
1473 void
1475 {
1476 testcase("Offer");
1477 using namespace test::jtx;
1478 Env env{*this};
1479 Account const alice{"alice"};
1480 Account const gw{"gateway"};
1481 auto const USD = gw["USD"];
1482 env.fund(XRP(10000), alice, gw);
1483 env.close();
1484
1485 env(offer(alice, USD(321), XRP(322)));
1486 env.close();
1487
1488 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1489 std::string offerIndex;
1490 {
1491 // Request the offer using owner and sequence.
1492 Json::Value jvParams;
1493 jvParams[jss::offer] = Json::objectValue;
1494 jvParams[jss::offer][jss::account] = alice.human();
1495 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
1496 jvParams[jss::ledger_hash] = ledgerHash;
1497 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1498 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1499 offerIndex = jrr[jss::index].asString();
1500 }
1501 {
1502 // Request the offer using its index.
1503 Json::Value jvParams;
1504 jvParams[jss::offer] = offerIndex;
1505 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1506 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1507 }
1508
1509 {
1510 // Malformed offer fields
1511 runLedgerEntryTest(env, jss::offer, {{jss::account, "malformedAddress"}, {jss::seq, "malformedRequest"}});
1512 }
1513 }
1514
1515 void
1517 {
1518 testcase("Pay Chan");
1519 using namespace test::jtx;
1520 using namespace std::literals::chrono_literals;
1521 Env env{*this};
1522 Account const alice{"alice"};
1523
1524 env.fund(XRP(10000), alice);
1525 env.close();
1526
1527 // Lambda to create a PayChan.
1528 auto payChanCreate = [](test::jtx::Account const& account,
1529 test::jtx::Account const& to,
1530 STAmount const& amount,
1531 NetClock::duration const& settleDelay,
1532 PublicKey const& pk) {
1533 Json::Value jv;
1534 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1535 jv[jss::Account] = account.human();
1536 jv[jss::Destination] = to.human();
1537 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1538 jv[sfSettleDelay.jsonName] = settleDelay.count();
1539 jv[sfPublicKey.jsonName] = strHex(pk.slice());
1540 return jv;
1541 };
1542
1543 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
1544 env.close();
1545
1546 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1547
1548 uint256 const payChanIndex{keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
1549 {
1550 // Request the payment channel using its index.
1551 Json::Value jvParams;
1552 jvParams[jss::payment_channel] = to_string(payChanIndex);
1553 jvParams[jss::ledger_hash] = ledgerHash;
1554 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1555 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
1556 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
1557 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
1558 }
1559 {
1560 // Request an index that is not a payment channel.
1561 Json::Value jvParams;
1562 jvParams[jss::payment_channel] = ledgerHash;
1563 jvParams[jss::ledger_hash] = ledgerHash;
1564 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1565 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1566 }
1567
1568 {
1569 // Malformed paychan field
1570 runLedgerEntryTest(env, jss::payment_channel);
1571 }
1572 }
1573
1574 void
1576 {
1577 testcase("RippleState");
1578 using namespace test::jtx;
1579 Env env{*this};
1580 Account const alice{"alice"};
1581 Account const gw{"gateway"};
1582 auto const USD = gw["USD"];
1583 env.fund(XRP(10000), alice, gw);
1584 env.close();
1585
1586 env.trust(USD(999), alice);
1587 env.close();
1588
1589 env(pay(gw, alice, USD(97)));
1590 env.close();
1591
1592 // check both aliases
1593 for (auto const& fieldName : {jss::ripple_state, jss::state})
1594 {
1595 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1596 {
1597 // Request the trust line using the accounts and currency.
1598 Json::Value jvParams;
1599 jvParams[fieldName] = Json::objectValue;
1600 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1601 jvParams[fieldName][jss::accounts][0u] = alice.human();
1602 jvParams[fieldName][jss::accounts][1u] = gw.human();
1603 jvParams[fieldName][jss::currency] = "USD";
1604 jvParams[jss::ledger_hash] = ledgerHash;
1605 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1606 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
1607 BEAST_EXPECT(jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
1608 }
1609 {
1610 // test basic malformed scenarios
1612 env,
1613 fieldName,
1614 {
1615 {jss::accounts, "malformedRequest"},
1616 {jss::currency, "malformedCurrency"},
1617 });
1618 }
1619 {
1620 // ripple_state one of the accounts is missing.
1621 Json::Value jvParams;
1622 jvParams[fieldName] = Json::objectValue;
1623 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1624 jvParams[fieldName][jss::accounts][0u] = alice.human();
1625 jvParams[fieldName][jss::currency] = "USD";
1626 jvParams[jss::ledger_hash] = ledgerHash;
1627 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1629 jrr,
1630 "malformedRequest",
1631 "Invalid field 'accounts', not length-2 array of "
1632 "Accounts.");
1633 }
1634 {
1635 // ripple_state more than 2 accounts.
1636 Json::Value jvParams;
1637 jvParams[fieldName] = Json::objectValue;
1638 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1639 jvParams[fieldName][jss::accounts][0u] = alice.human();
1640 jvParams[fieldName][jss::accounts][1u] = gw.human();
1641 jvParams[fieldName][jss::accounts][2u] = alice.human();
1642 jvParams[fieldName][jss::currency] = "USD";
1643 jvParams[jss::ledger_hash] = ledgerHash;
1644 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1646 jrr,
1647 "malformedRequest",
1648 "Invalid field 'accounts', not length-2 array of "
1649 "Accounts.");
1650 }
1651 {
1652 // ripple_state account[0] / account[1] is not an account.
1653 Json::Value jvParams;
1654 jvParams[fieldName] = Json::objectValue;
1655 auto tryField = [&](Json::Value badAccount) -> void {
1656 {
1657 // account[0]
1658 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1659 jvParams[fieldName][jss::accounts][0u] = badAccount;
1660 jvParams[fieldName][jss::accounts][1u] = gw.human();
1661 jvParams[fieldName][jss::currency] = "USD";
1662
1663 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1665 jrr, "malformedAddress", RPC::expected_field_message(jss::accounts, "array of Accounts"));
1666 }
1667
1668 {
1669 // account[1]
1670 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1671 jvParams[fieldName][jss::accounts][0u] = alice.human();
1672 jvParams[fieldName][jss::accounts][1u] = badAccount;
1673 jvParams[fieldName][jss::currency] = "USD";
1674
1675 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1677 jrr, "malformedAddress", RPC::expected_field_message(jss::accounts, "array of Accounts"));
1678 }
1679 };
1680
1681 auto const& badValues = getBadValues(FieldType::AccountField);
1682 for (auto const& value : badValues)
1683 {
1684 tryField(value);
1685 }
1686 tryField(Json::nullValue);
1687 }
1688 {
1689 // ripple_state account[0] == account[1].
1690 Json::Value jvParams;
1691 jvParams[fieldName] = Json::objectValue;
1692 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1693 jvParams[fieldName][jss::accounts][0u] = alice.human();
1694 jvParams[fieldName][jss::accounts][1u] = alice.human();
1695 jvParams[fieldName][jss::currency] = "USD";
1696 jvParams[jss::ledger_hash] = ledgerHash;
1697 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1698 checkErrorValue(jrr, "malformedRequest", "Cannot have a trustline to self.");
1699 }
1700 }
1701 }
1702
1703 void
1705 {
1706 testcase("Signer List");
1707 using namespace test::jtx;
1708 Env env{*this};
1709 runLedgerEntryTest(env, jss::signer_list);
1710 }
1711
1712 void
1714 {
1715 testcase("Ticket");
1716 using namespace test::jtx;
1717 Env env{*this};
1718 env.close();
1719
1720 // Create two tickets.
1721 std::uint32_t const tkt1{env.seq(env.master) + 1};
1722 env(ticket::create(env.master, 2));
1723 env.close();
1724
1725 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1726 // Request four tickets: one before the first one we created, the
1727 // two created tickets, and the ticket that would come after the
1728 // last created ticket.
1729 {
1730 // Not a valid ticket requested by index.
1731 Json::Value jvParams;
1732 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1 - 1));
1733 jvParams[jss::ledger_hash] = ledgerHash;
1734 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1735 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1736 }
1737 {
1738 // First real ticket requested by index.
1739 Json::Value jvParams;
1740 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
1741 jvParams[jss::ledger_hash] = ledgerHash;
1742 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1743 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
1744 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
1745 }
1746 {
1747 // Second real ticket requested by account and sequence.
1748 Json::Value jvParams;
1749 jvParams[jss::ticket] = Json::objectValue;
1750 jvParams[jss::ticket][jss::account] = env.master.human();
1751 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
1752 jvParams[jss::ledger_hash] = ledgerHash;
1753 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1754 BEAST_EXPECT(jrr[jss::node][jss::index] == to_string(getTicketIndex(env.master, tkt1 + 1)));
1755 }
1756 {
1757 // Not a valid ticket requested by account and sequence.
1758 Json::Value jvParams;
1759 jvParams[jss::ticket] = Json::objectValue;
1760 jvParams[jss::ticket][jss::account] = env.master.human();
1761 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
1762 jvParams[jss::ledger_hash] = ledgerHash;
1763 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1764 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1765 }
1766 {
1767 // Request a ticket using an account root entry.
1768 Json::Value jvParams;
1769 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
1770 jvParams[jss::ledger_hash] = ledgerHash;
1771 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1772 checkErrorValue(jrr, "unexpectedLedgerType", "Unexpected ledger type.");
1773 }
1774
1775 {
1776 // test basic malformed scenarios
1778 env,
1779 jss::ticket,
1780 {
1781 {jss::account, "malformedAddress"},
1782 {jss::ticket_seq, "malformedRequest"},
1783 });
1784 }
1785 }
1786
1787 void
1789 {
1790 testcase("DID");
1791 using namespace test::jtx;
1792 using namespace std::literals::chrono_literals;
1793 Env env{*this};
1794 Account const alice{"alice"};
1795
1796 env.fund(XRP(10000), alice);
1797 env.close();
1798
1799 // Lambda to create a DID.
1800 auto didCreate = [](test::jtx::Account const& account) {
1801 Json::Value jv;
1802 jv[jss::TransactionType] = jss::DIDSet;
1803 jv[jss::Account] = account.human();
1804 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
1805 jv[sfURI.jsonName] = strHex(std::string{"uri"});
1806 return jv;
1807 };
1808
1809 env(didCreate(alice));
1810 env.close();
1811
1812 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1813
1814 {
1815 // Request the DID using its index.
1816 Json::Value jvParams;
1817 jvParams[jss::did] = alice.human();
1818 jvParams[jss::ledger_hash] = ledgerHash;
1819 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1820 BEAST_EXPECT(jrr[jss::node][sfDIDDocument.jsonName] == strHex(std::string{"data"}));
1821 BEAST_EXPECT(jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
1822 }
1823 {
1824 // Request an index that is not a DID.
1825 Json::Value jvParams;
1826 jvParams[jss::did] = env.master.human();
1827 jvParams[jss::ledger_hash] = ledgerHash;
1828 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1829 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1830 }
1831 {
1832 // Malformed DID index
1833 Json::Value jvParams;
1834 testMalformedField(env, jvParams, jss::did, FieldType::AccountField, "malformedAddress");
1835 }
1836 }
1837
1838 void
1840 {
1841 testcase("Invalid Oracle Ledger Entry");
1842 using namespace xrpl::test::jtx;
1843 using namespace xrpl::test::jtx::oracle;
1844
1845 Env env(*this);
1846 Account const owner("owner");
1847 env.fund(XRP(1'000), owner);
1848 Oracle oracle(env, {.owner = owner, .fee = static_cast<int>(env.current()->fees().base.drops())});
1849
1850 {
1851 // test basic malformed scenarios
1853 env,
1854 jss::oracle,
1855 {
1856 {jss::account, "malformedAccount"},
1857 {jss::oracle_document_id, "malformedDocumentID"},
1858 });
1859 }
1860 }
1861
1862 void
1864 {
1865 testcase("Oracle Ledger Entry");
1866 using namespace xrpl::test::jtx;
1867 using namespace xrpl::test::jtx::oracle;
1868
1869 Env env(*this);
1870 auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
1871 std::vector<AccountID> accounts;
1873 for (int i = 0; i < 10; ++i)
1874 {
1875 Account const owner(std::string("owner") + std::to_string(i));
1876 env.fund(XRP(1'000), owner);
1877 // different accounts can have the same asset pair
1878 Oracle oracle(env, {.owner = owner, .documentID = i, .fee = baseFee});
1879 accounts.push_back(owner.id());
1880 oracles.push_back(oracle.documentID());
1881 // same account can have different asset pair
1882 Oracle oracle1(env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
1883 accounts.push_back(owner.id());
1884 oracles.push_back(oracle1.documentID());
1885 }
1886 for (int i = 0; i < accounts.size(); ++i)
1887 {
1888 auto const jv = [&]() {
1889 // document id is uint32
1890 if (i % 2)
1891 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
1892 // document id is string
1893 return Oracle::ledgerEntry(env, accounts[i], std::to_string(oracles[i]));
1894 }();
1895 try
1896 {
1897 BEAST_EXPECT(jv[jss::node][jss::Owner] == to_string(accounts[i]));
1898 }
1899 catch (...)
1900 {
1901 fail();
1902 }
1903 }
1904 }
1905
1906 void
1908 {
1909 testcase("MPT");
1910 using namespace test::jtx;
1911 using namespace std::literals::chrono_literals;
1912 Env env{*this};
1913 Account const alice{"alice"};
1914 Account const bob("bob");
1915
1916 MPTTester mptAlice(env, alice, {.holders = {bob}});
1917 mptAlice.create(
1918 {.transferFee = 10,
1919 .metadata = "123",
1920 .ownerCount = 1,
1923 mptAlice.authorize({.account = bob, .holderCount = 1});
1924
1925 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1926
1927 std::string const badMptID = "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
1928 {
1929 // Request the MPTIssuance using its MPTIssuanceID.
1930 Json::Value jvParams;
1931 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
1932 jvParams[jss::ledger_hash] = ledgerHash;
1933 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1934 BEAST_EXPECT(jrr[jss::node][sfMPTokenMetadata.jsonName] == strHex(std::string{"123"}));
1935 BEAST_EXPECT(jrr[jss::node][jss::mpt_issuance_id] == strHex(mptAlice.issuanceID()));
1936 }
1937 {
1938 // Request an index that is not a MPTIssuance.
1939 Json::Value jvParams;
1940 jvParams[jss::mpt_issuance] = badMptID;
1941 jvParams[jss::ledger_hash] = ledgerHash;
1942 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1943 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1944 }
1945 {
1946 // Request the MPToken using its owner + mptIssuanceID.
1947 Json::Value jvParams;
1948 jvParams[jss::mptoken] = Json::objectValue;
1949 jvParams[jss::mptoken][jss::account] = bob.human();
1950 jvParams[jss::mptoken][jss::mpt_issuance_id] = strHex(mptAlice.issuanceID());
1951 jvParams[jss::ledger_hash] = ledgerHash;
1952 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1953 BEAST_EXPECT(jrr[jss::node][sfMPTokenIssuanceID.jsonName] == strHex(mptAlice.issuanceID()));
1954 }
1955 {
1956 // Request the MPToken using a bad mptIssuanceID.
1957 Json::Value jvParams;
1958 jvParams[jss::mptoken] = Json::objectValue;
1959 jvParams[jss::mptoken][jss::account] = bob.human();
1960 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
1961 jvParams[jss::ledger_hash] = ledgerHash;
1962 Json::Value const jrr = env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
1963 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1964 }
1965 {
1966 // Malformed MPTIssuance index
1967 Json::Value jvParams;
1968 testMalformedField(env, jvParams, jss::mptoken, FieldType::HashOrObjectField, "malformedRequest");
1969 }
1970 }
1971
1972 void
1974 {
1975 testcase("PermissionedDomain");
1976
1977 using namespace test::jtx;
1978
1979 Env env(*this, testable_amendments() | featurePermissionedDomains);
1980 Account const issuer{"issuer"};
1981 Account const alice{"alice"};
1982 Account const bob{"bob"};
1983
1984 env.fund(XRP(5000), issuer, alice, bob);
1985 env.close();
1986
1987 auto const seq = env.seq(alice);
1988 env(pdomain::setTx(alice, {{alice, "first credential"}}));
1989 env.close();
1990 auto const objects = pdomain::getObjects(alice, env);
1991 if (!BEAST_EXPECT(objects.size() == 1))
1992 return;
1993
1994 {
1995 // Succeed
1996 Json::Value params;
1997 params[jss::ledger_index] = jss::validated;
1998 params[jss::permissioned_domain][jss::account] = alice.human();
1999 params[jss::permissioned_domain][jss::seq] = seq;
2000 auto jv = env.rpc("json", "ledger_entry", to_string(params));
2001 BEAST_EXPECT(
2002 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
2003 jv[jss::result].isMember(jss::node) &&
2004 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
2005 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::PermissionedDomain);
2006
2007 std::string const pdIdx = jv[jss::result][jss::index].asString();
2008 BEAST_EXPECT(strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
2009
2010 params.clear();
2011 params[jss::ledger_index] = jss::validated;
2012 params[jss::permissioned_domain] = pdIdx;
2013 jv = env.rpc("json", "ledger_entry", to_string(params));
2014 BEAST_EXPECT(
2015 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
2016 jv[jss::result].isMember(jss::node) &&
2017 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
2018 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::PermissionedDomain);
2019 }
2020
2021 {
2022 // Fail, invalid permissioned domain index
2023 Json::Value params;
2024 params[jss::ledger_index] = jss::validated;
2025 params[jss::permissioned_domain] =
2026 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
2027 "DE";
2028 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
2029 checkErrorValue(jrr[jss::result], "entryNotFound", "Entry not found.");
2030 }
2031 {
2032 // test basic malformed scenarios
2034 env,
2035 jss::permissioned_domain,
2036 {
2037 {jss::account, "malformedAddress"},
2038 {jss::seq, "malformedRequest"},
2039 });
2040 }
2041 }
2042
2044 void
2046 {
2047 using namespace test::jtx;
2048
2049 Account const alice{"alice"};
2050 Account const bob{"bob"};
2051
2052 Env env{*this, envconfig([](auto cfg) {
2053 cfg->START_UP = Config::FRESH;
2054 return cfg;
2055 })};
2056
2057 env.close();
2058
2069 auto checkResult = [&](bool good,
2070 Json::Value const& jv,
2071 Json::StaticString const& expectedType,
2072 std::optional<std::string> const& expectedError = {}) {
2073 if (good)
2074 {
2075 BEAST_EXPECTS(
2076 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
2077 jv[jss::result].isMember(jss::node) &&
2078 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
2079 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == expectedType,
2080 to_string(jv));
2081 }
2082 else
2083 {
2084 BEAST_EXPECTS(
2085 jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
2086 !jv[jss::result].isMember(jss::node) &&
2087 jv[jss::result][jss::error] == expectedError.value_or("entryNotFound"),
2088 to_string(jv));
2089 }
2090 };
2091
2102 auto test = [&](Json::StaticString const& field,
2103 Json::StaticString const& expectedType,
2104 Keylet const& expectedKey,
2105 bool good) {
2106 testcase << expectedType.c_str() << (good ? "" : " not") << " found";
2107
2108 auto const hexKey = strHex(expectedKey.key);
2109
2110 {
2111 // Test bad values
2112 // "field":null
2113 Json::Value params;
2114 params[jss::ledger_index] = jss::validated;
2115 params[field] = Json::nullValue;
2116 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2117 checkResult(false, jv, expectedType, "malformedRequest");
2118 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2119 }
2120
2121 {
2122 Json::Value params;
2123 // "field":"string"
2124 params[jss::ledger_index] = jss::validated;
2125 params[field] = "arbitrary string";
2126 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2127 checkResult(false, jv, expectedType, "malformedRequest");
2128 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2129 }
2130
2131 {
2132 Json::Value params;
2133 // "field":false
2134 params[jss::ledger_index] = jss::validated;
2135 params[field] = false;
2136 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2137 checkResult(false, jv, expectedType, "invalidParams");
2138 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2139 }
2140
2141 {
2142 Json::Value params;
2143
2144 // "field":[incorrect index hash]
2145 auto const badKey = strHex(expectedKey.key + uint256{1});
2146 params[jss::ledger_index] = jss::validated;
2147 params[field] = badKey;
2148 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2149 checkResult(false, jv, expectedType, "entryNotFound");
2150 BEAST_EXPECTS(jv[jss::result][jss::index] == badKey, to_string(jv));
2151 }
2152
2153 {
2154 Json::Value params;
2155 // "index":"field" using API 2
2156 params[jss::ledger_index] = jss::validated;
2157 params[jss::index] = field;
2158 params[jss::api_version] = 2;
2159 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2160 checkResult(false, jv, expectedType, "malformedRequest");
2161 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2162 }
2163
2164 std::string const pdIdx = [&]() {
2165 {
2166 Json::Value params;
2167 // Test good values
2168 // Use the "field":true notation
2169 params[jss::ledger_index] = jss::validated;
2170 params[field] = true;
2171 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2172 // Index will always be returned for valid parameters.
2173 std::string const pdIdx = jv[jss::result][jss::index].asString();
2174 BEAST_EXPECTS(hexKey == pdIdx, to_string(jv));
2175 checkResult(good, jv, expectedType);
2176
2177 return pdIdx;
2178 }
2179 }();
2180
2181 {
2182 Json::Value params;
2183 // "field":"[index hash]"
2184 params[jss::ledger_index] = jss::validated;
2185 params[field] = hexKey;
2186 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2187 checkResult(good, jv, expectedType);
2188 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2189 }
2190
2191 {
2192 // Bad value
2193 // Use the "index":"field" notation with API 2
2194 Json::Value params;
2195 params[jss::ledger_index] = jss::validated;
2196 params[jss::index] = field;
2197 params[jss::api_version] = 2;
2198 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2199 checkResult(false, jv, expectedType, "malformedRequest");
2200 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2201 }
2202
2203 {
2204 Json::Value params;
2205 // Use the "index":"field" notation with API 3
2206 params[jss::ledger_index] = jss::validated;
2207 params[jss::index] = field;
2208 params[jss::api_version] = 3;
2209 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2210 // Index is correct either way
2211 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2212 checkResult(good, jv, expectedType);
2213 }
2214
2215 {
2216 Json::Value params;
2217 // Use the "index":"[index hash]" notation
2218 params[jss::ledger_index] = jss::validated;
2219 params[jss::index] = pdIdx;
2220 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2221 // Index is correct either way
2222 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2223 checkResult(good, jv, expectedType);
2224 }
2225 };
2226
2227 test(jss::amendments, jss::Amendments, keylet::amendments(), true);
2228 test(jss::fee, jss::FeeSettings, keylet::fees(), true);
2229 // There won't be an nunl
2230 test(jss::nunl, jss::NegativeUNL, keylet::negativeUNL(), false);
2231 // Can only get the short skip list this way
2232 test(jss::hashes, jss::LedgerHashes, keylet::skip(), true);
2233 }
2234
2235 void
2237 {
2238 using namespace test::jtx;
2239
2240 Account const alice{"alice"};
2241 Account const bob{"bob"};
2242
2243 Env env{*this, envconfig([](auto cfg) {
2244 cfg->START_UP = Config::FRESH;
2245 return cfg;
2246 })};
2247
2248 env.close();
2249
2260 auto checkResult = [&](bool good,
2261 Json::Value const& jv,
2262 int expectedCount,
2263 std::optional<std::string> const& expectedError = {}) {
2264 if (good)
2265 {
2266 BEAST_EXPECTS(
2267 jv.isObject() && jv.isMember(jss::result) && !jv[jss::result].isMember(jss::error) &&
2268 jv[jss::result].isMember(jss::node) &&
2269 jv[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) &&
2270 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::LedgerHashes,
2271 to_string(jv));
2272 BEAST_EXPECTS(
2273 jv[jss::result].isMember(jss::node) && jv[jss::result][jss::node].isMember("Hashes") &&
2274 jv[jss::result][jss::node]["Hashes"].size() == expectedCount,
2275 to_string(jv[jss::result][jss::node]["Hashes"].size()));
2276 }
2277 else
2278 {
2279 BEAST_EXPECTS(
2280 jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
2281 !jv[jss::result].isMember(jss::node) &&
2282 jv[jss::result][jss::error] == expectedError.value_or("entryNotFound"),
2283 to_string(jv));
2284 }
2285 };
2286
2297 auto test = [&](Json::Value ledger, Keylet const& expectedKey, bool good, int expectedCount = 0) {
2298 testcase << "LedgerHashes: seq: " << env.current()->header().seq << " \"hashes\":" << to_string(ledger)
2299 << (good ? "" : " not") << " found";
2300
2301 auto const hexKey = strHex(expectedKey.key);
2302
2303 {
2304 // Test bad values
2305 // "hashes":null
2306 Json::Value params;
2307 params[jss::ledger_index] = jss::validated;
2308 params[jss::hashes] = Json::nullValue;
2309 auto jv = env.rpc("json", "ledger_entry", to_string(params));
2310 checkResult(false, jv, 0, "malformedRequest");
2311 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2312 }
2313
2314 {
2315 Json::Value params;
2316 // "hashes":"non-uint string"
2317 params[jss::ledger_index] = jss::validated;
2318 params[jss::hashes] = "arbitrary string";
2319 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2320 checkResult(false, jv, 0, "malformedRequest");
2321 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2322 }
2323
2324 {
2325 Json::Value params;
2326 // "hashes":"uint string" is invalid, too
2327 params[jss::ledger_index] = jss::validated;
2328 params[jss::hashes] = "10";
2329 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2330 checkResult(false, jv, 0, "malformedRequest");
2331 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2332 }
2333
2334 {
2335 Json::Value params;
2336 // "hashes":false
2337 params[jss::ledger_index] = jss::validated;
2338 params[jss::hashes] = false;
2339 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2340 checkResult(false, jv, 0, "invalidParams");
2341 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2342 }
2343
2344 {
2345 Json::Value params;
2346 // "hashes":-1
2347 params[jss::ledger_index] = jss::validated;
2348 params[jss::hashes] = -1;
2349 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2350 checkResult(false, jv, 0, "internal");
2351 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2352 }
2353
2354 // "hashes":[incorrect index hash]
2355 {
2356 Json::Value params;
2357 auto const badKey = strHex(expectedKey.key + uint256{1});
2358 params[jss::ledger_index] = jss::validated;
2359 params[jss::hashes] = badKey;
2360 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2361 checkResult(false, jv, 0, "entryNotFound");
2362 BEAST_EXPECT(jv[jss::result][jss::index] == badKey);
2363 }
2364
2365 {
2366 Json::Value params;
2367 // Test good values
2368 // Use the "hashes":ledger notation
2369 params[jss::ledger_index] = jss::validated;
2370 params[jss::hashes] = ledger;
2371 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2372 checkResult(good, jv, expectedCount);
2373 // Index will always be returned for valid parameters.
2374 std::string const pdIdx = jv[jss::result][jss::index].asString();
2375 BEAST_EXPECTS(hexKey == pdIdx, strHex(pdIdx));
2376 }
2377
2378 {
2379 Json::Value params;
2380 // "hashes":"[index hash]"
2381 params[jss::ledger_index] = jss::validated;
2382 params[jss::hashes] = hexKey;
2383 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2384 checkResult(good, jv, expectedCount);
2385 // Index is correct either way
2386 BEAST_EXPECTS(
2387 hexKey == jv[jss::result][jss::index].asString(), strHex(jv[jss::result][jss::index].asString()));
2388 }
2389
2390 {
2391 Json::Value params;
2392 // Use the "index":"[index hash]" notation
2393 params[jss::ledger_index] = jss::validated;
2394 params[jss::index] = hexKey;
2395 auto const jv = env.rpc("json", "ledger_entry", to_string(params));
2396 checkResult(good, jv, expectedCount);
2397 // Index is correct either way
2398 BEAST_EXPECTS(
2399 hexKey == jv[jss::result][jss::index].asString(), strHex(jv[jss::result][jss::index].asString()));
2400 }
2401 };
2402
2403 // short skip list
2404 test(true, keylet::skip(), true, 2);
2405 // long skip list at index 0
2406 test(1, keylet::skip(1), false);
2407 // long skip list at index 1
2408 test(1 << 17, keylet::skip(1 << 17), false);
2409
2410 // Close more ledgers, but stop short of the flag ledger
2411 for (auto i = env.current()->seq(); i <= 250; ++i)
2412 env.close();
2413
2414 // short skip list
2415 test(true, keylet::skip(), true, 249);
2416 // long skip list at index 0
2417 test(1, keylet::skip(1), false);
2418 // long skip list at index 1
2419 test(1 << 17, keylet::skip(1 << 17), false);
2420
2421 // Close a flag ledger so the first "long" skip list is created
2422 for (auto i = env.current()->seq(); i <= 260; ++i)
2423 env.close();
2424
2425 // short skip list
2426 test(true, keylet::skip(), true, 256);
2427 // long skip list at index 0
2428 test(1, keylet::skip(1), true, 1);
2429 // long skip list at index 1
2430 test(1 << 17, keylet::skip(1 << 17), false);
2431 }
2432
2433 void
2435 {
2436 testcase("command-line");
2437 using namespace test::jtx;
2438
2439 Env env{*this};
2440 Account const alice{"alice"};
2441 env.fund(XRP(10000), alice);
2442 env.close();
2443
2444 auto const checkId = keylet::check(env.master, env.seq(env.master));
2445
2446 env(check::create(env.master, alice, XRP(100)));
2447 env.close();
2448
2449 std::string const ledgerHash{to_string(env.closed()->header().hash)};
2450 {
2451 // Request a check.
2452 Json::Value const jrr = env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
2453 BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
2454 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
2455 }
2456 }
2457
2458public:
2459 void
2460 run() override
2461 {
2462 testInvalid();
2465 testAMM();
2466 testCheck();
2468 testDelegate();
2471 testDirectory();
2472 testEscrow();
2478 testOffer();
2479 testPayChan();
2482 testTicket();
2483 testDID();
2486 testMPT();
2488 testFixed();
2489 testHashes();
2490 testCLI();
2491 }
2492};
2493
2495{
2496 void
2497 checkErrorValue(Json::Value const& jv, std::string const& err, std::string const& msg)
2498 {
2499 if (BEAST_EXPECT(jv.isMember(jss::status)))
2500 BEAST_EXPECT(jv[jss::status] == "error");
2501 if (BEAST_EXPECT(jv.isMember(jss::error)))
2502 BEAST_EXPECT(jv[jss::error] == err);
2503 if (msg.empty())
2504 {
2505 BEAST_EXPECT(jv[jss::error_message] == Json::nullValue || jv[jss::error_message] == "");
2506 }
2507 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
2508 BEAST_EXPECT(jv[jss::error_message] == msg);
2509 }
2510
2511 void
2513 {
2514 testcase("ledger_entry: bridge");
2515 using namespace test::jtx;
2516
2517 Env mcEnv{*this, features};
2518 Env scEnv(*this, envconfig(), features);
2519
2520 createBridgeObjects(mcEnv, scEnv);
2521
2522 std::string const ledgerHash{to_string(mcEnv.closed()->header().hash)};
2523 std::string bridge_index;
2524 Json::Value mcBridge;
2525 {
2526 // request the bridge via RPC
2527 Json::Value jvParams;
2528 jvParams[jss::bridge_account] = mcDoor.human();
2529 jvParams[jss::bridge] = jvb;
2530 Json::Value const jrr = mcEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2531
2532 BEAST_EXPECT(jrr.isMember(jss::node));
2533 auto r = jrr[jss::node];
2534
2535 BEAST_EXPECT(r.isMember(jss::Account));
2536 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
2537
2538 BEAST_EXPECT(r.isMember(jss::Flags));
2539
2540 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
2541 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
2542
2543 // we not created an account yet
2544 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2545 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
2546
2547 // we have not claimed a locking chain tx yet
2548 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
2549 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
2550
2551 BEAST_EXPECT(r.isMember(jss::index));
2552 bridge_index = r[jss::index].asString();
2553 mcBridge = r;
2554 }
2555 {
2556 // request the bridge via RPC by index
2557 Json::Value jvParams;
2558 jvParams[jss::index] = bridge_index;
2559 Json::Value const jrr = mcEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2560
2561 BEAST_EXPECT(jrr.isMember(jss::node));
2562 BEAST_EXPECT(jrr[jss::node] == mcBridge);
2563 }
2564 {
2565 // swap door accounts and make sure we get an error value
2566 Json::Value jvParams;
2567 // Sidechain door account is "master", not scDoor
2568 jvParams[jss::bridge_account] = Account::master.human();
2569 jvParams[jss::bridge] = jvb;
2570 jvParams[jss::ledger_hash] = ledgerHash;
2571 Json::Value const jrr = mcEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2572
2573 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2574 }
2575 {
2576 // create two claim ids and verify that the bridge counter was
2577 // incremented
2579 mcEnv.close();
2581 mcEnv.close();
2582
2583 // request the bridge via RPC
2584 Json::Value jvParams;
2585 jvParams[jss::bridge_account] = mcDoor.human();
2586 jvParams[jss::bridge] = jvb;
2587 Json::Value const jrr = mcEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2588
2589 BEAST_EXPECT(jrr.isMember(jss::node));
2590 auto r = jrr[jss::node];
2591
2592 // we executed two create claim id txs
2593 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
2594 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2595 }
2596 }
2597
2598 void
2600 {
2601 testcase("ledger_entry: xchain_claim_id");
2602 using namespace test::jtx;
2603
2604 Env mcEnv{*this, features};
2605 Env scEnv(*this, envconfig(), features);
2606
2607 createBridgeObjects(mcEnv, scEnv);
2608
2610 scEnv.close();
2612 scEnv.close();
2613
2614 std::string bridge_index;
2615 {
2616 // request the xchain_claim_id via RPC
2617 Json::Value jvParams;
2618 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2619 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = 1;
2620 Json::Value const jrr = scEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2621
2622 BEAST_EXPECT(jrr.isMember(jss::node));
2623 auto r = jrr[jss::node];
2624
2625 BEAST_EXPECT(r.isMember(jss::Account));
2626 BEAST_EXPECT(r[jss::Account] == scAlice.human());
2627 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2628 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
2629 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2630 }
2631
2632 {
2633 // request the xchain_claim_id via RPC
2634 Json::Value jvParams;
2635 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2636 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] = 2;
2637 Json::Value const jrr = scEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2638
2639 BEAST_EXPECT(jrr.isMember(jss::node));
2640 auto r = jrr[jss::node];
2641
2642 BEAST_EXPECT(r.isMember(jss::Account));
2643 BEAST_EXPECT(r[jss::Account] == scBob.human());
2644 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2645 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2646 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2647 }
2648 }
2649
2650 void
2652 {
2653 testcase("ledger_entry: xchain_create_account_claim_id");
2654 using namespace test::jtx;
2655
2656 Env mcEnv{*this, features};
2657 Env scEnv(*this, envconfig(), features);
2658
2659 // note: signers.size() and quorum are both 5 in createBridgeObjects
2660 createBridgeObjects(mcEnv, scEnv);
2661
2662 auto scCarol = Account("scCarol"); // Don't fund it - it will be created with the
2663 // xchain transaction
2664 auto const amt = XRP(1000);
2666 mcEnv.close();
2667
2668 // send less than quorum of attestations (otherwise funds are
2669 // immediately transferred and no "claim" object is created)
2670 size_t constexpr num_attest = 3;
2671 auto attestations = create_account_attestations(
2672 scAttester,
2673 jvb,
2674 mcAlice,
2675 amt,
2676 reward,
2677 payee,
2678 /*wasLockingChainSend*/ true,
2679 1,
2680 scCarol,
2681 signers,
2683 for (size_t i = 0; i < num_attest; ++i)
2684 {
2685 scEnv(attestations[i]);
2686 }
2687 scEnv.close();
2688
2689 {
2690 // request the create account claim_id via RPC
2691 Json::Value jvParams;
2692 jvParams[jss::xchain_owned_create_account_claim_id] = jvXRPBridgeRPC;
2693 jvParams[jss::xchain_owned_create_account_claim_id][jss::xchain_owned_create_account_claim_id] = 1;
2694 Json::Value const jrr = scEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2695
2696 BEAST_EXPECT(jrr.isMember(jss::node));
2697 auto r = jrr[jss::node];
2698
2699 BEAST_EXPECT(r.isMember(jss::Account));
2700 BEAST_EXPECT(r[jss::Account] == Account::master.human());
2701
2702 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2703 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
2704
2705 BEAST_EXPECT(r.isMember(sfXChainCreateAccountAttestations.jsonName));
2706 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
2707 BEAST_EXPECT(attest.isArray());
2708 BEAST_EXPECT(attest.size() == 3);
2709 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(sfXChainCreateAccountProofSig.jsonName));
2710 Json::Value a[num_attest];
2711 for (size_t i = 0; i < num_attest; ++i)
2712 {
2713 a[i] = attest[Json::Value::UInt(0)][sfXChainCreateAccountProofSig.jsonName];
2714 BEAST_EXPECT(a[i].isMember(jss::Amount) && a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
2715 BEAST_EXPECT(a[i].isMember(jss::Destination) && a[i][jss::Destination] == scCarol.human());
2716 BEAST_EXPECT(
2717 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
2718 std::any_of(signers.begin(), signers.end(), [&](signer const& s) {
2719 return a[i][sfAttestationSignerAccount.jsonName] == s.account.human();
2720 }));
2721 BEAST_EXPECT(
2722 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
2723 std::any_of(payee.begin(), payee.end(), [&](Account const& account) {
2724 return a[i][sfAttestationRewardAccount.jsonName] == account.human();
2725 }));
2726 BEAST_EXPECT(
2727 a[i].isMember(sfWasLockingChainSend.jsonName) && a[i][sfWasLockingChainSend.jsonName] == 1);
2728 BEAST_EXPECT(
2729 a[i].isMember(sfSignatureReward.jsonName) &&
2730 a[i][sfSignatureReward.jsonName].asInt() == 1 * drop_per_xrp);
2731 }
2732 }
2733
2734 // complete attestations quorum - CreateAccountClaimID should not be
2735 // present anymore
2736 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
2737 {
2738 scEnv(attestations[i]);
2739 }
2740 scEnv.close();
2741 {
2742 // request the create account claim_id via RPC
2743 Json::Value jvParams;
2744 jvParams[jss::xchain_owned_create_account_claim_id] = jvXRPBridgeRPC;
2745 jvParams[jss::xchain_owned_create_account_claim_id][jss::xchain_owned_create_account_claim_id] = 1;
2746 Json::Value const jrr = scEnv.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
2747 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2748 }
2749 }
2750
2751public:
2752 void
2753 run() override
2754 {
2755 testBridge();
2756 testClaimID();
2758 }
2759};
2760
2761BEAST_DEFINE_TESTSUITE(LedgerEntry, rpc, xrpl);
2762BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, rpc, xrpl);
2763
2764} // namespace test
2765} // namespace xrpl
T any_of(T... args)
T begin(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:45
constexpr char const * c_str() const
Definition json_value.h:58
Represents a JSON value.
Definition json_value.h:131
Json::UInt UInt
Definition json_value.h:138
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 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 generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:517
bool modify(modify_type const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
void rawInsert(std::shared_ptr< SLE > const &sle) override
Unconditionally insert a state item.
Definition OpenView.cpp:207
A public key.
Definition PublicKey.h:43
void push_back(STObject const &object)
Definition STArray.h:188
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:72
virtual OpenLedger & openLedger()=0
static base_uint fromVoid(void const *data)
Definition base_uint.h:292
void run() override
Runs the suite.
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg, std::source_location const location=std::source_location::current())
void testMalformedSubfield(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString parentFieldName, Json::StaticString fieldName, FieldType typeID, std::string const &expectedError, bool required=true, std::source_location const location=std::source_location::current())
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, std::source_location const location=std::source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::source_location const location=std::source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::vector< Subfield > const &subfields, std::source_location const location=std::source_location::current())
void testFixed()
Test the ledger entry types that don't take parameters.
Json::Value getCorrectValue(Json::StaticString fieldName)
std::vector< Json::Value > getBadValues(FieldType fieldType)
Convenience class to test AMM functionality.
Definition AMM.h:105
Immutable cryptographic account descriptor.
Definition Account.h:20
std::string const & human() const
Returns the human readable public key.
Definition Account.h:95
static Account const master
The master account.
Definition Account.h:29
AccountID id() const
Returns the Account ID.
Definition Account.h:88
A transaction testing environment.
Definition Env.h:98
Application & app()
Definition Env.h:230
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:97
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:91
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:248
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:260
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:239
Account const & master
Definition Env.h:102
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:283
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:749
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:298
NetClock::time_point now()
Returns the current network time.
Definition Env.h:253
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:126
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:96
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:16
Sets the optional Destination field on an NFTokenOffer.
Definition token.h:141
Set the flags on a JTx.
Definition txflags.h:12
T current(T... args)
T empty(T... args)
T end(T... args)
T find_if(T... args)
T is_same_v
@ nullValue
'null' value
Definition json_value.h:20
@ arrayValue
array value (ordered list)
Definition json_value.h:26
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:281
std::string missing_field_message(std::string const &name)
Definition ErrorCodes.h:221
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:172
Keylet const & negativeUNL() noexcept
The (fixed) index of the object containing the ledger negativeUNL.
Definition Indexes.cpp:201
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:360
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:375
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:187
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:346
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:293
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Keylet const & fees() noexcept
The (fixed) index of the object containing the ledger fees.
Definition Indexes.cpp:194
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:510
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:49
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:13
Json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:12
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
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:12
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:15
Json::Value createOffer(jtx::Account const &account, uint256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
Definition token.cpp:87
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Definition token.cpp:49
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
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
require_t required(Args const &... args)
Compose many condition functors into one.
Definition require.h:30
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
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)
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
FeatureBitset testable_amendments()
Definition Env.h:55
auto const amount
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
std::vector< std::pair< Json::StaticString, FieldType > > mappings
FieldType getFieldType(Json::StaticString fieldName)
static uint256 ledgerHash(LedgerHeader const &info)
std::string getTypeName(FieldType typeID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:123
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition Indexes.cpp:133
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:132
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:224
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:145
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:211
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:131
T push_back(T... args)
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
void createBridgeObjects(Env &mcEnv, Env &scEnv)
std::vector< Account > const payee
std::vector< signer > const signers
Set the sequence number on a JTx.
Definition seq.h:15
A signer in a SignerList.
Definition multisign.h:20
T to_string(T... args)