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  uint256 base;
329  base.SetHex(
330  "fb71c9aa3310141da4b01d6c744a98286af2d72ab5448d5adc0910ca0c910880");
331 
332  uint256 item;
333  item.SetHex(
334  "bad0f021aa3b2f6754a8fe82a5779730aa0bbbab82f17201ef24900efc2c7312");
335 
336  {
337  // Create a chain of three pages:
338  Sandbox sb(env.closed().get(), tapNONE);
339  makePages(sb, base, 3);
340 
341  // Insert an item in the middle page:
342  {
343  auto p = sb.peek(keylet::page(base, 1));
344  BEAST_EXPECT(p);
345 
346  STVector256 v;
347  v.push_back(item);
348  p->setFieldV256(sfIndexes, v);
349  sb.update(p);
350  }
351 
352  // Now, try to delete the item from the middle
353  // page. This should cause all pages to be deleted:
354  BEAST_EXPECT(sb.dirRemove(
355  keylet::page(base, 0), 1, keylet::unchecked(item), false));
356  BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
357  BEAST_EXPECT(!sb.peek(keylet::page(base, 1)));
358  BEAST_EXPECT(!sb.peek(keylet::page(base, 0)));
359  }
360 
361  {
362  // Create a chain of four pages:
363  Sandbox sb(env.closed().get(), tapNONE);
364  makePages(sb, base, 4);
365 
366  // Now add items on pages 1 and 2:
367  {
368  auto p1 = sb.peek(keylet::page(base, 1));
369  BEAST_EXPECT(p1);
370 
371  STVector256 v1;
372  v1.push_back(~item);
373  p1->setFieldV256(sfIndexes, v1);
374  sb.update(p1);
375 
376  auto p2 = sb.peek(keylet::page(base, 2));
377  BEAST_EXPECT(p2);
378 
379  STVector256 v2;
380  v2.push_back(item);
381  p2->setFieldV256(sfIndexes, v2);
382  sb.update(p2);
383  }
384 
385  // Now, try to delete the item from page 2.
386  // This should cause pages 2 and 3 to be
387  // deleted:
388  BEAST_EXPECT(sb.dirRemove(
389  keylet::page(base, 0), 2, keylet::unchecked(item), false));
390  BEAST_EXPECT(!sb.peek(keylet::page(base, 3)));
391  BEAST_EXPECT(!sb.peek(keylet::page(base, 2)));
392 
393  auto p1 = sb.peek(keylet::page(base, 1));
394  BEAST_EXPECT(p1);
395  BEAST_EXPECT(p1->getFieldU64(sfIndexNext) == 0);
396  BEAST_EXPECT(p1->getFieldU64(sfIndexPrevious) == 0);
397 
398  auto p0 = sb.peek(keylet::page(base, 0));
399  BEAST_EXPECT(p0);
400  BEAST_EXPECT(p0->getFieldU64(sfIndexNext) == 1);
401  BEAST_EXPECT(p0->getFieldU64(sfIndexPrevious) == 1);
402  }
403  }
404 
405  void
406  run() override
407  {
409  testDirIsEmpty();
410  testRipd1353();
411  testEmptyChain();
412  }
413 };
414 
416 
417 } // namespace test
418 } // namespace ripple
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(Flow, app, ripple, 2)
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:276
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
ripple::sfIndexNext
const SF_U64 sfIndexNext(access, STI_UINT64, 1, "IndexNext")
Definition: SField.h:394
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:105
std::vector::reserve
T reserve(T... args)
Json::UInt
unsigned int UInt
Definition: json_forwards.h:27
ripple::getBookBase
uint256 getBookBase(Book const &book)
Definition: Indexes.cpp:77
std::vector
STL class.
std::vector::size
T size(T... args)
ripple::sfSequence
const SF_U32 sfSequence(access, STI_UINT32, 4, "Sequence")
Definition: SField.h:355
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::sfTakerPays
const SF_Amount sfTakerPays(access, STI_AMOUNT, 4, "TakerPays")
Definition: SField.h:442
ripple::keylet::child
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:126
ripple::getQualityNext
uint256 getQualityNext(uint256 const &uBase)
Definition: Indexes.cpp:95
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:31
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:232
algorithm
std::is_sorted
T is_sorted(T... args)
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::sfIndexPrevious
const SF_U64 sfIndexPrevious(access, STI_UINT64, 2, "IndexPrevious")
Definition: SField.h:395
ripple::base_uint< 256 >
std::vector::capacity
T capacity(T... args)
ripple::BookDirs
Definition: BookDirs.h:27
ripple::detail::ApplyViewBase::succ
boost::optional< key_type > succ(key_type const &key, boost::optional< key_type > const &last=boost::none) const override
Return the key of the next state item.
Definition: ApplyViewBase.cpp:65
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:282
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:34
ripple::base_uint::SetHex
bool SetHex(const char *psz, bool bStrict=false)
Parse a hex string into a base_uint The input can be:
Definition: base_uint.h:406
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
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:180
ripple::keylet::unchecked
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition: Indexes.cpp:270
ripple::sfIndexes
const SF_Vec256 sfIndexes(access, STI_VECTOR256, 1, "Indexes", SField::sMD_Never)
Definition: SField.h:489
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:466
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::close
void close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:111
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:201
ripple::STVector256
Definition: STVector256.h:29
ripple::sfTakerGets
const SF_Amount sfTakerGets(access, STI_AMOUNT, 5, "TakerGets")
Definition: SField.h:443
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:406
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:297
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:114
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