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