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