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