rippled
Loading...
Searching...
No Matches
Directory_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5 Permission to use, copy, modify, and/or distribute this software for any
6 purpose with or without fee is hereby granted, provided that the above
7 copyright notice and this permission notice appear in all copies.
8 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15*/
16//==============================================================================
17
18#include <test/jtx.h>
19
20#include <xrpl/basics/random.h>
21#include <xrpl/ledger/BookDirs.h>
22#include <xrpl/ledger/Sandbox.h>
23#include <xrpl/protocol/Feature.h>
24#include <xrpl/protocol/Protocol.h>
25#include <xrpl/protocol/jss.h>
26
27#include <algorithm>
28
29namespace ripple {
30namespace test {
31
33{
34 // Map [0-15576] into a unique 3 letter currency code
37 {
38 // There are only 17576 possible combinations
39 BEAST_EXPECT(i < 17577);
40
41 std::string code;
42
43 for (int j = 0; j != 3; ++j)
44 {
45 code.push_back('A' + (i % 26));
46 i /= 26;
47 }
48
49 return code;
50 }
51
52 // Insert n empty pages, numbered [0, ... n - 1], in the
53 // specified directory:
54 void
56 {
57 for (std::uint64_t i = 0; i < n; ++i)
58 {
59 auto p = std::make_shared<SLE>(keylet::page(base, i));
60
61 p->setFieldV256(sfIndexes, STVector256{});
62
63 if (i + 1 == n)
64 p->setFieldU64(sfIndexNext, 0);
65 else
66 p->setFieldU64(sfIndexNext, i + 1);
67
68 if (i == 0)
69 p->setFieldU64(sfIndexPrevious, n - 1);
70 else
71 p->setFieldU64(sfIndexPrevious, i - 1);
72
73 sb.insert(p);
74 }
75 }
76
77 void
79 {
80 using namespace jtx;
81
82 auto gw = Account("gw");
83 auto USD = gw["USD"];
84 auto alice = Account("alice");
85 auto bob = Account("bob");
86
87 testcase("Directory Ordering (with 'SortedDirectories' amendment)");
88
89 Env env(*this);
90 env.fund(XRP(10000000), alice, gw);
91
92 std::uint32_t const firstOfferSeq{env.seq(alice)};
93 for (std::size_t i = 1; i <= 400; ++i)
94 env(offer(alice, USD(i), XRP(i)));
95 env.close();
96
97 // Check Alice's directory: it should contain one
98 // entry for each offer she added, and, within each
99 // page the entries should be in sorted order.
100 {
101 auto const view = env.closed();
102
103 std::uint64_t page = 0;
104
105 do
106 {
107 auto p =
108 view->read(keylet::page(keylet::ownerDir(alice), page));
109
110 // Ensure that the entries in the page are sorted
111 auto const& v = p->getFieldV256(sfIndexes);
112 BEAST_EXPECT(std::is_sorted(v.begin(), v.end()));
113
114 // Ensure that the page contains the correct orders by
115 // calculating which sequence numbers belong here.
116 std::uint32_t const minSeq =
117 firstOfferSeq + (page * dirNodeMaxEntries);
118 std::uint32_t const maxSeq = minSeq + dirNodeMaxEntries;
119
120 for (auto const& e : v)
121 {
122 auto c = view->read(keylet::child(e));
123 BEAST_EXPECT(c);
124 BEAST_EXPECT(c->getFieldU32(sfSequence) >= minSeq);
125 BEAST_EXPECT(c->getFieldU32(sfSequence) < maxSeq);
126 }
127
128 page = p->getFieldU64(sfIndexNext);
129 } while (page != 0);
130 }
131
132 // Now check the orderbook: it should be in the order we placed
133 // the offers.
134 auto book = BookDirs(
135 *env.current(), Book({xrpIssue(), USD.issue(), std::nullopt}));
136 int count = 1;
137
138 for (auto const& offer : book)
139 {
140 count++;
141 BEAST_EXPECT(offer->getFieldAmount(sfTakerPays) == USD(count));
142 BEAST_EXPECT(offer->getFieldAmount(sfTakerGets) == XRP(count));
143 }
144 }
145
146 void
148 {
149 testcase("dirIsEmpty");
150
151 using namespace jtx;
152 auto const alice = Account("alice");
153 auto const bob = Account("bob");
154 auto const charlie = Account("charlie");
155 auto const gw = Account("gw");
156
157 Env env(*this);
158
159 env.fund(XRP(1000000), alice, charlie, gw);
160 env.close();
161
162 // alice should have an empty directory.
163 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
164
165 // Give alice a signer list, then there will be stuff in the directory.
166 env(signers(alice, 1, {{bob, 1}}));
167 env.close();
168 BEAST_EXPECT(!dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
169
170 env(signers(alice, jtx::none));
171 env.close();
172 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
173
174 std::vector<IOU> const currencies = [this, &gw]() {
176
177 c.reserve((2 * dirNodeMaxEntries) + 3);
178
179 while (c.size() != c.capacity())
180 c.push_back(gw[currcode(c.size())]);
181
182 return c;
183 }();
184
185 // First, Alices creates a lot of trustlines, and then
186 // deletes them in a different order:
187 {
188 auto cl = currencies;
189
190 for (auto const& c : cl)
191 {
192 env(trust(alice, c(50)));
193 env.close();
194 }
195
196 BEAST_EXPECT(!dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
197
198 std::shuffle(cl.begin(), cl.end(), default_prng());
199
200 for (auto const& c : cl)
201 {
202 env(trust(alice, c(0)));
203 env.close();
204 }
205
206 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
207 }
208
209 // Now, Alice creates offers to buy currency, creating
210 // implicit trust lines.
211 {
212 auto cl = currencies;
213
214 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
215
216 for (auto c : currencies)
217 {
218 env(trust(charlie, c(50)));
219 env.close();
220 env(pay(gw, charlie, c(50)));
221 env.close();
222 env(offer(alice, c(50), XRP(50)));
223 env.close();
224 }
225
226 BEAST_EXPECT(!dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
227
228 // Now fill the offers in a random order. Offer
229 // entries will drop, and be replaced by trust
230 // lines that are implicitly created.
231 std::shuffle(cl.begin(), cl.end(), default_prng());
232
233 for (auto const& c : cl)
234 {
235 env(offer(charlie, XRP(50), c(50)));
236 env.close();
237 }
238 BEAST_EXPECT(!dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
239 // Finally, Alice now sends the funds back to
240 // Charlie. The implicitly created trust lines
241 // should drop away:
242 std::shuffle(cl.begin(), cl.end(), default_prng());
243
244 for (auto const& c : cl)
245 {
246 env(pay(alice, charlie, c(50)));
247 env.close();
248 }
249
250 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
251 }
252 }
253
254 void
256 {
257 testcase("RIPD-1353 Empty Offer Directories");
258
259 using namespace jtx;
260 Env env(*this);
261
262 auto const gw = Account{"gateway"};
263 auto const alice = Account{"alice"};
264 auto const USD = gw["USD"];
265
266 env.fund(XRP(10000), alice, gw);
267 env.close();
268 env.trust(USD(1000), alice);
269 env(pay(gw, alice, USD(1000)));
270
271 auto const firstOfferSeq = env.seq(alice);
272
273 // Fill up three pages of offers
274 for (int i = 0; i < 3; ++i)
275 for (int j = 0; j < dirNodeMaxEntries; ++j)
276 env(offer(alice, XRP(1), USD(1)));
277 env.close();
278
279 // remove all the offers. Remove the middle page last
280 for (auto page : {0, 2, 1})
281 {
282 for (int i = 0; i < dirNodeMaxEntries; ++i)
283 {
284 env(offer_cancel(
285 alice, firstOfferSeq + page * dirNodeMaxEntries + i));
286 env.close();
287 }
288 }
289
290 // All the offers have been cancelled, so the book
291 // should have no entries and be empty:
292 {
293 Sandbox sb(env.closed().get(), tapNONE);
294 uint256 const bookBase =
295 getBookBase({xrpIssue(), USD.issue(), std::nullopt});
296
297 BEAST_EXPECT(dirIsEmpty(sb, keylet::page(bookBase)));
298 BEAST_EXPECT(!sb.succ(bookBase, getQualityNext(bookBase)));
299 }
300
301 // Alice returns the USD she has to the gateway
302 // and removes her trust line. Her owner directory
303 // should now be empty:
304 {
305 env.trust(USD(0), alice);
306 env(pay(alice, gw, alice["USD"](1000)));
307 env.close();
308 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
309 }
310 }
311
312 void
314 {
315 testcase("Empty Chain on Delete");
316
317 using namespace jtx;
318 Env env(*this);
319
320 auto const gw = Account{"gateway"};
321 auto const alice = Account{"alice"};
322 auto const USD = gw["USD"];
323
324 env.fund(XRP(10000), alice);
325 env.close();
326
327 constexpr uint256 base(
328 "fb71c9aa3310141da4b01d6c744a98286af2d72ab5448d5adc0910ca0c910880");
329
330 constexpr uint256 item(
331 "bad0f021aa3b2f6754a8fe82a5779730aa0bbbab82f17201ef24900efc2c7312");
332
333 {
334 // Create a chain of three pages:
335 Sandbox sb(env.closed().get(), tapNONE);
336 makePages(sb, base, 3);
337
338 // Insert an item in the middle page:
339 {
340 auto p = sb.peek(keylet::page(base, 1));
341 BEAST_EXPECT(p);
342
343 STVector256 v;
344 v.push_back(item);
345 p->setFieldV256(sfIndexes, v);
346 sb.update(p);
347 }
348
349 // Now, try to delete the item from the middle
350 // page. This should cause all pages to be deleted:
351 BEAST_EXPECT(sb.dirRemove(
352 keylet::page(base, 0), 1, keylet::unchecked(item), false));
353 BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
354 BEAST_EXPECT(!sb.peek(keylet::page(base, 1)));
355 BEAST_EXPECT(!sb.peek(keylet::page(base, 0)));
356 }
357
358 {
359 // Create a chain of four pages:
360 Sandbox sb(env.closed().get(), tapNONE);
361 makePages(sb, base, 4);
362
363 // Now add items on pages 1 and 2:
364 {
365 auto p1 = sb.peek(keylet::page(base, 1));
366 BEAST_EXPECT(p1);
367
368 STVector256 v1;
369 v1.push_back(~item);
370 p1->setFieldV256(sfIndexes, v1);
371 sb.update(p1);
372
373 auto p2 = sb.peek(keylet::page(base, 2));
374 BEAST_EXPECT(p2);
375
376 STVector256 v2;
377 v2.push_back(item);
378 p2->setFieldV256(sfIndexes, v2);
379 sb.update(p2);
380 }
381
382 // Now, try to delete the item from page 2.
383 // This should cause pages 2 and 3 to be
384 // deleted:
385 BEAST_EXPECT(sb.dirRemove(
386 keylet::page(base, 0), 2, keylet::unchecked(item), false));
387 BEAST_EXPECT(!sb.peek(keylet::page(base, 3)));
388 BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
389
390 auto p1 = sb.peek(keylet::page(base, 1));
391 BEAST_EXPECT(p1);
392 BEAST_EXPECT(p1->getFieldU64(sfIndexNext) == 0);
393 BEAST_EXPECT(p1->getFieldU64(sfIndexPrevious) == 0);
394
395 auto p0 = sb.peek(keylet::page(base, 0));
396 BEAST_EXPECT(p0);
397 BEAST_EXPECT(p0->getFieldU64(sfIndexNext) == 1);
398 BEAST_EXPECT(p0->getFieldU64(sfIndexPrevious) == 1);
399 }
400 }
401
402 void
404 {
405 testcase("fixPreviousTxnID");
406 using namespace jtx;
407
408 auto const gw = Account{"gateway"};
409 auto const alice = Account{"alice"};
410 auto const USD = gw["USD"];
411
412 auto ledger_data = [this](Env& env) {
413 Json::Value params;
414 params[jss::type] = jss::directory;
415 params[jss::ledger_index] = "validated";
416 auto const result =
417 env.rpc("json", "ledger_data", to_string(params))[jss::result];
418 BEAST_EXPECT(!result.isMember(jss::marker));
419 return result;
420 };
421
422 // fixPreviousTxnID is disabled.
423 Env env(*this, testable_amendments() - fixPreviousTxnID);
424 env.fund(XRP(10000), alice, gw);
425 env.close();
426 env.trust(USD(1000), alice);
427 env(pay(gw, alice, USD(1000)));
428 env.close();
429
430 {
431 auto const jrr = ledger_data(env);
432 auto const& jstate = jrr[jss::state];
433 BEAST_EXPECTS(checkArraySize(jstate, 2), jrr.toStyledString());
434 for (auto const& directory : jstate)
435 {
436 BEAST_EXPECT(
437 directory["LedgerEntryType"] ==
438 jss::DirectoryNode); // sanity check
439 // The PreviousTxnID and PreviousTxnLgrSeq fields should not be
440 // on the DirectoryNode object when the amendment is disabled
441 BEAST_EXPECT(!directory.isMember("PreviousTxnID"));
442 BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq"));
443 }
444 }
445
446 // Now enable the amendment so the directory node is updated.
447 env.enableFeature(fixPreviousTxnID);
448 env.close();
449
450 // Make sure the `PreviousTxnID` and `PreviousTxnLgrSeq` fields now
451 // exist
452 env(offer(alice, XRP(1), USD(1)));
453 auto const txID = to_string(env.tx()->getTransactionID());
454 auto const ledgerSeq = env.current()->info().seq;
455 env.close();
456 // Make sure the fields only exist if the object is touched
457 env(noop(gw));
458 env.close();
459
460 {
461 auto const jrr = ledger_data(env);
462 auto const& jstate = jrr[jss::state];
463 BEAST_EXPECTS(checkArraySize(jstate, 3), jrr.toStyledString());
464 for (auto const& directory : jstate)
465 {
466 BEAST_EXPECT(
467 directory["LedgerEntryType"] ==
468 jss::DirectoryNode); // sanity check
469 if (directory[jss::Owner] == gw.human())
470 {
471 // gw's directory did not get touched, so it
472 // should not have those fields populated
473 BEAST_EXPECT(!directory.isMember("PreviousTxnID"));
474 BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq"));
475 }
476 else
477 {
478 // All of the other directories, including the order
479 // book, did get touched, so they should have those
480 // fields
481 BEAST_EXPECT(
482 directory.isMember("PreviousTxnID") &&
483 directory["PreviousTxnID"].asString() == txID);
484 BEAST_EXPECT(
485 directory.isMember("PreviousTxnLgrSeq") &&
486 directory["PreviousTxnLgrSeq"].asUInt() == ledgerSeq);
487 }
488 }
489 }
490 }
491
492 void
493 run() override
494 {
497 testRipd1353();
500 }
501};
502
503BEAST_DEFINE_TESTSUITE_PRIO(Directory, ledger, ripple, 1);
504
505} // namespace test
506} // namespace ripple
T capacity(T... args)
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
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Specifies an order book.
Definition Book.h:36
void push_back(uint256 const &v)
Discardable, editable view to a ledger.
Definition Sandbox.h:35
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const override
Return the key of the next state item.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:115
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:268
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:525
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
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
void enableFeature(uint256 const feature)
Definition Env.cpp:660
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
T is_same_v
T is_sorted(T... args)
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:190
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:380
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:368
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
bool checkArraySize(Json::Value const &val, unsigned int size)
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:34
static none_t const none
Definition tags.h:34
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
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 offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:46
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition View.cpp:904
std::size_t constexpr dirNodeMaxEntries
The maximum number of entries per directory page.
Definition Protocol.h:56
uint256 getQualityNext(uint256 const &uBase)
Definition Indexes.cpp:141
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
@ tapNONE
Definition ApplyView.h:31
uint256 getBookBase(Book const &book)
Definition Indexes.cpp:115
beast::xor_shift_engine & default_prng()
Return the default random engine.
T push_back(T... args)
T shuffle(T... args)
T reserve(T... args)
T size(T... args)
std::string currcode(std::size_t i)
void makePages(Sandbox &sb, uint256 const &base, std::uint64_t n)
void run() override
Runs the suite.