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  env.current()->fees().base,
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 
191  // make a dummy escrow ledger entry, then change the type to an
192  // unsupported value so that the valid type invariant check
193  // will fail.
194  auto sleNew = std::make_shared<SLE>(
195  keylet::escrow(A1, (*sle)[sfSequence] + 2));
196 
197  // We don't use ltNICKNAME directly since it's marked deprecated
198  // to prevent accidental use elsewhere.
199  sleNew->type_ = static_cast<LedgerEntryType>('n');
200  ac.view().insert(sleNew);
201  return true;
202  });
203  }
204 
205  void
207  {
208  using namespace test::jtx;
209  testcase << "trust lines with XRP not allowed";
211  {{"an XRP trust line was created"}},
212  [](Account const& A1, Account const& A2, ApplyContext& ac) {
213  // create simple trust SLE with xrp currency
214  auto const sleNew = std::make_shared<SLE>(
215  keylet::line(A1, A2, xrpIssue().currency));
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  // non-native balance
231  auto const sle = ac.view().peek(keylet::account(A1.id()));
232  if (!sle)
233  return false;
234  STAmount nonNative(A2["USD"](51));
235  sle->setFieldAmount(sfBalance, nonNative);
236  ac.view().update(sle);
237  return true;
238  });
239 
241  {{"incorrect account XRP balance"},
242  {"XRP net change was positive: 99999999000000001"}},
243  [this](Account const& A1, Account const&, ApplyContext& ac) {
244  // balance exceeds genesis amount
245  auto const sle = ac.view().peek(keylet::account(A1.id()));
246  if (!sle)
247  return false;
248  // Use `drops(1)` to bypass a call to STAmount::canonicalize
249  // with an invalid value
250  sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
251  BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
252  ac.view().update(sle);
253  return true;
254  });
255 
257  {{"incorrect account XRP balance"},
258  {"XRP net change of -1000000001 doesn't match fee 0"}},
259  [this](Account const& A1, Account const&, ApplyContext& ac) {
260  // balance is negative
261  auto const sle = ac.view().peek(keylet::account(A1.id()));
262  if (!sle)
263  return false;
264  sle->setFieldAmount(sfBalance, STAmount{1, true});
265  BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
266  ac.view().update(sle);
267  return true;
268  });
269  }
270 
271  void
273  {
274  using namespace test::jtx;
275  using namespace std::string_literals;
276  testcase << "Transaction fee checks";
277 
279  {{"fee paid was negative: -1"},
280  {"XRP net change of 0 doesn't match fee -1"}},
281  [](Account const&, Account const&, ApplyContext&) { return true; },
282  XRPAmount{-1});
283 
285  {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
286  {"XRP net change of 0 doesn't match fee "s +
288  [](Account const&, Account const&, ApplyContext&) { return true; },
290 
292  {{"fee paid is 20 exceeds fee specified in transaction."},
293  {"XRP net change of 0 doesn't match fee 20"}},
294  [](Account const&, Account const&, ApplyContext&) { return true; },
295  XRPAmount{20},
296  STTx{ttACCOUNT_SET, [](STObject& tx) {
297  tx.setFieldAmount(sfFee, XRPAmount{10});
298  }});
299  }
300 
301  void
303  {
304  using namespace test::jtx;
305  testcase << "no bad offers";
306 
308  {{"offer with a bad amount"}},
309  [](Account const& A1, Account const&, ApplyContext& ac) {
310  // offer with negative takerpays
311  auto const sle = ac.view().peek(keylet::account(A1.id()));
312  if (!sle)
313  return false;
314  auto sleNew = std::make_shared<SLE>(
315  keylet::offer(A1.id(), (*sle)[sfSequence]));
316  sleNew->setAccountID(sfAccount, A1.id());
317  sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
318  sleNew->setFieldAmount(sfTakerPays, XRP(-1));
319  ac.view().insert(sleNew);
320  return true;
321  });
322 
324  {{"offer with a bad amount"}},
325  [](Account const& A1, Account const&, ApplyContext& ac) {
326  // offer with negative takergets
327  auto const sle = ac.view().peek(keylet::account(A1.id()));
328  if (!sle)
329  return false;
330  auto sleNew = std::make_shared<SLE>(
331  keylet::offer(A1.id(), (*sle)[sfSequence]));
332  sleNew->setAccountID(sfAccount, A1.id());
333  sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
334  sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
335  sleNew->setFieldAmount(sfTakerGets, XRP(-1));
336  ac.view().insert(sleNew);
337  return true;
338  });
339 
341  {{"offer with a bad amount"}},
342  [](Account const& A1, Account const&, ApplyContext& ac) {
343  // offer XRP to XRP
344  auto const sle = ac.view().peek(keylet::account(A1.id()));
345  if (!sle)
346  return false;
347  auto sleNew = std::make_shared<SLE>(
348  keylet::offer(A1.id(), (*sle)[sfSequence]));
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::ttACCOUNT_DELETE
@ ttACCOUNT_DELETE
This transaction type deletes an existing account.
Definition: TxFormats.h:122
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::ltTICKET
@ ltTICKET
A ledger object which describes a ticket.
Definition: LedgerFormats.h:80
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::tecINVARIANT_FAILED
@ tecINVARIANT_FAILED
Definition: TER.h:280
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::sfAmount
const SF_AMOUNT sfAmount
ripple::Invariants_test::testValidNewAccountRoot
void testValidNewAccountRoot()
Definition: Invariants_test.cpp:413
ripple::tefINVARIANT_FAILED
@ tefINVARIANT_FAILED
Definition: TER.h:165
ripple::sfSequence
const SF_UINT32 sfSequence
std::vector< std::string >
ripple::Invariants_test::testXRPBalanceCheck
void testXRPBalanceCheck()
Definition: Invariants_test.cpp:222
std::initializer_list::size
T size(T... args)
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:222
std::function
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:30
ripple::INITIAL_XRP
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
Definition: SystemParameters.h:43
ripple::Invariants_test::testAccountRootsNotRemoved
void testAccountRootsNotRemoved()
Definition: Invariants_test.cpp:117
ripple::ttPAYMENT
@ ttPAYMENT
This transaction type executes a payment.
Definition: TxFormats.h:59
ripple::sfTakerPays
const SF_AMOUNT sfTakerPays
ripple::keylet::escrow
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition: Indexes.cpp:318
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
ripple::Invariants_test::testNoXRPTrustLine
void testNoXRPTrustLine()
Definition: Invariants_test.cpp:206
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:568
ripple::STAmount
Definition: STAmount.h:45
ripple::sfTakerGets
const SF_AMOUNT sfTakerGets
ripple::STTx
Definition: STTx.h:45
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::keylet::line
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:193
ripple::Invariants_test
Definition: Invariants_test.cpp:31
ripple::ttACCOUNT_SET
@ ttACCOUNT_SET
This transaction type adjusts various account settings.
Definition: TxFormats.h:68
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::LedgerEntryType
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:53
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
beast::severities::kWarning
@ kWarning
Definition: Journal.h:37
ripple::sfBalance
const SF_AMOUNT sfBalance
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:95
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::sfFee
const SF_AMOUNT sfFee
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::Invariants_test::testNoZeroEscrow
void testNoZeroEscrow()
Definition: Invariants_test.cpp:359
ripple::Invariants_test::testNoBadOffers
void testNoBadOffers()
Definition: Invariants_test.cpp:302
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:222
ripple::Invariants_test::testTransactionFeeCheck
void testTransactionFeeCheck()
Definition: Invariants_test.cpp:272
ripple::XRPAmount
Definition: XRPAmount.h:46
std::initializer_list