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