rippled
Loading...
Searching...
No Matches
TransactionEntry_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/Env.h>
3
4#include <xrpl/json/json_reader.h>
5#include <xrpl/json/json_value.h>
6#include <xrpl/protocol/jss.h>
7
8#include <functional>
9
10namespace ripple {
11
13{
14 void
16 {
17 testcase("Invalid request params");
18 using namespace test::jtx;
19 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
20 cfg->FEES.reference_fee = 10;
21 return cfg;
22 })};
23
24 {
25 // no params
26 auto const result =
27 env.client().invoke("transaction_entry", {})[jss::result];
28 BEAST_EXPECT(result[jss::error] == "fieldNotFoundTransaction");
29 BEAST_EXPECT(result[jss::status] == "error");
30 }
31
32 {
34 params[jss::ledger] = 20;
35 auto const result =
36 env.client().invoke("transaction_entry", params)[jss::result];
37 BEAST_EXPECT(result[jss::error] == "lgrNotFound");
38 BEAST_EXPECT(result[jss::status] == "error");
39 }
40
41 {
43 params[jss::ledger] = "current";
44 params[jss::tx_hash] = "DEADBEEF";
45 auto const result =
46 env.client().invoke("transaction_entry", params)[jss::result];
47 BEAST_EXPECT(result[jss::error] == "notYetImplemented");
48 BEAST_EXPECT(result[jss::status] == "error");
49 }
50
51 {
53 params[jss::ledger] = "closed";
54 params[jss::tx_hash] = "DEADBEEF";
55 auto const result =
56 env.client().invoke("transaction_entry", params)[jss::result];
57 BEAST_EXPECT(!result[jss::ledger_hash].asString().empty());
58 BEAST_EXPECT(result[jss::error] == "malformedRequest");
59 BEAST_EXPECT(result[jss::status] == "error");
60 }
61
62 std::string const txHash{
63 "E2FE8D4AF3FCC3944DDF6CD8CDDC5E3F0AD50863EF8919AFEF10CB6408CD4D05"};
64
65 // Command line format
66 {
67 // No arguments
68 Json::Value const result{env.rpc("transaction_entry")};
69 BEAST_EXPECT(result[jss::ledger_hash].asString().empty());
70 BEAST_EXPECT(result[jss::error] == "badSyntax");
71 BEAST_EXPECT(result[jss::status] == "error");
72 }
73
74 {
75 // One argument
76 Json::Value const result{env.rpc("transaction_entry", txHash)};
77 BEAST_EXPECT(result[jss::error] == "badSyntax");
78 BEAST_EXPECT(result[jss::status] == "error");
79 }
80
81 {
82 // First argument with too few characters
83 Json::Value const result{
84 env.rpc("transaction_entry", txHash.substr(1), "closed")};
85 BEAST_EXPECT(result[jss::error] == "invalidParams");
86 BEAST_EXPECT(result[jss::status] == "error");
87 }
88
89 {
90 // First argument with too many characters
91 Json::Value const result{
92 env.rpc("transaction_entry", txHash + "A", "closed")};
93 BEAST_EXPECT(result[jss::error] == "invalidParams");
94 BEAST_EXPECT(result[jss::status] == "error");
95 }
96
97 {
98 // Second argument not valid
99 Json::Value const result{
100 env.rpc("transaction_entry", txHash, "closer")};
101 BEAST_EXPECT(result[jss::error] == "invalidParams");
102 BEAST_EXPECT(result[jss::status] == "error");
103 }
104
105 {
106 // Ledger index of 0 is not valid
107 Json::Value const result{env.rpc("transaction_entry", txHash, "0")};
108 BEAST_EXPECT(result[jss::error] == "invalidParams");
109 BEAST_EXPECT(result[jss::status] == "error");
110 }
111
112 {
113 // Three arguments
114 Json::Value const result{
115 env.rpc("transaction_entry", txHash, "closed", "extra")};
116 BEAST_EXPECT(result[jss::error] == "badSyntax");
117 BEAST_EXPECT(result[jss::status] == "error");
118 }
119
120 {
121 // Valid structure, but transaction not found.
122 Json::Value const result{
123 env.rpc("transaction_entry", txHash, "closed")};
124 BEAST_EXPECT(
125 !result[jss::result][jss::ledger_hash].asString().empty());
126 BEAST_EXPECT(
127 result[jss::result][jss::error] == "transactionNotFound");
128 BEAST_EXPECT(result[jss::result][jss::status] == "error");
129 }
130 }
131
132 void
133 testRequest(unsigned apiVersion)
134 {
135 testcase("Basic request API version " + std::to_string(apiVersion));
136 using namespace test::jtx;
137 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
138 cfg->FEES.reference_fee = 10;
139 return cfg;
140 })};
141
142 auto check_tx = [this, &env, apiVersion](
143 int index,
144 std::string const txhash,
145 std::string const expected_json = "",
146 std::string const expected_ledger_hash = "",
147 std::string const close_time_iso = "") {
148 // first request using ledger_index to lookup
149 Json::Value const resIndex{[&env, index, &txhash, apiVersion]() {
151 params[jss::ledger_index] = index;
152 params[jss::tx_hash] = txhash;
153 params[jss::api_version] = apiVersion;
154 return env.client().invoke(
155 "transaction_entry", params)[jss::result];
156 }()};
157
158 if (!BEAST_EXPECT(resIndex.isMember(jss::tx_json)))
159 return;
160
161 BEAST_EXPECT(resIndex[jss::validated] == true);
162 BEAST_EXPECT(resIndex[jss::ledger_index] == index);
163 BEAST_EXPECT(resIndex[jss::ledger_hash] == expected_ledger_hash);
164 if (apiVersion > 1)
165 {
166 BEAST_EXPECT(resIndex[jss::hash] == txhash);
167 BEAST_EXPECT(!resIndex[jss::tx_json].isMember(jss::hash));
168 BEAST_EXPECT(!resIndex[jss::tx_json].isMember(jss::Amount));
169
170 if (BEAST_EXPECT(!close_time_iso.empty()))
171 BEAST_EXPECT(
172 resIndex[jss::close_time_iso] == close_time_iso);
173 }
174 else
175 {
176 BEAST_EXPECT(resIndex[jss::tx_json][jss::hash] == txhash);
177 BEAST_EXPECT(!resIndex.isMember(jss::hash));
178 BEAST_EXPECT(!resIndex.isMember(jss::close_time_iso));
179 }
180
181 if (!expected_json.empty())
182 {
183 Json::Value expected;
184 Json::Reader().parse(expected_json, expected);
185 if (RPC::contains_error(expected))
186 Throw<std::runtime_error>(
187 "Internal JSONRPC_test error. Bad test JSON.");
188
189 for (auto memberIt = expected.begin();
190 memberIt != expected.end();
191 memberIt++)
192 {
193 auto const name = memberIt.memberName();
194 if (BEAST_EXPECT(resIndex[jss::tx_json].isMember(name)))
195 {
196 auto const received = resIndex[jss::tx_json][name];
197 BEAST_EXPECTS(
198 received == *memberIt,
199 txhash + " contains \n\"" + name + "\": " //
200 + to_string(received) //
201 + " but expected " //
202 + to_string(expected));
203 }
204 }
205 }
206
207 // second request using ledger_hash to lookup and verify
208 // both responses match
209 {
211 params[jss::ledger_hash] = resIndex[jss::ledger_hash];
212 params[jss::tx_hash] = txhash;
213 params[jss::api_version] = apiVersion;
214 Json::Value const resHash = env.client().invoke(
215 "transaction_entry", params)[jss::result];
216 BEAST_EXPECT(resHash == resIndex);
217 }
218
219 // Use the command line form with the index.
220 Json::Value const clIndex{env.rpc(
221 apiVersion,
222 "transaction_entry",
223 txhash,
224 std::to_string(index))};
225 BEAST_EXPECT(clIndex["result"] == resIndex);
226
227 // Use the command line form with the ledger_hash.
228 Json::Value const clHash{env.rpc(
229 apiVersion,
230 "transaction_entry",
231 txhash,
232 resIndex[jss::ledger_hash].asString())};
233 BEAST_EXPECT(clHash["result"] == resIndex);
234 };
235
236 Account A1{"A1"};
237 Account A2{"A2"};
238
239 env.fund(XRP(10000), A1);
240 auto fund_1_tx = to_string(env.tx()->getTransactionID());
241 BEAST_EXPECT(
242 fund_1_tx ==
243 "F4E9DF90D829A9E8B423FF68C34413E240D8D8BB0EFD080DF08114ED398E2506");
244
245 env.fund(XRP(10000), A2);
246 auto fund_2_tx = to_string(env.tx()->getTransactionID());
247 BEAST_EXPECT(
248 fund_2_tx ==
249 "6853CD8226A05068C951CB1F54889FF4E40C5B440DC1C5BA38F114C4E0B1E705");
250
251 env.close();
252
253 // these are actually AccountSet txs because fund does two txs and
254 // env.tx only reports the last one
255 check_tx(
256 env.closed()->seq(),
257 fund_1_tx,
258 R"({
259 "Account" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
260 "Fee" : "10",
261 "Sequence" : 3,
262 "SetFlag" : 8,
263 "SigningPubKey" : "0324CAAFA2212D2AEAB9D42D481535614AED486293E1FB1380FF070C3DD7FB4264",
264 "TransactionType" : "AccountSet",
265 "TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474",
266})",
267 "ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
268 "2000-01-01T00:00:10Z");
269 check_tx(
270 env.closed()->seq(),
271 fund_2_tx,
272 R"({
273 "Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
274 "Fee" : "10",
275 "Sequence" : 3,
276 "SetFlag" : 8,
277 "SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
278 "TransactionType" : "AccountSet",
279 "TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7",
280})",
281 "ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
282 "2000-01-01T00:00:10Z");
283
284 env.trust(A2["USD"](1000), A1);
285 // the trust tx is actually a payment since the trust method
286 // refunds fees with a payment after TrustSet..so just ignore the type
287 // in the check below
288 auto trust_tx = to_string(env.tx()->getTransactionID());
289 BEAST_EXPECT(
290 trust_tx ==
291 "C992D97D88FF444A1AB0C06B27557EC54B7F7DA28254778E60238BEA88E0C101");
292
293 env(pay(A2, A1, A2["USD"](5)));
294 auto pay_tx = to_string(env.tx()->getTransactionID());
295 env.close();
296 BEAST_EXPECT(
297 pay_tx ==
298 "988046D484ACE9F5F6A8C792D89C6EA2DB307B5DDA9864AEBA88E6782ABD0865");
299
300 check_tx(
301 env.closed()->seq(),
302 trust_tx,
303 R"({
304 "Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
305 "DeliverMax" : "10",
306 "Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
307 "Fee" : "10",
308 "Flags" : 2147483648,
309 "Sequence" : 3,
310 "SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
311 "TransactionType" : "Payment",
312 "TxnSignature" : "3044022033D9EBF7F02950AF2F6B13C07AEE641C8FEBDD540A338FCB9027A965A4AED35B02206E4E227DCC226A3456C0FEF953449D21645A24EB63CA0BB7C5B62470147FD1D1",
313})",
314 "3A6E375BFDFF029A571AFBB3BC46C4F52963FAF043B406D0E59A7194C1A8F98E",
315 "2000-01-01T00:00:20Z");
316
317 check_tx(
318 env.closed()->seq(),
319 pay_tx,
320 R"({
321 "Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
322 "DeliverMax" :
323 {
324 "currency" : "USD",
325 "issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
326 "value" : "5"
327 },
328 "Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
329 "Fee" : "10",
330 "Flags" : 2147483648,
331 "Sequence" : 4,
332 "SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
333 "TransactionType" : "Payment",
334 "TxnSignature" : "30450221008A722B7F16EDB2348886E88ED4EC682AE9973CC1EE0FF37C93BB2CEC821D3EDF022059E464472031BA5E0D88A93E944B6A8B8DB3E1D5E5D1399A805F615789DB0BED",
335})",
336 "3A6E375BFDFF029A571AFBB3BC46C4F52963FAF043B406D0E59A7194C1A8F98E",
337 "2000-01-01T00:00:20Z");
338
339 env(offer(A2, XRP(100), A2["USD"](1)));
340 auto offer_tx = to_string(env.tx()->getTransactionID());
341 BEAST_EXPECT(
342 offer_tx ==
343 "5FCC1A27A7664F82A0CC4BE5766FBBB7C560D52B93AA7B550CD33B27AEC7EFFB");
344
345 env.close();
346 check_tx(
347 env.closed()->seq(),
348 offer_tx,
349 R"({
350 "Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
351 "Fee" : "10",
352 "Sequence" : 5,
353 "SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
354 "TakerGets" :
355 {
356 "currency" : "USD",
357 "issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
358 "value" : "1"
359 },
360 "TakerPays" : "100000000",
361 "TransactionType" : "OfferCreate",
362 "TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2",
363})",
364 "73D6C8E66E0DC22F3E6F7D39BF795A6831BEB412823A986C7CC19470C93557C0",
365 "2000-01-01T00:00:30Z");
366 }
367
368public:
369 void
370 run() override
371 {
372 testBadInput();
375 }
376};
377
378BEAST_DEFINE_TESTSUITE(TransactionEntry, rpc, ripple);
379
380} // namespace ripple
T bind_front(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
const_iterator begin() const
const_iterator end() const
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void testRequest(unsigned apiVersion)
void run() override
Runs the suite.
T empty(T... args)
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:158
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
T to_string(T... args)