rippled
Invariants_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2017 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <test/jtx.h>
21 #include <test/jtx/Env.h>
22 #include <ripple/beast/utility/Journal.h>
23 #include <ripple/app/tx/apply.h>
24 #include <ripple/app/tx/impl/Transactor.h>
25 #include <ripple/app/tx/impl/ApplyContext.h>
26 #include <ripple/protocol/STLedgerEntry.h>
27 #include <boost/algorithm/string/predicate.hpp>
28 
29 namespace ripple {
30 
31 class Invariants_test : public beast::unit_test::suite
32 {
33  // this is common setup/method for running a failing invariant check. The
34  // precheck function is used to manipulate the ApplyContext with view
35  // changes that will cause the check to fail.
36  using Precheck = std::function <
37  bool (
38  test::jtx::Account const& a,
39  test::jtx::Account const& b,
40  ApplyContext& ac)>;
41 
42  void
44  std::vector<std::string> const& expect_logs,
45  Precheck const& precheck,
46  XRPAmount fee = XRPAmount{},
47  STTx tx = STTx {ttACCOUNT_SET, [](STObject&){ }},
50  {
51  using namespace test::jtx;
52  Env env {*this};
53 
54  Account A1 {"A1"};
55  Account A2 {"A2"};
56  env.fund (XRP (1000), A1, A2);
57  env.close();
58 
59  OpenView ov {*env.current()};
60  test::StreamSink sink {beast::severities::kWarning};
61  beast::Journal jlog {sink};
62  ApplyContext ac {
63  env.app(),
64  ov,
65  tx,
66  tesSUCCESS,
67  safe_cast<FeeUnit64>(env.current()->fees().units),
68  tapNONE,
69  jlog
70  };
71 
72  BEAST_EXPECT(precheck(A1, A2, ac));
73 
74  // invoke check twice to cover tec and tef cases
75  if (! BEAST_EXPECT(ters.size() == 2))
76  return;
77 
78  TER terActual = tesSUCCESS;
79  for (TER const terExpect : ters)
80  {
81  terActual = ac.checkInvariants(terActual, fee);
82  BEAST_EXPECT(terExpect == terActual);
83  BEAST_EXPECT(
84  boost::starts_with(sink.messages().str(), "Invariant failed:") ||
85  boost::starts_with(sink.messages().str(),
86  "Transaction caused an exception"));
87  //uncomment if you want to log the invariant failure message
88  //log << " --> " << sink.messages().str() << std::endl;
89  for (auto const& m : expect_logs)
90  {
91  BEAST_EXPECT(sink.messages().str().find(m) != std::string::npos);
92  }
93  }
94  }
95 
96  void
98  {
99  using namespace test::jtx;
100  testcase << "XRP created";
102  {{ "XRP net change was positive: 500" }},
103  [](Account const& A1, Account const&, ApplyContext& ac)
104  {
105  // put a single account in the view and "manufacture" some XRP
106  auto const sle = ac.view().peek (keylet::account(A1.id()));
107  if(! sle)
108  return false;
109  auto amt = sle->getFieldAmount (sfBalance);
110  sle->setFieldAmount (sfBalance, amt + STAmount{500});
111  ac.view().update (sle);
112  return true;
113  });
114  }
115 
116  void
118  {
119  using namespace test::jtx;
120  testcase << "account root removed";
121 
122  // An account was deleted, but not by an AccountDelete transaction.
124  {{ "an account root was deleted" }},
125  [](Account const& A1, Account const&, ApplyContext& ac)
126  {
127  // remove an account from the view
128  auto const sle = ac.view().peek (keylet::account(A1.id()));
129  if(! sle)
130  return false;
131  ac.view().erase (sle);
132  return true;
133  });
134 
135  // Successful AccountDelete transaction that didn't delete an account.
136  //
137  // Note that this is a case where a second invocation of the invariant
138  // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
139  // After a discussion with the team, we believe that's okay.
141  {{ "account deletion succeeded without deleting an account" }},
142  [](Account const&, Account const&, ApplyContext& ac){return true;},
143  XRPAmount{},
144  STTx {ttACCOUNT_DELETE, [](STObject& tx){ }},
146 
147  // Successful AccountDelete that deleted more than one account.
149  {{ "account deletion succeeded but deleted multiple accounts" }},
150  [](Account const& A1, Account const& A2, ApplyContext& ac)
151  {
152  // remove two accounts from the view
153  auto const sleA1 = ac.view().peek (keylet::account(A1.id()));
154  auto const sleA2 = ac.view().peek (keylet::account(A2.id()));
155  if(!sleA1 || !sleA2)
156  return false;
157  ac.view().erase (sleA1);
158  ac.view().erase (sleA2);
159  return true;
160  },
161  XRPAmount{},
162  STTx {ttACCOUNT_DELETE, [](STObject& tx){ }});
163  }
164 
165  void
167  {
168  using namespace test::jtx;
169  testcase << "ledger entry types don't match";
171  {{ "ledger entry type mismatch" },
172  { "XRP net change of -1000000000 doesn't match fee 0" }},
173  [](Account const& A1, Account const&, ApplyContext& ac)
174  {
175  // replace an entry in the table with an SLE of a different type
176  auto const sle = ac.view().peek (keylet::account(A1.id()));
177  if(! sle)
178  return false;
179  auto sleNew = std::make_shared<SLE> (ltTICKET, sle->key());
180  ac.rawView().rawReplace (sleNew);
181  return true;
182  });
183 
185  {{ "invalid ledger entry type added" }},
186  [](Account const& A1, Account const&, ApplyContext& ac)
187  {
188  // add an entry in the table with an SLE of an invalid type
189  auto const sle = ac.view().peek (keylet::account(A1.id()));
190  if(! sle)
191  return false;
192  // make a dummy escrow ledger entry, then change the type to an
193  // unsupported value so that the valid type invariant check
194  // will fail.
195  auto sleNew = std::make_shared<SLE> (
196  keylet::escrow(A1, (*sle)[sfSequence] + 2));
197  sleNew->type_ = ltNICKNAME;
198  ac.view().insert (sleNew);
199  return true;
200  });
201  }
202 
203  void
205  {
206  using namespace test::jtx;
207  testcase << "trust lines with XRP not allowed";
209  {{ "an XRP trust line was created" }},
210  [](Account const& A1, Account const& A2, ApplyContext& ac)
211  {
212  // create simple trust SLE with xrp currency
213  auto index = getRippleStateIndex (A1, A2, xrpIssue().currency);
214  auto const sleNew = std::make_shared<SLE>(
215  ltRIPPLE_STATE, index);
216  ac.view().insert (sleNew);
217  return true;
218  });
219  }
220 
221  void
223  {
224  using namespace test::jtx;
225  testcase << "XRP balance checks";
226 
228  {{ "Cannot return non-native STAmount as XRPAmount" }},
229  [](Account const& A1, Account const& A2, ApplyContext& ac)
230  {
231  //non-native balance
232  auto const sle = ac.view().peek (keylet::account(A1.id()));
233  if(! sle)
234  return false;
235  STAmount nonNative (A2["USD"](51));
236  sle->setFieldAmount (sfBalance, nonNative);
237  ac.view().update (sle);
238  return true;
239  });
240 
242  {{ "incorrect account XRP balance" },
243  { "XRP net change was positive: 99999999000000001" }},
244  [this](Account const& A1, Account const&, ApplyContext& ac)
245  {
246  // balance exceeds genesis amount
247  auto const sle = ac.view().peek (keylet::account(A1.id()));
248  if(! sle)
249  return false;
250  // Use `drops(1)` to bypass a call to STAmount::canonicalize
251  // with an invalid value
252  sle->setFieldAmount (sfBalance,
253  INITIAL_XRP + drops(1));
254  BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
255  ac.view().update (sle);
256  return true;
257  });
258 
260  {{ "incorrect account XRP balance" },
261  { "XRP net change of -1000000001 doesn't match fee 0" }},
262  [this](Account const& A1, Account const&, ApplyContext& ac)
263  {
264  // balance is negative
265  auto const sle = ac.view().peek (keylet::account(A1.id()));
266  if(! sle)
267  return false;
268  sle->setFieldAmount (sfBalance, STAmount{1, true});
269  BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
270  ac.view().update (sle);
271  return true;
272  });
273  }
274 
275  void
277  {
278  using namespace test::jtx;
279  using namespace std::string_literals;
280  testcase << "Transaction fee checks";
281 
283  {{ "fee paid was negative: -1" },
284  { "XRP net change of 0 doesn't match fee -1" }},
285  [](Account const&, Account const&, ApplyContext&) { return true; },
286  XRPAmount{-1});
287 
289  {{ "fee paid exceeds system limit: "s +
291  { "XRP net change of 0 doesn't match fee "s +
293  [](Account const&, Account const&, ApplyContext&) { return true; },
295 
297  {{"fee paid is 20 exceeds fee specified in transaction."},
298  {"XRP net change of 0 doesn't match fee 20"}},
299  [](Account const&, Account const&, ApplyContext&) { return true; },
300  XRPAmount{20},
302  [](STObject& tx){tx.setFieldAmount(sfFee, XRPAmount{10});} });
303  }
304 
305 
306  void
308  {
309  using namespace test::jtx;
310  testcase << "no bad offers";
311 
313  {{ "offer with a bad amount" }},
314  [](Account const& A1, Account const&, ApplyContext& ac)
315  {
316  // offer with negative takerpays
317  auto const sle = ac.view().peek (keylet::account(A1.id()));
318  if(! sle)
319  return false;
320  auto const offer_index =
321  getOfferIndex (A1.id(), (*sle)[sfSequence]);
322  auto sleNew = std::make_shared<SLE> (ltOFFER, offer_index);
323  sleNew->setAccountID (sfAccount, A1.id());
324  sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]);
325  sleNew->setFieldAmount (sfTakerPays, XRP(-1));
326  ac.view().insert (sleNew);
327  return true;
328  });
329 
331  {{ "offer with a bad amount" }},
332  [](Account const& A1, Account const&, ApplyContext& ac)
333  {
334  // offer with negative takergets
335  auto const sle = ac.view().peek (keylet::account(A1.id()));
336  if(! sle)
337  return false;
338  auto const offer_index =
339  getOfferIndex (A1.id(), (*sle)[sfSequence]);
340  auto sleNew = std::make_shared<SLE> (ltOFFER, offer_index);
341  sleNew->setAccountID (sfAccount, A1.id());
342  sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]);
343  sleNew->setFieldAmount (sfTakerPays, A1["USD"](10));
344  sleNew->setFieldAmount (sfTakerGets, XRP(-1));
345  ac.view().insert (sleNew);
346  return true;
347  });
348 
350  {{ "offer with a bad amount" }},
351  [](Account const& A1, Account const&, ApplyContext& ac)
352  {
353  // offer XRP to XRP
354  auto const sle = ac.view().peek (keylet::account(A1.id()));
355  if(! sle)
356  return false;
357  auto const offer_index =
358  getOfferIndex (A1.id(), (*sle)[sfSequence]);
359  auto sleNew = std::make_shared<SLE> (ltOFFER, offer_index);
360  sleNew->setAccountID (sfAccount, A1.id());
361  sleNew->setFieldU32 (sfSequence, (*sle)[sfSequence]);
362  sleNew->setFieldAmount (sfTakerPays, XRP(10));
363  sleNew->setFieldAmount (sfTakerGets, XRP(11));
364  ac.view().insert (sleNew);
365  return true;
366  });
367  }
368 
369  void
371  {
372  using namespace test::jtx;
373  testcase << "no zero escrow";
374 
376  {{ "Cannot return non-native STAmount as XRPAmount" }},
377  [](Account const& A1, Account const& A2, ApplyContext& ac)
378  {
379  // escrow with nonnative amount
380  auto const sle = ac.view().peek (keylet::account(A1.id()));
381  if(! sle)
382  return false;
383  auto sleNew = std::make_shared<SLE> (
384  keylet::escrow(A1, (*sle)[sfSequence] + 2));
385  STAmount nonNative (A2["USD"](51));
386  sleNew->setFieldAmount (sfAmount, nonNative);
387  ac.view().insert (sleNew);
388  return true;
389  });
390 
392  {{ "XRP net change of -1000000 doesn't match fee 0"},
393  { "escrow specifies invalid amount" }},
394  [](Account const& A1, Account const&, ApplyContext& ac)
395  {
396  // escrow with negative amount
397  auto const sle = ac.view().peek (keylet::account(A1.id()));
398  if(! sle)
399  return false;
400  auto sleNew = std::make_shared<SLE> (
401  keylet::escrow(A1, (*sle)[sfSequence] + 2));
402  sleNew->setFieldAmount (sfAmount, XRP(-1));
403  ac.view().insert (sleNew);
404  return true;
405  });
406 
408  {{ "XRP net change was positive: 100000000000000001" },
409  { "escrow specifies invalid amount" }},
410  [](Account const& A1, Account const&, ApplyContext& ac)
411  {
412  // escrow with too-large amount
413  auto const sle = ac.view().peek (keylet::account(A1.id()));
414  if(! sle)
415  return false;
416  auto sleNew = std::make_shared<SLE> (
417  keylet::escrow(A1, (*sle)[sfSequence] + 2));
418  // Use `drops(1)` to bypass a call to STAmount::canonicalize
419  // with an invalid value
420  sleNew->setFieldAmount (sfAmount,
421  INITIAL_XRP + drops(1));
422  ac.view().insert (sleNew);
423  return true;
424  });
425  }
426 
427  void
429  {
430  using namespace test::jtx;
431  testcase << "valid new account root";
432 
434  {{ "account root created by a non-Payment" }},
435  [](Account const&, Account const&, ApplyContext& ac)
436  {
437  // Insert a new account root created by a non-payment into
438  // the view.
439  const Account A3 {"A3"};
440  Keylet const acctKeylet = keylet::account (A3);
441  auto const sleNew = std::make_shared<SLE>(acctKeylet);
442  ac.view().insert (sleNew);
443  return true;
444  });
445 
447  {{ "multiple accounts created in a single transaction" }},
448  [](Account const&, Account const&, ApplyContext& ac)
449  {
450  // Insert two new account roots into the view.
451  {
452  const Account A3 {"A3"};
453  Keylet const acctKeylet = keylet::account (A3);
454  auto const sleA3 = std::make_shared<SLE>(acctKeylet);
455  ac.view().insert (sleA3);
456  }
457  {
458  const Account A4 {"A4"};
459  Keylet const acctKeylet = keylet::account (A4);
460  auto const sleA4 = std::make_shared<SLE>(acctKeylet);
461  ac.view().insert (sleA4);
462  }
463  return true;
464  });
465 
467  {{ "account created with wrong starting sequence number" }},
468  [](Account const&, Account const&, ApplyContext& ac)
469  {
470  // Insert a new account root with the wrong starting sequence.
471  const Account A3 {"A3"};
472  Keylet const acctKeylet = keylet::account (A3);
473  auto const sleNew = std::make_shared<SLE>(acctKeylet);
474  sleNew->setFieldU32 (sfSequence, ac.view().seq() + 1);
475  ac.view().insert (sleNew);
476  return true;
477  },
478  XRPAmount{},
479  STTx {ttPAYMENT, [](STObject& tx){ }});
480  }
481 
482 public:
483  void run () override
484  {
487  testTypesMatch ();
491  testNoBadOffers ();
492  testNoZeroEscrow ();
494  }
495 };
496 
497 BEAST_DEFINE_TESTSUITE (Invariants, ledger, ripple);
498 
499 } // ripple
500 
ripple::Invariants_test::testXRPNotCreated
void testXRPNotCreated()
Definition: Invariants_test.cpp:97
ripple::Invariants_test::doInvariantCheck
void doInvariantCheck(std::vector< std::string > const &expect_logs, Precheck const &precheck, XRPAmount fee=XRPAmount{}, STTx tx=STTx {ttACCOUNT_SET, [](STObject &){ }}, std::initializer_list< TER > ters={tecINVARIANT_FAILED, tefINVARIANT_FAILED})
Definition: Invariants_test.cpp:43
ripple::ttACCOUNT_DELETE
@ ttACCOUNT_DELETE
Definition: TxFormats.h:58
ripple::Invariants_test::run
void run() override
Definition: Invariants_test.cpp:483
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
ripple::ltNICKNAME
@ ltNICKNAME
Definition: LedgerFormats.h:92
ripple::tecINVARIANT_FAILED
@ tecINVARIANT_FAILED
Definition: TER.h:278
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::Invariants_test::testValidNewAccountRoot
void testValidNewAccountRoot()
Definition: Invariants_test.cpp:428
ripple::tefINVARIANT_FAILED
@ tefINVARIANT_FAILED
Definition: TER.h:163
std::vector< std::string >
ripple::Invariants_test::testXRPBalanceCheck
void testXRPBalanceCheck()
Definition: Invariants_test.cpp:222
std::initializer_list::size
T size(T... args)
ripple::sfSequence
const SF_U32 sfSequence(access, STI_UINT32, 4, "Sequence")
Definition: SField.h:340
ripple::getOfferIndex
uint256 getOfferIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:80
ripple::sfAccount
const SF_Account sfAccount(access, STI_ACCOUNT, 1, "Account")
Definition: SField.h:460
ripple::sfTakerPays
const SF_Amount sfTakerPays(access, STI_AMOUNT, 4, "TakerPays")
Definition: SField.h:426
std::function
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::sfAmount
const SF_Amount sfAmount(access, STI_AMOUNT, 1, "Amount")
Definition: SField.h:423
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:33
ripple::ltTICKET
@ ltTICKET
Definition: LedgerFormats.h:69
ripple::ttPAYMENT
@ ttPAYMENT
Definition: TxFormats.h:37
ripple::INITIAL_XRP
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
Definition: SystemParameters.h:45
ripple::Invariants_test::testAccountRootsNotRemoved
void testAccountRootsNotRemoved()
Definition: Invariants_test.cpp:117
ripple::STObject::setFieldAmount
void setFieldAmount(SField const &field, STAmount const &)
Definition: STObject.cpp:642
ripple::keylet::account
static const account_t account
Definition: Indexes.h:116
ripple::getRippleStateIndex
uint256 getRippleStateIndex(AccountID const &a, AccountID const &b, Currency const &currency)
Definition: Indexes.cpp:151
ripple::Invariants_test::testNoXRPTrustLine
void testNoXRPTrustLine()
Definition: Invariants_test.cpp:204
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:477
ripple::keylet::escrow
Keylet escrow(AccountID const &source, std::uint32_t seq)
An escrow entry.
Definition: Indexes.cpp:339
ripple::STAmount
Definition: STAmount.h:42
ripple::STTx
Definition: STTx.h:43
ripple::Invariants_test::testTypesMatch
void testTypesMatch()
Definition: Invariants_test.cpp:166
ripple::ApplyContext
State information when applying a tx.
Definition: ApplyContext.h:35
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
ripple::Invariants_test
Definition: Invariants_test.cpp:31
ripple::sfFee
const SF_Amount sfFee(access, STI_AMOUNT, 8, "Fee")
Definition: SField.h:430
ripple::STObject
Definition: STObject.h:51
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ltRIPPLE_STATE
@ ltRIPPLE_STATE
Definition: LedgerFormats.h:67
ripple::sfBalance
const SF_Amount sfBalance(access, STI_AMOUNT, 2, "Balance")
Definition: SField.h:424
ripple::ttACCOUNT_SET
@ ttACCOUNT_SET
Definition: TxFormats.h:40
beast::severities::kWarning
@ kWarning
Definition: Journal.h:39
ripple::sfTakerGets
const SF_Amount sfTakerGets(access, STI_AMOUNT, 5, "TakerGets")
Definition: SField.h:427
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:97
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::Invariants_test::testNoZeroEscrow
void testNoZeroEscrow()
Definition: Invariants_test.cpp:370
ripple::ltOFFER
@ ltOFFER
Definition: LedgerFormats.h:73
ripple::Invariants_test::testNoBadOffers
void testNoBadOffers()
Definition: Invariants_test.cpp:307
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:219
ripple::Invariants_test::testTransactionFeeCheck
void testTransactionFeeCheck()
Definition: Invariants_test.cpp:276
ripple::XRPAmount
Definition: XRPAmount.h:46
std::initializer_list