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