rippled
ShardArchiveHandler.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2014 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/app/misc/NetworkOPs.h>
21 #include <ripple/basics/Archive.h>
22 #include <ripple/core/ConfigSections.h>
23 #include <ripple/nodestore/DatabaseShard.h>
24 #include <ripple/rpc/ShardArchiveHandler.h>
25 #include <ripple/basics/BasicConfig.h>
26 #include <ripple/rpc/impl/Handler.h>
27 #include <ripple/protocol/ErrorCodes.h>
28 
29 #include <memory>
30 
31 namespace ripple {
32 namespace RPC {
33 
34 using namespace boost::filesystem;
35 using namespace std::chrono_literals;
36 
39 
40 boost::filesystem::path
42 {
43  return get(config.section(
44  ConfigSection::shardDatabase()), "download_path",
46  "path", "")) / "download";
47 }
48 
49 auto
51 {
52  std::lock_guard lock(instance_mutex_);
53 
54  return instance_;
55 }
56 
57 auto
59  Stoppable& parent) -> pointer
60 {
61  std::lock_guard lock(instance_mutex_);
62  assert(!instance_);
63 
64  instance_.reset(new ShardArchiveHandler(app, parent));
65 
66  return instance_;
67 }
68 
69 auto
71 {
72  std::lock_guard lock(instance_mutex_);
73  assert(!instance_);
74 
75  instance_.reset(new ShardArchiveHandler(app, parent, true));
76 
77  return instance_;
78 }
79 
80 bool
82 {
83  std::lock_guard lock(instance_mutex_);
84 
85  return instance_.get() != nullptr;
86 }
87 
89  Application& app,
90  Stoppable& parent,
91  bool recovery)
92  : Stoppable("ShardArchiveHandler", parent)
93  , app_(app)
94  , j_(app.journal("ShardArchiveHandler"))
95  , downloadDir_(getDownloadDirectory(app.config()))
96  , timer_(app_.getIOService())
97  , process_(false)
98 {
99  assert(app_.getShardStore());
100 
101  if(recovery)
102  downloader_.reset(new DatabaseDownloader (
103  app_.getIOService(), j_, app_.config()));
104 }
105 
106 bool
108 {
109  try
110  {
111  create_directories(downloadDir_);
112 
113  sqliteDB_ = std::make_unique<DatabaseCon>(
114  downloadDir_ ,
115  stateDBName,
118  }
119  catch(std::exception const& e)
120  {
121  JLOG(j_.error()) << "exception: " << e.what()
122  << " in function: " << __func__;
123 
124  return false;
125  }
126 
127  return true;
128 }
129 
130 bool
132 {
133  try
134  {
135  using namespace boost::filesystem;
136 
137  assert(exists(downloadDir_ / stateDBName) &&
138  is_regular_file(downloadDir_ / stateDBName));
139 
140  sqliteDB_ = std::make_unique<DatabaseCon>(
141  downloadDir_,
142  stateDBName,
145 
146  auto& session{sqliteDB_->getSession()};
147 
148  soci::rowset<soci::row> rs = (session.prepare
149  << "SELECT * FROM State;");
150 
152 
153  for (auto it = rs.begin(); it != rs.end(); ++it)
154  {
155  parsedURL url;
156 
157  if (!parseUrl(url, it->get<std::string>(1)))
158  {
159  JLOG(j_.error()) << "Failed to parse url: "
160  << it->get<std::string>(1);
161 
162  continue;
163  }
164 
165  add(it->get<int>(0), std::move(url), lock);
166  }
167 
168  // Failed to load anything
169  // from the state database.
170  if(archives_.empty())
171  {
172  release();
173  return false;
174  }
175  }
176  catch(std::exception const& e)
177  {
178  JLOG(j_.error()) << "exception: " << e.what()
179  << " in function: " << __func__;
180 
181  return false;
182  }
183 
184  return true;
185 }
186 
187 void
189 {
191 
192  if (downloader_)
193  {
194  downloader_->onStop();
195  downloader_.reset();
196  }
197 
198  stopped();
199 }
200 
201 bool
204 {
206 
207  if (!add(shardIndex, std::forward<parsedURL>(url.first), lock))
208  return false;
209 
210  auto& session{sqliteDB_->getSession()};
211 
212  session << "INSERT INTO State VALUES (:index, :url);",
213  soci::use(shardIndex),
214  soci::use(url.second);
215 
216  return true;
217 }
218 
219 bool
222 {
223  if (process_)
224  {
225  JLOG(j_.error()) <<
226  "Download and import already in progress";
227  return false;
228  }
229 
230  auto const it {archives_.find(shardIndex)};
231  if (it != archives_.end())
232  return url == it->second;
233 
234  if (!app_.getShardStore()->prepareShard(shardIndex))
235  return false;
236 
237  archives_.emplace(shardIndex, std::move(url));
238 
239  return true;
240 }
241 
242 bool
244 {
245  std::lock_guard lock(m_);
246  if (!app_.getShardStore())
247  {
248  JLOG(j_.error()) <<
249  "No shard store available";
250  return false;
251  }
252  if (process_)
253  {
254  JLOG(j_.warn()) <<
255  "Archives already being processed";
256  return false;
257  }
258  if (archives_.empty())
259  {
260  JLOG(j_.warn()) <<
261  "No archives to process";
262  return false;
263  }
264 
265  try
266  {
267  // Create temp root download directory
268  create_directories(downloadDir_);
269 
270  if (!downloader_)
271  {
272  // will throw if can't initialize ssl context
273  downloader_ = std::make_shared<DatabaseDownloader>(
274  app_.getIOService(), j_, app_.config());
275  }
276  }
277  catch (std::exception const& e)
278  {
279  JLOG(j_.error()) <<
280  "exception: " << e.what();
281  return false;
282  }
283 
284  return next(lock);
285 }
286 
287 void
289 {
291  doRelease(lock);
292 }
293 
294 bool
296 {
297  if (archives_.empty())
298  {
299  doRelease(l);
300  return false;
301  }
302 
303  // Create a temp archive directory at the root
304  auto const shardIndex {archives_.begin()->first};
305  auto const dstDir {downloadDir_ / std::to_string(shardIndex)};
306  try
307  {
308  create_directory(dstDir);
309  }
310  catch (std::exception const& e)
311  {
312  JLOG(j_.error()) <<
313  "exception: " << e.what();
314  remove(l);
315  return next(l);
316  }
317 
318  // Download the archive. Process in another thread
319  // to prevent holding up the lock if the downloader
320  // sleeps.
321  auto const& url {archives_.begin()->second};
323  jtCLIENT, "ShardArchiveHandler",
324  [this, ptr = shared_from_this(), url, dstDir](Job&)
325  {
326  if (!downloader_->download(
327  url.domain,
328  std::to_string(url.port.get_value_or(443)),
329  url.path,
330  11,
331  dstDir / "archive.tar.lz4",
333  ptr, std::placeholders::_1)))
334  {
335  std::lock_guard<std::mutex> l(m_);
336  remove(l);
337  next(l);
338  }
339  });
340 
341  process_ = true;
342  return true;
343 }
344 
345 void
347 {
348  {
349  std::lock_guard lock(m_);
350  try
351  {
352  if (!is_regular_file(dstPath))
353  {
354  auto ar {archives_.begin()};
355  JLOG(j_.error()) <<
356  "Downloading shard id " << ar->first <<
357  " form URL " << ar->second.domain << ar->second.path;
358  remove(lock);
359  next(lock);
360  return;
361  }
362  }
363  catch (std::exception const& e)
364  {
365  JLOG(j_.error()) <<
366  "exception: " << e.what();
367  remove(lock);
368  next(lock);
369  return;
370  }
371  }
372 
373  // Process in another thread to not hold up the IO service
375  jtCLIENT, "ShardArchiveHandler",
376  [=, dstPath = std::move(dstPath), ptr = shared_from_this()](Job&)
377  {
378  // If not synced then defer and retry
379  auto const mode {ptr->app_.getOPs().getOperatingMode()};
380  if (mode != OperatingMode::FULL)
381  {
382  std::lock_guard lock(m_);
383  timer_.expires_from_now(static_cast<std::chrono::seconds>(
384  (static_cast<std::size_t>(OperatingMode::FULL)
385  - static_cast<std::size_t>(mode)) * 10));
386  timer_.async_wait(
387  [=, dstPath = std::move(dstPath), ptr = std::move(ptr)]
388  (boost::system::error_code const& ec)
389  {
390  if (ec != boost::asio::error::operation_aborted)
391  ptr->complete(std::move(dstPath));
392  });
393  }
394  else
395  {
396  ptr->process(dstPath);
397  std::lock_guard lock(m_);
398  remove(lock);
399  next(lock);
400  }
401  });
402 }
403 
404 void
405 ShardArchiveHandler::process(path const& dstPath)
406 {
407  std::uint32_t shardIndex;
408  {
409  std::lock_guard lock(m_);
410  shardIndex = archives_.begin()->first;
411  }
412 
413  auto const shardDir {dstPath.parent_path() / std::to_string(shardIndex)};
414  try
415  {
416  // Extract the downloaded archive
417  extractTarLz4(dstPath, dstPath.parent_path());
418 
419  // The extracted root directory name must match the shard index
420  if (!is_directory(shardDir))
421  {
422  JLOG(j_.error()) <<
423  "Shard " << shardIndex <<
424  " mismatches archive shard directory";
425  return;
426  }
427  }
428  catch (std::exception const& e)
429  {
430  JLOG(j_.error()) <<
431  "exception: " << e.what();
432  return;
433  }
434 
435  // Import the shard into the shard store
436  if (!app_.getShardStore()->importShard(shardIndex, shardDir))
437  {
438  JLOG(j_.error()) <<
439  "Importing shard " << shardIndex;
440  return;
441  }
442 
443  JLOG(j_.debug()) <<
444  "Shard " << shardIndex << " downloaded and imported";
445 }
446 
447 void
449 {
450  auto const shardIndex {archives_.begin()->first};
451  app_.getShardStore()->removePreShard(shardIndex);
452  archives_.erase(shardIndex);
453 
454  auto& session{sqliteDB_->getSession()};
455 
456  session << "DELETE FROM State WHERE ShardIndex = :index;",
457  soci::use(shardIndex);
458 
459  auto const dstDir {downloadDir_ / std::to_string(shardIndex)};
460  try
461  {
462  remove_all(dstDir);
463  }
464  catch (std::exception const& e)
465  {
466  JLOG(j_.error()) <<
467  "exception: " << e.what();
468  }
469 }
470 
471 void
473 {
474  process_ = false;
475 
476  timer_.cancel();
477  for (auto const& ar : archives_)
478  app_.getShardStore()->removePreShard(ar.first);
479  archives_.clear();
480 
481  {
482  auto& session{sqliteDB_->getSession()};
483 
484  session << "DROP TABLE State;";
485  }
486 
487  sqliteDB_.reset();
488 
489  // Remove temp root download directory
490  try
491  {
492  remove_all(downloadDir_);
493  }
494  catch (std::exception const& e)
495  {
496  JLOG(j_.error()) << "exception: " << e.what()
497  << " in function: " << __func__;
498  }
499 
500  downloader_.reset();
501 }
502 
503 } // RPC
504 } // ripple
ripple::Application
Definition: Application.h:85
ripple::RPC::ShardArchiveHandler::getDownloadDirectory
static boost::filesystem::path getDownloadDirectory(Config const &config)
Definition: ShardArchiveHandler.cpp:41
ripple::RPC::ShardArchiveHandler::downloader_
std::shared_ptr< DatabaseDownloader > downloader_
Definition: ShardArchiveHandler.h:133
ripple::RPC::ShardArchiveHandler::downloadDir_
const boost::filesystem::path downloadDir_
Definition: ShardArchiveHandler.h:134
std::bind
T bind(T... args)
std::string
STL class.
std::shared_ptr
STL class.
ripple::RPC::ShardArchiveHandler::start
bool start()
Starts downloading and importing archives.
Definition: ShardArchiveHandler.cpp:243
ripple::jtCLIENT
@ jtCLIENT
Definition: Job.h:49
std::exception
STL class.
ripple::RPC::ShardArchiveHandler::add
bool add(std::uint32_t shardIndex, std::pair< parsedURL, std::string > &&url)
Definition: ShardArchiveHandler.cpp:202
ripple::Stoppable::stopped
void stopped()
Called by derived classes to indicate that the stoppable has stopped.
Definition: Stoppable.cpp:71
ripple::parsedURL
Definition: StringUtilities.h:122
std::pair
ripple::RPC::ShardArchiveHandler::recoverInstance
static pointer recoverInstance(Application &app, Stoppable &parent)
Definition: ShardArchiveHandler.cpp:70
ripple::ConfigSection::shardDatabase
static std::string shardDatabase()
Definition: ConfigSections.h:33
ripple::RPC::ShardArchiveHandler::pointer
std::shared_ptr< ShardArchiveHandler > pointer
Definition: ShardArchiveHandler.h:42
ripple::RPC::ShardArchiveHandler::onStop
void onStop() override
Override called when the stop notification is issued.
Definition: ShardArchiveHandler.cpp:188
std::chrono::seconds
ripple::RPC::ShardArchiveHandler::initFromDB
bool initFromDB()
Definition: ShardArchiveHandler.cpp:131
beast::Journal::warn
Stream warn() const
Definition: Journal.h:302
ripple::RPC::ShardArchiveHandler::doRelease
void doRelease(std::lock_guard< std::mutex > const &)
Definition: ShardArchiveHandler.cpp:472
std::lock_guard
STL class.
ripple::Application::getShardStore
virtual NodeStore::DatabaseShard * getShardStore()=0
ripple::ShardArchiveHandlerDBInit
static constexpr std::array< char const *, 3 > ShardArchiveHandlerDBInit
Definition: DBInit.h:197
ripple::JobQueue::addJob
bool addJob(JobType type, std::string const &name, JobHandler &&jobHandler)
Adds a job to the JobQueue.
Definition: JobQueue.h:156
ripple::extractTarLz4
void extractTarLz4(boost::filesystem::path const &src, boost::filesystem::path const &dst)
Extract a tar archive compressed with lz4.
Definition: Archive.cpp:29
ripple::DownloaderDBPragma
static constexpr std::array< char const *, 2 > DownloaderDBPragma
Definition: DBInit.h:190
ripple::RPC::ShardArchiveHandler::timer_
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_
Definition: ShardArchiveHandler.h:135
ripple::RPC::ShardArchiveHandler::instance_
static pointer instance_
Definition: ShardArchiveHandler.h:127
ripple::Stoppable
Provides an interface for starting and stopping.
Definition: Stoppable.h:200
ripple::RPC::ShardArchiveHandler::release
void release()
Definition: ShardArchiveHandler.cpp:288
ripple::RPC::ShardArchiveHandler::process
void process(boost::filesystem::path const &dstPath)
Definition: ShardArchiveHandler.cpp:405
std::enable_shared_from_this< ShardArchiveHandler >::shared_from_this
T shared_from_this(T... args)
ripple::RPC::ShardArchiveHandler::remove
void remove(std::lock_guard< std::mutex > &)
Definition: ShardArchiveHandler.cpp:448
ripple::Config
Definition: Config.h:67
ripple::Application::config
virtual Config & config()=0
std::to_string
T to_string(T... args)
ripple::Application::getJobQueue
virtual JobQueue & getJobQueue()=0
ripple::parseUrl
bool parseUrl(parsedURL &pUrl, std::string const &strUrl)
Definition: StringUtilities.cpp:53
beast::Journal::error
Stream error() const
Definition: Journal.h:307
ripple::RPC::ShardArchiveHandler::sqliteDB_
std::unique_ptr< DatabaseCon > sqliteDB_
Definition: ShardArchiveHandler.h:132
ripple::Job
Definition: Job.h:83
ripple::RPC::ShardArchiveHandler::complete
void complete(boost::filesystem::path dstPath)
Definition: ShardArchiveHandler.cpp:346
std::uint32_t
ripple::RPC::ShardArchiveHandler::j_
const beast::Journal j_
Definition: ShardArchiveHandler.h:131
memory
ripple::Application::getIOService
virtual boost::asio::io_service & getIOService()=0
ripple::stateDBName
static constexpr auto stateDBName
Definition: DBInit.h:186
ripple::NodeStore::DatabaseShard::importShard
virtual bool importShard(std::uint32_t shardIndex, boost::filesystem::path const &srcDir)=0
Import a shard into the shard database.
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::RPC::ShardArchiveHandler::archives_
std::map< std::uint32_t, parsedURL > archives_
Definition: ShardArchiveHandler.h:137
ripple::NodeStore::DatabaseShard::prepareShard
virtual bool prepareShard(std::uint32_t shardIndex)=0
Prepare a shard index to be imported into the database.
ripple::RPC::ShardArchiveHandler::app_
Application & app_
Definition: ShardArchiveHandler.h:130
ripple::RPC::ShardArchiveHandler::hasInstance
static bool hasInstance()
Definition: ShardArchiveHandler.cpp:81
ripple::RPC::ShardArchiveHandler
Handles the download and import one or more shard archives.
Definition: ShardArchiveHandler.h:36
ripple::RPC::ShardArchiveHandler::ShardArchiveHandler
ShardArchiveHandler()=delete
std::mutex
STL class.
beast::Journal::debug
Stream debug() const
Definition: Journal.h:292
ripple::RPC::ShardArchiveHandler::instance_mutex_
static std::mutex instance_mutex_
Definition: ShardArchiveHandler.h:126
std::size_t
ripple::RPC::ShardArchiveHandler::m_
std::mutex m_
Definition: ShardArchiveHandler.h:129
ripple::DatabaseDownloader
Definition: DatabaseDownloader.h:28
ripple::NodeStore::DatabaseShard::removePreShard
virtual void removePreShard(std::uint32_t shardIndex)=0
Remove a previously prepared shard index for import.
ripple::RPC::ShardArchiveHandler::process_
bool process_
Definition: ShardArchiveHandler.h:136
ripple::RPC::ShardArchiveHandler::init
bool init()
Definition: ShardArchiveHandler.cpp:107
ripple::RPC::ShardArchiveHandler::getInstance
static pointer getInstance()
Definition: ShardArchiveHandler.cpp:50
std::exception::what
T what(T... args)
ripple::RPC::ShardArchiveHandler::next
bool next(std::lock_guard< std::mutex > &l)
Definition: ShardArchiveHandler.cpp:295
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:124
ripple::BasicConfig::section
Section & section(std::string const &name)
Returns the section with the given name.
Definition: BasicConfig.cpp:140
ripple::OperatingMode::FULL
@ FULL
we have the ledger and can even validate