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 =
189 jv[jss::result][jss::transactions][0u][jss::meta]["AffectedNodes"][1u]["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(5s, [&](auto const& jval) { return jval[jss::type] == "ledgerClosed"; }));
265 }
266
267 {
268 // RPC unsubscribe to ledger stream
269 Json::Value jv;
270 jv[jss::streams] = Json::arrayValue;
271 jv[jss::streams].append("ledger");
272 jv = wsc->invoke("unsubscribe", jv);
273 if (wsc->version() == 2)
274 {
275 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
276 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
277 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
278 }
279 BEAST_EXPECT(jv[jss::status] == "success");
280 }
281 }
282
283 {
284 // Disconnect, reconnect
285 wsc = makeWSClient(env.app().config());
286 {
287 // RPC subscribe to ledger stream
288 Json::Value jv;
289 jv[jss::streams] = Json::arrayValue;
290 jv[jss::streams].append("ledger");
291 jv = wsc->invoke("subscribe", jv);
292 if (wsc->version() == 2)
293 {
294 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
295 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
296 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
297 }
298 BEAST_EXPECT(jv[jss::status] == "success");
299 }
300
301 // Close ledgers
302 for (auto i = 0; i < 2; ++i)
303 {
304 auto jv = wsc->invoke("ledger_accept");
305 if (wsc->version() == 2)
306 {
307 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
308 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
309 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
310 }
311 BEAST_EXPECT(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) { return jval[jss::type] == "ledgerClosed"; }));
317 }
318
319 {
320 // RPC unsubscribe to ledger stream
321 Json::Value jv;
322 jv[jss::streams] = Json::arrayValue;
323 jv[jss::streams].append("ledger");
324 jv = wsc->invoke("unsubscribe", jv);
325 if (wsc->version() == 2)
326 {
327 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
328 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
329 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
330 }
331 BEAST_EXPECT(jv[jss::status] == "success");
332 }
333 }
334
335 {
336 // RPC account_tx
337 Json::Value jv;
338 jv[jss::account] = Account("bob").human();
339 jv[jss::ledger_index_min] = -1;
340 jv[jss::ledger_index_max] = -1;
341 wsc = makeWSClient(env.app().config());
342 jv = wsc->invoke("account_tx", jv);
343 if (wsc->version() == 2)
344 {
345 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
346 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
347 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
348 }
349
350 // Check balance
351 auto ff =
352 jv[jss::result][jss::transactions][0u][jss::meta]["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
353 BEAST_EXPECT(ff[jss::Account] == Account("bob").human());
354 BEAST_EXPECT(ff["Balance"] == "10001000000");
355 }
356 }
357
358 void
360 {
361 using namespace std::chrono_literals;
362 using namespace jtx;
363 Env env(*this);
364 env.fund(XRP(10000), "alice");
365 env.close();
366 auto wsc = makeWSClient(env.app().config());
367
368 {
369 // RPC subscribe to accounts_proposed stream
370 Json::Value jv;
371 jv[jss::accounts_proposed] = Json::arrayValue;
372 jv[jss::accounts_proposed].append(Account("alice").human());
373 jv = wsc->invoke("subscribe", jv);
374 if (wsc->version() == 2)
375 {
376 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
377 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
378 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
379 }
380 BEAST_EXPECT(jv[jss::status] == "success");
381 }
382
383 {
384 // Submit account_set transaction
385 Json::Value jv;
386 jv[jss::secret] = toBase58(generateSeed("alice"));
387 jv[jss::tx_json] = fset("alice", 0);
388 jv[jss::tx_json][jss::Fee] = static_cast<int>(env.current()->fees().base.drops());
389 jv = wsc->invoke("submit", jv);
390 if (wsc->version() == 2)
391 {
392 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
393 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
394 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
395 }
396 BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS");
397 }
398
399 {
400 // Check stream update
401 BEAST_EXPECT(wsc->findMsg(
402 5s, [&](auto const& jv) { return jv[jss::transaction][jss::TransactionType] == jss::AccountSet; }));
403 }
404
405 {
406 // RPC unsubscribe to accounts_proposed stream
407 Json::Value jv;
408 jv[jss::accounts_proposed] = Json::arrayValue;
409 jv[jss::accounts_proposed].append(Account("alice").human());
410 jv = wsc->invoke("unsubscribe", jv);
411 if (wsc->version() == 2)
412 {
413 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
414 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
415 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
416 }
417 BEAST_EXPECT(jv[jss::status] == "success");
418 }
419 }
420
421 void
422 run() override
423 {
428 }
429};
430
431BEAST_DEFINE_TESTSUITE(RobustTransaction, rpc, xrpl);
432
433} // namespace test
434} // 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:229
Immutable cryptographic account descriptor.
Definition Account.h:20
std::string const & human() const
Returns the human readable public key.
Definition Account.h:95
A transaction testing environment.
Definition Env.h:98
Application & app()
Definition Env.h:230
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:97
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:260
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:239
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:298
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:90
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:285
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:92
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57