Kagome
Polkadot Runtime Engine in C++17
storage_explorer.cpp
Go to the documentation of this file.
1 
6 #include <boost/program_options.hpp>
7 #include <libp2p/log/configurator.hpp>
8 
17 #include "log/configurator.hpp"
20 
31 
32 using ArgumentList = gsl::span<const char *>;
33 
34 class CommandExecutionError : public std::runtime_error {
35  public:
36  CommandExecutionError(std::string_view command_name, const std::string &what)
37  : std::runtime_error{what}, command_name{command_name} {}
38 
39  friend std::ostream &operator<<(std::ostream &out,
40  CommandExecutionError const &err) {
41  return out << "Error in command '" << err.command_name
42  << "': " << err.what() << "\n";
43  }
44 
45  private:
46  std::string_view command_name;
47 };
48 
49 class Command {
50  public:
51  Command(std::string name, std::string description)
52  : name{std::move(name)}, description{std::move(description)} {}
53 
54  virtual ~Command() = default;
55 
56  virtual void execute(std::ostream &out, const ArgumentList &args) = 0;
57 
58  std::string_view getName() const {
59  return name;
60  }
61 
62  std::string_view getDescription() const {
63  return description;
64  }
65 
66  protected:
67  void assertArgumentCount(const ArgumentList &args, int min, int max) {
68  if (args.size() < min or args.size() > max) {
70  name,
71  fmt::format("Argument count mismatch: expected {} to {}, got {}",
72  min,
73  max,
74  args.size())};
75  }
76  }
77 
78  template <typename... Ts>
79  [[noreturn]] void throwError(const char *fmt, Ts &&...ts) const {
80  throw CommandExecutionError{name,
81  fmt::format(fmt, std::forward<Ts>(ts)...)};
82  }
83 
84  template <typename T>
85  T unwrapResult(std::string_view context, outcome::result<T> &&res) const {
86  if (res.has_value()) return std::move(res).value();
87  throwError("{}: {}", context, res.error().message());
88  }
89 
90  private:
91  std::string name;
92  std::string description;
93 };
94 
96  public:
97  using CommandFunctor = std::function<void(int, char **)>;
98 
99  void addCommand(std::unique_ptr<Command> cmd) {
100  std::string name{cmd->getName()};
101  commands_.insert({name, std::move(cmd)});
102  }
103 
104  void invoke(const ArgumentList &args) const noexcept {
105  if (args.size() < 2) {
106  std::cerr << "Unspecified command!\nAvailable commands are:\n";
107  printCommands(std::cerr);
108  }
109  if (auto command = commands_.find(args[1]); command != commands_.cend()) {
110  ArgumentList cmd_args{args.subspan(1)};
111  try {
112  command->second->execute(std::cout, cmd_args);
113  } catch (CommandExecutionError &e) {
114  std::cerr << "Command execution error: " << e;
115  } catch (std::exception &e) {
116  std::cerr << "Exception occurred: " << e.what();
117  }
118  } else {
119  std::cerr << "Unknown command '" << args[1]
120  << "'!\nAvailable commands are:\n";
121  printCommands(std::cerr);
122  }
123  }
124 
125  void printCommands(std::ostream &out) const {
126  for (auto &[name, cmd] : commands_) {
127  out << name << "\t" << cmd->getDescription() << "\n";
128  }
129  }
130 
131  private:
132  std::unordered_map<std::string, std::unique_ptr<Command>> commands_;
133 };
134 
135 std::optional<kagome::primitives::BlockId> parseBlockId(const char *string) {
137  if (strlen(string) == 2 * kagome::primitives::BlockHash::size()) {
139  if (auto id_bytes = kagome::common::unhex(string); id_bytes) {
140  std::copy_n(id_bytes.value().begin(),
142  id_hash.begin());
143  } else {
144  std::cerr << "Invalid block hash!\n";
145  return std::nullopt;
146  }
147  id = std::move(id_hash);
148  } else {
149  try {
150  id = std::stoi(string);
151  } catch (std::invalid_argument &) {
152  std::cerr << "Block number must be a hex hash or a number!\n";
153  return std::nullopt;
154  }
155  }
156  return id;
157 }
158 
159 class PrintHelpCommand final : public Command {
160  public:
161  explicit PrintHelpCommand(CommandParser const &parser)
162  : Command{"help", "print help message"}, parser{parser} {}
163 
164  virtual void execute(std::ostream &out, const ArgumentList &args) override {
165  assertArgumentCount(args, 1, 1);
166  parser.printCommands(out);
167  }
168 
169  private:
171 };
172 
173 class InspectBlockCommand : public Command {
174  public:
175  explicit InspectBlockCommand(std::shared_ptr<BlockStorage> block_storage)
176  : Command{"inspect-block",
177  "# or hash - print info about the block with the given number "
178  "or hash"},
179  block_storage{block_storage} {}
180 
181  virtual void execute(std::ostream &out, const ArgumentList &args) override {
182  assertArgumentCount(args, 2, 2);
183  auto opt_id = parseBlockId(args[1]);
184  if (!opt_id) {
185  throwError("Failed to parse block id '{}'", args[1]);
186  }
187  if (auto res = block_storage->getBlockHeader(opt_id.value()); res) {
188  if (!res.value().has_value()) {
189  throwError("Block header not found for '{}'", args[1]);
190  }
191  std::cout << "#: " << res.value()->number << "\n";
192  std::cout << "Parent hash: " << res.value()->parent_hash.toHex() << "\n";
193  std::cout << "State root: " << res.value()->state_root.toHex() << "\n";
194  std::cout << "Extrinsics root: " << res.value()->extrinsics_root.toHex()
195  << "\n";
196 
197  if (auto body_res = block_storage->getBlockBody(opt_id.value());
198  body_res) {
199  if (!body_res.value().has_value()) {
200  throwError("Block body not found for '{}'", args[1]);
201  }
202  std::cout << "# of extrinsics: " << body_res.value()->size() << "\n";
203 
204  } else {
205  throwError("Internal error: {}}", body_res.error().message());
206  }
207 
208  } else {
209  throwError("Internal error: {}}", res.error().message());
210  }
211  }
212 
213  private:
214  std::shared_ptr<BlockStorage> block_storage;
215 };
216 
217 class RemoveBlockCommand : public Command {
218  public:
219  explicit RemoveBlockCommand(std::shared_ptr<BlockStorage> block_storage)
220  : Command{"remove-block",
221  "# or hash - remove the block from the block tree"},
222  block_storage{block_storage} {}
223 
224  virtual void execute(std::ostream &out, const ArgumentList &args) override {
225  assertArgumentCount(args, 2, 2);
226  auto opt_id = parseBlockId(args[1]);
227  if (!opt_id) {
228  throwError("Failed to parse block id '{}'", args[1]);
229  }
230  auto data = block_storage->getBlockData(opt_id.value());
231  if (!data) {
232  throwError("Failed getting block data: {}", data.error().message());
233  }
234  if (!data.value().has_value()) {
235  throwError("Block data not found");
236  }
237  if (auto res = block_storage->removeBlock(
238  {data.value().value().header->number, data.value().value().hash});
239  !res) {
240  throwError("{}", res.error().message());
241  }
242  }
243 
244  private:
245  std::shared_ptr<BlockStorage> block_storage;
246 };
247 
248 class QueryStateCommand : public Command {
249  public:
250  explicit QueryStateCommand(std::shared_ptr<TrieStorage> trie_storage)
251  : Command{"query-state",
252  "state_hash, key - query value at a given key and state"},
253  trie_storage{trie_storage} {}
254 
255  virtual void execute(std::ostream &out, const ArgumentList &args) override {
256  assertArgumentCount(args, 3, 3);
257 
258  kagome::storage::trie::RootHash state_root{};
259  if (auto id_bytes = kagome::common::unhex(args[1]); id_bytes) {
260  std::copy_n(id_bytes.value().begin(),
262  state_root.begin());
263  } else {
264  throwError("Invalid block hash!");
265  }
266  auto batch = trie_storage->getEphemeralBatchAt(state_root);
267  if (!batch) {
268  throwError("Failed getting trie batch: {}", batch.error().message());
269  }
271  if (auto key_bytes = kagome::common::unhex(args[2]); key_bytes) {
272  key = kagome::common::Buffer{std::move(key_bytes.value())};
273  } else {
274  throwError("Invalid key!");
275  }
276  auto value_res = batch.value()->tryGet(key);
277  if (value_res.has_error()) {
278  throwError("Error retrieving value from Trie: {}",
279  value_res.error().message());
280  }
281  auto &value_opt = value_res.value();
282  if (value_opt.has_value()) {
283  std::cout << "Value is " << value_opt->get().toHex() << "\n";
284  } else {
285  std::cout << "No value by given key\n";
286  }
287  }
288 
289  private:
290  std::shared_ptr<TrieStorage> trie_storage;
291 };
292 
293 class SearchChainCommand : public Command {
294  public:
296  std::shared_ptr<BlockStorage> block_storage,
297  std::shared_ptr<TrieStorage> trie_storage,
298  std::shared_ptr<kagome::authority::AuthorityManager> authority_manager,
299  std::shared_ptr<Hasher> hasher)
300  : Command{"search-chain",
301  "target [start block/0] [end block/deepest finalized] - search "
302  "the finalized chain for the target entity. Currently, "
303  "'justification' or 'authority-update' are supported "},
304  block_storage{block_storage},
305  trie_storage{trie_storage},
306  hasher{hasher} {
307  BOOST_ASSERT(block_storage != nullptr);
308  BOOST_ASSERT(trie_storage != nullptr);
309  BOOST_ASSERT(authority_manager != nullptr);
310  BOOST_ASSERT(hasher != nullptr);
311  }
312 
313  enum class Target { Justification, AuthorityUpdate, LastBlock };
314 
315  virtual void execute(std::ostream &out, const ArgumentList &args) override {
316  assertArgumentCount(args, 2, 4);
317  Target target = parseTarget(args[1]);
318  if (target == Target::LastBlock) {
319  std::cout << "#" << block_storage->getLastFinalized().value().number
320  << " " << block_storage->getLastFinalized().value().hash.toHex()
321  << "\n";
322  return;
323  }
324 
325  BlockId start, end;
326 
327  if (args.size() > 2) {
328  auto start_id_opt = parseBlockId(args[2]);
329  if (!start_id_opt) {
330  throwError("Failed to parse block id '{}'", args[2]);
331  }
332  start = start_id_opt.value();
333  } else {
334  start = 0;
335  }
336  if (args.size() > 3) {
337  auto end_id_opt = parseBlockId(args[3]);
338  if (!end_id_opt) {
339  throwError("Failed to parse block id '{}'", args[3]);
340  }
341  end = end_id_opt.value();
342  } else {
343  auto last_finalized = unwrapResult("Getting last finalized block",
344  block_storage->getLastFinalized());
345  end = last_finalized.number;
346  }
347 
348  auto start_header_opt = unwrapResult("Getting 'start' block header",
349  block_storage->getBlockHeader(start));
350  if (!start_header_opt) {
351  throwError("Start block header {} not found", start);
352  }
353  auto &start_header = start_header_opt.value();
354 
355  auto end_header_opt = unwrapResult("Getting 'end' block header",
356  block_storage->getBlockHeader(end));
357  if (!end_header_opt) {
358  throwError("'End block header {} not found", end);
359  }
360  auto &end_header = end_header_opt.value();
361 
362  for (int64_t current = start_header.number, stop = end_header.number;
363  current <= stop;
364  current++) {
365  auto current_header_opt =
366  unwrapResult(fmt::format("Getting header of block #{}", current),
367  block_storage->getBlockHeader(current));
368  if (!current_header_opt) {
369  throwError("Block header #{} not found", current);
370  }
371  searchBlock(out, current_header_opt.value(), target);
372  }
373  }
374 
375  private:
376  Target parseTarget(const char *arg) const {
377  if (strcmp(arg, "justification") == 0) return Target::Justification;
378  if (strcmp(arg, "authority-update") == 0) return Target::AuthorityUpdate;
379  if (strcmp(arg, "last-finalized") == 0) return Target::LastBlock;
380  throwError("Invalid target '{}'", arg);
381  }
382 
383  void searchBlock(std::ostream &out,
384  BlockHeader const &header,
385  Target target) const {
386  switch (target) {
387  case Target::Justification:
388  return searchForJustification(out, header);
389  case Target::AuthorityUpdate:
390  return searchForAuthorityUpdate(out, header);
391  case Target::LastBlock:
392  return;
393  }
394  BOOST_UNREACHABLE_RETURN();
395  }
396 
397  void searchForJustification(std::ostream &out,
398  BlockHeader const &header) const {
399  auto just_opt = unwrapResult(
400  fmt::format("Getting justification for block #{}", header.number),
401  block_storage->getJustification(header.number));
402  if (just_opt) {
403  out << header.number << ", ";
404  }
405  }
406 
407  void searchForAuthorityUpdate(std::ostream &out,
408  BlockHeader const &header) const {
409  for (auto &digest_item : header.digest) {
410  auto *consensus_digest =
411  boost::get<kagome::primitives::Consensus>(&digest_item);
412  if (consensus_digest) {
413  auto decoded = unwrapResult("Decoding consensus digest",
414  consensus_digest->decode());
415  if (decoded.consensus_engine_id
417  reportAuthorityUpdate(out, header.number, decoded.asGrandpaDigest());
418  }
419  }
420  }
421  }
422 
423  void reportAuthorityUpdate(std::ostream &out,
424  BlockNumber digest_origin,
425  GrandpaDigest const &digest) const {
426  using namespace kagome::primitives;
427  if (auto *scheduled_change = boost::get<ScheduledChange>(&digest);
428  scheduled_change) {
429  out << "ScheduledChange at #" << digest_origin << " for ";
430  if (scheduled_change->subchain_length > 0) {
431  out << "#" << digest_origin + scheduled_change->subchain_length;
432  } else {
433  out << "the same block";
434  }
435  out << "\n";
436 
437  } else if (auto *forced_change = boost::get<ForcedChange>(&digest);
438  forced_change) {
439  out << "ForcedChange at " << digest_origin << ", delay starts at #"
440  << forced_change->delay_start << " for "
441  << forced_change->subchain_length << " blocks (so activates at #"
442  << forced_change->delay_start + forced_change->subchain_length << ")";
443  out << "\n";
444 
445  } else if (auto *pause = boost::get<Pause>(&digest); pause) {
446  out << "Pause at " << digest_origin << " for "
447  << digest_origin + pause->subchain_length << "\n";
448 
449  } else if (auto *resume = boost::get<Resume>(&digest); resume) {
450  out << "Resume at " << digest_origin << " for "
451  << digest_origin + resume->subchain_length << "\n";
452 
453  } else if (auto *disabled = boost::get<OnDisabled>(&digest); disabled) {
454  out << "Disabled at " << digest_origin << " for authority "
455  << disabled->authority_index << "\n";
456  }
457  }
458 
459  std::shared_ptr<BlockStorage> block_storage;
460  std::shared_ptr<TrieStorage> trie_storage;
461  std::shared_ptr<Hasher> hasher;
462 };
463 
464 int main(int argc, const char **argv) {
465  ArgumentList args{argv, argc};
466 
467  CommandParser parser;
468  parser.addCommand(std::make_unique<PrintHelpCommand>(parser));
469 
470  auto logging_system = std::make_shared<soralog::LoggingSystem>(
471  std::make_shared<kagome::log::Configurator>(
472  std::make_shared<libp2p::log::Configurator>()));
473 
474  auto r = logging_system->configure();
475  if (not r.message.empty()) {
476  (r.has_error ? std::cerr : std::cout) << r.message << std::endl;
477  }
478  if (r.has_error) {
479  exit(EXIT_FAILURE);
480  }
481  kagome::log::setLoggingSystem(logging_system);
482  kagome::log::setLevelOfGroup("*", kagome::log::Level::WARN);
483 
484  auto logger = kagome::log::createLogger("AppConfiguration", "main");
486 
487  int kagome_args_start = -1;
488  for (int i = 1; i < args.size(); i++) {
489  if (strcmp(args[i], "--") == 0) {
490  kagome_args_start = i;
491  }
492  }
493  if (kagome_args_start == -1) {
494  std::cerr
495  << "You must specify arguments for kagome initialization after '--'\n";
496  return -1;
497  }
498 
499  if (!configuration.initializeFromArgs(
500  argc - kagome_args_start, args.subspan(kagome_args_start).data())) {
501  std::cerr << "Failed to initialize kagome!\n";
502  return -1;
503  }
504 
505  kagome::injector::KagomeNodeInjector injector{configuration};
506  auto block_storage = injector.injectBlockStorage();
507  auto trie_storage = injector.injectTrieStorage();
508  auto app_state_manager = injector.injectAppStateManager();
509  auto block_tree = injector.injectBlockTree();
510  auto executor = injector.injectExecutor();
511  auto persistent_storage = injector.injectStorage();
512  auto hasher = std::make_shared<kagome::crypto::HasherImpl>();
513 
514  auto header_repo =
515  std::make_shared<kagome::blockchain::BlockHeaderRepositoryImpl>(
516  persistent_storage, hasher);
517  auto grandpa_api =
518  std::make_shared<kagome::runtime::GrandpaApiImpl>(header_repo, executor);
519 
520  auto authority_manager =
521  std::make_shared<kagome::authority::AuthorityManagerImpl>(
523  app_state_manager,
524  block_tree,
525  trie_storage,
526  grandpa_api,
527  hasher,
528  persistent_storage,
529  header_repo);
530 
531  parser.addCommand(std::make_unique<InspectBlockCommand>(block_storage));
532  parser.addCommand(std::make_unique<RemoveBlockCommand>(block_storage));
533  parser.addCommand(std::make_unique<QueryStateCommand>(trie_storage));
534  parser.addCommand(std::make_unique<SearchChainCommand>(
535  block_storage, trie_storage, authority_manager, hasher));
536 
537  parser.invoke(args.subspan(0, kagome_args_start));
538 
539  return 0;
540 }
void invoke(const ArgumentList &args) const noexcept
std::shared_ptr< TrieStorage > trie_storage
Class represents arbitrary (including empty) byte buffer.
Definition: buffer.hpp:29
std::unordered_map< std::string, std::unique_ptr< Command > > commands_
boost::variant< Unused< 0 >, ScheduledChange, ForcedChange, OnDisabled, Pause, Resume > GrandpaDigest
https://github.com/paritytech/substrate/blob/polkadot-v0.9.8/primitives/finality-grandpa/src/lib.rs#L92
Definition: digest.hpp:73
std::shared_ptr< BlockStorage > block_storage
void reportAuthorityUpdate(std::ostream &out, BlockNumber digest_origin, GrandpaDigest const &digest) const
void searchForAuthorityUpdate(std::ostream &out, BlockHeader const &header) const
QueryStateCommand(std::shared_ptr< TrieStorage > trie_storage)
InspectBlockCommand(std::shared_ptr< BlockStorage > block_storage)
std::shared_ptr< Hasher > hasher
T unwrapResult(std::string_view context, outcome::result< T > &&res) const
Target parseTarget(const char *arg) const
Command(std::string name, std::string description)
void throwError(const char *fmt, Ts &&...ts) const
STL namespace.
std::shared_ptr< BlockStorage > block_storage
void printCommands(std::ostream &out) const
std::string_view getDescription() const
Digest digest
chain-specific auxiliary data
void searchForJustification(std::ostream &out, BlockHeader const &header) const
std::string_view getName() const
std::shared_ptr< blockchain::BlockStorage > injectBlockStorage()
interface for Grandpa runtime functions
Definition: grandpa_api.hpp:26
CommandParser const & parser
uint32_t BlockNumber
Definition: common.hpp:18
RemoveBlockCommand(std::shared_ptr< BlockStorage > block_storage)
virtual void execute(std::ostream &out, const ArgumentList &args) override
PrintHelpCommand(CommandParser const &parser)
void addCommand(std::unique_ptr< Command > cmd)
std::shared_ptr< BlockStorage > block_storage
std::string name
void setLoggingSystem(std::shared_ptr< soralog::LoggingSystem > logging_system)
Definition: logger.cpp:63
virtual void execute(std::ostream &out, const ArgumentList &args) override
primitives::BlockNumber BlockNumber
Definition: common.hpp:24
virtual void execute(std::ostream &out, const ArgumentList &args) override
outcome::result< std::vector< uint8_t > > unhex(std::string_view hex)
Converts hex representation to bytes.
Definition: hexutil.cpp:70
void assertArgumentCount(const ArgumentList &args, int min, int max)
gsl::span< const char * > ArgumentList
std::string_view command_name
SearchChainCommand(std::shared_ptr< BlockStorage > block_storage, std::shared_ptr< TrieStorage > trie_storage, std::shared_ptr< kagome::authority::AuthorityManager > authority_manager, std::shared_ptr< Hasher > hasher)
static constexpr size_t size()
Definition: blob.hpp:146
CommandExecutionError(std::string_view command_name, const std::string &what)
BlockNumber number
index of the block in the chain
std::string description
boost::variant< BlockHash, BlockNumber > BlockId
Block id is the variant over BlockHash and BlockNumber.
Definition: block_id.hpp:18
const auto kGrandpaEngineId
Definition: digest.hpp:28
virtual void execute(std::ostream &out, const ArgumentList &args) override
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
virtual void execute(std::ostream &out, const ArgumentList &args) override
void searchBlock(std::ostream &out, BlockHeader const &header, Target target) const
std::shared_ptr< TrieStorage > trie_storage
std::optional< kagome::primitives::BlockId > parseBlockId(const char *string)
int main(int argc, const char **argv)
std::function< void(int, char **)> CommandFunctor
friend std::ostream & operator<<(std::ostream &out, CommandExecutionError const &err)
bool setLevelOfGroup(const std::string &group_name, Level level)
Definition: logger.cpp:132