rippled
Loading...
Searching...
No Matches
Credentials_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21
22#include <xrpl/basics/strHex.h>
23#include <xrpl/ledger/ApplyViewImpl.h>
24#include <xrpl/ledger/CredentialHelpers.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/Protocol.h>
28#include <xrpl/protocol/TxFlags.h>
29#include <xrpl/protocol/jss.h>
30
31#include <string_view>
32
33namespace ripple {
34namespace test {
35
37{
38 void
40 {
41 using namespace test::jtx;
42
43 char const credType[] = "abcde";
44 char const uri[] = "uri";
45
46 Account const issuer{"issuer"};
47 Account const subject{"subject"};
48 Account const other{"other"};
49
50 Env env{*this, features};
51
52 {
53 testcase("Create for subject.");
54
55 auto const credKey = credentials::keylet(subject, issuer, credType);
56
57 env.fund(XRP(5000), subject, issuer, other);
58 env.close();
59
60 // Test Create credentials
61 env(credentials::create(subject, issuer, credType),
62 credentials::uri(uri));
63 env.close();
64 {
65 auto const sleCred = env.le(credKey);
66 BEAST_EXPECT(static_cast<bool>(sleCred));
67 if (!sleCred)
68 return;
69
70 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
71 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
72 BEAST_EXPECT(!sleCred->getFieldU32(sfFlags));
73 BEAST_EXPECT(ownerCount(env, issuer) == 1);
74 BEAST_EXPECT(!ownerCount(env, subject));
75 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
76 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
77 auto const jle =
78 credentials::ledgerEntry(env, subject, issuer, credType);
79 BEAST_EXPECT(
80 jle.isObject() && jle.isMember(jss::result) &&
81 !jle[jss::result].isMember(jss::error) &&
82 jle[jss::result].isMember(jss::node) &&
83 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
84 jle[jss::result][jss::node]["LedgerEntryType"] ==
85 jss::Credential &&
86 jle[jss::result][jss::node][jss::Issuer] ==
87 issuer.human() &&
88 jle[jss::result][jss::node][jss::Subject] ==
89 subject.human() &&
90 jle[jss::result][jss::node]["CredentialType"] ==
91 strHex(std::string_view(credType)));
92 }
93
94 env(credentials::accept(subject, issuer, credType));
95 env.close();
96 {
97 // check switching owner of the credentials from issuer to
98 // subject
99 auto const sleCred = env.le(credKey);
100 BEAST_EXPECT(static_cast<bool>(sleCred));
101 if (!sleCred)
102 return;
103
104 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
105 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
106 BEAST_EXPECT(!ownerCount(env, issuer));
107 BEAST_EXPECT(ownerCount(env, subject) == 1);
108 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
109 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
110 BEAST_EXPECT(sleCred->getFieldU32(sfFlags) == lsfAccepted);
111 }
112
113 env(credentials::deleteCred(subject, subject, issuer, credType));
114 env.close();
115 {
116 BEAST_EXPECT(!env.le(credKey));
117 BEAST_EXPECT(!ownerCount(env, issuer));
118 BEAST_EXPECT(!ownerCount(env, subject));
119
120 // check no credential exists anymore
121 auto const jle =
122 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),
136 credentials::uri(uri));
137 env.close();
138 {
139 auto const sleCred = env.le(credKey);
140 BEAST_EXPECT(static_cast<bool>(sleCred));
141 if (!sleCred)
142 return;
143
144 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id());
145 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
146 BEAST_EXPECT((sleCred->getFieldU32(sfFlags) & lsfAccepted));
147 BEAST_EXPECT(
148 sleCred->getFieldU64(sfIssuerNode) ==
149 sleCred->getFieldU64(sfSubjectNode));
150 BEAST_EXPECT(ownerCount(env, issuer) == 1);
151 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
152 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
153 auto const jle =
154 credentials::ledgerEntry(env, issuer, issuer, credType);
155 BEAST_EXPECT(
156 jle.isObject() && jle.isMember(jss::result) &&
157 !jle[jss::result].isMember(jss::error) &&
158 jle[jss::result].isMember(jss::node) &&
159 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
160 jle[jss::result][jss::node]["LedgerEntryType"] ==
161 jss::Credential &&
162 jle[jss::result][jss::node][jss::Issuer] ==
163 issuer.human() &&
164 jle[jss::result][jss::node][jss::Subject] ==
165 issuer.human() &&
166 jle[jss::result][jss::node]["CredentialType"] ==
167 strHex(std::string_view(credType)));
168 }
169
170 env(credentials::deleteCred(issuer, issuer, issuer, credType));
171 env.close();
172 {
173 BEAST_EXPECT(!env.le(credKey));
174 BEAST_EXPECT(!ownerCount(env, issuer));
175
176 // check no credential exists anymore
177 auto const jle =
178 credentials::ledgerEntry(env, issuer, issuer, credType);
179 BEAST_EXPECT(
180 jle.isObject() && jle.isMember(jss::result) &&
181 jle[jss::result].isMember(jss::error) &&
182 jle[jss::result][jss::error] == "entryNotFound");
183 }
184 }
185 }
186
187 void
189 {
190 using namespace test::jtx;
191
192 char const credType[] = "abcde";
193
194 Account const issuer{"issuer"};
195 Account const subject{"subject"};
196 Account const other{"other"};
197
198 Env env{*this, features};
199
200 // fund subject and issuer
201 env.fund(XRP(5000), issuer, subject, other);
202 env.close();
203
204 {
205 testcase("Delete issuer before accept");
206
207 auto const credKey = credentials::keylet(subject, issuer, credType);
208 env(credentials::create(subject, issuer, credType));
209 env.close();
210
211 // delete issuer
212 {
213 int const delta = env.seq(issuer) + 255;
214 for (int i = 0; i < delta; ++i)
215 env.close();
216 auto const acctDelFee{drops(env.current()->fees().increment)};
217 env(acctdelete(issuer, other), fee(acctDelFee));
218 env.close();
219 }
220
221 // check credentials deleted too
222 {
223 BEAST_EXPECT(!env.le(credKey));
224 BEAST_EXPECT(!ownerCount(env, subject));
225
226 // check no credential exists anymore
227 auto const jle =
228 credentials::ledgerEntry(env, subject, issuer, credType);
229 BEAST_EXPECT(
230 jle.isObject() && jle.isMember(jss::result) &&
231 jle[jss::result].isMember(jss::error) &&
232 jle[jss::result][jss::error] == "entryNotFound");
233 }
234
235 // resurrection
236 env.fund(XRP(5000), issuer);
237 env.close();
238 }
239
240 {
241 testcase("Delete issuer after accept");
242
243 auto const credKey = credentials::keylet(subject, issuer, credType);
244 env(credentials::create(subject, issuer, credType));
245 env.close();
246 env(credentials::accept(subject, issuer, credType));
247 env.close();
248
249 // delete issuer
250 {
251 int const delta = env.seq(issuer) + 255;
252 for (int i = 0; i < delta; ++i)
253 env.close();
254 auto const acctDelFee{drops(env.current()->fees().increment)};
255 env(acctdelete(issuer, other), fee(acctDelFee));
256 env.close();
257 }
258
259 // check credentials deleted too
260 {
261 BEAST_EXPECT(!env.le(credKey));
262 BEAST_EXPECT(!ownerCount(env, subject));
263
264 // check no credential exists anymore
265 auto const jle =
266 credentials::ledgerEntry(env, subject, issuer, credType);
267 BEAST_EXPECT(
268 jle.isObject() && jle.isMember(jss::result) &&
269 jle[jss::result].isMember(jss::error) &&
270 jle[jss::result][jss::error] == "entryNotFound");
271 }
272
273 // resurrection
274 env.fund(XRP(5000), issuer);
275 env.close();
276 }
277
278 {
279 testcase("Delete subject before accept");
280
281 auto const credKey = credentials::keylet(subject, issuer, credType);
282 env(credentials::create(subject, issuer, credType));
283 env.close();
284
285 // delete subject
286 {
287 int const delta = env.seq(subject) + 255;
288 for (int i = 0; i < delta; ++i)
289 env.close();
290 auto const acctDelFee{drops(env.current()->fees().increment)};
291 env(acctdelete(subject, other), fee(acctDelFee));
292 env.close();
293 }
294
295 // check credentials deleted too
296 {
297 BEAST_EXPECT(!env.le(credKey));
298 BEAST_EXPECT(!ownerCount(env, issuer));
299
300 // check no credential exists anymore
301 auto const jle =
302 credentials::ledgerEntry(env, subject, issuer, credType);
303 BEAST_EXPECT(
304 jle.isObject() && jle.isMember(jss::result) &&
305 jle[jss::result].isMember(jss::error) &&
306 jle[jss::result][jss::error] == "entryNotFound");
307 }
308
309 // resurrection
310 env.fund(XRP(5000), subject);
311 env.close();
312 }
313
314 {
315 testcase("Delete subject after accept");
316
317 auto const credKey = credentials::keylet(subject, issuer, credType);
318 env(credentials::create(subject, issuer, credType));
319 env.close();
320 env(credentials::accept(subject, issuer, credType));
321 env.close();
322
323 // delete subject
324 {
325 int const delta = env.seq(subject) + 255;
326 for (int i = 0; i < delta; ++i)
327 env.close();
328 auto const acctDelFee{drops(env.current()->fees().increment)};
329 env(acctdelete(subject, other), fee(acctDelFee));
330 env.close();
331 }
332
333 // check credentials deleted too
334 {
335 BEAST_EXPECT(!env.le(credKey));
336 BEAST_EXPECT(!ownerCount(env, issuer));
337
338 // check no credential exists anymore
339 auto const jle =
340 credentials::ledgerEntry(env, subject, issuer, credType);
341 BEAST_EXPECT(
342 jle.isObject() && jle.isMember(jss::result) &&
343 jle[jss::result].isMember(jss::error) &&
344 jle[jss::result][jss::error] == "entryNotFound");
345 }
346
347 // resurrection
348 env.fund(XRP(5000), subject);
349 env.close();
350 }
351
352 {
353 testcase("Delete by other");
354
355 auto const credKey = credentials::keylet(subject, issuer, credType);
356 auto jv = credentials::create(subject, issuer, credType);
357 uint32_t const t = env.current()
358 ->info()
359 .parentCloseTime.time_since_epoch()
360 .count();
361 jv[sfExpiration.jsonName] = t + 20;
362 env(jv);
363
364 // time advance
365 env.close();
366 env.close();
367 env.close();
368
369 // Other account delete credentials
370 env(credentials::deleteCred(other, subject, issuer, credType));
371 env.close();
372
373 // check credentials object
374 {
375 BEAST_EXPECT(!env.le(credKey));
376 BEAST_EXPECT(!ownerCount(env, issuer));
377 BEAST_EXPECT(!ownerCount(env, subject));
378
379 // check no credential exists anymore
380 auto const jle =
381 credentials::ledgerEntry(env, subject, issuer, credType);
382 BEAST_EXPECT(
383 jle.isObject() && jle.isMember(jss::result) &&
384 jle[jss::result].isMember(jss::error) &&
385 jle[jss::result][jss::error] == "entryNotFound");
386 }
387 }
388
389 {
390 testcase("Delete by subject");
391
392 env(credentials::create(subject, issuer, credType));
393 env.close();
394
395 // Subject can delete
396 env(credentials::deleteCred(subject, subject, issuer, credType));
397 env.close();
398 {
399 auto const credKey =
400 credentials::keylet(subject, issuer, credType);
401 BEAST_EXPECT(!env.le(credKey));
402 BEAST_EXPECT(!ownerCount(env, subject));
403 BEAST_EXPECT(!ownerCount(env, issuer));
404 auto const jle =
405 credentials::ledgerEntry(env, subject, issuer, credType);
406 BEAST_EXPECT(
407 jle.isObject() && jle.isMember(jss::result) &&
408 jle[jss::result].isMember(jss::error) &&
409 jle[jss::result][jss::error] == "entryNotFound");
410 }
411 }
412
413 {
414 testcase("Delete by issuer");
415 env(credentials::create(subject, issuer, credType));
416 env.close();
417
418 env(credentials::deleteCred(issuer, subject, issuer, credType));
419 env.close();
420 {
421 auto const credKey =
422 credentials::keylet(subject, issuer, credType);
423 BEAST_EXPECT(!env.le(credKey));
424 BEAST_EXPECT(!ownerCount(env, subject));
425 BEAST_EXPECT(!ownerCount(env, issuer));
426 auto const jle =
427 credentials::ledgerEntry(env, subject, issuer, credType);
428 BEAST_EXPECT(
429 jle.isObject() && jle.isMember(jss::result) &&
430 jle[jss::result].isMember(jss::error) &&
431 jle[jss::result][jss::error] == "entryNotFound");
432 }
433 }
434 }
435
436 void
438 {
439 using namespace test::jtx;
440
441 char const credType[] = "abcde";
442
443 Account const issuer{"issuer"};
444 Account const subject{"subject"};
445
446 {
447 using namespace jtx;
448 Env env{*this, features};
449
450 env.fund(XRP(5000), subject, issuer);
451 env.close();
452
453 {
454 testcase("Credentials fail, no subject param.");
455 auto jv = credentials::create(subject, issuer, credType);
456 jv.removeMember(jss::Subject);
457 env(jv, ter(temMALFORMED));
458 }
459
460 {
461 auto jv = credentials::create(subject, issuer, credType);
462 jv[jss::Subject] = to_string(xrpAccount());
463 env(jv, ter(temMALFORMED));
464 }
465
466 {
467 testcase("Credentials fail, no credentialType param.");
468 auto jv = credentials::create(subject, issuer, credType);
469 jv.removeMember(sfCredentialType.jsonName);
470 env(jv, ter(temMALFORMED));
471 }
472
473 {
474 testcase("Credentials fail, empty credentialType param.");
475 auto jv = credentials::create(subject, issuer, "");
476 env(jv, ter(temMALFORMED));
477 }
478
479 {
480 testcase(
481 "Credentials fail, credentialType length > "
482 "maxCredentialTypeLength.");
483 constexpr std::string_view longCredType =
484 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
485 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
486 static_assert(longCredType.size() > maxCredentialTypeLength);
487 auto jv = credentials::create(subject, issuer, longCredType);
488 env(jv, ter(temMALFORMED));
489 }
490
491 {
492 testcase("Credentials fail, URI length > 256.");
493 constexpr std::string_view longURI =
494 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
495 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p "
496 "9hfup;wDJFBVSD8f72 "
497 "pfhiusdovnbs;"
498 "djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd "
499 "vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB"
500 "WULE"
501 "fv28o37gfwEFB3872TFO8GSDSDVD";
502 static_assert(longURI.size() > maxCredentialURILength);
503 env(credentials::create(subject, issuer, credType),
504 credentials::uri(longURI),
506 }
507
508 {
509 testcase("Credentials fail, URI empty.");
510 env(credentials::create(subject, issuer, credType),
513 }
514
515 {
516 testcase("Credentials fail, expiration in the past.");
517 auto jv = credentials::create(subject, issuer, credType);
518 // current time in ripple epoch - 1s
519 uint32_t const t = env.current()
520 ->info()
521 .parentCloseTime.time_since_epoch()
522 .count() -
523 1;
524 jv[sfExpiration.jsonName] = t;
525 env(jv, ter(tecEXPIRED));
526 }
527
528 {
529 testcase("Credentials fail, invalid fee.");
530
531 auto jv = credentials::create(subject, issuer, credType);
532 jv[jss::Fee] = -1;
533 env(jv, ter(temBAD_FEE));
534 }
535
536 {
537 testcase("Credentials fail, duplicate.");
538 auto const jv = credentials::create(subject, issuer, credType);
539 env(jv);
540 env.close();
541 env(jv, ter(tecDUPLICATE));
542 env.close();
543
544 // check credential still present
545 auto const jle =
546 credentials::ledgerEntry(env, subject, issuer, credType);
547 BEAST_EXPECT(
548 jle.isObject() && jle.isMember(jss::result) &&
549 !jle[jss::result].isMember(jss::error) &&
550 jle[jss::result].isMember(jss::node) &&
551 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
552 jle[jss::result][jss::node]["LedgerEntryType"] ==
553 jss::Credential &&
554 jle[jss::result][jss::node][jss::Issuer] ==
555 issuer.human() &&
556 jle[jss::result][jss::node][jss::Subject] ==
557 subject.human() &&
558 jle[jss::result][jss::node]["CredentialType"] ==
559 strHex(std::string_view(credType)));
560 }
561
562 {
563 testcase("Credentials fail, directory full");
564 std::uint32_t const issuerSeq{env.seq(issuer) + 1};
565 env(ticket::create(issuer, 63));
566 env.close();
567
568 // Everything below can only be tested on open ledger.
569 auto const res1 = directory::bumpLastPage(
570 env,
572 keylet::ownerDir(issuer.id()),
574 BEAST_EXPECT(res1);
575
576 auto const jv = credentials::create(issuer, subject, credType);
577 env(jv, ter(tecDIR_FULL));
578 // Free one directory entry by using a ticket
579 env(noop(issuer), ticket::use(issuerSeq + 40));
580
581 // Fill subject directory
582 env(ticket::create(subject, 63));
583 auto const res2 = directory::bumpLastPage(
584 env,
586 keylet::ownerDir(subject.id()),
588 BEAST_EXPECT(res2);
589 env(jv, ter(tecDIR_FULL));
590
591 // End test
592 env.close();
593 }
594 }
595
596 {
597 using namespace jtx;
598 Env env{*this, features};
599
600 env.fund(XRP(5000), issuer);
601 env.close();
602
603 {
604 testcase("Credentials fail, subject doesn't exist.");
605 auto const jv = credentials::create(subject, issuer, credType);
606 env(jv, ter(tecNO_TARGET));
607 }
608 }
609
610 {
611 using namespace jtx;
612 Env env{*this, features};
613
614 auto const reserve = drops(env.current()->fees().reserve);
615 env.fund(reserve, subject, issuer);
616 env.close();
617
618 testcase("Credentials fail, not enough reserve.");
619 {
620 auto const jv = credentials::create(subject, issuer, credType);
622 env.close();
623 }
624 }
625 }
626
627 void
629 {
630 using namespace jtx;
631
632 char const credType[] = "abcde";
633 Account const issuer{"issuer"};
634 Account const subject{"subject"};
635 Account const other{"other"};
636
637 {
638 Env env{*this, features};
639
640 env.fund(XRP(5000), subject, issuer);
641
642 {
643 testcase("CredentialsAccept fail, Credential doesn't exist.");
644 env(credentials::accept(subject, issuer, credType),
646 env.close();
647 }
648
649 {
650 testcase("CredentialsAccept fail, invalid Issuer account.");
651 auto jv = credentials::accept(subject, issuer, credType);
652 jv[jss::Issuer] = to_string(xrpAccount());
653 env(jv, ter(temINVALID_ACCOUNT_ID));
654 env.close();
655 }
656
657 {
658 testcase(
659 "CredentialsAccept fail, invalid credentialType param.");
660 auto jv = credentials::accept(subject, issuer, "");
661 env(jv, ter(temMALFORMED));
662 }
663 }
664
665 {
666 Env env{*this, features};
667
668 env.fund(drops(env.current()->fees().accountReserve(1)), issuer);
669 env.fund(drops(env.current()->fees().accountReserve(0)), subject);
670 env.close();
671
672 {
673 testcase("CredentialsAccept fail, not enough reserve.");
674 env(credentials::create(subject, issuer, credType));
675 env.close();
676
677 env(credentials::accept(subject, issuer, credType),
679 env.close();
680
681 // check credential still present
682 auto const jle =
683 credentials::ledgerEntry(env, subject, issuer, credType);
684 BEAST_EXPECT(
685 jle.isObject() && jle.isMember(jss::result) &&
686 !jle[jss::result].isMember(jss::error) &&
687 jle[jss::result].isMember(jss::node) &&
688 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
689 jle[jss::result][jss::node]["LedgerEntryType"] ==
690 jss::Credential &&
691 jle[jss::result][jss::node][jss::Issuer] ==
692 issuer.human() &&
693 jle[jss::result][jss::node][jss::Subject] ==
694 subject.human() &&
695 jle[jss::result][jss::node]["CredentialType"] ==
696 strHex(std::string_view(credType)));
697 }
698 }
699
700 {
701 using namespace jtx;
702 Env env{*this, features};
703
704 env.fund(XRP(5000), subject, issuer);
705 env.close();
706
707 {
708 env(credentials::create(subject, issuer, credType));
709 env.close();
710
711 testcase("CredentialsAccept fail, invalid fee.");
712 auto jv = credentials::accept(subject, issuer, credType);
713 jv[jss::Fee] = -1;
714 env(jv, ter(temBAD_FEE));
715
716 testcase("CredentialsAccept fail, lsfAccepted already set.");
717 env(credentials::accept(subject, issuer, credType));
718 env.close();
719 env(credentials::accept(subject, issuer, credType),
721 env.close();
722
723 // check credential still present
724 auto const jle =
725 credentials::ledgerEntry(env, subject, issuer, credType);
726 BEAST_EXPECT(
727 jle.isObject() && jle.isMember(jss::result) &&
728 !jle[jss::result].isMember(jss::error) &&
729 jle[jss::result].isMember(jss::node) &&
730 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
731 jle[jss::result][jss::node]["LedgerEntryType"] ==
732 jss::Credential &&
733 jle[jss::result][jss::node][jss::Issuer] ==
734 issuer.human() &&
735 jle[jss::result][jss::node][jss::Subject] ==
736 subject.human() &&
737 jle[jss::result][jss::node]["CredentialType"] ==
738 strHex(std::string_view(credType)));
739 }
740
741 {
742 char const credType2[] = "efghi";
743
744 testcase("CredentialsAccept fail, expired credentials.");
745 auto jv = credentials::create(subject, issuer, credType2);
746 uint32_t const t = env.current()
747 ->info()
748 .parentCloseTime.time_since_epoch()
749 .count();
750 jv[sfExpiration.jsonName] = t;
751 env(jv);
752 env.close();
753
754 // credentials are expired now
755 env(credentials::accept(subject, issuer, credType2),
756 ter(tecEXPIRED));
757 env.close();
758
759 // check that expired credentials were deleted
760 auto const jDelCred =
761 credentials::ledgerEntry(env, subject, issuer, credType2);
762 BEAST_EXPECT(
763 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
764 jDelCred[jss::result].isMember(jss::error) &&
765 jDelCred[jss::result][jss::error] == "entryNotFound");
766
767 BEAST_EXPECT(ownerCount(env, issuer) == 0);
768 BEAST_EXPECT(ownerCount(env, subject) == 1);
769 }
770 }
771
772 {
773 using namespace jtx;
774 Env env{*this, features};
775
776 env.fund(XRP(5000), issuer, subject, other);
777 env.close();
778
779 {
780 testcase("CredentialsAccept fail, issuer doesn't exist.");
781 auto jv = credentials::create(subject, issuer, credType);
782 env(jv);
783 env.close();
784
785 // delete issuer
786 int const delta = env.seq(issuer) + 255;
787 for (int i = 0; i < delta; ++i)
788 env.close();
789 auto const acctDelFee{drops(env.current()->fees().increment)};
790 env(acctdelete(issuer, other), fee(acctDelFee));
791
792 // can't accept - no issuer account
793 jv = credentials::accept(subject, issuer, credType);
794 env(jv, ter(tecNO_ISSUER));
795 env.close();
796
797 // check that expired credentials were deleted
798 auto const jDelCred =
799 credentials::ledgerEntry(env, subject, issuer, credType);
800 BEAST_EXPECT(
801 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
802 jDelCred[jss::result].isMember(jss::error) &&
803 jDelCred[jss::result][jss::error] == "entryNotFound");
804 }
805 }
806 }
807
808 void
810 {
811 using namespace test::jtx;
812
813 char const credType[] = "abcde";
814 Account const issuer{"issuer"};
815 Account const subject{"subject"};
816 Account const other{"other"};
817
818 {
819 using namespace jtx;
820 Env env{*this, features};
821
822 env.fund(XRP(5000), subject, issuer, other);
823 env.close();
824
825 {
826 testcase("CredentialsDelete fail, no Credentials.");
827 env(credentials::deleteCred(subject, subject, issuer, credType),
829 env.close();
830 }
831
832 {
833 testcase("CredentialsDelete fail, invalid Subject account.");
834 auto jv =
835 credentials::deleteCred(subject, subject, issuer, credType);
836 jv[jss::Subject] = to_string(xrpAccount());
837 env(jv, ter(temINVALID_ACCOUNT_ID));
838 env.close();
839 }
840
841 {
842 testcase("CredentialsDelete fail, invalid Issuer account.");
843 auto jv =
844 credentials::deleteCred(subject, subject, issuer, credType);
845 jv[jss::Issuer] = to_string(xrpAccount());
846 env(jv, ter(temINVALID_ACCOUNT_ID));
847 env.close();
848 }
849
850 {
851 testcase(
852 "CredentialsDelete fail, invalid credentialType param.");
853 auto jv = credentials::deleteCred(subject, subject, issuer, "");
854 env(jv, ter(temMALFORMED));
855 }
856
857 {
858 char const credType2[] = "fghij";
859
860 env(credentials::create(subject, issuer, credType2));
861 env.close();
862
863 // Other account can't delete credentials without expiration
864 env(credentials::deleteCred(other, subject, issuer, credType2),
866 env.close();
867
868 // check credential still present
869 auto const jle =
870 credentials::ledgerEntry(env, subject, issuer, credType2);
871 BEAST_EXPECT(
872 jle.isObject() && jle.isMember(jss::result) &&
873 !jle[jss::result].isMember(jss::error) &&
874 jle[jss::result].isMember(jss::node) &&
875 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
876 jle[jss::result][jss::node]["LedgerEntryType"] ==
877 jss::Credential &&
878 jle[jss::result][jss::node][jss::Issuer] ==
879 issuer.human() &&
880 jle[jss::result][jss::node][jss::Subject] ==
881 subject.human() &&
882 jle[jss::result][jss::node]["CredentialType"] ==
883 strHex(std::string_view(credType2)));
884 }
885
886 {
887 testcase("CredentialsDelete fail, time not expired yet.");
888
889 auto jv = credentials::create(subject, issuer, credType);
890 // current time in ripple epoch + 1000s
891 uint32_t const t = env.current()
892 ->info()
893 .parentCloseTime.time_since_epoch()
894 .count() +
895 1000;
896 jv[sfExpiration.jsonName] = t;
897 env(jv);
898 env.close();
899
900 // Other account can't delete credentials that not expired
901 env(credentials::deleteCred(other, subject, issuer, credType),
903 env.close();
904
905 // check credential still present
906 auto const jle =
907 credentials::ledgerEntry(env, subject, issuer, credType);
908 BEAST_EXPECT(
909 jle.isObject() && jle.isMember(jss::result) &&
910 !jle[jss::result].isMember(jss::error) &&
911 jle[jss::result].isMember(jss::node) &&
912 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
913 jle[jss::result][jss::node]["LedgerEntryType"] ==
914 jss::Credential &&
915 jle[jss::result][jss::node][jss::Issuer] ==
916 issuer.human() &&
917 jle[jss::result][jss::node][jss::Subject] ==
918 subject.human() &&
919 jle[jss::result][jss::node]["CredentialType"] ==
920 strHex(std::string_view(credType)));
921 }
922
923 {
924 testcase("CredentialsDelete fail, no Issuer and Subject.");
925
926 auto jv =
927 credentials::deleteCred(subject, subject, issuer, credType);
928 jv.removeMember(jss::Subject);
929 jv.removeMember(jss::Issuer);
930 env(jv, ter(temMALFORMED));
931 env.close();
932 }
933
934 {
935 testcase("CredentialsDelete fail, invalid fee.");
936
937 auto jv =
938 credentials::deleteCred(subject, subject, issuer, credType);
939 jv[jss::Fee] = -1;
940 env(jv, ter(temBAD_FEE));
941 env.close();
942 }
943
944 {
945 testcase("deleteSLE fail, bad SLE.");
947 env.current().get(), ApplyFlags::tapNONE);
948 auto ter =
949 ripple::credentials::deleteSLE(*view, {}, env.journal);
950 BEAST_EXPECT(ter == tecNO_ENTRY);
951 }
952 }
953 }
954
955 void
957 {
958 using namespace test::jtx;
959
960 char const credType[] = "abcde";
961 Account const issuer{"issuer"};
962 Account const subject{"subject"};
963
964 {
965 using namespace jtx;
966 Env env{*this, features};
967
968 env.fund(XRP(5000), subject, issuer);
969 env.close();
970
971 {
972 testcase("Credentials fail, Feature is not enabled.");
973 env(credentials::create(subject, issuer, credType),
975 env(credentials::accept(subject, issuer, credType),
977 env(credentials::deleteCred(subject, subject, issuer, credType),
979 }
980 }
981 }
982
983 void
985 {
986 using namespace test::jtx;
987
988 char const credType[] = "abcde";
989 Account const issuer{"issuer"};
990 Account const subject{"subject"};
991
992 {
993 using namespace jtx;
994 Env env{*this};
995
996 env.fund(XRP(5000), subject, issuer);
997 env.close();
998
999 env(credentials::create(subject, issuer, credType));
1000 env.close();
1001
1002 env(credentials::accept(subject, issuer, credType));
1003 env.close();
1004
1005 testcase("account_tx");
1006
1007 std::string txHash0, txHash1;
1008 {
1009 Json::Value params;
1010 params[jss::account] = subject.human();
1011 auto const jv = env.rpc(
1012 "json", "account_tx", to_string(params))[jss::result];
1013
1014 BEAST_EXPECT(jv[jss::transactions].size() == 4);
1015 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1016 BEAST_EXPECT(
1017 tx0[jss::TransactionType] == jss::CredentialAccept);
1018 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
1019 BEAST_EXPECT(
1020 tx1[jss::TransactionType] == jss::CredentialCreate);
1021 txHash0 = tx0[jss::hash].asString();
1022 txHash1 = tx1[jss::hash].asString();
1023 }
1024
1025 {
1026 Json::Value params;
1027 params[jss::account] = issuer.human();
1028 auto const jv = env.rpc(
1029 "json", "account_tx", to_string(params))[jss::result];
1030
1031 BEAST_EXPECT(jv[jss::transactions].size() == 4);
1032 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1033 BEAST_EXPECT(
1034 tx0[jss::TransactionType] == jss::CredentialAccept);
1035 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
1036 BEAST_EXPECT(
1037 tx1[jss::TransactionType] == jss::CredentialCreate);
1038
1039 BEAST_EXPECT(txHash0 == tx0[jss::hash].asString());
1040 BEAST_EXPECT(txHash1 == tx1[jss::hash].asString());
1041 }
1042
1043 testcase("account_objects");
1044 std::string objectIdx;
1045 {
1046 Json::Value params;
1047 params[jss::account] = subject.human();
1048 auto jv = env.rpc(
1049 "json", "account_objects", to_string(params))[jss::result];
1050
1051 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1052 auto const& object(jv[jss::account_objects][0u]);
1053
1054 BEAST_EXPECT(
1055 object["LedgerEntryType"].asString() == jss::Credential);
1056 objectIdx = object[jss::index].asString();
1057 }
1058
1059 {
1060 Json::Value params;
1061 params[jss::account] = issuer.human();
1062 auto jv = env.rpc(
1063 "json", "account_objects", to_string(params))[jss::result];
1064
1065 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1066 auto const& object(jv[jss::account_objects][0u]);
1067
1068 BEAST_EXPECT(
1069 object["LedgerEntryType"].asString() == jss::Credential);
1070 BEAST_EXPECT(objectIdx == object[jss::index].asString());
1071 }
1072 }
1073 }
1074
1075 void
1077 {
1078 using namespace test::jtx;
1079
1080 bool const enabled = features[fixInvalidTxFlags];
1081 testcase(
1082 std::string("Test flag, fix ") +
1083 (enabled ? "enabled" : "disabled"));
1084
1085 char const credType[] = "abcde";
1086 Account const issuer{"issuer"};
1087 Account const subject{"subject"};
1088
1089 {
1090 using namespace jtx;
1091 Env env{*this, features};
1092
1093 env.fund(XRP(5000), subject, issuer);
1094 env.close();
1095
1096 {
1097 ter const expected(
1098 enabled ? TER(temINVALID_FLAG) : TER(tesSUCCESS));
1099 env(credentials::create(subject, issuer, credType),
1101 expected);
1102 env(credentials::accept(subject, issuer, credType),
1104 expected);
1105 env(credentials::deleteCred(subject, subject, issuer, credType),
1107 expected);
1108 }
1109 }
1110 }
1111
1112 void
1113 run() override
1114 {
1115 using namespace test::jtx;
1120 testCreateFailed(all - fixDirectoryLimit);
1123 testFeatureFailed(all - featureCredentials);
1124 testFlags(all - fixInvalidTxFlags);
1125 testFlags(all);
1126 testRPC();
1127 }
1128};
1129
1130BEAST_DEFINE_TESTSUITE(Credentials, app, ripple);
1131
1132} // namespace test
1133} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:290
Set the fee on a JTx.
Definition fee.h:37
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set a ticket sequence on a JTx.
Definition ticket.h:48
Set the flags on a JTx.
Definition txflags.h:31
T is_same_v
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:48
Keylet keylet(test::jtx::Account const &subject, test::jtx::Account const &issuer, std::string_view credType)
Definition credentials.h:34
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:62
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
auto bumpLastPage(Env &env, std::uint64_t newLastPage, Keylet directory, std::function< bool(ApplyView &, uint256, std::uint64_t)> adjust) -> Expected< void, Error >
Move the position of the last page in the user's directory on open ledger to newLastPage.
Definition directory.cpp:30
auto maximumPageIndex(Env const &env) -> std::uint64_t
Definition directory.h:70
bool adjustOwnerNode(ApplyView &view, uint256 key, std::uint64_t page)
Implementation of adjust for the most common ledger entry, i.e.
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:31
std::uint32_t ownerCount(Env const &env, Account const &account)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
bool checkVL(Slice const &result, std::string const &expected)
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
AccountID const & xrpAccount()
Compute AccountID from public key.
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition Protocol.h:103
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:230
constexpr std::uint32_t tfPassive
Definition TxFlags.h:98
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:106
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_ISSUER
Definition TER.h:299
@ tecNO_TARGET
Definition TER.h:304
@ tecDIR_FULL
Definition TER.h:287
@ tecDUPLICATE
Definition TER.h:315
@ tecNO_PERMISSION
Definition TER.h:305
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecEXPIRED
Definition TER.h:314
@ tesSUCCESS
Definition TER.h:244
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
@ tapNONE
Definition ApplyView.h:31
TERSubset< CanCvtToTER > TER
Definition TER.h:645
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:142
@ temBAD_FEE
Definition TER.h:92
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
@ temINVALID_ACCOUNT_ID
Definition TER.h:119
T size(T... args)
void testAcceptFailed(FeatureBitset features)
void testSuccessful(FeatureBitset features)
void testDeleteFailed(FeatureBitset features)
void testFeatureFailed(FeatureBitset features)
void testFlags(FeatureBitset features)
void testCredentialsDelete(FeatureBitset features)
void run() override
Runs the suite.
void testCreateFailed(FeatureBitset features)