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