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(*env.current(), Book({xrpIssue(), USD.issue()}));
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 = getBookBase({xrpIssue(), USD.issue()});
295
296 BEAST_EXPECT(dirIsEmpty(sb, keylet::page(bookBase)));
297 BEAST_EXPECT(!sb.succ(bookBase, getQualityNext(bookBase)));
298 }
299
300 // Alice returns the USD she has to the gateway
301 // and removes her trust line. Her owner directory
302 // should now be empty:
303 {
304 env.trust(USD(0), alice);
305 env(pay(alice, gw, alice["USD"](1000)));
306 env.close();
307 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
308 }
309 }
310
311 void
313 {
314 testcase("Empty Chain on Delete");
315
316 using namespace jtx;
317 Env env(*this);
318
319 auto const gw = Account{"gateway"};
320 auto const alice = Account{"alice"};
321 auto const USD = gw["USD"];
322
323 env.fund(XRP(10000), alice);
324 env.close();
325
326 constexpr uint256 base(
327 "fb71c9aa3310141da4b01d6c744a98286af2d72ab5448d5adc0910ca0c910880");
328
329 constexpr uint256 item(
330 "bad0f021aa3b2f6754a8fe82a5779730aa0bbbab82f17201ef24900efc2c7312");
331
332 {
333 // Create a chain of three pages:
334 Sandbox sb(env.closed().get(), tapNONE);
335 makePages(sb, base, 3);
336
337 // Insert an item in the middle page:
338 {
339 auto p = sb.peek(keylet::page(base, 1));
340 BEAST_EXPECT(p);
341
342 STVector256 v;
343 v.push_back(item);
344 p->setFieldV256(sfIndexes, v);
345 sb.update(p);
346 }
347
348 // Now, try to delete the item from the middle
349 // page. This should cause all pages to be deleted:
350 BEAST_EXPECT(sb.dirRemove(
351 keylet::page(base, 0), 1, keylet::unchecked(item), false));
352 BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
353 BEAST_EXPECT(!sb.peek(keylet::page(base, 1)));
354 BEAST_EXPECT(!sb.peek(keylet::page(base, 0)));
355 }
356
357 {
358 // Create a chain of four pages:
359 Sandbox sb(env.closed().get(), tapNONE);
360 makePages(sb, base, 4);
361
362 // Now add items on pages 1 and 2:
363 {
364 auto p1 = sb.peek(keylet::page(base, 1));
365 BEAST_EXPECT(p1);
366
367 STVector256 v1;
368 v1.push_back(~item);
369 p1->setFieldV256(sfIndexes, v1);
370 sb.update(p1);
371
372 auto p2 = sb.peek(keylet::page(base, 2));
373 BEAST_EXPECT(p2);
374
375 STVector256 v2;
376 v2.push_back(item);
377 p2->setFieldV256(sfIndexes, v2);
378 sb.update(p2);
379 }
380
381 // Now, try to delete the item from page 2.
382 // This should cause pages 2 and 3 to be
383 // deleted:
384 BEAST_EXPECT(sb.dirRemove(
385 keylet::page(base, 0), 2, keylet::unchecked(item), false));
386 BEAST_EXPECT(!sb.peek(keylet::page(base, 3)));
387 BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
388
389 auto p1 = sb.peek(keylet::page(base, 1));
390 BEAST_EXPECT(p1);
391 BEAST_EXPECT(p1->getFieldU64(sfIndexNext) == 0);
392 BEAST_EXPECT(p1->getFieldU64(sfIndexPrevious) == 0);
393
394 auto p0 = sb.peek(keylet::page(base, 0));
395 BEAST_EXPECT(p0);
396 BEAST_EXPECT(p0->getFieldU64(sfIndexNext) == 1);
397 BEAST_EXPECT(p0->getFieldU64(sfIndexPrevious) == 1);
398 }
399 }
400
401 void
403 {
404 testcase("fixPreviousTxnID");
405 using namespace jtx;
406
407 auto const gw = Account{"gateway"};
408 auto const alice = Account{"alice"};
409 auto const USD = gw["USD"];
410
411 auto ledger_data = [this](Env& env) {
412 Json::Value params;
413 params[jss::type] = jss::directory;
414 params[jss::ledger_index] = "validated";
415 auto const result =
416 env.rpc("json", "ledger_data", to_string(params))[jss::result];
417 BEAST_EXPECT(!result.isMember(jss::marker));
418 return result;
419 };
420
421 // fixPreviousTxnID is disabled.
422 Env env(*this, supported_amendments() - fixPreviousTxnID);
423 env.fund(XRP(10000), alice, gw);
424 env.close();
425 env.trust(USD(1000), alice);
426 env(pay(gw, alice, USD(1000)));
427 env.close();
428
429 {
430 auto const jrr = ledger_data(env);
431 auto const& jstate = jrr[jss::state];
432 BEAST_EXPECTS(checkArraySize(jstate, 2), jrr.toStyledString());
433 for (auto const& directory : jstate)
434 {
435 BEAST_EXPECT(
436 directory["LedgerEntryType"] ==
437 jss::DirectoryNode); // sanity check
438 // The PreviousTxnID and PreviousTxnLgrSeq fields should not be
439 // on the DirectoryNode object when the amendment is disabled
440 BEAST_EXPECT(!directory.isMember("PreviousTxnID"));
441 BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq"));
442 }
443 }
444
445 // Now enable the amendment so the directory node is updated.
446 env.enableFeature(fixPreviousTxnID);
447 env.close();
448
449 // Make sure the `PreviousTxnID` and `PreviousTxnLgrSeq` fields now
450 // exist
451 env(offer(alice, XRP(1), USD(1)));
452 auto const txID = to_string(env.tx()->getTransactionID());
453 auto const ledgerSeq = env.current()->info().seq;
454 env.close();
455 // Make sure the fields only exist if the object is touched
456 env(noop(gw));
457 env.close();
458
459 {
460 auto const jrr = ledger_data(env);
461 auto const& jstate = jrr[jss::state];
462 BEAST_EXPECTS(checkArraySize(jstate, 3), jrr.toStyledString());
463 for (auto const& directory : jstate)
464 {
465 BEAST_EXPECT(
466 directory["LedgerEntryType"] ==
467 jss::DirectoryNode); // sanity check
468 if (directory[jss::Owner] == gw.human())
469 {
470 // gw's directory did not get touched, so it
471 // should not have those fields populated
472 BEAST_EXPECT(!directory.isMember("PreviousTxnID"));
473 BEAST_EXPECT(!directory.isMember("PreviousTxnLgrSeq"));
474 }
475 else
476 {
477 // All of the other directories, including the order
478 // book, did get touched, so they should have those
479 // fields
480 BEAST_EXPECT(
481 directory.isMember("PreviousTxnID") &&
482 directory["PreviousTxnID"].asString() == txID);
483 BEAST_EXPECT(
484 directory.isMember("PreviousTxnLgrSeq") &&
485 directory["PreviousTxnLgrSeq"].asUInt() == ledgerSeq);
486 }
487 }
488 }
489 }
490
491 void
492 run() override
493 {
496 testRipd1353();
499 }
500};
501
502BEAST_DEFINE_TESTSUITE_PRIO(Directory, ledger, ripple, 1);
503
504} // namespace test
505} // 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:190
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:120
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:212
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:455
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:330
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:264
void enableFeature(uint256 const feature)
Definition: Env.cpp:590
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
T is_sorted(T... args)
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:182
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition: Indexes.cpp:372
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition: Indexes.cpp:360
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:366
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:73
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: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:133
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:114
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition: View.cpp:782
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.