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