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