rippled
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 <ripple/basics/random.h>
19 #include <ripple/ledger/BookDirs.h>
20 #include <ripple/ledger/Directory.h>
21 #include <ripple/ledger/Sandbox.h>
22 #include <ripple/protocol/Feature.h>
23 #include <ripple/protocol/Protocol.h>
24 #include <ripple/protocol/jss.h>
25 #include <algorithm>
26 #include <test/jtx.h>
27 
28 namespace ripple {
29 namespace test {
30 
31 struct Directory_test : public beast::unit_test::suite
32 {
33  // Map [0-15576] into a 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
54  makePages(Sandbox& sb, uint256 const& base, std::uint64_t n)
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
400  run() override
401  {
403  testDirIsEmpty();
404  testRipd1353();
405  testEmptyChain();
406  }
407 };
408 
410 
411 } // namespace test
412 } // namespace ripple
ripple::sfIndexNext
const SF_UINT64 sfIndexNext
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:303
ripple::detail::ApplyViewBase::succ
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.
Definition: ApplyViewBase.cpp:64
ripple::test::Directory_test::makePages
void makePages(Sandbox &sb, uint256 const &base, std::uint64_t n)
Definition: Directory_test.cpp:54
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
std::string
STL class.
ripple::test::jtx::none
static const none_t none
Definition: tags.h:34
ripple::test::jtx::Env::closed
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:115
std::vector::reserve
T reserve(T... args)
ripple::sfSequence
const SF_UINT32 sfSequence
ripple::getBookBase
uint256 getBookBase(Book const &book)
Definition: Indexes.cpp:82
std::vector< IOU >
std::vector::size
T size(T... args)
ripple::test::jtx::trust
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:30
ripple::STVector256::push_back
void push_back(uint256 const &v)
Definition: STVector256.h:216
ripple::test::jtx::offer_cancel
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition: offer.cpp:45
ripple::test::Directory_test::testDirectoryOrdering
void testDirectoryOrdering()
Definition: Directory_test.cpp:77
ripple::detail::ApplyViewBase::update
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
Definition: ApplyViewBase.cpp:146
ripple::keylet::child
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:139
ripple::getQualityNext
uint256 getQualityNext(uint256 const &uBase)
Definition: Indexes.cpp:100
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:30
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
algorithm
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2)
std::is_sorted
T is_sorted(T... args)
ripple::sfIndexes
const SF_VECTOR256 sfIndexes
std::string::push_back
T push_back(T... args)
ripple::ApplyView::dirRemove
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
ripple::base_uint< 256 >
ripple::sfTakerPays
const SF_AMOUNT sfTakerPays
std::vector::capacity
T capacity(T... args)
ripple::BookDirs
Definition: BookDirs.h:27
ripple::sfIndexPrevious
const SF_UINT64 sfIndexPrevious
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:309
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:34
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::default_prng
beast::xor_shift_engine & default_prng()
Return the default random engine.
Definition: ripple/basics/random.h:65
ripple::test::Directory_test::testRipd1353
void testRipd1353()
Definition: Directory_test.cpp:253
ripple::sfTakerGets
const SF_AMOUNT sfTakerGets
std::uint64_t
ripple::test::jtx::Env::seq
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:207
ripple::keylet::unchecked
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition: Indexes.cpp:297
ripple::test::Directory_test::currcode
std::string currcode(std::size_t i)
Definition: Directory_test.cpp:35
ripple::test::Directory_test::testEmptyChain
void testEmptyChain()
Definition: Directory_test.cpp:310
ripple::test::Directory_test
Definition: Directory_test.cpp:31
ripple::detail::ApplyViewBase::insert
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
Definition: ApplyViewBase.cpp:140
ripple::dirIsEmpty
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition: View.cpp:590
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
ripple::STVector256
Definition: STVector256.h:29
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:95
ripple::test::Directory_test::testDirIsEmpty
void testDirIsEmpty()
Definition: Directory_test.cpp:145
ripple::test::Directory_test::run
void run() override
Definition: Directory_test.cpp:400
std::size_t
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::Book
Specifies an order book.
Definition: Book.h:33
ripple::detail::ApplyViewBase::peek
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Definition: ApplyViewBase.cpp:128
std::shuffle
T shuffle(T... args)
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:300
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::dirNodeMaxEntries
constexpr std::size_t dirNodeMaxEntries
The maximum number of entries per directory page.
Definition: Protocol.h:55