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