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