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