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