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