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  Json::Value cancelOffer;
283  cancelOffer[jss::Account] = alice.human();
284  cancelOffer[jss::OfferSequence] =
285  Json::UInt(firstOfferSeq + page * dirNodeMaxEntries + i);
286  cancelOffer[jss::TransactionType] = jss::OfferCancel;
287  env(cancelOffer);
288  env.close();
289  }
290  }
291 
292  // All the offers have been cancelled, so the book
293  // should have no entries and be empty:
294  {
295  Sandbox sb(env.closed().get(), tapNONE);
296  uint256 const bookBase = getBookBase({xrpIssue(), USD.issue()});
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
404  run() override
405  {
407  testDirIsEmpty();
408  testRipd1353();
409  testEmptyChain();
410  }
411 };
412 
414 
415 } // namespace test
416 } // namespace ripple
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(Flow, app, ripple, 2)
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:300
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:65
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
Json::UInt
unsigned int UInt
Definition: json_forwards.h:27
ripple::getBookBase
uint256 getBookBase(Book const &book)
Definition: Indexes.cpp:79
std::vector
STL class.
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:158
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:147
ripple::keylet::child
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:136
ripple::getQualityNext
uint256 getQualityNext(uint256 const &uBase)
Definition: Indexes.cpp:97
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:250
algorithm
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:306
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:198
ripple::keylet::unchecked
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition: Indexes.cpp:294
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:314
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:141
ripple::dirIsEmpty
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition: View.cpp:470
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:219
ripple::STVector256
Definition: STVector256.h:29
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:97
ripple::test::Directory_test::testDirIsEmpty
void testDirIsEmpty()
Definition: Directory_test.cpp:145
ripple::test::Directory_test::run
void run() override
Definition: Directory_test.cpp:404
std::size_t
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::Book
Specifies an order book.
Definition: Book.h:32
ripple::detail::ApplyViewBase::peek
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Definition: ApplyViewBase.cpp:129
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:299
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
ripple::dirNodeMaxEntries
constexpr std::size_t dirNodeMaxEntries
The maximum number of entries per directory page.
Definition: Protocol.h:51
Json::Value
Represents a JSON value.
Definition: json_value.h:145