xrpld
Loading...
Searching...
No Matches
Credentials_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/acctdelete.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/credentials.h>
8#include <test/jtx/deposit.h>
9#include <test/jtx/directory.h>
10#include <test/jtx/fee.h>
11#include <test/jtx/noop.h>
12#include <test/jtx/pay.h>
13#include <test/jtx/permissioned_domains.h>
14#include <test/jtx/ter.h>
15#include <test/jtx/ticket.h>
16#include <test/jtx/txflags.h>
17
18#include <xrpl/basics/strHex.h>
19#include <xrpl/beast/unit_test/suite.h>
20#include <xrpl/json/to_string.h>
21#include <xrpl/ledger/ApplyView.h>
22#include <xrpl/ledger/ApplyViewImpl.h>
23#include <xrpl/ledger/helpers/CredentialHelpers.h>
24#include <xrpl/protocol/AccountID.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/LedgerFormats.h>
28#include <xrpl/protocol/Protocol.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STTx.h>
31#include <xrpl/protocol/TER.h>
32#include <xrpl/protocol/TxFlags.h>
33#include <xrpl/protocol/jss.h>
34
35#include <cstdint>
36#include <cstring>
37#include <memory>
38#include <string_view>
39
40namespace xrpl::test {
41
43{
44 void
46 {
47 using namespace test::jtx;
48
49 char const credType[] = "abcde";
50 char const uri[] = "uri";
51
52 Account const issuer{"issuer"};
53 Account const subject{"subject"};
54 Account const other{"other"};
55
56 Env env{*this, features};
57
58 {
59 testcase("Create for subject.");
60
61 auto const credKey = credentials::keylet(subject, issuer, credType);
62
63 env.fund(XRP(5000), subject, issuer, other);
64 env.close();
65
66 // Test Create credentials
67 env(credentials::create(subject, issuer, credType), credentials::Uri(uri));
68 env.close();
69 {
70 auto const sleCred = env.le(credKey);
71 BEAST_EXPECT(static_cast<bool>(sleCred));
72 if (!sleCred)
73 return;
74
75 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
76 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
77 BEAST_EXPECT(!sleCred->getFieldU32(sfFlags));
78 BEAST_EXPECT(ownerCount(env, issuer) == 1);
79 BEAST_EXPECT(!ownerCount(env, subject));
80 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
81 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
82 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
83 BEAST_EXPECT(
84 jle.isObject() && jle.isMember(jss::result) &&
85 !jle[jss::result].isMember(jss::error) &&
86 jle[jss::result].isMember(jss::node) &&
87 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
88 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
89 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
90 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
91 jle[jss::result][jss::node]["CredentialType"] ==
92 strHex(std::string_view(credType)));
93 }
94
95 env(credentials::accept(subject, issuer, credType));
96 env.close();
97 {
98 // check switching owner of the credentials from issuer to
99 // subject
100 auto const sleCred = env.le(credKey);
101 BEAST_EXPECT(static_cast<bool>(sleCred));
102 if (!sleCred)
103 return;
104
105 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
106 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
107 BEAST_EXPECT(!ownerCount(env, issuer));
108 BEAST_EXPECT(ownerCount(env, subject) == 1);
109 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
110 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
111 BEAST_EXPECT(sleCred->getFieldU32(sfFlags) == lsfAccepted);
112 }
113
114 env(credentials::deleteCred(subject, subject, issuer, credType));
115 env.close();
116 {
117 BEAST_EXPECT(!env.le(credKey));
118 BEAST_EXPECT(!ownerCount(env, issuer));
119 BEAST_EXPECT(!ownerCount(env, subject));
120
121 // check no credential exists anymore
122 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
123 BEAST_EXPECT(
124 jle.isObject() && jle.isMember(jss::result) &&
125 jle[jss::result].isMember(jss::error) &&
126 jle[jss::result][jss::error] == "entryNotFound");
127 }
128 }
129
130 {
131 testcase("Create for themself.");
132
133 auto const credKey = credentials::keylet(issuer, issuer, credType);
134
135 env(credentials::create(issuer, issuer, credType), credentials::Uri(uri));
136 env.close();
137 {
138 auto const sleCred = env.le(credKey);
139 BEAST_EXPECT(static_cast<bool>(sleCred));
140 if (!sleCred)
141 return;
142
143 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id());
144 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
145 BEAST_EXPECT(sleCred->isFlag(lsfAccepted));
146 BEAST_EXPECT(
147 sleCred->getFieldU64(sfIssuerNode) == sleCred->getFieldU64(sfSubjectNode));
148 BEAST_EXPECT(ownerCount(env, issuer) == 1);
149 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
150 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
151 auto const jle = credentials::ledgerEntry(env, issuer, issuer, credType);
152 BEAST_EXPECT(
153 jle.isObject() && jle.isMember(jss::result) &&
154 !jle[jss::result].isMember(jss::error) &&
155 jle[jss::result].isMember(jss::node) &&
156 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
157 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
158 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
159 jle[jss::result][jss::node][jss::Subject] == issuer.human() &&
160 jle[jss::result][jss::node]["CredentialType"] ==
161 strHex(std::string_view(credType)));
162 }
163
164 env(credentials::deleteCred(issuer, issuer, issuer, credType));
165 env.close();
166 {
167 BEAST_EXPECT(!env.le(credKey));
168 BEAST_EXPECT(!ownerCount(env, issuer));
169
170 // check no credential exists anymore
171 auto const jle = credentials::ledgerEntry(env, issuer, issuer, credType);
172 BEAST_EXPECT(
173 jle.isObject() && jle.isMember(jss::result) &&
174 jle[jss::result].isMember(jss::error) &&
175 jle[jss::result][jss::error] == "entryNotFound");
176 }
177 }
178 }
179
180 void
182 {
183 using namespace test::jtx;
184
185 char const credType[] = "abcde";
186
187 Account const issuer{"issuer"};
188 Account const subject{"subject"};
189 Account const other{"other"};
190
191 Env env{*this, features};
192
193 // fund subject and issuer
194 env.fund(XRP(5000), issuer, subject, other);
195 env.close();
196
197 {
198 testcase("Delete issuer before accept");
199
200 auto const credKey = credentials::keylet(subject, issuer, credType);
201 env(credentials::create(subject, issuer, credType));
202 env.close();
203
204 // delete issuer
205 {
206 int const delta = env.seq(issuer) + 255;
207 for (int i = 0; i < delta; ++i)
208 env.close();
209 auto const acctDelFee{drops(env.current()->fees().increment)};
210 env(acctdelete(issuer, other), Fee(acctDelFee));
211 env.close();
212 }
213
214 // check credentials deleted too
215 {
216 BEAST_EXPECT(!env.le(credKey));
217 BEAST_EXPECT(!ownerCount(env, subject));
218
219 // check no credential exists anymore
220 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
221 BEAST_EXPECT(
222 jle.isObject() && jle.isMember(jss::result) &&
223 jle[jss::result].isMember(jss::error) &&
224 jle[jss::result][jss::error] == "entryNotFound");
225 }
226
227 // resurrection
228 env.fund(XRP(5000), issuer);
229 env.close();
230 }
231
232 {
233 testcase("Delete issuer after accept");
234
235 auto const credKey = credentials::keylet(subject, issuer, credType);
236 env(credentials::create(subject, issuer, credType));
237 env.close();
238 env(credentials::accept(subject, issuer, credType));
239 env.close();
240
241 // delete issuer
242 {
243 int const delta = env.seq(issuer) + 255;
244 for (int i = 0; i < delta; ++i)
245 env.close();
246 auto const acctDelFee{drops(env.current()->fees().increment)};
247 env(acctdelete(issuer, other), Fee(acctDelFee));
248 env.close();
249 }
250
251 // check credentials deleted too
252 {
253 BEAST_EXPECT(!env.le(credKey));
254 BEAST_EXPECT(!ownerCount(env, subject));
255
256 // check no credential exists anymore
257 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
258 BEAST_EXPECT(
259 jle.isObject() && jle.isMember(jss::result) &&
260 jle[jss::result].isMember(jss::error) &&
261 jle[jss::result][jss::error] == "entryNotFound");
262 }
263
264 // resurrection
265 env.fund(XRP(5000), issuer);
266 env.close();
267 }
268
269 {
270 testcase("Delete subject before accept");
271
272 auto const credKey = credentials::keylet(subject, issuer, credType);
273 env(credentials::create(subject, issuer, credType));
274 env.close();
275
276 // delete subject
277 {
278 int const delta = env.seq(subject) + 255;
279 for (int i = 0; i < delta; ++i)
280 env.close();
281 auto const acctDelFee{drops(env.current()->fees().increment)};
282 env(acctdelete(subject, other), Fee(acctDelFee));
283 env.close();
284 }
285
286 // check credentials deleted too
287 {
288 BEAST_EXPECT(!env.le(credKey));
289 BEAST_EXPECT(!ownerCount(env, issuer));
290
291 // check no credential exists anymore
292 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
293 BEAST_EXPECT(
294 jle.isObject() && jle.isMember(jss::result) &&
295 jle[jss::result].isMember(jss::error) &&
296 jle[jss::result][jss::error] == "entryNotFound");
297 }
298
299 // resurrection
300 env.fund(XRP(5000), subject);
301 env.close();
302 }
303
304 {
305 testcase("Delete subject after accept");
306
307 auto const credKey = credentials::keylet(subject, issuer, credType);
308 env(credentials::create(subject, issuer, credType));
309 env.close();
310 env(credentials::accept(subject, issuer, credType));
311 env.close();
312
313 // delete subject
314 {
315 int const delta = env.seq(subject) + 255;
316 for (int i = 0; i < delta; ++i)
317 env.close();
318 auto const acctDelFee{drops(env.current()->fees().increment)};
319 env(acctdelete(subject, other), Fee(acctDelFee));
320 env.close();
321 }
322
323 // check credentials deleted too
324 {
325 BEAST_EXPECT(!env.le(credKey));
326 BEAST_EXPECT(!ownerCount(env, issuer));
327
328 // check no credential exists anymore
329 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
330 BEAST_EXPECT(
331 jle.isObject() && jle.isMember(jss::result) &&
332 jle[jss::result].isMember(jss::error) &&
333 jle[jss::result][jss::error] == "entryNotFound");
334 }
335
336 // resurrection
337 env.fund(XRP(5000), subject);
338 env.close();
339 }
340
341 {
342 testcase("Delete by other");
343
344 auto const credKey = credentials::keylet(subject, issuer, credType);
345 auto jv = credentials::create(subject, issuer, credType);
346 uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count();
347 jv[sfExpiration.jsonName] = t + 20;
348 env(jv);
349
350 // time advance
351 env.close();
352 env.close();
353 env.close();
354
355 // Other account delete credentials
356 env(credentials::deleteCred(other, subject, issuer, credType));
357 env.close();
358
359 // check credentials object
360 {
361 BEAST_EXPECT(!env.le(credKey));
362 BEAST_EXPECT(!ownerCount(env, issuer));
363 BEAST_EXPECT(!ownerCount(env, subject));
364
365 // check no credential exists anymore
366 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
367 BEAST_EXPECT(
368 jle.isObject() && jle.isMember(jss::result) &&
369 jle[jss::result].isMember(jss::error) &&
370 jle[jss::result][jss::error] == "entryNotFound");
371 }
372 }
373
374 {
375 testcase("Delete by subject");
376
377 env(credentials::create(subject, issuer, credType));
378 env.close();
379
380 // Subject can delete
381 env(credentials::deleteCred(subject, subject, issuer, credType));
382 env.close();
383 {
384 auto const credKey = credentials::keylet(subject, issuer, credType);
385 BEAST_EXPECT(!env.le(credKey));
386 BEAST_EXPECT(!ownerCount(env, subject));
387 BEAST_EXPECT(!ownerCount(env, issuer));
388 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
389 BEAST_EXPECT(
390 jle.isObject() && jle.isMember(jss::result) &&
391 jle[jss::result].isMember(jss::error) &&
392 jle[jss::result][jss::error] == "entryNotFound");
393 }
394 }
395
396 {
397 testcase("Delete by issuer");
398 env(credentials::create(subject, issuer, credType));
399 env.close();
400
401 env(credentials::deleteCred(issuer, subject, issuer, credType));
402 env.close();
403 {
404 auto const credKey = credentials::keylet(subject, issuer, credType);
405 BEAST_EXPECT(!env.le(credKey));
406 BEAST_EXPECT(!ownerCount(env, subject));
407 BEAST_EXPECT(!ownerCount(env, issuer));
408 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
409 BEAST_EXPECT(
410 jle.isObject() && jle.isMember(jss::result) &&
411 jle[jss::result].isMember(jss::error) &&
412 jle[jss::result][jss::error] == "entryNotFound");
413 }
414 }
415 }
416
417 void
419 {
420 using namespace test::jtx;
421
422 char const credType[] = "abcde";
423
424 Account const issuer{"issuer"};
425 Account const subject{"subject"};
426
427 {
428 using namespace jtx;
429 Env env{*this, features};
430
431 env.fund(XRP(5000), subject, issuer);
432 env.close();
433
434 {
435 testcase("Credentials fail, no subject param.");
436 auto jv = credentials::create(subject, issuer, credType);
437 jv.removeMember(jss::Subject);
438 env(jv, Ter(temMALFORMED));
439 }
440
441 {
442 auto jv = credentials::create(subject, issuer, credType);
443 jv[jss::Subject] = to_string(xrpAccount());
444 env(jv, Ter(temMALFORMED));
445 }
446
447 {
448 testcase("Credentials fail, no credentialType param.");
449 auto jv = credentials::create(subject, issuer, credType);
450 jv.removeMember(sfCredentialType.jsonName);
451 env(jv, Ter(temMALFORMED));
452 }
453
454 {
455 testcase("Credentials fail, empty credentialType param.");
456 auto jv = credentials::create(subject, issuer, "");
457 env(jv, Ter(temMALFORMED));
458 }
459
460 {
461 testcase(
462 "Credentials fail, credentialType length > "
463 "maxCredentialTypeLength.");
464 static constexpr std::string_view kLongCredType =
465 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
466 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
467 static_assert(kLongCredType.size() > kMaxCredentialTypeLength);
468 auto jv = credentials::create(subject, issuer, kLongCredType);
469 env(jv, Ter(temMALFORMED));
470 }
471
472 {
473 testcase("Credentials fail, URI length > 256.");
474 static constexpr std::string_view kLongUri =
475 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
476 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p "
477 "9hfup;wDJFBVSD8f72 "
478 "pfhiusdovnbs;"
479 "djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd "
480 "vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB"
481 "WULE"
482 "fv28o37gfwEFB3872TFO8GSDSDVD";
483 static_assert(kLongUri.size() > kMaxCredentialUriLength);
484 env(credentials::create(subject, issuer, credType),
485 credentials::Uri(kLongUri),
487 }
488
489 {
490 testcase("Credentials fail, URI empty.");
491 env(credentials::create(subject, issuer, credType),
494 }
495
496 {
497 testcase("Credentials fail, expiration in the past.");
498 auto jv = credentials::create(subject, issuer, credType);
499 // current time in XRPL epoch - 1s
500 uint32_t const t =
501 env.current()->header().parentCloseTime.time_since_epoch().count() - 1;
502 jv[sfExpiration.jsonName] = t;
503 env(jv, Ter(tecEXPIRED));
504 }
505
506 {
507 testcase("Credentials fail, invalid fee.");
508
509 auto jv = credentials::create(subject, issuer, credType);
510 jv[jss::Fee] = -1;
511 env(jv, Ter(temBAD_FEE));
512 }
513
514 {
515 testcase("Credentials fail, duplicate.");
516 auto const jv = credentials::create(subject, issuer, credType);
517 env(jv);
518 env.close();
519 env(jv, Ter(tecDUPLICATE));
520 env.close();
521
522 // check credential still present
523 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
524 BEAST_EXPECT(
525 jle.isObject() && jle.isMember(jss::result) &&
526 !jle[jss::result].isMember(jss::error) &&
527 jle[jss::result].isMember(jss::node) &&
528 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
529 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
530 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
531 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
532 jle[jss::result][jss::node]["CredentialType"] ==
533 strHex(std::string_view(credType)));
534 }
535
536 {
537 testcase("Credentials fail, directory full");
538 std::uint32_t const issuerSeq{env.seq(issuer) + 1};
539 env(ticket::create(issuer, 63));
540 env.close();
541
542 // Everything below can only be tested on open ledger.
543 auto const res1 = directory::bumpLastPage(
544 env,
546 keylet::ownerDir(issuer.id()),
548 BEAST_EXPECT(res1);
549
550 // NOLINTNEXTLINE(readability-suspicious-call-argument)
551 auto const jv = credentials::create(issuer, subject, credType);
552 env(jv, Ter(tecDIR_FULL));
553 // Free one directory entry by using a ticket
554 env(noop(issuer), ticket::Use(issuerSeq + 40));
555
556 // Fill subject directory
557 env(ticket::create(subject, 63));
558 auto const res2 = directory::bumpLastPage(
559 env,
561 keylet::ownerDir(subject.id()),
563 BEAST_EXPECT(res2);
564 env(jv, Ter(tecDIR_FULL));
565
566 // End test
567 env.close();
568 }
569 }
570
571 {
572 using namespace jtx;
573 Env env{*this, features};
574
575 env.fund(XRP(5000), issuer);
576 env.close();
577
578 {
579 testcase("Credentials fail, subject doesn't exist.");
580 auto const jv = credentials::create(subject, issuer, credType);
581 env(jv, Ter(tecNO_TARGET));
582 }
583 }
584
585 {
586 using namespace jtx;
587 Env env{*this, features};
588
589 auto const reserve = drops(env.current()->fees().reserve);
590 env.fund(reserve, subject, issuer);
591 env.close();
592
593 testcase("Credentials fail, not enough reserve.");
594 {
595 auto const jv = credentials::create(subject, issuer, credType);
597 env.close();
598 }
599 }
600 }
601
602 void
604 {
605 using namespace jtx;
606
607 char const credType[] = "abcde";
608 Account const issuer{"issuer"};
609 Account const subject{"subject"};
610 Account const other{"other"};
611
612 {
613 Env env{*this, features};
614
615 env.fund(XRP(5000), subject, issuer);
616
617 {
618 testcase("CredentialsAccept fail, Credential doesn't exist.");
619 env(credentials::accept(subject, issuer, credType), Ter(tecNO_ENTRY));
620 env.close();
621 }
622
623 {
624 testcase("CredentialsAccept fail, invalid Issuer account.");
625 auto jv = credentials::accept(subject, issuer, credType);
626 jv[jss::Issuer] = to_string(xrpAccount());
627 env(jv, Ter(temINVALID_ACCOUNT_ID));
628 env.close();
629 }
630
631 {
632 testcase("CredentialsAccept fail, invalid credentialType param.");
633 auto jv = credentials::accept(subject, issuer, "");
634 env(jv, Ter(temMALFORMED));
635 }
636 }
637
638 {
639 Env env{*this, features};
640
641 env.fund(drops(env.current()->fees().accountReserve(1)), issuer);
642 env.fund(drops(env.current()->fees().accountReserve(0)), subject);
643 env.close();
644
645 {
646 testcase("CredentialsAccept fail, not enough reserve.");
647 env(credentials::create(subject, issuer, credType));
648 env.close();
649
650 env(credentials::accept(subject, issuer, credType), Ter(tecINSUFFICIENT_RESERVE));
651 env.close();
652
653 // check credential still present
654 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
655 BEAST_EXPECT(
656 jle.isObject() && jle.isMember(jss::result) &&
657 !jle[jss::result].isMember(jss::error) &&
658 jle[jss::result].isMember(jss::node) &&
659 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
660 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
661 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
662 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
663 jle[jss::result][jss::node]["CredentialType"] ==
664 strHex(std::string_view(credType)));
665 }
666 }
667
668 {
669 using namespace jtx;
670 Env env{*this, features};
671
672 env.fund(XRP(5000), subject, issuer);
673 env.close();
674
675 {
676 env(credentials::create(subject, issuer, credType));
677 env.close();
678
679 testcase("CredentialsAccept fail, invalid fee.");
680 auto jv = credentials::accept(subject, issuer, credType);
681 jv[jss::Fee] = -1;
682 env(jv, Ter(temBAD_FEE));
683
684 testcase("CredentialsAccept fail, lsfAccepted already set.");
685 env(credentials::accept(subject, issuer, credType));
686 env.close();
687 env(credentials::accept(subject, issuer, credType), Ter(tecDUPLICATE));
688 env.close();
689
690 // check credential still present
691 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
692 BEAST_EXPECT(
693 jle.isObject() && jle.isMember(jss::result) &&
694 !jle[jss::result].isMember(jss::error) &&
695 jle[jss::result].isMember(jss::node) &&
696 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
697 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
698 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
699 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
700 jle[jss::result][jss::node]["CredentialType"] ==
701 strHex(std::string_view(credType)));
702 }
703
704 {
705 char const credType2[] = "efghi";
706
707 testcase("CredentialsAccept fail, expired credentials.");
708 auto jv = credentials::create(subject, issuer, credType2);
709 uint32_t const t =
710 env.current()->header().parentCloseTime.time_since_epoch().count();
711 jv[sfExpiration.jsonName] = t;
712 env(jv);
713 env.close();
714
715 // credentials are expired now
716 env(credentials::accept(subject, issuer, credType2), Ter(tecEXPIRED));
717 env.close();
718
719 // check that expired credentials were deleted
720 auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType2);
721 BEAST_EXPECT(
722 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
723 jDelCred[jss::result].isMember(jss::error) &&
724 jDelCred[jss::result][jss::error] == "entryNotFound");
725
726 BEAST_EXPECT(ownerCount(env, issuer) == 0);
727 BEAST_EXPECT(ownerCount(env, subject) == 1);
728 }
729 }
730
731 {
732 using namespace jtx;
733 Env env{*this, features};
734
735 env.fund(XRP(5000), issuer, subject, other);
736 env.close();
737
738 {
739 testcase("CredentialsAccept fail, issuer doesn't exist.");
740 auto jv = credentials::create(subject, issuer, credType);
741 env(jv);
742 env.close();
743
744 // delete issuer
745 int const delta = env.seq(issuer) + 255;
746 for (int i = 0; i < delta; ++i)
747 env.close();
748 auto const acctDelFee{drops(env.current()->fees().increment)};
749 env(acctdelete(issuer, other), Fee(acctDelFee));
750
751 // can't accept - no issuer account
752 jv = credentials::accept(subject, issuer, credType);
753 env(jv, Ter(tecNO_ISSUER));
754 env.close();
755
756 // check that expired credentials were deleted
757 auto const jDelCred = credentials::ledgerEntry(env, subject, issuer, credType);
758 BEAST_EXPECT(
759 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
760 jDelCred[jss::result].isMember(jss::error) &&
761 jDelCred[jss::result][jss::error] == "entryNotFound");
762 }
763 }
764 }
765
766 void
768 {
769 using namespace test::jtx;
770
771 char const credType[] = "abcde";
772 Account const issuer{"issuer"};
773 Account const subject{"subject"};
774 Account const other{"other"};
775
776 {
777 using namespace jtx;
778 Env env{*this, features};
779
780 env.fund(XRP(5000), subject, issuer, other);
781 env.close();
782
783 {
784 testcase("CredentialsDelete fail, no Credentials.");
785 env(credentials::deleteCred(subject, subject, issuer, credType), Ter(tecNO_ENTRY));
786 env.close();
787 }
788
789 {
790 testcase("CredentialsDelete fail, invalid Subject account.");
791 auto jv = credentials::deleteCred(subject, subject, issuer, credType);
792 jv[jss::Subject] = to_string(xrpAccount());
793 env(jv, Ter(temINVALID_ACCOUNT_ID));
794 env.close();
795 }
796
797 {
798 testcase("CredentialsDelete fail, invalid Issuer account.");
799 auto jv = credentials::deleteCred(subject, subject, issuer, credType);
800 jv[jss::Issuer] = to_string(xrpAccount());
801 env(jv, Ter(temINVALID_ACCOUNT_ID));
802 env.close();
803 }
804
805 {
806 testcase("CredentialsDelete fail, invalid credentialType param.");
807 auto jv = credentials::deleteCred(subject, subject, issuer, "");
808 env(jv, Ter(temMALFORMED));
809 }
810
811 {
812 char const credType2[] = "fghij";
813
814 env(credentials::create(subject, issuer, credType2));
815 env.close();
816
817 // Other account can't delete credentials without expiration
818 env(credentials::deleteCred(other, subject, issuer, credType2),
820 env.close();
821
822 // check credential still present
823 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType2);
824 BEAST_EXPECT(
825 jle.isObject() && jle.isMember(jss::result) &&
826 !jle[jss::result].isMember(jss::error) &&
827 jle[jss::result].isMember(jss::node) &&
828 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
829 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
830 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
831 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
832 jle[jss::result][jss::node]["CredentialType"] ==
833 strHex(std::string_view(credType2)));
834 }
835
836 {
837 testcase("CredentialsDelete fail, time not expired yet.");
838
839 auto jv = credentials::create(subject, issuer, credType);
840 // current time in XRPL epoch + 1000s
841 uint32_t const t =
842 env.current()->header().parentCloseTime.time_since_epoch().count() + 1000;
843 jv[sfExpiration.jsonName] = t;
844 env(jv);
845 env.close();
846
847 // Other account can't delete credentials that not expired
848 env(credentials::deleteCred(other, subject, issuer, credType),
850 env.close();
851
852 // check credential still present
853 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
854 BEAST_EXPECT(
855 jle.isObject() && jle.isMember(jss::result) &&
856 !jle[jss::result].isMember(jss::error) &&
857 jle[jss::result].isMember(jss::node) &&
858 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
859 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
860 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
861 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
862 jle[jss::result][jss::node]["CredentialType"] ==
863 strHex(std::string_view(credType)));
864 }
865
866 {
867 testcase("CredentialsDelete fail, no Issuer and Subject.");
868
869 auto jv = credentials::deleteCred(subject, subject, issuer, credType);
870 jv.removeMember(jss::Subject);
871 jv.removeMember(jss::Issuer);
872 env(jv, Ter(temMALFORMED));
873 env.close();
874 }
875
876 {
877 testcase("CredentialsDelete fail, invalid fee.");
878
879 auto jv = credentials::deleteCred(subject, subject, issuer, credType);
880 jv[jss::Fee] = -1;
881 env(jv, Ter(temBAD_FEE));
882 env.close();
883 }
884
885 {
886 testcase("deleteSLE fail, bad SLE.");
887 auto view =
889 auto ter = xrpl::credentials::deleteSLE(*view, {}, env.journal);
890 BEAST_EXPECT(ter == tecNO_ENTRY);
891 }
892 }
893 }
894
895 void
897 {
898 using namespace test::jtx;
899
900 char const credType[] = "abcde";
901 Account const issuer{"issuer"};
902 Account const subject{"subject"};
903
904 {
905 using namespace jtx;
906 Env env{*this, features};
907
908 env.fund(XRP(5000), subject, issuer);
909 env.close();
910
911 {
912 testcase("Credentials fail, Feature is not enabled.");
913 env(credentials::create(subject, issuer, credType), Ter(temDISABLED));
914 env(credentials::accept(subject, issuer, credType), Ter(temDISABLED));
915 env(credentials::deleteCred(subject, subject, issuer, credType), Ter(temDISABLED));
916 }
917 }
918 }
919
920 void
922 {
923 using namespace test::jtx;
924
925 char const credType[] = "abcde";
926 Account const issuer{"issuer"};
927 Account const subject{"subject"};
928
929 {
930 using namespace jtx;
931 Env env{*this};
932
933 env.fund(XRP(5000), subject, issuer);
934 env.close();
935
936 env(credentials::create(subject, issuer, credType));
937 env.close();
938
939 env(credentials::accept(subject, issuer, credType));
940 env.close();
941
942 testcase("account_tx");
943
944 std::string txHash0, txHash1;
945 {
946 json::Value params;
947 params[jss::account] = subject.human();
948 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
949
950 BEAST_EXPECT(jv[jss::transactions].size() == 4);
951 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
952 BEAST_EXPECT(tx0[jss::TransactionType] == jss::CredentialAccept);
953 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
954 BEAST_EXPECT(tx1[jss::TransactionType] == jss::CredentialCreate);
955 txHash0 = tx0[jss::hash].asString();
956 txHash1 = tx1[jss::hash].asString();
957 }
958
959 {
960 json::Value params;
961 params[jss::account] = issuer.human();
962 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
963
964 BEAST_EXPECT(jv[jss::transactions].size() == 4);
965 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
966 BEAST_EXPECT(tx0[jss::TransactionType] == jss::CredentialAccept);
967 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
968 BEAST_EXPECT(tx1[jss::TransactionType] == jss::CredentialCreate);
969
970 BEAST_EXPECT(txHash0 == tx0[jss::hash].asString());
971 BEAST_EXPECT(txHash1 == tx1[jss::hash].asString());
972 }
973
974 testcase("account_objects");
975 std::string objectIdx;
976 {
977 json::Value params;
978 params[jss::account] = subject.human();
979 auto jv = env.rpc("json", "account_objects", to_string(params))[jss::result];
980
981 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
982 auto const& object(jv[jss::account_objects][0u]);
983
984 BEAST_EXPECT(object["LedgerEntryType"].asString() == jss::Credential);
985 objectIdx = object[jss::index].asString();
986 }
987
988 {
989 json::Value params;
990 params[jss::account] = issuer.human();
991 auto jv = env.rpc("json", "account_objects", to_string(params))[jss::result];
992
993 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
994 auto const& object(jv[jss::account_objects][0u]);
995
996 BEAST_EXPECT(object["LedgerEntryType"].asString() == jss::Credential);
997 BEAST_EXPECT(objectIdx == object[jss::index].asString());
998 }
999 }
1000 }
1001
1002 void
1004 {
1005 using namespace test::jtx;
1006
1007 bool const enabled = features[fixInvalidTxFlags];
1008 testcase(std::string("Test flag, fix ") + (enabled ? "enabled" : "disabled"));
1009
1010 char const credType[] = "abcde";
1011 Account const issuer{"issuer"};
1012 Account const subject{"subject"};
1013
1014 {
1015 using namespace jtx;
1016 Env env{*this, features};
1017
1018 env.fund(XRP(5000), subject, issuer);
1019 env.close();
1020
1021 {
1022 Ter const expected(enabled ? TER(temINVALID_FLAG) : TER(tesSUCCESS));
1023 env(credentials::create(subject, issuer, credType),
1024 Txflags(tfTransferable),
1025 expected);
1026 env(credentials::accept(subject, issuer, credType),
1027 Txflags(tfSellNFToken),
1028 expected);
1029 env(credentials::deleteCred(subject, subject, issuer, credType),
1030 Txflags(tfPassive),
1031 expected);
1032 }
1033 }
1034 }
1035
1036 void
1038 {
1039 bool const fixEnabled = features[fixCleanup3_1_3];
1040 testcase(
1041 "removeExpired ignores deleteSLE failure " +
1042 (fixEnabled ? std::string(" after fix") : std::string(" before fix")));
1043
1044 using namespace test::jtx;
1045
1046 char const credType[] = "abcde";
1047 Account const issuer{"issuer"};
1048 Account const subject{"subject"};
1049 Account const becky{"becky"};
1050
1051 Env env{*this, features};
1052 env.fund(XRP(10000), issuer, subject, becky);
1053 env.close();
1054
1055 // Create credential with short expiration
1056 auto jv = credentials::create(subject, issuer, credType);
1057 uint32_t const expiration =
1058 env.current()->header().parentCloseTime.time_since_epoch().count() + 40;
1059 jv[sfExpiration.jsonName] = expiration;
1060 env(jv);
1061 env.close();
1062
1063 auto const credLE = credentials::ledgerEntry(env, subject, issuer, credType);
1064 std::string const credIdx = credLE[jss::result][jss::index].asString();
1065
1066 // Subject accepts the credential
1067 env(credentials::accept(subject, issuer, credType));
1068 env.close();
1069
1070 // Build the credential keylet
1071 auto const credKeylet =
1072 keylet::credential(subject.id(), issuer.id(), Slice(credType, std::strlen(credType)));
1073
1074 // Verify credential exists and is accepted
1075 {
1076 auto const sleCred = env.current()->read(credKeylet);
1077 BEAST_EXPECT(sleCred && sleCred->isFlag(lsfAccepted));
1078 }
1079
1080 // Create DepositPreauth
1081 env(deposit::authCredentials(becky, {{.issuer = subject, .credType = credType}}));
1082 env.close();
1083 // env();
1084 auto jtx = env.jt(pay(subject, becky, XRP(100)), credentials::Ids({credIdx}));
1085 if (!BEAST_EXPECT(jtx.stx))
1086 return;
1087 auto const stx = std::make_shared<STTx>(*jtx.stx);
1088
1089 // Create PermissionedDomain
1090 env(pdomain::setTx(becky, {{.issuer = issuer, .credType = credType}}));
1091 env.close();
1092 auto const objects = pdomain::getObjects(becky, env);
1093 if (!BEAST_EXPECT(!objects.empty()))
1094 return;
1095 auto const domain = objects.begin()->first;
1096
1097 using namespace std::chrono_literals;
1098 env.close(50s);
1099
1100 // Verify time has advanced past expiration
1101 {
1102 auto const sleCred = env.current()->read(credKeylet);
1103 BEAST_EXPECT(
1104 sleCred &&
1105 xrpl::credentials::checkExpired(*sleCred, env.current()->header().parentCloseTime));
1106 }
1107
1108 // Create an ApplyViewImpl on top of the current closed ledger
1109 // and corrupt it by erasing the issuer's account SLE
1110 auto const open = env.current();
1111 ApplyViewImpl av(&*open, TapNone);
1112
1113 // Erase the issuer's account to simulate ledger corruption
1114 auto sleIssuer = av.peek(keylet::account(issuer.id()));
1115 if (!BEAST_EXPECT(sleIssuer))
1116 return;
1117 av.erase(sleIssuer);
1118 BEAST_EXPECT(!av.exists(keylet::account(issuer.id())));
1119
1120 // Credential still exists before removeExpired
1121 BEAST_EXPECT(av.exists(credKeylet));
1122
1123 // Call removeExpired on the corrupted view
1124 STVector256 credHashes;
1125 credHashes.pushBack(credKeylet.key);
1127
1128 auto const dpTer = xrpl::verifyDepositPreauth(*stx, av, subject, becky, {}, j);
1129 auto sleCredAfter = av.read(credKeylet);
1130 BEAST_EXPECT(sleCredAfter && sleCredAfter->isFlag(lsfAccepted));
1131
1132 auto const domTer = xrpl::verifyValidDomain(av, subject.id(), domain, j);
1133 sleCredAfter = av.read(credKeylet);
1134 BEAST_EXPECT(sleCredAfter && sleCredAfter->isFlag(lsfAccepted));
1135
1136 if (fixEnabled)
1137 {
1138 // removeExpired returns error, cred wasn't deleted
1139 BEAST_EXPECT(dpTer == tecINTERNAL);
1140 BEAST_EXPECT(domTer == tecINTERNAL);
1141 }
1142 else
1143 {
1144 // removeExpired returns true (claims it found & deleted expired
1145 // creds)
1146 BEAST_EXPECT(dpTer == tecEXPIRED);
1147 BEAST_EXPECT(isTesSuccess(domTer));
1148 }
1149 }
1150
1151 void
1152 run() override
1153 {
1154 using namespace test::jtx;
1155 FeatureBitset const all{testableAmendments()};
1156 testSuccessful(all);
1158 testCreateFailed(all);
1159 testCreateFailed(all - fixDirectoryLimit);
1160 testAcceptFailed(all);
1161 testDeleteFailed(all);
1162 testFeatureFailed(all - featureCredentials);
1163 testFlags(all - fixInvalidTxFlags);
1164 testFlags(all);
1165 testRPC();
1166
1167 testRemoveExpiredCorruption(all - fixCleanup3_1_3);
1168 testRemoveExpiredCorruption(all | fixCleanup3_1_3);
1169 }
1170};
1171
1172BEAST_DEFINE_TESTSUITE(Credentials, app, xrpl);
1173
1174} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:38
static Sink & getNullSink()
Returns a Sink which does nothing.
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
Editable, discardable view that can build metadata for one tx.
void pushBack(uint256 const &v)
An immutable linear range of bytes.
Definition Slice.h:26
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
void erase(SLE::ref sle) override
Remove a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
std::string const & human() const
Returns the human readable public key.
Definition jtx/Account.h:92
AccountID id() const
Returns the Account ID.
Definition jtx/Account.h:85
A transaction testing environment.
Definition Env.h:143
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:284
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:275
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:864
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:566
beast::Journal const journal
Definition Env.h:184
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Set the fee on a JTx.
Definition fee.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
Set the flags on a JTx.
Definition txflags.h:9
Set a ticket sequence on a JTx.
Definition ticket.h:26
T make_shared(T... args)
TER deleteSLE(ApplyView &view, SLE::ref sleCredential, beast::Journal j)
bool checkExpired(SLE const &sleCredential, NetClock::time_point const &closed)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Definition Indexes.cpp:545
json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:40
json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:29
json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:16
json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:56
Keylet keylet(test::jtx::Account const &subject, test::jtx::Account const &issuer, std::string_view credType)
Definition credentials.h:10
json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:38
auto bumpLastPage(Env &env, std::uint64_t newLastPage, Keylet directory, std::function< bool(ApplyView &, uint256, std::uint64_t)> adjust) -> std::expected< void, Error >
Move the position of the last page in the user's directory on open ledger to newLastPage.
Definition directory.cpp:25
auto maximumPageIndex(Env const &env) -> std::uint64_t
Definition directory.h:47
bool adjustOwnerNode(ApplyView &view, uint256 key, std::uint64_t page)
Implementation of adjust for the most common ledger entry, i.e.
std::map< uint256, json::Value > getObjects(Account const &account, Env &env, bool withType)
json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:16
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value noop(Account const &account)
The null transaction.
Definition noop.h:9
std::uint32_t ownerCount(Env const &env, Account const &account)
FeatureBitset testableAmendments()
Definition Env.h:76
json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
bool checkVL(Slice const &result, std::string const &expected)
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::size_t kMaxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:225
TER verifyValidDomain(ApplyView &view, AccountID const &account, uint256 domainID, beast::Journal j)
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition SociDB.cpp:92
@ TapNone
Definition ApplyView.h:13
constexpr std::size_t kMaxCredentialUriLength
The maximum length of a URI inside a Credential.
Definition Protocol.h:222
@ temBAD_FEE
Definition TER.h:78
@ temINVALID_FLAG
Definition TER.h:97
@ temMALFORMED
Definition TER.h:73
@ temINVALID_ACCOUNT_ID
Definition TER.h:105
@ temDISABLED
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecDIR_FULL
Definition TER.h:285
@ tecNO_ENTRY
Definition TER.h:304
@ tecNO_TARGET
Definition TER.h:302
@ tecINTERNAL
Definition TER.h:308
@ tecEXPIRED
Definition TER.h:312
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecNO_PERMISSION
Definition TER.h:303
@ tecNO_ISSUER
Definition TER.h:297
@ tecDUPLICATE
Definition TER.h:313
@ tesSUCCESS
Definition TER.h:240
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
T size(T... args)
T strlen(T... args)
void run() override
Runs the suite.
void testDeleteFailed(FeatureBitset features)
void testCredentialsDelete(FeatureBitset features)
void testRemoveExpiredCorruption(FeatureBitset features)
void testCreateFailed(FeatureBitset features)
void testAcceptFailed(FeatureBitset features)
void testFlags(FeatureBitset features)
void testFeatureFailed(FeatureBitset features)
void testSuccessful(FeatureBitset features)