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(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
34 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
35 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
36 }
37 }
38
39 {
40 // Submit past ledger sequence transaction
41 Json::Value payment;
42 payment[jss::secret] = toBase58(generateSeed("alice"));
43 payment[jss::tx_json] = pay("alice", "bob", XRP(1));
44 payment[jss::tx_json][sfLastLedgerSequence.fieldName] = 1;
45 auto jv = wsc->invoke("submit", payment);
46 if (wsc->version() == 2)
47 {
48 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
49 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
50 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
51 }
52 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefMAX_LEDGER");
53
54 // Submit past sequence transaction
55 payment[jss::tx_json] = pay("alice", "bob", XRP(1));
56 payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") - 1;
57 jv = wsc->invoke("submit", payment);
58 if (wsc->version() == 2)
59 {
60 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
61 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
62 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
63 }
64 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefPAST_SEQ");
65
66 // Submit future sequence transaction
67 payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") + 1;
68 jv = wsc->invoke("submit", payment);
69 if (wsc->version() == 2)
70 {
71 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
72 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
73 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
74 }
75 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "terPRE_SEQ");
76
77 // Submit transaction to bridge the sequence gap
78 payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice");
79 jv = wsc->invoke("submit", payment);
80 if (wsc->version() == 2)
81 {
82 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
83 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
84 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
85 }
86 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
87
88 // Wait for the jobqueue to process everything
89 env.app().getJobQueue().rendezvous();
90
91 // Finalize transactions
92 jv = wsc->invoke("ledger_accept");
93 if (wsc->version() == 2)
94 {
95 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
96 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
97 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
98 }
99 BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
100 }
101
102 {
103 // Check balances
104 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
105 auto const& ff = jv[jss::meta]["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
106 return ff[jss::Account] == Account("bob").human() && ff["Balance"] == "10001000000";
107 }));
108
109 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
110 auto const& ff = jv[jss::meta]["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
111 return ff[jss::Account] == Account("bob").human() && ff["Balance"] == "10002000000";
112 }));
113 }
114
115 {
116 // RPC unsubscribe to transactions stream
117 Json::Value jv;
118 jv[jss::streams] = Json::arrayValue;
119 jv[jss::streams].append("transactions");
120 jv = wsc->invoke("unsubscribe", jv);
121 if (wsc->version() == 2)
122 {
123 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
124 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
125 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
126 }
127 BEAST_EXPECT(jv[jss::status] == "success");
128 }
129 }
130
131 /*
132 Submit a normal payment. Client disconnects after the proposed
133 transaction result is received.
134
135 Client reconnects in the future. During this time it is presumed that the
136 transaction should have succeeded.
137
138 Upon reconnection, recent account transaction history is loaded.
139 The submitted transaction should be detected, and the transaction should
140 ultimately succeed.
141 */
142 void
144 {
145 using namespace jtx;
146 Env env(*this);
147 env.fund(XRP(10000), "alice", "bob");
148 env.close();
149 auto wsc = makeWSClient(env.app().config());
150
151 {
152 // Submit normal payment
153 Json::Value jv;
154 jv[jss::secret] = toBase58(generateSeed("alice"));
155 jv[jss::tx_json] = pay("alice", "bob", XRP(1));
156 jv = wsc->invoke("submit", jv);
157 if (wsc->version() == 2)
158 {
159 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
160 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
161 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
162 }
163 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
164
165 // Disconnect
166 wsc.reset();
167
168 // Server finalizes transaction
169 env.close();
170 }
171
172 {
173 // RPC account_tx
174 Json::Value jv;
175 jv[jss::account] = Account("bob").human();
176 jv[jss::ledger_index_min] = -1;
177 jv[jss::ledger_index_max] = -1;
178 wsc = makeWSClient(env.app().config());
179 jv = wsc->invoke("account_tx", jv);
180 if (wsc->version() == 2)
181 {
182 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
183 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
184 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
185 }
186
187 // Check balance
188 auto ff = jv[jss::result][jss::transactions][0u][jss::meta]["AffectedNodes"][1u]
189 ["ModifiedNode"]["FinalFields"];
190 BEAST_EXPECT(ff[jss::Account] == Account("bob").human());
191 BEAST_EXPECT(ff["Balance"] == "10001000000");
192 }
193 }
194
195 void
197 {
198 using namespace std::chrono_literals;
199 using namespace jtx;
200 Env env(*this);
201 env.fund(XRP(10000), "alice", "bob");
202 env.close();
203 auto wsc = makeWSClient(env.app().config());
204
205 {
206 // Submit normal payment
207 Json::Value jv;
208 jv[jss::secret] = toBase58(generateSeed("alice"));
209 jv[jss::tx_json] = pay("alice", "bob", XRP(1));
210 jv = wsc->invoke("submit", jv);
211 if (wsc->version() == 2)
212 {
213 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
214 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
215 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
216 }
217 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
218
219 // Finalize transaction
220 jv = wsc->invoke("ledger_accept");
221 if (wsc->version() == 2)
222 {
223 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
224 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
225 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
226 }
227 BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
228
229 // Wait for the jobqueue to process everything
230 env.app().getJobQueue().rendezvous();
231 }
232
233 {
234 {
235 // RPC subscribe to ledger stream
236 Json::Value jv;
237 jv[jss::streams] = Json::arrayValue;
238 jv[jss::streams].append("ledger");
239 jv = wsc->invoke("subscribe", jv);
240 if (wsc->version() == 2)
241 {
242 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
243 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
244 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
245 }
246 BEAST_EXPECT(jv[jss::status] == "success");
247 }
248
249 // Close ledgers
250 for (auto i = 0; i < 8; ++i)
251 {
252 auto jv = wsc->invoke("ledger_accept");
253 if (wsc->version() == 2)
254 {
255 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
256 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
257 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
258 }
259 BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
260
261 // Wait for the jobqueue to process everything
262 env.app().getJobQueue().rendezvous();
263
264 BEAST_EXPECT(wsc->findMsg(
265 5s, [&](auto const& jval) { return jval[jss::type] == "ledgerClosed"; }));
266 }
267
268 {
269 // RPC unsubscribe to ledger stream
270 Json::Value jv;
271 jv[jss::streams] = Json::arrayValue;
272 jv[jss::streams].append("ledger");
273 jv = wsc->invoke("unsubscribe", jv);
274 if (wsc->version() == 2)
275 {
276 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
277 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
278 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
279 }
280 BEAST_EXPECT(jv[jss::status] == "success");
281 }
282 }
283
284 {
285 // Disconnect, reconnect
286 wsc = makeWSClient(env.app().config());
287 {
288 // RPC subscribe to ledger stream
289 Json::Value jv;
290 jv[jss::streams] = Json::arrayValue;
291 jv[jss::streams].append("ledger");
292 jv = wsc->invoke("subscribe", jv);
293 if (wsc->version() == 2)
294 {
295 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
296 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
297 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
298 }
299 BEAST_EXPECT(jv[jss::status] == "success");
300 }
301
302 // Close ledgers
303 for (auto i = 0; i < 2; ++i)
304 {
305 auto jv = wsc->invoke("ledger_accept");
306 if (wsc->version() == 2)
307 {
308 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
309 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
310 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
311 }
312 BEAST_EXPECT(jv[jss::result].isMember(jss::ledger_current_index));
313
314 // Wait for the jobqueue to process everything
315 env.app().getJobQueue().rendezvous();
316
317 BEAST_EXPECT(wsc->findMsg(
318 5s, [&](auto const& jval) { return jval[jss::type] == "ledgerClosed"; }));
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(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
330 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
331 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
332 }
333 BEAST_EXPECT(jv[jss::status] == "success");
334 }
335 }
336
337 {
338 // RPC account_tx
339 Json::Value jv;
340 jv[jss::account] = Account("bob").human();
341 jv[jss::ledger_index_min] = -1;
342 jv[jss::ledger_index_max] = -1;
343 wsc = makeWSClient(env.app().config());
344 jv = wsc->invoke("account_tx", jv);
345 if (wsc->version() == 2)
346 {
347 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
348 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
349 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
350 }
351
352 // Check balance
353 auto ff = jv[jss::result][jss::transactions][0u][jss::meta]["AffectedNodes"][1u]
354 ["ModifiedNode"]["FinalFields"];
355 BEAST_EXPECT(ff[jss::Account] == Account("bob").human());
356 BEAST_EXPECT(ff["Balance"] == "10001000000");
357 }
358 }
359
360 void
362 {
363 using namespace std::chrono_literals;
364 using namespace jtx;
365 Env env(*this);
366 env.fund(XRP(10000), "alice");
367 env.close();
368 auto wsc = makeWSClient(env.app().config());
369
370 {
371 // RPC subscribe to accounts_proposed stream
372 Json::Value jv;
373 jv[jss::accounts_proposed] = Json::arrayValue;
374 jv[jss::accounts_proposed].append(Account("alice").human());
375 jv = wsc->invoke("subscribe", jv);
376 if (wsc->version() == 2)
377 {
378 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
379 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && 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 // Submit account_set transaction
387 Json::Value jv;
388 jv[jss::secret] = toBase58(generateSeed("alice"));
389 jv[jss::tx_json] = fset("alice", 0);
390 jv[jss::tx_json][jss::Fee] = static_cast<int>(env.current()->fees().base.drops());
391 jv = wsc->invoke("submit", jv);
392 if (wsc->version() == 2)
393 {
394 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
395 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
396 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
397 }
398 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
399 }
400
401 {
402 // Check stream update
403 BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
404 return jv[jss::transaction][jss::TransactionType] == jss::AccountSet;
405 }));
406 }
407
408 {
409 // RPC unsubscribe to accounts_proposed stream
410 Json::Value jv;
411 jv[jss::accounts_proposed] = Json::arrayValue;
412 jv[jss::accounts_proposed].append(Account("alice").human());
413 jv = wsc->invoke("unsubscribe", jv);
414 if (wsc->version() == 2)
415 {
416 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
417 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
418 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
419 }
420 BEAST_EXPECT(jv[jss::status] == "success");
421 }
422 }
423
424 void
425 run() override
426 {
431 }
432};
433
434BEAST_DEFINE_TESTSUITE(RobustTransaction, rpc, xrpl);
435
436} // namespace test
437} // namespace xrpl
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:51
virtual Config & config()=0
void rendezvous()
Block until no jobs running.
Definition JobQueue.cpp:230
virtual JobQueue & getJobQueue()=0
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
A transaction testing environment.
Definition Env.h:122
Application & app()
Definition Env.h:259
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:249
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:15
@ arrayValue
array value (ordered list)
Definition json_value.h:25
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
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:296
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57