rippled
DeliveredAmount_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2019 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/beast/unit_test.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/jss.h>
23 #include <test/jtx.h>
24 #include <test/jtx/WSClient.h>
25 
26 namespace ripple {
27 namespace test {
28 
29 // Helper class to track the expected number `delivered_amount` results.
31 {
32  // If the test occurs before or after the switch time
34  // number of payments expected 'delivered_amount' available
36  // Number of payments with field with `delivered_amount` set to the
37  // string "unavailable"
39  // Number of payments with no `delivered_amount` field
41 
42  // Increment one of the expected numExpected{Available_, Unavailable_, NotSet_} values.
43  // Which value to increment depends on:
44  // 1) If the ledger is before or after the switch time
45  // 2) If the tx is a partial payment
46  // 3) If the payment is successful or not
47  void
48  adjCounters(bool success, bool partial)
49  {
50  if (!success)
51  {
53  return;
54  }
55  if (!afterSwitchTime_)
56  {
57  if (partial)
59  else
61  return;
62  }
63  // normal case: after switch time & successful transaction
65  }
66 
67 public:
68  explicit CheckDeliveredAmount(bool afterSwitchTime)
69  : afterSwitchTime_(afterSwitchTime)
70  {
71  }
72 
73  void
75  {
76  adjCounters(true, false);
77  }
78 
79  void
81  {
82  adjCounters(false, false);
83  }
84  void
86  {
87  adjCounters(true, true);
88  }
89 
90  // After all the txns are checked, all the `numExpected` variables should be
91  // zero. The `checkTxn` function decrements these variables.
92  bool
94  {
97  }
98 
99  // Check if the transaction has `delivered_amount` in the metaData as
100  // expected from our rules. Decrements the appropriate `numExpected`
101  // variable. After all the txns are checked, all the `numExpected` variables
102  // should be zero.
103  bool
104  checkTxn(Json::Value const& t, Json::Value const& metaData)
105  {
106  if (t[jss::TransactionType].asString() != jss::Payment)
107  return true;
108 
109  bool isSet = metaData.isMember(jss::delivered_amount);
110  bool isSetUnavailable = false;
111  bool isSetAvailable = false;
112  if (isSet)
113  {
114  if (metaData[jss::delivered_amount] != "unavailable")
115  isSetAvailable = true;
116  else
117  isSetUnavailable = true;
118  }
119  if (isSetAvailable)
121  else if (isSetUnavailable)
123  else if (!isSet)
125 
126  if (isSet)
127  {
128  if (metaData.isMember(sfDeliveredAmount.jsonName))
129  {
130  if (metaData[jss::delivered_amount] !=
131  metaData[sfDeliveredAmount.jsonName])
132  return false;
133  }
134  else
135  {
136  if (afterSwitchTime_)
137  {
138  if (metaData[jss::delivered_amount] != t[jss::Amount])
139  return false;
140  }
141  else
142  {
143  if (metaData[jss::delivered_amount] != "unavailable")
144  return false;
145  }
146  }
147  }
148 
149  if (metaData[sfTransactionResult.jsonName] != "tesSUCCESS")
150  {
151  if (isSet)
152  return false;
153  }
154  else
155  {
156  if (afterSwitchTime_)
157  {
158  if (!isSetAvailable)
159  return false;
160  }
161  else
162  {
163  if (metaData.isMember(sfDeliveredAmount.jsonName))
164  {
165  if (!isSetAvailable)
166  return false;
167  }
168  else
169  {
170  if (!isSetUnavailable)
171  return false;
172  }
173  }
174  }
175  return true;
176  }
177 };
178 
179 class DeliveredAmount_test : public beast::unit_test::suite
180 {
181  void
183  {
184  testcase("Ledger Request Subscribe DeliveredAmount");
185 
186  using namespace test::jtx;
187  using namespace std::chrono_literals;
188 
189  Account const alice("alice");
190  Account const bob("bob");
191  Account const carol("carol");
192  auto const gw = Account("gateway");
193  auto const USD = gw["USD"];
194 
195  for (bool const afterSwitchTime : {true, false})
196  {
197  Env env{*this};
198  env.fund(XRP(10000), alice, bob, carol, gw);
199  env.trust(USD(1000), alice, bob, carol);
200  if (afterSwitchTime)
201  env.close(NetClock::time_point{446000000s});
202  else
203  env.close();
204 
205  CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime};
206  {
207  // add payments, but do no close until subscribed
208 
209  // normal payments
210  env(pay(gw, alice, USD(50)));
211  checkDeliveredAmount.adjCountersSuccess();
212  env(pay(gw, alice, XRP(50)));
213  checkDeliveredAmount.adjCountersSuccess();
214 
215  // partial payment
216  env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
217  checkDeliveredAmount.adjCountersPartialPayment();
218  env.require(balance(bob, USD(1000)));
219 
220  // failed payment
221  env(pay(bob, carol, USD(9999999)), ter(tecPATH_PARTIAL));
222  checkDeliveredAmount.adjCountersFail();
223  env.require(balance(carol, USD(0)));
224  }
225 
226  auto wsc = makeWSClient(env.app().config());
227 
228  {
229  Json::Value stream;
230  // RPC subscribe to ledger stream
231  stream[jss::streams] = Json::arrayValue;
232  stream[jss::streams].append("ledger");
233  stream[jss::accounts] = Json::arrayValue;
234  stream[jss::accounts].append(toBase58(alice.id()));
235  stream[jss::accounts].append(toBase58(bob.id()));
236  stream[jss::accounts].append(toBase58(carol.id()));
237  auto jv = wsc->invoke("subscribe", stream);
238  if (wsc->version() == 2)
239  {
240  BEAST_EXPECT(
241  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
242  BEAST_EXPECT(
243  jv.isMember(jss::ripplerpc) &&
244  jv[jss::ripplerpc] == "2.0");
245  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
246  }
247  BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 3);
248  }
249  {
250  env.close();
251  // Check stream update
252  while (true)
253  {
254  auto const r = wsc->findMsg(1s, [&](auto const& jv) {
255  return jv[jss::ledger_index] == 4;
256  });
257  if (!r)
258  break;
259 
260  if (!r->isMember(jss::transaction))
261  continue;
262 
263  BEAST_EXPECT(checkDeliveredAmount.checkTxn(
264  (*r)[jss::transaction], (*r)[jss::meta]));
265  }
266  }
267  BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters());
268  }
269  }
270  void
272  {
273  testcase("Ledger Request RPC DeliveredAmount");
274 
275  using namespace test::jtx;
276  using namespace std::chrono_literals;
277 
278  Account const alice("alice");
279  Account const bob("bob");
280  Account const carol("carol");
281  auto const gw = Account("gateway");
282  auto const USD = gw["USD"];
283 
284  for (bool const afterSwitchTime : {true, false})
285  {
286  Env env{*this};
287  env.fund(XRP(10000), alice, bob, carol, gw);
288  env.trust(USD(1000), alice, bob, carol);
289  if (afterSwitchTime)
290  env.close(NetClock::time_point{446000000s});
291  else
292  env.close();
293 
294  CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime};
295  // normal payments
296  env(pay(gw, alice, USD(50)));
297  checkDeliveredAmount.adjCountersSuccess();
298  env(pay(gw, alice, XRP(50)));
299  checkDeliveredAmount.adjCountersSuccess();
300 
301  // partial payment
302  env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
303  checkDeliveredAmount.adjCountersPartialPayment();
304  env.require(balance(bob, USD(1000)));
305 
306  // failed payment
307  env(pay(gw, carol, USD(9999999)), ter(tecPATH_PARTIAL));
308  checkDeliveredAmount.adjCountersFail();
309  env.require(balance(carol, USD(0)));
310 
311  env.close();
312  std::string index;
313  Json::Value jvParams;
314  jvParams[jss::ledger_index] = 4u;
315  jvParams[jss::transactions] = true;
316  jvParams[jss::expand] = true;
317  auto const jtxn = env.rpc(
318  "json",
319  "ledger",
320  to_string(
321  jvParams))[jss::result][jss::ledger][jss::transactions];
322  for (auto const& t : jtxn)
323  BEAST_EXPECT(
324  checkDeliveredAmount.checkTxn(t, t[jss::metaData]));
325  BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters());
326  }
327  }
328 
329 public:
330  void
331  run() override
332  {
335  }
336 };
337 
338 BEAST_DEFINE_TESTSUITE(DeliveredAmount, app, ripple);
339 
340 } // namespace test
341 } // namespace ripple
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:109
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
std::string
STL class.
ripple::test::DeliveredAmount_test::testAccountDeliveredAmountSubscribe
void testAccountDeliveredAmountSubscribe()
Definition: DeliveredAmount_test.cpp:182
ripple::tfPartialPayment
const std::uint32_t tfPartialPayment
Definition: TxFlags.h:84
ripple::test::jtx::ter
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:33
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:44
ripple::test::CheckDeliveredAmount::adjCounters
void adjCounters(bool success, bool partial)
Definition: DeliveredAmount_test.cpp:48
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::sfDeliveredAmount
const SF_Amount sfDeliveredAmount(access, STI_AMOUNT, 18, "DeliveredAmount")
Definition: SField.h:437
ripple::test::CheckDeliveredAmount::checkExpectedCounters
bool checkExpectedCounters() const
Definition: DeliveredAmount_test.cpp:93
ripple::test::DeliveredAmount_test::testTxDeliveredAmountRPC
void testTxDeliveredAmountRPC()
Definition: DeliveredAmount_test.cpp:271
ripple::test::CheckDeliveredAmount::adjCountersSuccess
void adjCountersSuccess()
Definition: DeliveredAmount_test.cpp:74
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::test::CheckDeliveredAmount::numExpectedNotSet_
int numExpectedNotSet_
Definition: DeliveredAmount_test.cpp:40
ripple::test::CheckDeliveredAmount::numExpectedSetUnavailable_
int numExpectedSetUnavailable_
Definition: DeliveredAmount_test.cpp:38
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::test::CheckDeliveredAmount::numExpectedAvailable_
int numExpectedAvailable_
Definition: DeliveredAmount_test.cpp:35
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:140
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:97
ripple::test::DeliveredAmount_test::run
void run() override
Definition: DeliveredAmount_test.cpp:331
ripple::test::CheckDeliveredAmount::CheckDeliveredAmount
CheckDeliveredAmount(bool afterSwitchTime)
Definition: DeliveredAmount_test.cpp:68
ripple::test::jtx::txflags
Set the flags on a JTx.
Definition: txflags.h:30
std::chrono::time_point
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:961
ripple::tecPATH_PARTIAL
@ tecPATH_PARTIAL
Definition: TER.h:247
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::DeliveredAmount_test
Definition: DeliveredAmount_test.cpp:179
ripple::test::CheckDeliveredAmount::afterSwitchTime_
bool afterSwitchTime_
Definition: DeliveredAmount_test.cpp:33
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:214
ripple::sfTransactionResult
const SF_U8 sfTransactionResult(access, STI_UINT8, 3, "TransactionResult")
Definition: SField.h:326
ripple::test::makeWSClient
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:307
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::CheckDeliveredAmount
Definition: DeliveredAmount_test.cpp:30
ripple::test::CheckDeliveredAmount::adjCountersPartialPayment
void adjCountersPartialPayment()
Definition: DeliveredAmount_test.cpp:85
ripple::test::CheckDeliveredAmount::adjCountersFail
void adjCountersFail()
Definition: DeliveredAmount_test.cpp:80
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
ripple::test::CheckDeliveredAmount::checkTxn
bool checkTxn(Json::Value const &t, Json::Value const &metaData)
Definition: DeliveredAmount_test.cpp:104
Json::Value
Represents a JSON value.
Definition: json_value.h:141