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