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 <ripple/app/tx/apply.h>
21 #include <ripple/app/tx/impl/ApplyContext.h>
22 #include <ripple/app/tx/impl/Transactor.h>
23 #include <ripple/beast/utility/Journal.h>
24 #include <ripple/protocol/STLedgerEntry.h>
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <test/jtx.h>
27 #include <test/jtx/Env.h>
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<bool(
37  test::jtx::Account const& a,
38  test::jtx::Account const& b,
39  ApplyContext& ac)>;
40 
41  void
43  std::vector<std::string> const& expect_logs,
44  Precheck const& precheck,
45  XRPAmount fee = XRPAmount{},
46  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  BEAST_EXPECT(precheck(A1, A2, ac));
72 
73  // invoke check twice to cover tec and tef cases
74  if (!BEAST_EXPECT(ters.size() == 2))
75  return;
76 
77  TER terActual = tesSUCCESS;
78  for (TER const terExpect : ters)
79  {
80  terActual = ac.checkInvariants(terActual, fee);
81  BEAST_EXPECT(terExpect == terActual);
82  BEAST_EXPECT(
83  boost::starts_with(
84  sink.messages().str(), "Invariant failed:") ||
85  boost::starts_with(
86  sink.messages().str(), "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(
92  sink.messages().str().find(m) != std::string::npos);
93  }
94  }
95  }
96 
97  void
99  {
100  using namespace test::jtx;
101  testcase << "XRP created";
103  {{"XRP net change was positive: 500"}},
104  [](Account const& A1, Account const&, ApplyContext& ac) {
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  // remove an account from the view
127  auto const sle = ac.view().peek(keylet::account(A1.id()));
128  if (!sle)
129  return false;
130  ac.view().erase(sle);
131  return true;
132  });
133 
134  // Successful AccountDelete transaction that didn't delete an account.
135  //
136  // Note that this is a case where a second invocation of the invariant
137  // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
138  // After a discussion with the team, we believe that's okay.
140  {{"account deletion succeeded without deleting an account"}},
141  [](Account const&, Account const&, ApplyContext& ac) {
142  return true;
143  },
144  XRPAmount{},
145  STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
147 
148  // Successful AccountDelete that deleted more than one account.
150  {{"account deletion succeeded but deleted multiple accounts"}},
151  [](Account const& A1, Account const& A2, ApplyContext& ac) {
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  // replace an entry in the table with an SLE of a different type
175  auto const sle = ac.view().peek(keylet::account(A1.id()));
176  if (!sle)
177  return false;
178  auto sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
179  ac.rawView().rawReplace(sleNew);
180  return true;
181  });
182 
184  {{"invalid ledger entry type added"}},
185  [](Account const& A1, Account const&, ApplyContext& ac) {
186  // add an entry in the table with an SLE of an invalid type
187  auto const sle = ac.view().peek(keylet::account(A1.id()));
188  if (!sle)
189  return false;
190  // make a dummy escrow ledger entry, then change the type to an
191  // unsupported value so that the valid type invariant check
192  // will fail.
193  auto sleNew = std::make_shared<SLE>(
194  keylet::escrow(A1, (*sle)[sfSequence] + 2));
195  sleNew->type_ = ltNICKNAME;
196  ac.view().insert(sleNew);
197  return true;
198  });
199  }
200 
201  void
203  {
204  using namespace test::jtx;
205  testcase << "trust lines with XRP not allowed";
207  {{"an XRP trust line was created"}},
208  [](Account const& A1, Account const& A2, ApplyContext& ac) {
209  // create simple trust SLE with xrp currency
210  auto index = getRippleStateIndex(A1, A2, xrpIssue().currency);
211  auto const sleNew =
212  std::make_shared<SLE>(ltRIPPLE_STATE, index);
213  ac.view().insert(sleNew);
214  return true;
215  });
216  }
217 
218  void
220  {
221  using namespace test::jtx;
222  testcase << "XRP balance checks";
223 
225  {{"Cannot return non-native STAmount as XRPAmount"}},
226  [](Account const& A1, Account const& A2, ApplyContext& ac) {
227  // non-native balance
228  auto const sle = ac.view().peek(keylet::account(A1.id()));
229  if (!sle)
230  return false;
231  STAmount nonNative(A2["USD"](51));
232  sle->setFieldAmount(sfBalance, nonNative);
233  ac.view().update(sle);
234  return true;
235  });
236 
238  {{"incorrect account XRP balance"},
239  {"XRP net change was positive: 99999999000000001"}},
240  [this](Account const& A1, Account const&, ApplyContext& ac) {
241  // balance exceeds genesis amount
242  auto const sle = ac.view().peek(keylet::account(A1.id()));
243  if (!sle)
244  return false;
245  // Use `drops(1)` to bypass a call to STAmount::canonicalize
246  // with an invalid value
247  sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
248  BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
249  ac.view().update(sle);
250  return true;
251  });
252 
254  {{"incorrect account XRP balance"},
255  {"XRP net change of -1000000001 doesn't match fee 0"}},
256  [this](Account const& A1, Account const&, ApplyContext& ac) {
257  // balance is negative
258  auto const sle = ac.view().peek(keylet::account(A1.id()));
259  if (!sle)
260  return false;
261  sle->setFieldAmount(sfBalance, STAmount{1, true});
262  BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
263  ac.view().update(sle);
264  return true;
265  });
266  }
267 
268  void
270  {
271  using namespace test::jtx;
272  using namespace std::string_literals;
273  testcase << "Transaction fee checks";
274 
276  {{"fee paid was negative: -1"},
277  {"XRP net change of 0 doesn't match fee -1"}},
278  [](Account const&, Account const&, ApplyContext&) { return true; },
279  XRPAmount{-1});
280 
282  {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
283  {"XRP net change of 0 doesn't match fee "s +
285  [](Account const&, Account const&, ApplyContext&) { return true; },
287 
289  {{"fee paid is 20 exceeds fee specified in transaction."},
290  {"XRP net change of 0 doesn't match fee 20"}},
291  [](Account const&, Account const&, ApplyContext&) { return true; },
292  XRPAmount{20},
293  STTx{ttACCOUNT_SET, [](STObject& tx) {
294  tx.setFieldAmount(sfFee, XRPAmount{10});
295  }});
296  }
297 
298  void
300  {
301  using namespace test::jtx;
302  testcase << "no bad offers";
303 
305  {{"offer with a bad amount"}},
306  [](Account const& A1, Account const&, ApplyContext& ac) {
307  // offer with negative takerpays
308  auto const sle = ac.view().peek(keylet::account(A1.id()));
309  if (!sle)
310  return false;
311  auto const offer_index =
312  getOfferIndex(A1.id(), (*sle)[sfSequence]);
313  auto sleNew = std::make_shared<SLE>(ltOFFER, offer_index);
314  sleNew->setAccountID(sfAccount, A1.id());
315  sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
316  sleNew->setFieldAmount(sfTakerPays, XRP(-1));
317  ac.view().insert(sleNew);
318  return true;
319  });
320 
322  {{"offer with a bad amount"}},
323  [](Account const& A1, Account const&, ApplyContext& ac) {
324  // offer with negative takergets
325  auto const sle = ac.view().peek(keylet::account(A1.id()));
326  if (!sle)
327  return false;
328  auto const offer_index =
329  getOfferIndex(A1.id(), (*sle)[sfSequence]);
330  auto sleNew = std::make_shared<SLE>(ltOFFER, offer_index);
331  sleNew->setAccountID(sfAccount, A1.id());
332  sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
333  sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
334  sleNew->setFieldAmount(sfTakerGets, XRP(-1));
335  ac.view().insert(sleNew);
336  return true;
337  });
338 
340  {{"offer with a bad amount"}},
341  [](Account const& A1, Account const&, ApplyContext& ac) {
342  // offer XRP to XRP
343  auto const sle = ac.view().peek(keylet::account(A1.id()));
344  if (!sle)
345  return false;
346  auto const offer_index =
347  getOfferIndex(A1.id(), (*sle)[sfSequence]);
348  auto sleNew = std::make_shared<SLE>(ltOFFER, offer_index);
349  sleNew->setAccountID(sfAccount, A1.id());
350  sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
351  sleNew->setFieldAmount(sfTakerPays, XRP(10));
352  sleNew->setFieldAmount(sfTakerGets, XRP(11));
353  ac.view().insert(sleNew);
354  return true;
355  });
356  }
357 
358  void
360  {
361  using namespace test::jtx;
362  testcase << "no zero escrow";
363 
365  {{"Cannot return non-native STAmount as XRPAmount"}},
366  [](Account const& A1, Account const& A2, ApplyContext& ac) {
367  // escrow with nonnative amount
368  auto const sle = ac.view().peek(keylet::account(A1.id()));
369  if (!sle)
370  return false;
371  auto sleNew = std::make_shared<SLE>(
372  keylet::escrow(A1, (*sle)[sfSequence] + 2));
373  STAmount nonNative(A2["USD"](51));
374  sleNew->setFieldAmount(sfAmount, nonNative);
375  ac.view().insert(sleNew);
376  return true;
377  });
378 
380  {{"XRP net change of -1000000 doesn't match fee 0"},
381  {"escrow specifies invalid amount"}},
382  [](Account const& A1, Account const&, ApplyContext& ac) {
383  // escrow with negative amount
384  auto const sle = ac.view().peek(keylet::account(A1.id()));
385  if (!sle)
386  return false;
387  auto sleNew = std::make_shared<SLE>(
388  keylet::escrow(A1, (*sle)[sfSequence] + 2));
389  sleNew->setFieldAmount(sfAmount, XRP(-1));
390  ac.view().insert(sleNew);
391  return true;
392  });
393 
395  {{"XRP net change was positive: 100000000000000001"},
396  {"escrow specifies invalid amount"}},
397  [](Account const& A1, Account const&, ApplyContext& ac) {
398  // escrow with too-large amount
399  auto const sle = ac.view().peek(keylet::account(A1.id()));
400  if (!sle)
401  return false;
402  auto sleNew = std::make_shared<SLE>(
403  keylet::escrow(A1, (*sle)[sfSequence] + 2));
404  // Use `drops(1)` to bypass a call to STAmount::canonicalize
405  // with an invalid value
406  sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
407  ac.view().insert(sleNew);
408  return true;
409  });
410  }
411 
412  void
414  {
415  using namespace test::jtx;
416  testcase << "valid new account root";
417 
419  {{"account root created by a non-Payment"}},
420  [](Account const&, Account const&, ApplyContext& ac) {
421  // Insert a new account root created by a non-payment into
422  // the view.
423  const Account A3{"A3"};
424  Keylet const acctKeylet = keylet::account(A3);
425  auto const sleNew = std::make_shared<SLE>(acctKeylet);
426  ac.view().insert(sleNew);
427  return true;
428  });
429 
431  {{"multiple accounts created in a single transaction"}},
432  [](Account const&, Account const&, ApplyContext& ac) {
433  // Insert two new account roots into the view.
434  {
435  const Account A3{"A3"};
436  Keylet const acctKeylet = keylet::account(A3);
437  auto const sleA3 = std::make_shared<SLE>(acctKeylet);
438  ac.view().insert(sleA3);
439  }
440  {
441  const Account A4{"A4"};
442  Keylet const acctKeylet = keylet::account(A4);
443  auto const sleA4 = std::make_shared<SLE>(acctKeylet);
444  ac.view().insert(sleA4);
445  }
446  return true;
447  });
448 
450  {{"account created with wrong starting sequence number"}},
451  [](Account const&, Account const&, ApplyContext& ac) {
452  // Insert a new account root with the wrong starting sequence.
453  const Account A3{"A3"};
454  Keylet const acctKeylet = keylet::account(A3);
455  auto const sleNew = std::make_shared<SLE>(acctKeylet);
456  sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
457  ac.view().insert(sleNew);
458  return true;
459  },
460  XRPAmount{},
461  STTx{ttPAYMENT, [](STObject& tx) {}});
462  }
463 
464 public:
465  void
466  run() override
467  {
470  testTypesMatch();
474  testNoBadOffers();
477  }
478 };
479 
480 BEAST_DEFINE_TESTSUITE(Invariants, ledger, ripple);
481 
482 } // namespace ripple
ripple::Invariants_test::testXRPNotCreated
void testXRPNotCreated()
Definition: Invariants_test.cpp:98
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::ttACCOUNT_DELETE
@ ttACCOUNT_DELETE
Definition: TxFormats.h:57
ripple::Invariants_test::run
void run() override
Definition: Invariants_test.cpp:466
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
ripple::ltNICKNAME
@ ltNICKNAME
Definition: LedgerFormats.h:91
ripple::tecINVARIANT_FAILED
@ tecINVARIANT_FAILED
Definition: TER.h:271
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::Invariants_test::testValidNewAccountRoot
void testValidNewAccountRoot()
Definition: Invariants_test.cpp:413
ripple::tefINVARIANT_FAILED
@ tefINVARIANT_FAILED
Definition: TER.h:159
std::vector< std::string >
ripple::Invariants_test::testXRPBalanceCheck
void testXRPBalanceCheck()
Definition: Invariants_test.cpp:219
std::initializer_list::size
T size(T... args)
ripple::sfSequence
const SF_U32 sfSequence(access, STI_UINT32, 4, "Sequence")
Definition: SField.h:355
ripple::getOfferIndex
uint256 getOfferIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:77
ripple::sfAccount
const SF_Account sfAccount(access, STI_ACCOUNT, 1, "Account")
Definition: SField.h:474
ripple::sfTakerPays
const SF_Amount sfTakerPays(access, STI_AMOUNT, 4, "TakerPays")
Definition: SField.h:440
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:437
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:31
ripple::ltTICKET
@ ltTICKET
Definition: LedgerFormats.h:68
ripple::ttPAYMENT
@ ttPAYMENT
Definition: TxFormats.h:36
ripple::INITIAL_XRP
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
Definition: SystemParameters.h:42
ripple::Invariants_test::testAccountRootsNotRemoved
void testAccountRootsNotRemoved()
Definition: Invariants_test.cpp:117
ripple::STObject::setFieldAmount
void setFieldAmount(SField const &field, STAmount const &)
Definition: STObject.cpp:691
ripple::keylet::account
static const account_t account
Definition: Indexes.h:120
ripple::getRippleStateIndex
uint256 getRippleStateIndex(AccountID const &a, AccountID const &b, Currency const &currency)
Definition: Indexes.cpp:138
ripple::Invariants_test::testNoXRPTrustLine
void testNoXRPTrustLine()
Definition: Invariants_test.cpp:202
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:547
ripple::keylet::escrow
Keylet escrow(AccountID const &source, std::uint32_t seq)
An escrow entry.
Definition: Indexes.cpp:315
ripple::STAmount
Definition: STAmount.h:42
ripple::STTx
Definition: STTx.h:42
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:58
ripple::Invariants_test
Definition: Invariants_test.cpp:31
ripple::sfFee
const SF_Amount sfFee(access, STI_AMOUNT, 8, "Fee")
Definition: SField.h:444
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:66
ripple::sfBalance
const SF_Amount sfBalance(access, STI_AMOUNT, 2, "Balance")
Definition: SField.h:438
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:42
ripple::ttACCOUNT_SET
@ ttACCOUNT_SET
Definition: TxFormats.h:39
beast::severities::kWarning
@ kWarning
Definition: Journal.h:37
ripple::sfTakerGets
const SF_Amount sfTakerGets(access, STI_AMOUNT, 5, "TakerGets")
Definition: SField.h:441
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:359
ripple::ltOFFER
@ ltOFFER
Definition: LedgerFormats.h:72
ripple::Invariants_test::testNoBadOffers
void testNoBadOffers()
Definition: Invariants_test.cpp:299
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:213
ripple::Invariants_test::testTransactionFeeCheck
void testTransactionFeeCheck()
Definition: Invariants_test.cpp:269
ripple::XRPAmount
Definition: XRPAmount.h:46
std::initializer_list