Kagome
Polkadot Runtime Engine in C++17
kagome_db_editor.cpp
Go to the documentation of this file.
1 #include <boost/throw_exception.hpp>
2 #include <chrono>
3 #include <fstream>
4 #include <string_view>
5 #include <thread>
6 
7 #if defined(BACKWARD_HAS_BACKTRACE)
8 #include <backward.hpp>
9 #endif
10 
11 #undef TRUE
12 #undef FALSE
13 
14 #include <boost/algorithm/string/predicate.hpp>
15 #include <boost/di.hpp>
16 #include <soralog/impl/configurator_from_yaml.hpp>
17 
23 #include "common/outcome_throw.hpp"
35 #include "utils/profiler.hpp"
36 
37 namespace di = boost::di;
38 using namespace std::chrono_literals;
39 using namespace kagome;
40 using namespace storage::trie;
41 
42 template <class T>
43 using sptr = std::shared_ptr<T>;
44 
45 template <typename T>
46 struct is_optional : std::false_type {};
47 
48 template <typename T>
49 struct is_optional<typename std::optional<T>> : std::true_type {};
50 
51 template <typename T>
52 inline auto check(T &&res) {
53  if (not res.has_value()) {
54  if constexpr (is_optional<T>::value) {
55  throw std::runtime_error("No value");
56  } else {
57  kagome::common::raise(res.error());
58  }
59  }
60  return std::forward<T>(res);
61 }
62 
63 namespace {
64  std::string embedded_config(R"(
65 # ----------------
66 sinks:
67  - name: console
68  type: console
69  thread: none
70  color: false
71  latency: 0
72 groups:
73  - name: main
74  sink: console
75  level: trace
76  is_fallback: true
77  children:
78  - name: kagome-db-editor
79  - name: trie
80  level: debug
81  - name: storage
82  - name: changes_trie
83  - name: blockchain
84  - name: profile
85 # ----------------
86 )");
87 }
88 
89 class Configurator : public soralog::ConfiguratorFromYAML {
90  public:
91  Configurator() : ConfiguratorFromYAML(embedded_config) {}
92 };
93 
94 enum ArgNum : uint8_t { DB_PATH = 1, STATE_HASH, MODE };
95 enum Command : uint8_t { COMPACT, DUMP };
96 
97 void usage() {
98  std::string help(R"(
99 Kagome DB Editor
100 Usage:
101  kagome-db-editor <db-path> <root-state> <command>
102 
103  <db-path> full or relative path to kagome database. It is usually path
104  polkadot/db inside base path set in kagome options.
105  <root-state> root state hash in 0x prefixed hex format. [Optional]
106  <command>
107  dump: dumps the state from the DB to file hex_full_state.yaml in
108  format ready for use in polkadot-test.
109  compact: compacts the kagome DB. Leaves only keys of the state passed
110  as an arguments. Removes all other keys. [Default]
111 
112 Example:
113  kagome-db-editor base-path/polkadot/db 0x1e22e dump
114  kagome-db-editor base-path/polkadot/db
115 )");
116  std::cout << help;
117 };
118 
119 outcome::result<std::unique_ptr<PersistentTrieBatch>> persistent_batch(
120  const std::unique_ptr<TrieStorageImpl> &trie, const RootHash &hash) {
121  OUTCOME_TRY(batch, trie->getPersistentBatchAt(hash));
122  auto cursor = batch->trieCursor();
123  auto res = check(cursor->next());
124  int count = 0;
125  auto log = log::createLogger("main", "kagome-db-editor");
126  {
127  TicToc t1("Process state.", log);
128  while (cursor->key().has_value()) {
129  count++;
130  res = check(cursor->next());
131  }
132  }
133  log->trace("{} keys were processed at the state.", ++count);
134  return std::move(batch);
135 }
136 
138  const std::unique_ptr<PersistentTrieBatch> &batch,
139  std::set<RootHash> &hashes) {
140  auto log = log::createLogger("main", "kagome-db-editor");
141 
142  const auto &child_prefix = storage::kChildStorageDefaultPrefix;
143  auto cursor = batch->trieCursor();
144  auto res = cursor->seekUpperBound(child_prefix);
145  if (res.has_value()) {
146  auto key = cursor->key();
147  while (key.has_value() && boost::starts_with(key.value(), child_prefix)) {
148  if (auto value_res = batch->tryGet(key.value());
149  value_res.has_value() && value_res.value().has_value()) {
150  auto &value_opt = value_res.value();
151  log->trace("Found child root hash {}", value_opt.value().get().toHex());
152  hashes.insert(
153  common::Hash256::fromSpan(value_opt.value().get()).value());
154  }
155  res = cursor->next();
156  key = cursor->key();
157  }
158  }
159 }
160 
161 auto is_hash(const char *s) {
162  return std::strlen(s) == common::Hash256::size() * 2 + 2
163  && std::equal(s, s + 2, "0x");
164 };
165 
166 int main(int argc, char *argv[]) {
167 #if defined(BACKWARD_HAS_BACKTRACE)
168  backward::SignalHandling sh;
169 #endif
170 
171  Command cmd;
172  if (argc == 2 or (argc == 3 && is_hash(argv[2]))
173  or (argc == 4 and std::strcmp(argv[MODE], "compact") == 0)) {
174  cmd = COMPACT;
175  } else if (argc == 4 and std::strcmp(argv[MODE], "dump") == 0) {
176  cmd = DUMP;
177  } else {
178  usage();
179  return 0;
180  }
181  std::optional<RootHash> target_state_param;
182  if (argc > 2) {
183  if (!is_hash(argv[2])) {
184  std::cout << "ERROR: Invalid state hash\n";
185  usage();
186  return -1;
187  }
188  target_state_param = RootHash::fromHexWithPrefix(argv[2]).value();
189  }
190 
191  auto logging_system = std::make_shared<soralog::LoggingSystem>(
192  std::make_shared<Configurator>());
193  std::ignore = logging_system->configure();
194  log::setLoggingSystem(logging_system);
195 
196  auto log = log::createLogger("main", "kagome-db-editor");
197 
199  bool need_additional_compaction = false;
200  {
201  auto factory = std::make_shared<PolkadotTrieFactoryImpl>();
202 
203  std::shared_ptr<storage::RocksDB> storage;
204  try {
205  storage =
206  storage::RocksDB::create(argv[DB_PATH], rocksdb::Options()).value();
207  } catch (std::system_error &e) {
208  log->error("{}", e.what());
209  usage();
210  return 0;
211  }
212 
213  auto injector = di::make_injector(
214  di::bind<TrieSerializer>.template to([](const auto &injector) {
215  return std::make_shared<TrieSerializerImpl>(
216  injector.template create<sptr<PolkadotTrieFactory>>(),
217  injector.template create<sptr<Codec>>(),
218  injector.template create<sptr<TrieStorageBackend>>());
219  }),
220  di::bind<TrieStorageBackend>.template to(
221  [&storage, &prefix](const auto &) {
222  auto backend =
223  std::make_shared<TrieStorageBackendImpl>(storage, prefix);
224  return backend;
225  }),
226  di::bind<storage::changes_trie::ChangesTracker>.template to<storage::changes_trie::StorageChangesTrackerImpl>(),
227  di::bind<Codec>.template to<PolkadotCodec>(),
228  di::bind<PolkadotTrieFactory>.to(factory),
229  di::bind<crypto::Hasher>.template to<crypto::HasherImpl>(),
230  di::bind<blockchain::BlockHeaderRepository>.template to<blockchain::BlockHeaderRepositoryImpl>(),
231  di::bind<network::ExtrinsicObserver>.template to<network::ExtrinsicObserverImpl>());
232 
233  auto hasher = injector.template create<sptr<crypto::Hasher>>();
234 
235  auto block_storage =
236  check(blockchain::BlockStorageImpl::create({}, storage, hasher))
237  .value();
238 
239  auto block_tree_leaf_hashes =
240  check(block_storage->getBlockTreeLeaves()).value();
241 
242  BOOST_ASSERT_MSG(not block_tree_leaf_hashes.empty(),
243  "Must be known or calculated at least one leaf");
244 
245  // Find the least and best leaf
246  std::set<primitives::BlockInfo> leafs;
247  primitives::BlockInfo least_leaf(
248  std::numeric_limits<primitives::BlockNumber>::max(), {});
249  primitives::BlockInfo best_leaf(
250  std::numeric_limits<primitives::BlockNumber>::min(), {});
251  for (auto hash : block_tree_leaf_hashes) {
252  auto number = check(check(block_storage->getBlockHeader(hash)).value())
253  .value()
254  .number;
255  const auto &leaf = *leafs.emplace(number, hash).first;
256  SL_TRACE(log, "Leaf {} found", leaf);
257  if (leaf.number <= least_leaf.number) {
258  least_leaf = leaf;
259  }
260  if (leaf.number >= best_leaf.number) {
261  best_leaf = leaf;
262  }
263  }
264 
265  primitives::BlockInfo last_finalized_block;
266  primitives::BlockHeader last_finalized_block_header;
267  storage::trie::RootHash last_finalized_block_state_root;
268  storage::trie::RootHash after_finalized_block_state_root;
269 
270  std::set<primitives::BlockInfo> to_remove;
271 
272  // Backward search of finalized block and connect blocks to remove
273  for (;;) {
274  auto it = leafs.rbegin();
275  auto node = leafs.extract((++it).base());
276  auto &block = node.value();
277 
278  auto header =
279  check(check(block_storage->getBlockHeader(block.hash)).value())
280  .value();
281  if (header.number == 0) {
282  last_finalized_block = block;
283  last_finalized_block_header = header;
284  last_finalized_block_state_root = header.state_root;
285  break;
286  }
287 
288  auto justifications =
289  check(block_storage->getJustification(block.hash)).value();
290  if (justifications.has_value()) {
291  last_finalized_block = block;
292  last_finalized_block_header = header;
293  last_finalized_block_state_root = header.state_root;
294  break;
295  }
296 
297  after_finalized_block_state_root = header.state_root;
298 
299  leafs.emplace(header.number - 1, header.parent_hash);
300  to_remove.insert(std::move(node));
301  }
302  RootHash target_state =
303  target_state_param.value_or(last_finalized_block_state_root);
304 
305  log->trace("Autodetected finalized block is {}, state root is {:l}",
306  last_finalized_block,
307  last_finalized_block_state_root);
308 
309  for (auto it = to_remove.rbegin(); it != to_remove.rend(); ++it) {
310  check(block_storage->removeBlock(*it)).value();
311  }
312 
313  SL_TRACE(log, "Save {} as single leaf", last_finalized_block);
314  check(block_storage->setBlockTreeLeaves({last_finalized_block.hash}))
315  .value();
316 
317  // we place the only existing state hash at runtime look up key
318  // it won't work for code substitute
319  {
320  std::vector<runtime::RuntimeUpgradeTrackerImpl::RuntimeUpgradeData>
321  runtime_upgrade_data{};
322  runtime_upgrade_data.emplace_back(last_finalized_block,
323  last_finalized_block_header.state_root);
324  auto encoded_res = check(scale::encode(runtime_upgrade_data));
326  common::Buffer(encoded_res.value())))
327  .value();
328  }
329 
330  auto trie =
331  TrieStorageImpl::createFromStorage(
332  injector.template create<sptr<Codec>>(),
333  injector.template create<sptr<TrieSerializer>>(),
334  injector
336  .value();
337 
338  if (COMPACT == cmd) {
339  auto batch = check(persistent_batch(trie, target_state)).value();
340  auto finalized_batch =
341  check(persistent_batch(trie, target_state)).value();
342 
343  std::vector<std::unique_ptr<PersistentTrieBatch>> child_batches;
344  {
345  std::set<RootHash> child_root_hashes;
346  child_storage_root_hashes(batch, child_root_hashes);
347  child_storage_root_hashes(finalized_batch, child_root_hashes);
348  for (const auto &child_root_hash : child_root_hashes) {
349  auto child_batch_res = persistent_batch(trie, child_root_hash);
350  if (child_batch_res.has_value()) {
351  child_batches.emplace_back(std::move(child_batch_res.value()));
352  } else {
353  log->error("Child batch 0x{} not found in the storage",
354  child_root_hash.toHex());
355  }
356  }
357  }
358 
359  auto db_cursor = storage->cursor();
360  auto db_batch = storage->batch();
361  auto res = check(db_cursor->seek(prefix));
362  int count = 0;
363  {
364  TicToc t2("Process DB.", log);
365  while (db_cursor->isValid() && db_cursor->key().has_value()
366  && boost::starts_with(db_cursor->key().value(), prefix)) {
367  auto res2 = check(db_batch->remove(db_cursor->key().value()));
368  count++;
369  if (not(count % 10000000)) {
370  log->trace("{} keys were processed at the db.", count);
371  res2 = check(db_batch->commit());
372  dynamic_cast<storage::RocksDB *>(storage.get())
373  ->compact(prefix, check(db_cursor->key()).value());
374  db_cursor = storage->cursor();
375  db_batch = storage->batch();
376  res = check(db_cursor->seek(prefix));
377  }
378  res2 = check(db_cursor->next());
379  }
380  std::ignore = check(db_batch->commit());
381  }
382  log->trace("{} keys were processed at the db.", ++count);
383 
384  {
385  TicToc t3("Commit state.", log);
386  check(finalized_batch->commit()).value();
387  check(batch->commit()).value();
388  for (const auto &child_batch : child_batches) {
389  check(child_batch->commit()).value();
390  }
391  }
392 
393  {
394  TicToc t4("Compaction 1.", log);
395  dynamic_cast<storage::RocksDB *>(storage.get())
396  ->compact(common::Buffer(), common::Buffer());
397  }
398 
399  need_additional_compaction = true;
400  } else if (DUMP == cmd) {
401  auto batch =
402  check(trie->getEphemeralBatchAt(last_finalized_block.hash)).value();
403  auto cursor = batch->trieCursor();
404  auto res = check(cursor->next());
405  {
406  TicToc t1("Dump full state.", log);
407  int count = 0;
408  std::ofstream ofs;
409  ofs.open("hex_full_state.yaml");
410  ofs << "keys:\n";
411  while (cursor->key().has_value()) {
412  ofs << " - " << cursor->key().value().toHex() << "\n";
413  if (not(++count % 10000)) {
414  log->trace("{} keys were dumped.", count);
415  }
416  res = cursor->next();
417  }
418 
419  cursor = batch->trieCursor();
420  res = check(cursor->next());
421  ofs << "values:\n";
422  count = 0;
423  while (cursor->key().has_value()) {
424  ofs << " - "
425  << check(batch->get(check(cursor->key()).value())).value().get()
426  << "\n";
427  if (not(++count % 50000)) {
428  log->trace("{} values were dumped.", count);
429  }
430  res = check(cursor->next());
431  }
432  ofs.close();
433  }
434  }
435  }
436 
437  if (need_additional_compaction) {
438  TicToc t5("Compaction 2.", log);
439  auto storage =
440  check(storage::RocksDB::create(argv[1], rocksdb::Options())).value();
441  dynamic_cast<storage::RocksDB *>(storage.get())
442  ->compact(common::Buffer(), common::Buffer());
443  }
444 }
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
void raise(T t)
throws outcome::result error as boost exception
void usage()
auto check(T &&res)
outcome::result< std::unique_ptr< PersistentTrieBatch > > persistent_batch(const std::unique_ptr< TrieStorageImpl > &trie, const RootHash &hash)
STL namespace.
std::shared_ptr< T > sptr
void child_storage_root_hashes(const std::unique_ptr< PersistentTrieBatch > &batch, std::set< RootHash > &hashes)
std::unique_ptr< Cursor > cursor() override
Returns new key-value iterator.
Definition: rocksdb.cpp:101
storage::trie::RootHash state_root
root of the Merkle tree
const common::Buffer kChildStorageDefaultPrefix
void setLoggingSystem(std::shared_ptr< soralog::LoggingSystem > logging_system)
Definition: logger.cpp:63
virtual std::unique_ptr< PolkadotTrieCursor > trieCursor()=0
outcome::result< std::unique_ptr< PersistentTrieBatch > > getPersistentBatchAt(const RootHash &root) override
auto is_hash(const char *s)
int main(int argc, char *argv[])
virtual outcome::result< std::optional< ConstValueView > > tryGet(const Key &key) const =0
Get value by key.
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
const common::Buffer kRuntimeHashesLookupKey