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