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