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