rippled
RobustTransaction_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5  Permission to use, copy, modify, and/or distribute this software for any
6  purpose with or without fee is hereby granted, provided that the above
7  copyright notice and this permission notice appear in all copies.
8  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 //==============================================================================
17 
18 #include <ripple/beast/unit_test.h>
19 #include <ripple/core/JobQueue.h>
20 #include <ripple/protocol/jss.h>
21 #include <test/jtx.h>
22 #include <test/jtx/WSClient.h>
23 
24 namespace ripple {
25 namespace test {
26 
27 class RobustTransaction_test : public beast::unit_test::suite
28 {
29 public:
30  void
32  {
33  using namespace std::chrono_literals;
34  using namespace jtx;
35  Env env(*this);
36  env.fund(XRP(10000), "alice", "bob");
37  env.close();
38  auto wsc = makeWSClient(env.app().config());
39 
40  {
41  // RPC subscribe to transactions stream
42  Json::Value jv;
43  jv[jss::streams] = Json::arrayValue;
44  jv[jss::streams].append("transactions");
45  jv = wsc->invoke("subscribe", jv);
46  BEAST_EXPECT(jv[jss::status] == "success");
47  if (wsc->version() == 2)
48  {
49  BEAST_EXPECT(
50  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
51  BEAST_EXPECT(
52  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
53  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
54  }
55  }
56 
57  {
58  // Submit past ledger sequence transaction
59  Json::Value payment;
60  payment[jss::secret] = toBase58(generateSeed("alice"));
61  payment[jss::tx_json] = pay("alice", "bob", XRP(1));
62  payment[jss::tx_json][sfLastLedgerSequence.fieldName] = 1;
63  auto jv = wsc->invoke("submit", payment);
64  if (wsc->version() == 2)
65  {
66  BEAST_EXPECT(
67  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
68  BEAST_EXPECT(
69  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
70  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
71  }
72  BEAST_EXPECT(
73  jv[jss::result][jss::engine_result] == "tefMAX_LEDGER");
74 
75  // Submit past sequence transaction
76  payment[jss::tx_json] = pay("alice", "bob", XRP(1));
77  payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") - 1;
78  jv = wsc->invoke("submit", payment);
79  if (wsc->version() == 2)
80  {
81  BEAST_EXPECT(
82  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
83  BEAST_EXPECT(
84  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
85  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
86  }
87  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefPAST_SEQ");
88 
89  // Submit future sequence transaction
90  payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") + 1;
91  jv = wsc->invoke("submit", payment);
92  if (wsc->version() == 2)
93  {
94  BEAST_EXPECT(
95  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
96  BEAST_EXPECT(
97  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
98  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
99  }
100  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "terPRE_SEQ");
101 
102  // Submit transaction to bridge the sequence gap
103  payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice");
104  jv = wsc->invoke("submit", payment);
105  if (wsc->version() == 2)
106  {
107  BEAST_EXPECT(
108  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
109  BEAST_EXPECT(
110  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
111  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
112  }
113  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
114 
115  // Wait for the jobqueue to process everything
116  env.app().getJobQueue().rendezvous();
117 
118  // Finalize transactions
119  jv = wsc->invoke("ledger_accept");
120  if (wsc->version() == 2)
121  {
122  BEAST_EXPECT(
123  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
124  BEAST_EXPECT(
125  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
126  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
127  }
128  BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
129  }
130 
131  {
132  // Check balances
133  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
134  auto const& ff = jv[jss::meta]["AffectedNodes"][1u]
135  ["ModifiedNode"]["FinalFields"];
136  return ff[jss::Account] == Account("bob").human() &&
137  ff["Balance"] == "10001000000";
138  }));
139 
140  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
141  auto const& ff = jv[jss::meta]["AffectedNodes"][1u]
142  ["ModifiedNode"]["FinalFields"];
143  return ff[jss::Account] == Account("bob").human() &&
144  ff["Balance"] == "10002000000";
145  }));
146  }
147 
148  {
149  // RPC unsubscribe to transactions stream
150  Json::Value jv;
151  jv[jss::streams] = Json::arrayValue;
152  jv[jss::streams].append("transactions");
153  jv = wsc->invoke("unsubscribe", jv);
154  if (wsc->version() == 2)
155  {
156  BEAST_EXPECT(
157  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
158  BEAST_EXPECT(
159  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
160  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
161  }
162  BEAST_EXPECT(jv[jss::status] == "success");
163  }
164  }
165 
166  /*
167  Submit a normal payment. Client disconnects after the proposed
168  transaction result is received.
169 
170  Client reconnects in the future. During this time it is presumed that the
171  transaction should have succeeded.
172 
173  Upon reconnection, recent account transaction history is loaded.
174  The submitted transaction should be detected, and the transaction should
175  ultimately succeed.
176  */
177  void
179  {
180  using namespace jtx;
181  Env env(*this);
182  env.fund(XRP(10000), "alice", "bob");
183  env.close();
184  auto wsc = makeWSClient(env.app().config());
185 
186  {
187  // Submit normal payment
188  Json::Value jv;
189  jv[jss::secret] = toBase58(generateSeed("alice"));
190  jv[jss::tx_json] = pay("alice", "bob", XRP(1));
191  jv = wsc->invoke("submit", jv);
192  if (wsc->version() == 2)
193  {
194  BEAST_EXPECT(
195  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
196  BEAST_EXPECT(
197  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
198  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
199  }
200  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
201 
202  // Disconnect
203  wsc.reset();
204 
205  // Server finalizes transaction
206  env.close();
207  }
208 
209  {
210  // RPC account_tx
211  Json::Value jv;
212  jv[jss::account] = Account("bob").human();
213  jv[jss::ledger_index_min] = -1;
214  jv[jss::ledger_index_max] = -1;
215  wsc = makeWSClient(env.app().config());
216  jv = wsc->invoke("account_tx", jv);
217  if (wsc->version() == 2)
218  {
219  BEAST_EXPECT(
220  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
221  BEAST_EXPECT(
222  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
223  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
224  }
225 
226  // Check balance
227  auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
228  ["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
229  BEAST_EXPECT(ff[jss::Account] == Account("bob").human());
230  BEAST_EXPECT(ff["Balance"] == "10001000000");
231  }
232  }
233 
234  void
236  {
237  using namespace std::chrono_literals;
238  using namespace jtx;
239  Env env(*this);
240  env.fund(XRP(10000), "alice", "bob");
241  env.close();
242  auto wsc = makeWSClient(env.app().config());
243 
244  {
245  // Submit normal payment
246  Json::Value jv;
247  jv[jss::secret] = toBase58(generateSeed("alice"));
248  jv[jss::tx_json] = pay("alice", "bob", XRP(1));
249  jv = wsc->invoke("submit", jv);
250  if (wsc->version() == 2)
251  {
252  BEAST_EXPECT(
253  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
254  BEAST_EXPECT(
255  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
256  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
257  }
258  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
259 
260  // Finalize transaction
261  jv = wsc->invoke("ledger_accept");
262  if (wsc->version() == 2)
263  {
264  BEAST_EXPECT(
265  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
266  BEAST_EXPECT(
267  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
268  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
269  }
270  BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
271 
272  // Wait for the jobqueue to process everything
273  env.app().getJobQueue().rendezvous();
274  }
275 
276  {
277  {
278  // RPC subscribe to ledger stream
279  Json::Value jv;
280  jv[jss::streams] = Json::arrayValue;
281  jv[jss::streams].append("ledger");
282  jv = wsc->invoke("subscribe", jv);
283  if (wsc->version() == 2)
284  {
285  BEAST_EXPECT(
286  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
287  BEAST_EXPECT(
288  jv.isMember(jss::ripplerpc) &&
289  jv[jss::ripplerpc] == "2.0");
290  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
291  }
292  BEAST_EXPECT(jv[jss::status] == "success");
293  }
294 
295  // Close ledgers
296  for (auto i = 0; i < 8; ++i)
297  {
298  auto jv = wsc->invoke("ledger_accept");
299  if (wsc->version() == 2)
300  {
301  BEAST_EXPECT(
302  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
303  BEAST_EXPECT(
304  jv.isMember(jss::ripplerpc) &&
305  jv[jss::ripplerpc] == "2.0");
306  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
307  }
308  BEAST_EXPECT(
309  jv[jss::result].isMember(jss::ledger_current_index));
310 
311  // Wait for the jobqueue to process everything
312  env.app().getJobQueue().rendezvous();
313 
314  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jval) {
315  return jval[jss::type] == "ledgerClosed";
316  }));
317  }
318 
319  {
320  // RPC unsubscribe to ledger stream
321  Json::Value jv;
322  jv[jss::streams] = Json::arrayValue;
323  jv[jss::streams].append("ledger");
324  jv = wsc->invoke("unsubscribe", jv);
325  if (wsc->version() == 2)
326  {
327  BEAST_EXPECT(
328  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
329  BEAST_EXPECT(
330  jv.isMember(jss::ripplerpc) &&
331  jv[jss::ripplerpc] == "2.0");
332  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
333  }
334  BEAST_EXPECT(jv[jss::status] == "success");
335  }
336  }
337 
338  {
339  // Disconnect, reconnect
340  wsc = makeWSClient(env.app().config());
341  {
342  // RPC subscribe to ledger stream
343  Json::Value jv;
344  jv[jss::streams] = Json::arrayValue;
345  jv[jss::streams].append("ledger");
346  jv = wsc->invoke("subscribe", jv);
347  if (wsc->version() == 2)
348  {
349  BEAST_EXPECT(
350  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
351  BEAST_EXPECT(
352  jv.isMember(jss::ripplerpc) &&
353  jv[jss::ripplerpc] == "2.0");
354  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
355  }
356  BEAST_EXPECT(jv[jss::status] == "success");
357  }
358 
359  // Close ledgers
360  for (auto i = 0; i < 2; ++i)
361  {
362  auto jv = wsc->invoke("ledger_accept");
363  if (wsc->version() == 2)
364  {
365  BEAST_EXPECT(
366  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
367  BEAST_EXPECT(
368  jv.isMember(jss::ripplerpc) &&
369  jv[jss::ripplerpc] == "2.0");
370  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
371  }
372  BEAST_EXPECT(
373  jv[jss::result].isMember(jss::ledger_current_index));
374 
375  // Wait for the jobqueue to process everything
376  env.app().getJobQueue().rendezvous();
377 
378  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jval) {
379  return jval[jss::type] == "ledgerClosed";
380  }));
381  }
382 
383  {
384  // RPC unsubscribe to ledger stream
385  Json::Value jv;
386  jv[jss::streams] = Json::arrayValue;
387  jv[jss::streams].append("ledger");
388  jv = wsc->invoke("unsubscribe", jv);
389  if (wsc->version() == 2)
390  {
391  BEAST_EXPECT(
392  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
393  BEAST_EXPECT(
394  jv.isMember(jss::ripplerpc) &&
395  jv[jss::ripplerpc] == "2.0");
396  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
397  }
398  BEAST_EXPECT(jv[jss::status] == "success");
399  }
400  }
401 
402  {
403  // RPC account_tx
404  Json::Value jv;
405  jv[jss::account] = Account("bob").human();
406  jv[jss::ledger_index_min] = -1;
407  jv[jss::ledger_index_max] = -1;
408  wsc = makeWSClient(env.app().config());
409  jv = wsc->invoke("account_tx", jv);
410  if (wsc->version() == 2)
411  {
412  BEAST_EXPECT(
413  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
414  BEAST_EXPECT(
415  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
416  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
417  }
418 
419  // Check balance
420  auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
421  ["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
422  BEAST_EXPECT(ff[jss::Account] == Account("bob").human());
423  BEAST_EXPECT(ff["Balance"] == "10001000000");
424  }
425  }
426 
427  void
429  {
430  using namespace std::chrono_literals;
431  using namespace jtx;
432  Env env(*this);
433  env.fund(XRP(10000), "alice");
434  env.close();
435  auto wsc = makeWSClient(env.app().config());
436 
437  {
438  // RPC subscribe to accounts_proposed stream
439  Json::Value jv;
440  jv[jss::accounts_proposed] = Json::arrayValue;
441  jv[jss::accounts_proposed].append(Account("alice").human());
442  jv = wsc->invoke("subscribe", jv);
443  if (wsc->version() == 2)
444  {
445  BEAST_EXPECT(
446  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
447  BEAST_EXPECT(
448  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
449  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
450  }
451  BEAST_EXPECT(jv[jss::status] == "success");
452  }
453 
454  {
455  // Submit account_set transaction
456  Json::Value jv;
457  jv[jss::secret] = toBase58(generateSeed("alice"));
458  jv[jss::tx_json] = fset("alice", 0);
459  jv[jss::tx_json][jss::Fee] = 10;
460  jv = wsc->invoke("submit", jv);
461  if (wsc->version() == 2)
462  {
463  BEAST_EXPECT(
464  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
465  BEAST_EXPECT(
466  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
467  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
468  }
469  BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
470  }
471 
472  {
473  // Check stream update
474  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
475  return jv[jss::transaction][jss::TransactionType] ==
476  jss::AccountSet;
477  }));
478  }
479 
480  {
481  // RPC unsubscribe to accounts_proposed stream
482  Json::Value jv;
483  jv[jss::accounts_proposed] = Json::arrayValue;
484  jv[jss::accounts_proposed].append(Account("alice").human());
485  jv = wsc->invoke("unsubscribe", jv);
486  if (wsc->version() == 2)
487  {
488  BEAST_EXPECT(
489  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
490  BEAST_EXPECT(
491  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
492  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
493  }
494  BEAST_EXPECT(jv[jss::status] == "success");
495  }
496  }
497 
498  void
499  run() override
500  {
502  testReconnect();
505  }
506 };
507 
508 BEAST_DEFINE_TESTSUITE(RobustTransaction, app, ripple);
509 
510 } // namespace test
511 } // namespace ripple
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::test::RobustTransaction_test
Definition: RobustTransaction_test.cpp:27
ripple::sfSequence
const SF_U32 sfSequence(access, STI_UINT32, 4, "Sequence")
Definition: SField.h:356
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:129
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:109
ripple::test::RobustTransaction_test::testSequenceRealignment
void testSequenceRealignment()
Definition: RobustTransaction_test.cpp:31
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:240
ripple::sfLastLedgerSequence
const SF_U32 sfLastLedgerSequence(access, STI_UINT32, 27, "LastLedgerSequence")
Definition: SField.h:380
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::Application::config
virtual Config & config()=0
ripple::test::jtx::fset
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:28
ripple::test::RobustTransaction_test::run
void run() override
Definition: RobustTransaction_test.cpp:499
ripple::Application::getJobQueue
virtual JobQueue & getJobQueue()=0
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::test::RobustTransaction_test::testAccountsProposed
void testAccountsProposed()
Definition: RobustTransaction_test.cpp:428
ripple::test::jtx::Env::seq
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:198
ripple::JobQueue::rendezvous
void rendezvous()
Block until no tasks running.
Definition: JobQueue.cpp:276
ripple::generateSeed
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:69
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::jtx::Env::close
bool close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:219
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:299
ripple::test::RobustTransaction_test::testReconnectAfterWait
void testReconnectAfterWait()
Definition: RobustTransaction_test.cpp:235
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::RobustTransaction_test::testReconnect
void testReconnect()
Definition: RobustTransaction_test.cpp:178
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
Json::Value
Represents a JSON value.
Definition: json_value.h:145