rippled
Loading...
Searching...
No Matches
PermissionedDomains_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/tx/detail/PermissionedDomainSet.h>
22#include <xrpld/ledger/ApplyViewImpl.h>
23#include <xrpl/basics/Blob.h>
24#include <xrpl/basics/Slice.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Issue.h>
27#include <xrpl/protocol/jss.h>
28
29#include <exception>
30#include <map>
31#include <optional>
32#include <string>
33#include <utility>
34#include <vector>
35
36namespace ripple {
37namespace test {
38
39using namespace jtx;
40
41static std::string
43{
44 try
45 {
46 env(jv, ter(temMALFORMED));
47 }
48 catch (std::exception const& ex)
49 {
50 return ex.what();
51 }
52 return {};
53}
54
56{
60 | featurePermissionedDomains | featureCredentials};
61
62 // Verify that each tx type can execute if the feature is enabled.
63 void
65 {
66 testcase("Enabled");
67 Account const alice("alice");
68 Env env(*this, withFeature_);
69 env.fund(XRP(1000), alice);
70 pdomain::Credentials credentials{{alice, "first credential"}};
71 env(pdomain::setTx(alice, credentials));
72 BEAST_EXPECT(env.ownerCount(alice) == 1);
73 auto objects = pdomain::getObjects(alice, env);
74 BEAST_EXPECT(objects.size() == 1);
75 // Test that account_objects is correct without passing it the type
76 BEAST_EXPECT(objects == pdomain::getObjects(alice, env, false));
77 auto const domain = objects.begin()->first;
78 env(pdomain::deleteTx(alice, domain));
79 }
80
81 // Verify that PD cannot be created or updated if credentials are disabled
82 void
84 {
85 auto amendments = supported_amendments();
86 amendments.set(featurePermissionedDomains);
87 amendments.reset(featureCredentials);
88 testcase("Credentials disabled");
89 Account const alice("alice");
90 Env env(*this, amendments);
91 env.fund(XRP(1000), alice);
92 pdomain::Credentials credentials{{alice, "first credential"}};
93 env(pdomain::setTx(alice, credentials), ter(temDISABLED));
94 }
95
96 // Verify that each tx does not execute if feature is disabled
97 void
99 {
100 testcase("Disabled");
101 Account const alice("alice");
102 Env env(*this, withoutFeature_);
103 env.fund(XRP(1000), alice);
104 pdomain::Credentials credentials{{alice, "first credential"}};
105 env(pdomain::setTx(alice, credentials), ter(temDISABLED));
106 env(pdomain::deleteTx(alice, uint256(75)), ter(temDISABLED));
107 }
108
109 // Verify that bad inputs fail for each of create new and update
110 // behaviors of PermissionedDomainSet
111 void
113 Account const& account,
114 Env& env,
115 std::optional<uint256> domain = std::nullopt)
116 {
117 Account const alice2("alice2");
118 Account const alice3("alice3");
119 Account const alice4("alice4");
120 Account const alice5("alice5");
121 Account const alice6("alice6");
122 Account const alice7("alice7");
123 Account const alice8("alice8");
124 Account const alice9("alice9");
125 Account const alice10("alice10");
126 Account const alice11("alice11");
127 Account const alice12("alice12");
128 auto const setFee(drops(env.current()->fees().increment));
129
130 // Test empty credentials.
131 env(pdomain::setTx(account, pdomain::Credentials(), domain),
133
134 // Test 11 credentials.
135 pdomain::Credentials const credentials11{
136 {alice2, "credential1"},
137 {alice3, "credential2"},
138 {alice4, "credential3"},
139 {alice5, "credential4"},
140 {alice6, "credential5"},
141 {alice7, "credential6"},
142 {alice8, "credential7"},
143 {alice9, "credential8"},
144 {alice10, "credential9"},
145 {alice11, "credential10"},
146 {alice12, "credential11"}};
147 BEAST_EXPECT(
148 credentials11.size() ==
150 env(pdomain::setTx(account, credentials11, domain),
152
153 // Test credentials including non-existent issuer.
154 Account const nobody("nobody");
155 pdomain::Credentials const credentialsNon{
156 {alice2, "credential1"},
157 {alice3, "credential2"},
158 {alice4, "credential3"},
159 {nobody, "credential4"},
160 {alice5, "credential5"},
161 {alice6, "credential6"},
162 {alice7, "credential7"}};
163 env(pdomain::setTx(account, credentialsNon, domain), ter(tecNO_ISSUER));
164
165 // Test bad fee
166 env(pdomain::setTx(account, credentials11, domain),
167 fee(1, true),
168 ter(temBAD_FEE));
169
170 pdomain::Credentials const credentials4{
171 {alice2, "credential1"},
172 {alice3, "credential2"},
173 {alice4, "credential3"},
174 {alice5, "credential4"},
175 };
176 auto txJsonMutable = pdomain::setTx(account, credentials4, domain);
177 auto const credentialOrig = txJsonMutable["AcceptedCredentials"][2u];
178
179 // Remove Issuer from a credential and apply.
180 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember(
181 jss::Issuer);
182 BEAST_EXPECT(
183 exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
184
185 // Make an empty CredentialType.
186 txJsonMutable["AcceptedCredentials"][2u] = credentialOrig;
187 txJsonMutable["AcceptedCredentials"][2u][jss::Credential]
188 ["CredentialType"] = "";
189 env(txJsonMutable, ter(temMALFORMED));
190
191 // Make too long CredentialType.
192 constexpr std::string_view longCredentialType =
193 "Cred0123456789012345678901234567890123456789012345678901234567890";
194 static_assert(longCredentialType.size() == maxCredentialTypeLength + 1);
195 txJsonMutable["AcceptedCredentials"][2u] = credentialOrig;
196 txJsonMutable["AcceptedCredentials"][2u][jss::Credential]
197 ["CredentialType"] = std::string(longCredentialType);
198 BEAST_EXPECT(
199 exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
200
201 // Remove Credentialtype from a credential and apply.
202 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember(
203 "CredentialType");
204 BEAST_EXPECT(
205 exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
206
207 // Remove both
208 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember(
209 jss::Issuer);
210 BEAST_EXPECT(
211 exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
212
213 // Make 2 identical credentials. Duplicates are not supported by
214 // permissioned domains, so transactions should return errors
215 {
216 pdomain::Credentials const credentialsDup{
217 {alice7, "credential6"},
218 {alice2, "credential1"},
219 {alice3, "credential2"},
220 {alice2, "credential1"},
221 {alice5, "credential4"},
222 };
223
225 for (auto const& c : credentialsDup)
226 human2Acc.emplace(c.issuer.human(), c.issuer);
227
228 auto const sorted = pdomain::sortCredentials(credentialsDup);
229 BEAST_EXPECT(sorted.size() == 4);
230 env(pdomain::setTx(account, credentialsDup, domain),
232
233 env.close();
234 env(pdomain::setTx(account, sorted, domain));
235
236 uint256 d;
237 if (domain)
238 d = *domain;
239 else
240 d = pdomain::getNewDomain(env.meta());
241 env.close();
242 auto objects = pdomain::getObjects(account, env);
243 auto const fromObject =
244 pdomain::credentialsFromJson(objects[d], human2Acc);
245 auto const sortedCreds = pdomain::sortCredentials(credentialsDup);
246 BEAST_EXPECT(fromObject == sortedCreds);
247 }
248
249 // Have equal issuers but different credentials and make sure they
250 // sort correctly.
251 {
252 pdomain::Credentials const credentialsSame{
253 {alice2, "credential3"},
254 {alice3, "credential2"},
255 {alice2, "credential9"},
256 {alice5, "credential4"},
257 {alice2, "credential6"},
258 };
260 for (auto const& c : credentialsSame)
261 human2Acc.emplace(c.issuer.human(), c.issuer);
262
263 BEAST_EXPECT(
264 credentialsSame != pdomain::sortCredentials(credentialsSame));
265 env(pdomain::setTx(account, credentialsSame, domain));
266
267 uint256 d;
268 if (domain)
269 d = *domain;
270 else
271 d = pdomain::getNewDomain(env.meta());
272 env.close();
273 auto objects = pdomain::getObjects(account, env);
274 auto const fromObject =
275 pdomain::credentialsFromJson(objects[d], human2Acc);
276 auto const sortedCreds = pdomain::sortCredentials(credentialsSame);
277 BEAST_EXPECT(fromObject == sortedCreds);
278 }
279 }
280
281 // Test PermissionedDomainSet
282 void
284 {
285 testcase("Set");
286 Env env(*this, withFeature_);
288
289 const int accNum = 12;
290 Account const alice[accNum] = {
291 "alice",
292 "alice2",
293 "alice3",
294 "alice4",
295 "alice5",
296 "alice6",
297 "alice7",
298 "alice8",
299 "alice9",
300 "alice10",
301 "alice11",
302 "alice12"};
304 for (auto const& c : alice)
305 human2Acc.emplace(c.human(), c);
306
307 for (int i = 0; i < accNum; ++i)
308 env.fund(XRP(1000), alice[i]);
309
310 // Create new from existing account with a single credential.
311 pdomain::Credentials const credentials1{{alice[2], "credential1"}};
312 {
313 env(pdomain::setTx(alice[0], credentials1));
314 BEAST_EXPECT(env.ownerCount(alice[0]) == 1);
315 auto tx = env.tx()->getJson(JsonOptions::none);
316 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainSet");
317 BEAST_EXPECT(tx["Account"] == alice[0].human());
318 auto objects = pdomain::getObjects(alice[0], env);
319 auto domain = objects.begin()->first;
320 BEAST_EXPECT(domain.isNonZero());
321 auto object = objects.begin()->second;
322 BEAST_EXPECT(object["LedgerEntryType"] == "PermissionedDomain");
323 BEAST_EXPECT(object["Owner"] == alice[0].human());
324 BEAST_EXPECT(object["Sequence"] == tx["Sequence"]);
325 BEAST_EXPECT(
326 pdomain::credentialsFromJson(object, human2Acc) ==
327 credentials1);
328 }
329
330 // Make longest possible CredentialType.
331 {
332 constexpr std::string_view longCredentialType =
333 "Cred0123456789012345678901234567890123456789012345678901234567"
334 "89";
335 static_assert(longCredentialType.size() == maxCredentialTypeLength);
336 pdomain::Credentials const longCredentials{
337 {alice[1], std::string(longCredentialType)}};
338
339 env(pdomain::setTx(alice[0], longCredentials));
340
341 // One account can create multiple domains
342 BEAST_EXPECT(env.ownerCount(alice[0]) == 2);
343
344 auto tx = env.tx()->getJson(JsonOptions::none);
345 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainSet");
346 BEAST_EXPECT(tx["Account"] == alice[0].human());
347
348 bool findSeq = false;
349 for (auto const& [domain, object] :
350 pdomain::getObjects(alice[0], env))
351 {
352 findSeq = object["Sequence"] == tx["Sequence"];
353 if (findSeq)
354 {
355 BEAST_EXPECT(domain.isNonZero());
356 BEAST_EXPECT(
357 object["LedgerEntryType"] == "PermissionedDomain");
358 BEAST_EXPECT(object["Owner"] == alice[0].human());
359 BEAST_EXPECT(
360 pdomain::credentialsFromJson(object, human2Acc) ==
361 longCredentials);
362 break;
363 }
364 }
365 BEAST_EXPECT(findSeq);
366 }
367
368 // Create new from existing account with 10 credentials.
369 // Last credential describe domain owner itself
370 pdomain::Credentials const credentials10{
371 {alice[2], "credential1"},
372 {alice[3], "credential2"},
373 {alice[4], "credential3"},
374 {alice[5], "credential4"},
375 {alice[6], "credential5"},
376 {alice[7], "credential6"},
377 {alice[8], "credential7"},
378 {alice[9], "credential8"},
379 {alice[10], "credential9"},
380 {alice[0], "credential10"},
381 };
382 uint256 domain2;
383 {
384 BEAST_EXPECT(
385 credentials10.size() ==
387 BEAST_EXPECT(
388 credentials10 != pdomain::sortCredentials(credentials10));
389 env(pdomain::setTx(alice[0], credentials10));
390 auto tx = env.tx()->getJson(JsonOptions::none);
391 domain2 = pdomain::getNewDomain(env.meta());
392 auto objects = pdomain::getObjects(alice[0], env);
393 auto object = objects[domain2];
394 BEAST_EXPECT(
395 pdomain::credentialsFromJson(object, human2Acc) ==
396 pdomain::sortCredentials(credentials10));
397 }
398
399 // Update with 1 credential.
400 env(pdomain::setTx(alice[0], credentials1, domain2));
401 BEAST_EXPECT(
403 pdomain::getObjects(alice[0], env)[domain2], human2Acc) ==
404 credentials1);
405
406 // Update with 10 credentials.
407 env(pdomain::setTx(alice[0], credentials10, domain2));
408 env.close();
409 BEAST_EXPECT(
411 pdomain::getObjects(alice[0], env)[domain2], human2Acc) ==
412 pdomain::sortCredentials(credentials10));
413
414 // Update from the wrong owner.
415 env(pdomain::setTx(alice[2], credentials1, domain2),
417
418 // Update a uint256(0) domain
419 env(pdomain::setTx(alice[0], credentials1, uint256(0)),
421
422 // Update non-existent domain
423 env(pdomain::setTx(alice[0], credentials1, uint256(75)),
425
426 // Wrong flag
427 env(pdomain::setTx(alice[0], credentials1),
430
431 // Test bad data when creating a domain.
432 testBadData(alice[0], env);
433 // Test bad data when updating a domain.
434 testBadData(alice[0], env, domain2);
435
436 // Try to delete the account with domains.
437 auto const acctDelFee(drops(env.current()->fees().increment));
438 constexpr std::size_t deleteDelta = 255;
439 {
440 // Close enough ledgers to make it potentially deletable if empty.
441 std::size_t ownerSeq = env.seq(alice[0]);
442 while (deleteDelta + ownerSeq > env.current()->seq())
443 env.close();
444 env(acctdelete(alice[0], alice[2]),
445 fee(acctDelFee),
447 }
448
449 {
450 // Delete the domains and then the owner account.
451 for (auto const& objs : pdomain::getObjects(alice[0], env))
452 env(pdomain::deleteTx(alice[0], objs.first));
453 env.close();
454 std::size_t ownerSeq = env.seq(alice[0]);
455 while (deleteDelta + ownerSeq > env.current()->seq())
456 env.close();
457 env(acctdelete(alice[0], alice[2]), fee(acctDelFee));
458 }
459 }
460
461 // Test PermissionedDomainDelete
462 void
464 {
465 testcase("Delete");
466 Env env(*this, withFeature_);
467 Account const alice("alice");
468
469 env.fund(XRP(1000), alice);
470 auto const setFee(drops(env.current()->fees().increment));
471
472 pdomain::Credentials credentials{{alice, "first credential"}};
473 env(pdomain::setTx(alice, credentials));
474 env.close();
475
476 auto objects = pdomain::getObjects(alice, env);
477 BEAST_EXPECT(objects.size() == 1);
478 auto const domain = objects.begin()->first;
479
480 // Delete a domain that doesn't belong to the account.
481 Account const bob("bob");
482 env.fund(XRP(1000), bob);
483 env(pdomain::deleteTx(bob, domain), ter(tecNO_PERMISSION));
484
485 // Delete a non-existent domain.
486 env(pdomain::deleteTx(alice, uint256(75)), ter(tecNO_ENTRY));
487
488 // Test bad fee
489 env(pdomain::deleteTx(alice, uint256(75)),
491 fee(1, true));
492
493 // Wrong flag
494 env(pdomain::deleteTx(alice, domain),
497
498 // Delete a zero domain.
499 env(pdomain::deleteTx(alice, uint256(0)), ter(temMALFORMED));
500
501 // Make sure owner count reflects the existing domain.
502 BEAST_EXPECT(env.ownerCount(alice) == 1);
503 auto const objID = pdomain::getObjects(alice, env).begin()->first;
504 BEAST_EXPECT(pdomain::objectExists(objID, env));
505
506 // Delete domain that belongs to user.
507 env(pdomain::deleteTx(alice, domain));
508 auto const tx = env.tx()->getJson(JsonOptions::none);
509 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainDelete");
510
511 // Make sure the owner count goes back to 0.
512 BEAST_EXPECT(env.ownerCount(alice) == 0);
513
514 // The object needs to be gone.
515 BEAST_EXPECT(pdomain::getObjects(alice, env).empty());
516 BEAST_EXPECT(!pdomain::objectExists(objID, env));
517 }
518
519 void
521 {
522 // Verify that the reserve behaves as expected for creating.
523 testcase("Account Reserve");
524
525 using namespace test::jtx;
526
527 Env env(*this, withFeature_);
528 Account const alice("alice");
529
530 // Fund alice enough to exist, but not enough to meet
531 // the reserve.
532 auto const acctReserve = env.current()->fees().accountReserve(0);
533 auto const incReserve = env.current()->fees().increment;
534 env.fund(acctReserve, alice);
535 env.close();
536 BEAST_EXPECT(env.balance(alice) == acctReserve);
537 BEAST_EXPECT(env.ownerCount(alice) == 0);
538
539 // alice does not have enough XRP to cover the reserve.
540 pdomain::Credentials credentials{{alice, "first credential"}};
541 env(pdomain::setTx(alice, credentials), ter(tecINSUFFICIENT_RESERVE));
542 BEAST_EXPECT(env.ownerCount(alice) == 0);
543 BEAST_EXPECT(pdomain::getObjects(alice, env).size() == 0);
544 env.close();
545
546 auto const baseFee = env.current()->fees().base.drops();
547
548 // Pay alice almost enough to make the reserve.
549 env(pay(env.master, alice, incReserve + drops(2 * baseFee) - drops(1)));
550 BEAST_EXPECT(
551 env.balance(alice) ==
552 acctReserve + incReserve + drops(baseFee) - drops(1));
553 env.close();
554
555 // alice still does not have enough XRP for the reserve.
556 env(pdomain::setTx(alice, credentials), ter(tecINSUFFICIENT_RESERVE));
557 env.close();
558 BEAST_EXPECT(env.ownerCount(alice) == 0);
559
560 // Pay alice enough to make the reserve.
561 env(pay(env.master, alice, drops(baseFee) + drops(1)));
562 env.close();
563
564 // Now alice can create a PermissionedDomain.
565 env(pdomain::setTx(alice, credentials));
566 env.close();
567 BEAST_EXPECT(env.ownerCount(alice) == 1);
568 }
569
570public:
571 void
572 run() override
573 {
574 testEnabled();
576 testDisabled();
577 testSet();
578 testDelete();
580 }
581};
582
583BEAST_DEFINE_TESTSUITE(PermissionedDomains, app, ripple);
584
585} // namespace test
586} // 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
void testBadData(Account const &account, Env &env, std::optional< uint256 > domain=std::nullopt)
Immutable cryptographic account descriptor.
Definition: Account.h:38
A transaction testing environment.
Definition: Env.h:117
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition: Env.cpp:207
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:216
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:459
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:325
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
Account const & master
Definition: Env.h:121
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:237
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:451
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:183
void set_parse_failure_expected(bool b)
Definition: Env.h:410
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 emplace(T... args)
Credentials sortCredentials(Credentials const &input)
Credentials credentialsFromJson(Json::Value const &object, std::unordered_map< std::string, Account > const &human2Acc)
bool objectExists(uint256 const &objID, Env &env)
Json::Value deleteTx(AccountID const &account, uint256 const &domain)
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
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 std::string exceptionExpected(Env &env, Json::Value const &jv)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
base_uint< 256 > uint256
Definition: base_uint.h:557
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:110
constexpr std::uint32_t tfClawTwoAssets
Definition: TxFlags.h:221
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_PERMISSION
Definition: TER.h:292
@ tecHAS_OBLIGATIONS
Definition: TER.h:304
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ temBAD_FEE
Definition: TER.h:92
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temARRAY_EMPTY
Definition: TER.h:140
@ temARRAY_TOO_LARGE
Definition: TER.h:141
@ temDISABLED
Definition: TER.h:114
T size(T... args)
T what(T... args)