xrpld
Loading...
Searching...
No Matches
LedgerLoad_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/beast/unit_test/suite.h>
12#include <xrpl/beast/utility/Journal.h>
13#include <xrpl/beast/utility/temp_dir.h>
14#include <xrpl/core/StartUpType.h>
15#include <xrpl/json/json_value.h>
16#include <xrpl/json/to_string.h>
17#include <xrpl/protocol/SField.h>
18#include <xrpl/protocol/jss.h>
19
20#include <boost/algorithm/string/erase.hpp>
21#include <boost/filesystem/operations.hpp>
22#include <boost/system/detail/error_code.hpp>
23
24#include <cassert>
25#include <fstream>
26#include <ios>
27#include <memory>
28#include <optional>
29#include <stdexcept>
30#include <string>
31
32namespace xrpl {
33
35{
36 auto static ledgerConfig(
38 std::string const& dbPath,
39 std::string const& ledger,
40 StartUpType type,
41 std::optional<uint256> trapTxHash)
42 {
43 cfg->startLedger = ledger;
44 cfg->startUp = type;
45 cfg->trapTxHash = trapTxHash;
46 assert(!dbPath.empty());
47 cfg->legacy("database_path", dbPath);
48 return cfg;
49 }
50
51 // setup for test cases
52 struct SetupData
53 {
55 // NOLINTBEGIN(readability-redundant-member-init)
60 // NOLINTEND(readability-redundant-member-init)
61 };
62
63 SetupData
65 {
66 using namespace test::jtx;
67 SetupData retval = {.dbPath = td.path()};
68
69 retval.ledgerFile = td.file("ledgerdata.json");
70
71 Env env{*this};
73
74 for (auto i = 0; i < 20; ++i)
75 {
76 Account const acct{"A" + std::to_string(i)};
77 env.fund(XRP(10000), acct);
78 env.close();
79 if (i > 0 && BEAST_EXPECT(prev.has_value()))
80 {
81 env.trust(acct["USD"](1000), *prev); // NOLINT(bugprone-unchecked-optional-access)
82 env(pay(
83 acct, *prev, acct["USD"](5))); // NOLINT(bugprone-unchecked-optional-access)
84 }
85 env(offer(acct, XRP(100), acct["USD"](1)));
86 env.close();
87 prev.emplace(acct);
88 }
89
90 retval.ledger = env.rpc("ledger", "current", "full")[jss::result];
91 BEAST_EXPECT(retval.ledger[jss::ledger][jss::accountState].size() == 102);
92
93 retval.hashes = [&] {
94 for (auto const& it : retval.ledger[jss::ledger][jss::accountState])
95 {
96 if (it[sfLedgerEntryType.fieldName] == jss::LedgerHashes)
97 return it[sfHashes.fieldName];
98 }
99 return json::Value{};
100 }();
101
102 BEAST_EXPECT(retval.hashes.size() == 41);
103 retval.trapTxHash = [&]() {
104 auto const txs = env.rpc(
105 "ledger", std::to_string(41), "tx")[jss::result][jss::ledger][jss::transactions];
106 BEAST_EXPECT(txs.isArray() && txs.size() > 0);
107 uint256 tmp;
108 BEAST_EXPECT(tmp.parseHex(txs[0u][jss::hash].asString()));
109 return tmp;
110 }();
111
112 // write this ledger data to a file.
113 std::ofstream o(retval.ledgerFile, std::ios::out | std::ios::trunc);
114 o << to_string(retval.ledger);
115 o.close();
116 return retval;
117 }
118
119 void
121 {
122 testcase("Load a saved ledger");
123 using namespace test::jtx;
124
125 // create a new env with the ledger file specified for startup
126 Env env(
127 *this,
128 envconfig(ledgerConfig, sd.dbPath, sd.ledgerFile, StartUpType::LoadFile, std::nullopt),
129 nullptr,
131 auto jrb = env.rpc("ledger", "current", "full")[jss::result];
132 BEAST_EXPECT(
133 sd.ledger[jss::ledger][jss::accountState].size() ==
134 jrb[jss::ledger][jss::accountState].size());
135 }
136
137 void
139 {
140 testcase("Load ledger: Bad Files");
141 using namespace test::jtx;
142 using namespace boost::filesystem;
143
144 // empty path
145 except([&] {
146 Env const env(
147 *this,
148 envconfig(ledgerConfig, sd.dbPath, "", StartUpType::LoadFile, std::nullopt),
149 nullptr,
151 });
152
153 // file does not exist
154 except([&] {
155 Env const env(
156 *this,
157 envconfig(
158 ledgerConfig, sd.dbPath, "badfile.json", StartUpType::LoadFile, std::nullopt),
159 nullptr,
161 });
162
163 // make a corrupted version of the ledger file (last 10 bytes removed).
164 boost::system::error_code ec;
165 auto ledgerFileCorrupt = boost::filesystem::path{sd.dbPath} / "ledgerdata_bad.json";
166 copy_file(sd.ledgerFile, ledgerFileCorrupt, copy_options::overwrite_existing, ec);
167 if (!BEAST_EXPECTS(!ec, ec.message()))
168 return;
169 auto filesize = file_size(ledgerFileCorrupt, ec);
170 if (!BEAST_EXPECTS(!ec, ec.message()))
171 return;
172 resize_file(ledgerFileCorrupt, filesize - 10, ec);
173 if (!BEAST_EXPECTS(!ec, ec.message()))
174 return;
175
176 except([&] {
177 Env const env(
178 *this,
179 envconfig(
181 sd.dbPath,
182 ledgerFileCorrupt.string(),
184 std::nullopt),
185 nullptr,
187 });
188 }
189
190 void
192 {
193 testcase("Load by hash");
194 using namespace test::jtx;
195
196 // create a new env with the ledger hash specified for startup
197 auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
198 boost::erase_all(ledgerHash, "\"");
199 Env env(
200 *this,
201 envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Load, std::nullopt),
202 nullptr,
204 auto jrb = env.rpc("ledger", "current", "full")[jss::result];
205 BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 98);
206 BEAST_EXPECT(
207 jrb[jss::ledger][jss::accountState].size() <=
208 sd.ledger[jss::ledger][jss::accountState].size());
209 }
210
211 void
213 {
214 testcase("Load and replay by hash");
215 using namespace test::jtx;
216
217 // create a new env with the ledger hash specified for startup
218 auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
219 boost::erase_all(ledgerHash, "\"");
220 Env env(
221 *this,
222 envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, std::nullopt),
223 nullptr,
225 auto const jrb = env.rpc("ledger", "current", "full")[jss::result];
226 BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97);
227 // in replace mode do not automatically accept the ledger being replayed
228
229 env.close();
230 auto const closed = env.rpc("ledger", "current", "full")[jss::result];
231 BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98);
232 BEAST_EXPECT(
233 closed[jss::ledger][jss::accountState].size() <=
234 sd.ledger[jss::ledger][jss::accountState].size());
235 }
236
237 void
239 {
240 testcase("Load and replay transaction by hash");
241 using namespace test::jtx;
242
243 // create a new env with the ledger hash specified for startup
244 auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
245 boost::erase_all(ledgerHash, "\"");
246 Env env(
247 *this,
248 envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, sd.trapTxHash),
249 nullptr,
251 auto const jrb = env.rpc("ledger", "current", "full")[jss::result];
252 BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97);
253 // in replace mode do not automatically accept the ledger being replayed
254
255 env.close();
256 auto const closed = env.rpc("ledger", "current", "full")[jss::result];
257 BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98);
258 BEAST_EXPECT(
259 closed[jss::ledger][jss::accountState].size() <=
260 sd.ledger[jss::ledger][jss::accountState].size());
261 }
262
263 void
265 {
266 testcase("Load and replay transaction by hash failure");
267 using namespace test::jtx;
268
269 // create a new env with the ledger hash specified for startup
270 auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
271 boost::erase_all(ledgerHash, "\"");
272 try
273 {
274 // will throw an exception, because we cannot load a ledger for
275 // replay when trapTxHash is set to an invalid transaction
276 Env const env(
277 *this,
278 envconfig(ledgerConfig, sd.dbPath, ledgerHash, StartUpType::Replay, ~sd.trapTxHash),
279 nullptr,
281 BEAST_EXPECT(false);
282 }
283 catch (std::runtime_error const&)
284 {
285 BEAST_EXPECT(true);
286 }
287 catch (...)
288 {
289 BEAST_EXPECT(false);
290 }
291 }
292
293 void
295 {
296 testcase("Load by keyword");
297 using namespace test::jtx;
298
299 // create a new env with the ledger "latest" specified for startup
300 Env env(
301 *this,
302 envconfig(ledgerConfig, sd.dbPath, "latest", StartUpType::Load, std::nullopt),
303 nullptr,
305 auto jrb = env.rpc("ledger", "current", "full")[jss::result];
306 BEAST_EXPECT(
307 sd.ledger[jss::ledger][jss::accountState].size() ==
308 jrb[jss::ledger][jss::accountState].size());
309 }
310
311 void
313 {
314 testcase("Load by index");
315 using namespace test::jtx;
316
317 // create a new env with specific ledger index at startup
318 Env env(
319 *this,
320 envconfig(ledgerConfig, sd.dbPath, "43", StartUpType::Load, std::nullopt),
321 nullptr,
323 auto jrb = env.rpc("ledger", "current", "full")[jss::result];
324 BEAST_EXPECT(
325 sd.ledger[jss::ledger][jss::accountState].size() ==
326 jrb[jss::ledger][jss::accountState].size());
327 }
328
329public:
330 void
331 run() override
332 {
333 beast::TempDir const td;
334 auto sd = setupLedger(td);
335
336 // test cases
337 testLoad(sd);
338 testBadFiles(sd);
339 testLoadByHash(sd);
340 testReplay(sd);
341 testReplayTx(sd);
343 testLoadLatest(sd);
344 testLoadIndex(sd);
345 }
346};
347
348BEAST_DEFINE_TESTSUITE(LedgerLoad, app, xrpl);
349
350} // namespace xrpl
RAII temporary directory.
Definition temp_dir.h:15
std::string path() const
Get the native path for the temporary directory.
Definition temp_dir.h:47
std::string file(std::string const &name) const
Get the native path for the a file.
Definition temp_dir.h:57
A testsuite class.
Definition suite.h:50
bool except(F &&f, String const &reason)
Definition suite.h:433
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
UInt size() const
Number of values in array or object.
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:507
void testLoadLatest(SetupData const &sd)
SetupData setupLedger(beast::TempDir const &td)
void testReplay(SetupData const &sd)
void testReplayTxFail(SetupData const &sd)
void run() override
Runs the suite.
static auto ledgerConfig(std::unique_ptr< Config > cfg, std::string const &dbPath, std::string const &ledger, StartUpType type, std::optional< uint256 > trapTxHash)
void testBadFiles(SetupData const &sd)
void testReplayTx(SetupData const &sd)
void testLoadByHash(SetupData const &sd)
void testLoadIndex(SetupData const &sd)
void testLoad(SetupData const &sd)
T close(T... args)
T empty(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
BaseUInt< 256 > uint256
Definition base_uint.h:562
StartUpType
Definition StartUpType.h:8
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
T to_string(T... args)