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