rippled
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 <ripple/json/json_reader.h>
21 #include <ripple/json/json_value.h>
22 #include <ripple/protocol/jss.h>
23 #include <ripple/rpc/impl/RPCHelpers.h>
24 #include <test/jtx.h>
25 #include <test/jtx/Env.h>
26 
27 #include <functional>
28 
29 namespace ripple {
30 
31 class TransactionEntry_test : public beast::unit_test::suite
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  if (apiVersion == RPC::apiMaximumSupportedVersion)
234  {
235  Json::Value const clIndex{env.rpc(
236  "transaction_entry", txhash, std::to_string(index))};
237  BEAST_EXPECT(clIndex["result"] == resIndex);
238  }
239 
240  // Use the command line form with the ledger_hash.
241  if (apiVersion == RPC::apiMaximumSupportedVersion)
242  {
243  Json::Value const clHash{env.rpc(
244  "transaction_entry",
245  txhash,
246  resIndex[jss::ledger_hash].asString())};
247  BEAST_EXPECT(clHash["result"] == resIndex);
248  }
249  };
250 
251  Account A1{"A1"};
252  Account A2{"A2"};
253 
254  env.fund(XRP(10000), A1);
255  auto fund_1_tx =
256  boost::lexical_cast<std::string>(env.tx()->getTransactionID());
257  BEAST_EXPECT(
258  fund_1_tx ==
259  "F4E9DF90D829A9E8B423FF68C34413E240D8D8BB0EFD080DF08114ED398E2506");
260 
261  env.fund(XRP(10000), A2);
262  auto fund_2_tx =
263  boost::lexical_cast<std::string>(env.tx()->getTransactionID());
264  BEAST_EXPECT(
265  fund_2_tx ==
266  "6853CD8226A05068C951CB1F54889FF4E40C5B440DC1C5BA38F114C4E0B1E705");
267 
268  env.close();
269 
270  // these are actually AccountSet txs because fund does two txs and
271  // env.tx only reports the last one
272  check_tx(
273  env.closed()->seq(),
274  fund_1_tx,
275  R"({
276  "Account" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
277  "Fee" : "10",
278  "Sequence" : 3,
279  "SetFlag" : 8,
280  "SigningPubKey" : "0324CAAFA2212D2AEAB9D42D481535614AED486293E1FB1380FF070C3DD7FB4264",
281  "TransactionType" : "AccountSet",
282  "TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474",
283 })",
284  "ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
285  "2000-01-01T00:00:10Z");
286  check_tx(
287  env.closed()->seq(),
288  fund_2_tx,
289  R"({
290  "Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
291  "Fee" : "10",
292  "Sequence" : 3,
293  "SetFlag" : 8,
294  "SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
295  "TransactionType" : "AccountSet",
296  "TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7",
297 })",
298  "ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
299  "2000-01-01T00:00:10Z");
300 
301  env.trust(A2["USD"](1000), A1);
302  // the trust tx is actually a payment since the trust method
303  // refunds fees with a payment after TrustSet..so just ignore the type
304  // in the check below
305  auto trust_tx =
306  boost::lexical_cast<std::string>(env.tx()->getTransactionID());
307  BEAST_EXPECT(
308  trust_tx ==
309  "C992D97D88FF444A1AB0C06B27557EC54B7F7DA28254778E60238BEA88E0C101");
310 
311  env(pay(A2, A1, A2["USD"](5)));
312  auto pay_tx =
313  boost::lexical_cast<std::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  "39AA166131D56622EFD96CB4B2BD58003ACD37091C90977FF6B81419DB451775",
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  "39AA166131D56622EFD96CB4B2BD58003ACD37091C90977FF6B81419DB451775",
356  "2000-01-01T00:00:20Z");
357 
358  env(offer(A2, XRP(100), A2["USD"](1)));
359  auto offer_tx =
360  boost::lexical_cast<std::string>(env.tx()->getTransactionID());
361  BEAST_EXPECT(
362  offer_tx ==
363  "5FCC1A27A7664F82A0CC4BE5766FBBB7C560D52B93AA7B550CD33B27AEC7EFFB");
364 
365  env.close();
366  check_tx(
367  env.closed()->seq(),
368  offer_tx,
369  R"({
370  "Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
371  "Fee" : "10",
372  "Sequence" : 5,
373  "SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
374  "TakerGets" :
375  {
376  "currency" : "USD",
377  "issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
378  "value" : "1"
379  },
380  "TakerPays" : "100000000",
381  "TransactionType" : "OfferCreate",
382  "TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2",
383 })",
384  "0589B876DF5AFE335781E8FC12C2EC62A80151DF13BBAFE9EB2DA62E798ED434",
385  "2000-01-01T00:00:30Z");
386  }
387 
388 public:
389  void
390  run() override
391  {
392  testBadInput();
395  }
396 };
397 
398 BEAST_DEFINE_TESTSUITE(TransactionEntry, rpc, ripple);
399 
400 } // namespace ripple
ripple::TransactionEntry_test::run
void run() override
Definition: TransactionEntry_test.cpp:335
std::string
STL class.
ripple::TransactionEntry_test
Definition: TransactionEntry_test.cpp:31
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
functional
ripple::TransactionEntry_test::testRequest
void testRequest(unsigned apiVersion)
Definition: TransactionEntry_test.cpp:149
ripple::test::jtx::forAllApiVersions
void forAllApiVersions(VersionedTestCallable auto... testCallable)
Definition: Env.h:743
std::bind_front
T bind_front(T... args)
Json::Value::end
const_iterator end() const
Definition: json_value.cpp:1064
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::RPC::contains_error
bool contains_error(Json::Value const &json)
Returns true if the json contains an rpc error specification.
Definition: ErrorCodes.cpp:196
std::to_string
T to_string(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::TransactionEntry_test::testBadInput
void testBadInput()
Definition: TransactionEntry_test.cpp:34
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::RPC::apiMaximumSupportedVersion
constexpr unsigned int apiMaximumSupportedVersion
Definition: RPCHelpers.h:237
std::string::empty
T empty(T... args)
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
Json::Value::begin
const_iterator begin() const
Definition: json_value.cpp:1046
Json::Value
Represents a JSON value.
Definition: json_value.h:145