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{60};
198  auto const start = std::chrono::system_clock::now();
199 
200  while (true)
201  {
202  // Verify that the status object accurately
203  // reflects import progress.
204 
205  auto const completeShards =
206  shardStore->getShardInfo()->finalized();
207 
208  if (!completeShards.empty())
209  {
210  auto const result = env.rpc(
211  "json",
212  "node_to_shard",
213  to_string(jvParams))[jss::result];
214 
215  if (!importCompleted(shardStore, numberOfShards, result))
216  {
217  BEAST_EXPECT(result[jss::firstShardIndex] == 1);
218  BEAST_EXPECT(result[jss::lastShardIndex] == 10);
219  }
220  }
221 
222  if (boost::icl::contains(completeShards, 1))
223  {
224  auto const result = env.rpc(
225  "json",
226  "node_to_shard",
227  to_string(jvParams))[jss::result];
228 
229  BEAST_EXPECT(
230  result[jss::currentShardIndex] >= 1 ||
231  importCompleted(shardStore, numberOfShards, result));
232 
233  break;
234  }
235 
237  std::chrono::system_clock::now() - start > maxWait)
238  {
239  BEAST_EXPECTS(
240  false, "Import timeout: could just be a slow machine.");
241  break;
242  }
243  }
244 
245  // Wait for the import to complete
246  while (!boost::icl::contains(
247  shardStore->getShardInfo()->finalized(), 10))
248  {
250  std::chrono::system_clock::now() - start > maxWait)
251  {
252  BEAST_EXPECT(
253  importCompleted(shardStore, numberOfShards, result));
254  break;
255  }
256  }
257  }
258  }
259 
260  void
262  {
263  testcase("Stop");
264 
265  beast::temp_dir tempDir;
266 
267  jtx::Env env = [&] {
268  auto c = jtx::envconfig();
269  auto& section = c->section(ConfigSection::shardDatabase());
270  section.set("path", tempDir.path());
271  section.set("max_historical_shards", "20");
272  section.set("ledgers_per_shard", "256");
273  section.set("earliest_seq", "257");
274  auto& sectionNode = c->section(ConfigSection::nodeDatabase());
275  sectionNode.set("earliest_seq", "257");
276  sectionNode.set("ledgers_per_shard", "256");
277  c->setupControl(true, true, true);
278 
279  return jtx::Env(*this, std::move(c));
280  }();
281 
282  std::uint8_t const numberOfShards = 10;
283 
284  // Create some ledgers so that we can initiate a
285  // shard store database import.
286  for (int i = 0; i < env.app().getShardStore()->ledgersPerShard() *
287  (numberOfShards + 1);
288  ++i)
289  {
290  env.close();
291  }
292 
293  auto shardStore = env.app().getShardStore();
294  if (!BEAST_EXPECT(shardStore))
295  return;
296 
297  {
298  // Initiate a shard store import via the RPC
299  // interface.
300 
301  Json::Value jvParams;
302  jvParams[jss::action] = "start";
303 
304  auto const result = env.rpc(
305  "json", "node_to_shard", to_string(jvParams))[jss::result];
306 
307  BEAST_EXPECT(
308  result[jss::message] == "Database import initiated...");
309  }
310 
311  {
312  // Verify that the import is in progress with
313  // the node_to_shard status RPC command
314 
315  Json::Value jvParams;
316  jvParams[jss::action] = "status";
317 
318  auto const result = env.rpc(
319  "json", "node_to_shard", to_string(jvParams))[jss::result];
320 
321  BEAST_EXPECT(
322  result[jss::status] == "success" ||
323  importCompleted(shardStore, numberOfShards, result));
324 
325  std::chrono::seconds const maxWait{10};
326  auto const start = std::chrono::system_clock::now();
327 
328  while (shardStore->getShardInfo()->finalized().empty())
329  {
330  // Wait for at least one shard to complete
331 
333  std::chrono::system_clock::now() - start > maxWait)
334  {
335  BEAST_EXPECTS(
336  false, "Import timeout: could just be a slow machine.");
337  break;
338  }
339  }
340  }
341 
342  {
343  Json::Value jvParams;
344  jvParams[jss::action] = "stop";
345 
346  auto const result = env.rpc(
347  "json", "node_to_shard", to_string(jvParams))[jss::result];
348 
349  BEAST_EXPECT(
350  result[jss::message] == "Database import halt initiated..." ||
351  importCompleted(shardStore, numberOfShards, result));
352  }
353 
354  std::chrono::seconds const maxWait{10};
355  auto const start = std::chrono::system_clock::now();
356 
357  while (true)
358  {
359  // Wait until we can verify that the import has
360  // stopped
361 
362  Json::Value jvParams;
363  jvParams[jss::action] = "status";
364 
365  auto const result = env.rpc(
366  "json", "node_to_shard", to_string(jvParams))[jss::result];
367 
368  // When the import has stopped, polling the
369  // status returns an error
370  if (result.isMember(jss::error))
371  {
372  if (BEAST_EXPECT(result.isMember(jss::error_message)))
373  {
374  BEAST_EXPECT(
375  result[jss::error_message] ==
376  "Database import not running");
377  }
378 
379  break;
380  }
381 
383  std::chrono::system_clock::now() - start > maxWait)
384  {
385  BEAST_EXPECTS(
386  false, "Import timeout: could just be a slow machine.");
387  break;
388  }
389  }
390  }
391 
392  void
393  run() override
394  {
395  testDisabled();
396  testStart();
397  testStop();
398  }
399 };
400 
401 BEAST_DEFINE_TESTSUITE(NodeToShardRPC, rpc, ripple);
402 } // namespace test
403 } // namespace ripple
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::test::NodeToShardRPC_test::testStop
void testStop()
Definition: NodeToShardRPC_test.cpp:261
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:393
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::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:684
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)
std::chrono::system_clock::now
T now(T... args)