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