rippled
NodeToShardRPC_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2021 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/beast/unit_test.h>
21 #include <ripple/beast/utility/temp_dir.h>
22 #include <ripple/core/ConfigSections.h>
23 #include <ripple/nodestore/DatabaseShard.h>
24 #include <ripple/protocol/ErrorCodes.h>
25 #include <ripple/protocol/jss.h>
26 #include <test/jtx/Env.h>
27 
28 namespace ripple {
29 namespace test {
30 
31 class NodeToShardRPC_test : public beast::unit_test::suite
32 {
33  bool
35  NodeStore::DatabaseShard* shardStore,
36  std::uint8_t const numberOfShards,
37  Json::Value const& result)
38  {
39  auto const info = shardStore->getShardInfo();
40 
41  // Assume completed if the import isn't running
42  auto const completed =
43  result[jss::error_message] == "Database import not running";
44 
45  if (completed)
46  {
47  BEAST_EXPECT(
48  info->incomplete().size() + info->finalized().size() ==
49  numberOfShards);
50  }
51 
52  return completed;
53  }
54 
55 public:
56  void
58  {
59  testcase("Disabled");
60 
61  beast::temp_dir tempDir;
62 
63  jtx::Env env = [&] {
64  auto c = jtx::envconfig();
65  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
66  sectionNode.set("earliest_seq", "257");
67  sectionNode.set("ledgers_per_shard", "256");
68  c->setupControl(true, true, true);
69 
70  return jtx::Env(*this, std::move(c));
71  }();
72 
73  std::uint8_t const numberOfShards = 10;
74 
75  // Create some ledgers so that we can initiate a
76  // shard store database import.
77  for (int i = 0; i < 256 * (numberOfShards + 1); ++i)
78  {
79  env.close();
80  }
81 
82  {
83  auto shardStore = env.app().getShardStore();
84  if (!BEAST_EXPECT(!shardStore))
85  return;
86  }
87 
88  {
89  // Try the node_to_shard status RPC command. Should fail.
90 
91  Json::Value jvParams;
92  jvParams[jss::action] = "status";
93 
94  auto const result = env.rpc(
95  "json", "node_to_shard", to_string(jvParams))[jss::result];
96 
97  BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED);
98  }
99 
100  {
101  // Try to start a shard store import via the RPC
102  // interface. Should fail.
103 
104  Json::Value jvParams;
105  jvParams[jss::action] = "start";
106 
107  auto const result = env.rpc(
108  "json", "node_to_shard", to_string(jvParams))[jss::result];
109 
110  BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED);
111  }
112 
113  {
114  // Try the node_to_shard status RPC command. Should fail.
115 
116  Json::Value jvParams;
117  jvParams[jss::action] = "status";
118 
119  auto const result = env.rpc(
120  "json", "node_to_shard", to_string(jvParams))[jss::result];
121 
122  BEAST_EXPECT(result[jss::error_code] == rpcNOT_ENABLED);
123  }
124  }
125 
126  void
128  {
129  testcase("Start");
130 
131  beast::temp_dir tempDir;
132 
133  jtx::Env env = [&] {
134  auto c = jtx::envconfig();
135  auto& section = c->section(ConfigSection::shardDatabase());
136  section.set("path", tempDir.path());
137  section.set("max_historical_shards", "20");
138  section.set("ledgers_per_shard", "256");
139  section.set("earliest_seq", "257");
140  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
141  sectionNode.set("earliest_seq", "257");
142  sectionNode.set("ledgers_per_shard", "256");
143  c->setupControl(true, true, true);
144 
145  return jtx::Env(*this, std::move(c));
146  }();
147 
148  std::uint8_t const numberOfShards = 10;
149 
150  // Create some ledgers so that we can initiate a
151  // shard store database import.
152  for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() *
153  (numberOfShards + 1);
154  ++i)
155  {
156  env.close();
157  }
158 
159  auto shardStore = env.app().getShardStore();
160  if (!BEAST_EXPECT(shardStore))
161  return;
162 
163  {
164  // Initiate a shard store import via the RPC
165  // interface.
166 
167  Json::Value jvParams;
168  jvParams[jss::action] = "start";
169 
170  auto const result = env.rpc(
171  "json", "node_to_shard", to_string(jvParams))[jss::result];
172 
173  BEAST_EXPECT(
174  result[jss::message] == "Database import initiated...");
175  }
176 
177  while (!shardStore->getDatabaseImportSequence())
178  {
179  // Wait until the import starts
181  }
182 
183  {
184  // Verify that the import is in progress with
185  // the node_to_shard status RPC command
186 
187  Json::Value jvParams;
188  jvParams[jss::action] = "status";
189 
190  auto const result = env.rpc(
191  "json", "node_to_shard", to_string(jvParams))[jss::result];
192 
193  BEAST_EXPECT(
194  result[jss::status] == "success" ||
195  importCompleted(shardStore, numberOfShards, result));
196 
197  std::chrono::seconds const maxWait{180};
198 
199  {
200  auto const start = std::chrono::system_clock::now();
201  while (true)
202  {
203  // Verify that the status object accurately
204  // reflects import progress.
205 
206  auto const completeShards =
207  shardStore->getShardInfo()->finalized();
208 
209  if (!completeShards.empty())
210  {
211  auto const result = env.rpc(
212  "json",
213  "node_to_shard",
214  to_string(jvParams))[jss::result];
215 
216  if (!importCompleted(
217  shardStore, numberOfShards, result))
218  {
219  BEAST_EXPECT(result[jss::firstShardIndex] == 1);
220  BEAST_EXPECT(result[jss::lastShardIndex] == 10);
221  }
222  }
223 
224  if (boost::icl::contains(completeShards, 1))
225  {
226  auto const result = env.rpc(
227  "json",
228  "node_to_shard",
229  to_string(jvParams))[jss::result];
230 
231  BEAST_EXPECT(
232  result[jss::currentShardIndex] >= 1 ||
234  shardStore, numberOfShards, result));
235 
236  break;
237  }
238 
241  std::chrono::system_clock::now() - start > maxWait)
242  {
243  BEAST_EXPECTS(
244  false,
245  "Import timeout: could just be a slow machine.");
246  break;
247  }
248  }
249  }
250 
251  {
252  // Wait for the import to complete
253  auto const start = std::chrono::system_clock::now();
254  while (!boost::icl::contains(
255  shardStore->getShardInfo()->finalized(), 10))
256  {
259  std::chrono::system_clock::now() - start > maxWait)
260  {
261  BEAST_EXPECT(importCompleted(
262  shardStore, numberOfShards, result));
263  break;
264  }
265  }
266  }
267  }
268  }
269 
270  void
272  {
273  testcase("Stop");
274 
275  beast::temp_dir tempDir;
276 
277  jtx::Env env = [&] {
278  auto c = jtx::envconfig();
279  auto& section = c->section(ConfigSection::shardDatabase());
280  section.set("path", tempDir.path());
281  section.set("max_historical_shards", "20");
282  section.set("ledgers_per_shard", "256");
283  section.set("earliest_seq", "257");
284  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
285  sectionNode.set("earliest_seq", "257");
286  sectionNode.set("ledgers_per_shard", "256");
287  c->setupControl(true, true, true);
288 
289  return jtx::Env(
290  *this, std::move(c), nullptr, beast::severities::kDisabled);
291  }();
292 
293  std::uint8_t const numberOfShards = 10;
294 
295  // Create some ledgers so that we can initiate a
296  // shard store database import.
297  for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() *
298  (numberOfShards + 1);
299  ++i)
300  {
301  env.close();
302  }
303 
304  auto shardStore = env.app().getShardStore();
305  if (!BEAST_EXPECT(shardStore))
306  return;
307 
308  {
309  // Initiate a shard store import via the RPC
310  // interface.
311 
312  Json::Value jvParams;
313  jvParams[jss::action] = "start";
314 
315  auto const result = env.rpc(
316  "json", "node_to_shard", to_string(jvParams))[jss::result];
317 
318  BEAST_EXPECT(
319  result[jss::message] == "Database import initiated...");
320  }
321 
322  {
323  // Verify that the import is in progress with
324  // the node_to_shard status RPC command
325 
326  Json::Value jvParams;
327  jvParams[jss::action] = "status";
328 
329  auto const result = env.rpc(
330  "json", "node_to_shard", to_string(jvParams))[jss::result];
331 
332  BEAST_EXPECT(
333  result[jss::status] == "success" ||
334  importCompleted(shardStore, numberOfShards, result));
335 
336  std::chrono::seconds const maxWait{30};
337  auto const start = std::chrono::system_clock::now();
338 
339  while (shardStore->getShardInfo()->finalized().empty())
340  {
341  // Wait for at least one shard to complete
342 
344  std::chrono::system_clock::now() - start > maxWait)
345  {
346  BEAST_EXPECTS(
347  false, "Import timeout: could just be a slow machine.");
348  break;
349  }
350  }
351  }
352 
353  {
354  Json::Value jvParams;
355  jvParams[jss::action] = "stop";
356 
357  auto const result = env.rpc(
358  "json", "node_to_shard", to_string(jvParams))[jss::result];
359 
360  BEAST_EXPECT(
361  result[jss::message] == "Database import halt initiated..." ||
362  importCompleted(shardStore, numberOfShards, result));
363  }
364 
365  std::chrono::seconds const maxWait{30};
366  auto const start = std::chrono::system_clock::now();
367 
368  while (true)
369  {
370  // Wait until we can verify that the import has
371  // stopped
372 
373  Json::Value jvParams;
374  jvParams[jss::action] = "status";
375 
376  auto const result = env.rpc(
377  "json", "node_to_shard", to_string(jvParams))[jss::result];
378 
379  // When the import has stopped, polling the
380  // status returns an error
381  if (result.isMember(jss::error))
382  {
383  if (BEAST_EXPECT(result.isMember(jss::error_message)))
384  {
385  BEAST_EXPECT(
386  result[jss::error_message] ==
387  "Database import not running");
388  }
389 
390  break;
391  }
392 
394  std::chrono::system_clock::now() - start > maxWait)
395  {
396  BEAST_EXPECTS(
397  false, "Import timeout: could just be a slow machine.");
398  break;
399  }
400  }
401  }
402 
403  void
404  run() override
405  {
406  testDisabled();
407  testStart();
408  testStop();
409  }
410 };
411 
412 BEAST_DEFINE_TESTSUITE_MANUAL(NodeToShardRPC, rpc, ripple);
413 } // namespace test
414 } // namespace ripple
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::test::NodeToShardRPC_test::testStop
void testStop()
Definition: NodeToShardRPC_test.cpp:271
beast::severities::kDisabled
@ kDisabled
Definition: Journal.h:41
ripple::ConfigSection::shardDatabase
static std::string shardDatabase()
Definition: ConfigSections.h:38
std::chrono::milliseconds
ripple::Application::getShardStore
virtual NodeStore::DatabaseShard * getShardStore()=0
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::test::NodeToShardRPC_test::testStart
void testStart()
Definition: NodeToShardRPC_test.cpp:127
ripple::NodeStore::DatabaseShard
A collection of historical shards.
Definition: DatabaseShard.h:37
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::rpcNOT_ENABLED
@ rpcNOT_ENABLED
Definition: ErrorCodes.h:59
ripple::test::NodeToShardRPC_test
Definition: NodeToShardRPC_test.cpp:31
std::uint8_t
ripple::test::NodeToShardRPC_test::testDisabled
void testDisabled()
Definition: NodeToShardRPC_test.cpp:57
beast::temp_dir::path
std::string path() const
Get the native path for the temporary directory.
Definition: temp_dir.h:66
ripple::test::NodeToShardRPC_test::importCompleted
bool importCompleted(NodeStore::DatabaseShard *shardStore, std::uint8_t const numberOfShards, Json::Value const &result)
Definition: NodeToShardRPC_test.cpp:34
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
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
ripple::test::NodeToShardRPC_test::run
void run() override
Definition: NodeToShardRPC_test.cpp:404
ripple::NodeStore::DatabaseShard::getShardInfo
virtual std::unique_ptr< ShardInfo > getShardInfo() const =0
Query information about shards held.
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::ConfigSection::nodeDatabase
static std::string nodeDatabase()
Definition: ConfigSections.h:33
beast::temp_dir
RAII temporary directory.
Definition: temp_dir.h:33
ripple::test::BEAST_DEFINE_TESTSUITE_MANUAL
BEAST_DEFINE_TESTSUITE_MANUAL(LedgerReplayerLong, app, ripple)
ripple::NodeStore::Database::ledgersPerShard
std::uint32_t ledgersPerShard() const noexcept
Definition: Database.h:230
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
Json::Value
Represents a JSON value.
Definition: json_value.h:145
std::chrono::system_clock::now
T now(T... args)