Kagome
Polkadot Runtime Engine in C++17
block_tree_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <algorithm>
9 #include <stack>
10 
17 #include "crypto/blake2/blake2b.h"
18 #include "log/profiling_logger.hpp"
21 
22 namespace {
23  constexpr auto blockHeightMetricName = "kagome_block_height";
24  constexpr auto knownChainLeavesMetricName = "kagome_number_leaves";
25 } // namespace
26 
27 namespace kagome::blockchain {
28  using Buffer = common::Buffer;
29  using Prefix = prefix::Prefix;
31 
32  namespace {
34  outcome::result<std::set<primitives::BlockInfo>> loadLeaves(
35  const std::shared_ptr<BlockStorage> &storage,
36  const std::shared_ptr<BlockHeaderRepository> &header_repo,
37  const log::Logger &log) {
38  BOOST_ASSERT(storage != nullptr);
39  BOOST_ASSERT(header_repo != nullptr);
40 
41  std::set<primitives::BlockInfo> block_tree_leaves;
42  {
43  OUTCOME_TRY(block_tree_unordered_leaves, storage->getBlockTreeLeaves());
44  SL_TRACE(log,
45  "List of leaves has loaded: {} leaves",
46  block_tree_unordered_leaves.size());
47 
48  BOOST_ASSERT_MSG(not block_tree_unordered_leaves.empty(),
49  "Must be known or calculated at least one leaf");
50 
51  for (auto &hash : block_tree_unordered_leaves) {
52  auto res = header_repo->getNumberById(hash);
53  if (res.has_error()) {
54  if (res
55  == outcome::failure(
57  SL_TRACE(log, "Leaf {} not found", hash);
58  continue;
59  }
60  SL_ERROR(
61  log, "Leaf {} is corrupted: {}", hash, res.error().message());
62  return res.as_failure();
63  }
64  auto number = res.value();
65  SL_TRACE(log, "Leaf {} found", primitives::BlockInfo(number, hash));
66  block_tree_leaves.emplace(number, hash);
67  }
68  }
69 
70  if (block_tree_leaves.empty()) {
71  SL_WARN(log, "No one leaf was found. Trying to repair");
72 
73  primitives::BlockNumber number = 0;
74  auto lower = std::numeric_limits<primitives::BlockNumber>::min();
75  auto upper = std::numeric_limits<primitives::BlockNumber>::max();
76 
77  for (;;) {
78  number = lower + (upper - lower) / 2;
79 
80  auto res = storage->hasBlockHeader(number);
81  if (res.has_failure()) {
82  SL_CRITICAL(
83  log, "Search best block has failed: {}", res.error().message());
85  }
86 
87  if (res.value()) {
88  SL_TRACE(log, "bisect {} -> found", number);
89  lower = number;
90  } else {
91  SL_TRACE(log, "bisect {} -> not found", number);
92  upper = number - 1;
93  }
94  if (lower == upper) {
95  number = lower;
96  break;
97  }
98  }
99 
100  OUTCOME_TRY(hash, header_repo->getHashById(number));
101  block_tree_leaves.emplace(number, hash);
102 
103  if (auto res = storage->setBlockTreeLeaves({hash}); res.has_error()) {
104  SL_CRITICAL(log,
105  "Can't save recovered block tree leaves: {}",
106  res.error().message());
107  return res.as_failure();
108  }
109  }
110 
111  return block_tree_leaves;
112  }
113  } // namespace
114 
115  outcome::result<std::shared_ptr<BlockTreeImpl>> BlockTreeImpl::create(
116  std::shared_ptr<BlockHeaderRepository> header_repo,
117  std::shared_ptr<BlockStorage> storage,
118  std::shared_ptr<network::ExtrinsicObserver> extrinsic_observer,
119  std::shared_ptr<crypto::Hasher> hasher,
122  extrinsic_events_engine,
123  std::shared_ptr<subscription::ExtrinsicEventKeyRepository>
124  extrinsic_event_key_repo,
125  std::shared_ptr<storage::changes_trie::ChangesTracker> changes_tracker,
126  std::shared_ptr<const class JustificationStoragePolicy>
127  justification_storage_policy) {
128  BOOST_ASSERT(storage != nullptr);
129  BOOST_ASSERT(header_repo != nullptr);
130 
131  log::Logger log = log::createLogger("BlockTree", "blockchain");
132 
133  OUTCOME_TRY(block_tree_leaves, loadLeaves(storage, header_repo, log));
134 
135  BOOST_ASSERT_MSG(not block_tree_leaves.empty(),
136  "Must be known or calculated at least one leaf");
137 
138  // Find the least and best leaf
139  auto least_leaf = *block_tree_leaves.begin();
140  auto best_leaf = *block_tree_leaves.rbegin();
141 
142  OUTCOME_TRY(last_finalized_block_info, storage->getLastFinalized());
143 
144  // call chain_events_engine->notify to init babe_config_repo preventive
145  auto finalized_block_header_res =
146  storage->getBlockHeader(last_finalized_block_info.hash);
147  BOOST_ASSERT_MSG(finalized_block_header_res.has_value()
148  and finalized_block_header_res.value().has_value(),
149  "Initialized block tree must be have finalized block");
150  chain_events_engine->notify(
152  finalized_block_header_res.value().value());
153 
154  OUTCOME_TRY(last_finalized_justification,
155  storage->getJustification(last_finalized_block_info.hash));
156 
157  auto hash_tmp = last_finalized_block_info.hash;
158 
159  // Load non-finalized block from block storage
160  std::multimap<primitives::BlockInfo, primitives::BlockHeader> collected;
161 
162  {
163  std::unordered_set<primitives::BlockHash> observed;
164  for (auto &leaf : block_tree_leaves) {
165  for (auto hash = leaf.hash;;) {
166  if (hash == last_finalized_block_info.hash) {
167  break;
168  }
169 
170  if (not observed.emplace(hash).second) {
171  break;
172  }
173 
174  auto header_res = storage->getBlockHeader(hash);
175  if (header_res.has_error()) {
176  SL_WARN(log,
177  "Can't get header of existing non-finalized block {}: {}",
178  hash,
179  header_res.error().message());
180  break;
181  }
182  auto &header_opt = header_res.value();
183  if (!header_opt.has_value()) {
184  SL_WARN(log,
185  "Can't get header of existing block {}: not found in block "
186  "storage",
187  hash,
188  header_res.error().message());
189  break;
190  }
191 
192  const auto &header = header_opt.value();
193  primitives::BlockInfo block(header.number, hash);
194 
195  collected.emplace(block, header);
196 
197  hash = header.parent_hash;
198  }
199  }
200  }
201 
202  // Prepare and create block tree basing last finalized block
203  auto tree = std::make_shared<TreeNode>(
204  last_finalized_block_info.hash, last_finalized_block_info.number, true);
205  SL_DEBUG(log, "Last finalized block #{}", tree->depth);
206  auto meta = std::make_shared<TreeMeta>(tree, last_finalized_justification);
207 
208  auto *block_tree =
209  new BlockTreeImpl(std::move(header_repo),
210  std::move(storage),
211  std::make_unique<CachedTree>(tree, meta),
212  std::move(extrinsic_observer),
213  std::move(hasher),
214  std::move(chain_events_engine),
215  std::move(extrinsic_events_engine),
216  std::move(extrinsic_event_key_repo),
217  std::move(changes_tracker),
218  std::move(justification_storage_policy));
219 
220  // Add non-finalized block to the block tree
221  for (auto &e : collected) {
222  const auto &block = e.first;
223  const auto header = std::move(e.second);
224 
225  auto res = block_tree->addExistingBlock(block.hash, header);
226  if (res.has_error()) {
227  SL_WARN(log,
228  "Can't add existing non-finalized block {} to block tree: {}",
229  block,
230  res.error().message());
231  }
232  SL_TRACE(
233  log, "Existing non-finalized block {} is added to block tree", block);
234  }
235 
236  return std::shared_ptr<BlockTreeImpl>(block_tree);
237  }
238 
239  outcome::result<void> BlockTreeImpl::recover(
240  primitives::BlockId target_block,
241  std::shared_ptr<BlockStorage> storage,
242  std::shared_ptr<BlockHeaderRepository> header_repo,
243  std::shared_ptr<const storage::trie::TrieStorage> trie_storage,
244  std::shared_ptr<blockchain::BlockTree> block_tree) {
245  BOOST_ASSERT(storage != nullptr);
246  BOOST_ASSERT(header_repo != nullptr);
247  BOOST_ASSERT(trie_storage != nullptr);
248 
249  log::Logger log = log::createLogger("BlockTree", "blockchain");
250 
251  OUTCOME_TRY(block_tree_leaves, loadLeaves(storage, header_repo, log));
252 
253  BOOST_ASSERT_MSG(not block_tree_leaves.empty(),
254  "Must be known or calculated at least one leaf");
255 
256  // Check if target block exists
257  auto target_block_header_opt_res = storage->getBlockHeader(target_block);
258  if (target_block_header_opt_res.has_error()) {
259  SL_CRITICAL(log,
260  "Can't get header of target block: {}",
261  target_block_header_opt_res.error().message());
262  return target_block_header_opt_res.as_failure();
263  }
264  const auto &target_block_header_opt = target_block_header_opt_res.value();
265  if (not target_block_header_opt.has_value()) {
267  }
268 
269  const auto &target_block_header = target_block_header_opt.value();
270  const auto &state_root = target_block_header.state_root;
271 
272  // Check if target block has state
273  if (auto res = trie_storage->getEphemeralBatchAt(state_root);
274  res.has_error()) {
275  SL_WARN(
276  log, "Can't get state of target block: {}", res.error().message());
277  SL_CRITICAL(
278  log,
279  "You will need to use `--sync Fast' CLI arg the next time you start");
280  }
281 
282  for (auto it = block_tree_leaves.rbegin(); it != block_tree_leaves.rend();
283  it = block_tree_leaves.rbegin()) {
284  auto block = *it;
285  if (target_block_header.number >= block.number) {
286  break;
287  }
288 
289  auto header_opt_res = storage->getBlockHeader(block.hash);
290  if (header_opt_res.has_error()) {
291  SL_CRITICAL(log,
292  "Can't get header of one of removing block: {}",
293  header_opt_res.error().message());
294  return header_opt_res.as_failure();
295  }
296  const auto &header_opt = header_opt_res.value();
297  if (not header_opt.has_value()) {
299  }
300 
301  const auto &header = header_opt.value();
302  block_tree_leaves.emplace(block.number - 1, header.parent_hash);
303  block_tree_leaves.erase(block);
304 
305  std::vector<primitives::BlockHash> leaves;
306  std::transform(block_tree_leaves.begin(),
307  block_tree_leaves.end(),
308  std::back_inserter(leaves),
309  [](const auto it) { return it.hash; });
310  if (auto res = storage->setBlockTreeLeaves(leaves); res.has_error()) {
311  SL_CRITICAL(log,
312  "Can't save updated block tree leaves: {}",
313  res.error().message());
314  return res.as_failure();
315  }
316 
317  if (auto res = block_tree->removeLeaf(block.hash); res.has_error()) {
318  SL_CRITICAL(
319  log, "Can't remove block {}: {}", block, res.error().message());
320  return res.as_failure();
321  }
322  }
323 
324  return outcome::success();
325  }
326 
328  std::shared_ptr<BlockHeaderRepository> header_repo,
329  std::shared_ptr<BlockStorage> storage,
330  std::unique_ptr<CachedTree> cached_tree,
331  std::shared_ptr<network::ExtrinsicObserver> extrinsic_observer,
332  std::shared_ptr<crypto::Hasher> hasher,
335  extrinsic_events_engine,
336  std::shared_ptr<subscription::ExtrinsicEventKeyRepository>
337  extrinsic_event_key_repo,
338  std::shared_ptr<storage::changes_trie::ChangesTracker> changes_tracker,
339  std::shared_ptr<const JustificationStoragePolicy>
340  justification_storage_policy)
341  : header_repo_{std::move(header_repo)},
342  storage_{std::move(storage)},
343  tree_{std::move(cached_tree)},
344  extrinsic_observer_{std::move(extrinsic_observer)},
345  hasher_{std::move(hasher)},
346  chain_events_engine_(std::move(chain_events_engine)),
347  extrinsic_events_engine_(std::move(extrinsic_events_engine)),
348  extrinsic_event_key_repo_{std::move(extrinsic_event_key_repo)},
349  trie_changes_tracker_(std::move(changes_tracker)),
350  justification_storage_policy_{std::move(justification_storage_policy)} {
351  BOOST_ASSERT(header_repo_ != nullptr);
352  BOOST_ASSERT(storage_ != nullptr);
353  BOOST_ASSERT(tree_ != nullptr);
354  BOOST_ASSERT(extrinsic_observer_ != nullptr);
355  BOOST_ASSERT(hasher_ != nullptr);
356  BOOST_ASSERT(chain_events_engine_ != nullptr);
357  BOOST_ASSERT(extrinsic_events_engine_ != nullptr);
358  BOOST_ASSERT(extrinsic_event_key_repo_ != nullptr);
359  BOOST_ASSERT(trie_changes_tracker_ != nullptr);
360  BOOST_ASSERT(justification_storage_policy_ != nullptr);
361  BOOST_ASSERT(telemetry_ != nullptr);
362 
363  // Register metrics
364  metrics_registry_->registerGaugeFamily(blockHeightMetricName,
365  "Block height info of the chain");
366 
367  metric_best_block_height_ = metrics_registry_->registerGaugeMetric(
368  blockHeightMetricName, {{"status", "best"}});
370  tree_->getMetadata().deepest_leaf.lock()->depth);
371 
372  metric_finalized_block_height_ = metrics_registry_->registerGaugeMetric(
373  blockHeightMetricName, {{"status", "finalized"}});
375  tree_->getMetadata().last_finalized.lock()->depth);
376 
377  metrics_registry_->registerGaugeFamily(
378  knownChainLeavesMetricName, "Number of known chain leaves (aka forks)");
379 
381  metrics_registry_->registerGaugeMetric(knownChainLeavesMetricName);
382  metric_known_chain_leaves_->set(tree_->getMetadata().leaves.size());
383 
384  telemetry_->setGenesisBlockHash(getGenesisBlockHash());
385  }
386 
388  if (genesis_block_hash_.has_value()) {
389  return genesis_block_hash_.value();
390  }
391 
392  auto res = header_repo_->getHashByNumber(0);
393  BOOST_ASSERT_MSG(res.has_value(),
394  "Block tree must contain at least genesis block");
395 
396  const_cast<std::decay_t<decltype(genesis_block_hash_)> &>(
398  .emplace(res.value());
399  return genesis_block_hash_.value();
400  }
401 
402  outcome::result<void> BlockTreeImpl::addBlockHeader(
403  const primitives::BlockHeader &header) {
404  auto parent = tree_->getRoot().findByHash(header.parent_hash);
405  if (!parent) {
407  }
408  OUTCOME_TRY(block_hash, storage_->putBlockHeader(header));
409 
410  std::optional<consensus::EpochDigest> next_epoch;
411  if (auto digest = consensus::getNextEpochDigest(header);
412  digest.has_value()) {
413  next_epoch.emplace(std::move(digest.value()));
414  }
415 
416  // update local meta with the new block
417  auto new_node =
418  std::make_shared<TreeNode>(block_hash, header.number, parent);
419 
420  tree_->updateMeta(new_node);
421 
422  OUTCOME_TRY(reorganize());
423 
424  OUTCOME_TRY(
425  storage_->setBlockTreeLeaves({tree_->getMetadata().leaves.begin(),
426  tree_->getMetadata().leaves.end()}));
427 
428  metric_known_chain_leaves_->set(tree_->getMetadata().leaves.size());
430  tree_->getMetadata().deepest_leaf.lock()->depth);
431 
433  header);
434 
435  return outcome::success();
436  }
437 
438  outcome::result<void> BlockTreeImpl::addBlock(
439  const primitives::Block &block) {
440  // Check if we know parent of this block; if not, we cannot insert it
441  auto parent = tree_->getRoot().findByHash(block.header.parent_hash);
442  if (!parent) {
444  }
445 
446  // Save block
447  OUTCOME_TRY(block_hash, storage_->putBlock(block));
448 
449  std::optional<consensus::EpochDigest> next_epoch;
450  if (auto digest = consensus::getNextEpochDigest(block.header);
451  digest.has_value()) {
452  next_epoch.emplace(std::move(digest.value()));
453  }
454 
455  // Update local meta with the block
456  auto new_node =
457  std::make_shared<TreeNode>(block_hash, block.header.number, parent);
458 
459  tree_->updateMeta(new_node);
460 
461  OUTCOME_TRY(reorganize());
462 
463  OUTCOME_TRY(
464  storage_->setBlockTreeLeaves({tree_->getMetadata().leaves.begin(),
465  tree_->getMetadata().leaves.end()}));
466 
468  block.header);
469  trie_changes_tracker_->onBlockAdded(block_hash);
470  for (const auto &ext : block.body) {
471  if (auto key =
472  extrinsic_event_key_repo_->get(hasher_->blake2b_256(ext.data))) {
473  extrinsic_events_engine_->notify(
474  key.value(),
476  block_hash));
477  }
478  }
479 
480  metric_known_chain_leaves_->set(tree_->getMetadata().leaves.size());
482  tree_->getMetadata().deepest_leaf.lock()->depth);
483 
484  return outcome::success();
485  }
486 
487  outcome::result<void> BlockTreeImpl::removeLeaf(
488  const primitives::BlockHash &block_hash) {
489  // Check if block is leaf
490  if (tree_->getMetadata().leaves.count(block_hash) == 0) {
492  }
493 
494  auto node = tree_->getRoot().findByHash(block_hash);
495  BOOST_ASSERT_MSG(node != nullptr,
496  "As checked before, block exists as one of leaves");
497 
498  if (not node->parent.expired()) {
499  // Remove from block tree, ...
500  tree_->removeFromMeta(node);
501 
502  OUTCOME_TRY(reorganize());
503 
504  } else {
505  // ... or repair tree by parent of root
506  auto hash_res = header_repo_->getHashByNumber(node->depth - 1);
507  BOOST_ASSERT_MSG(hash_res.has_value(),
508  "Non genesis block must have parent");
509 
510  primitives::BlockInfo block{node->depth - 1, hash_res.value()};
511  auto tree = std::make_shared<TreeNode>(block.hash, block.number, true);
512  auto meta = std::make_shared<TreeMeta>(tree, std::nullopt);
513  tree_ = std::make_unique<CachedTree>(std::move(tree), std::move(meta));
514  }
515 
516  // Remove from storage
517  OUTCOME_TRY(storage_->removeBlock({node->depth, node->block_hash}));
518 
519  OUTCOME_TRY(
520  storage_->setBlockTreeLeaves({tree_->getMetadata().leaves.begin(),
521  tree_->getMetadata().leaves.end()}));
522 
523  return outcome::success();
524  }
525 
526  outcome::result<void> BlockTreeImpl::addExistingBlock(
527  const primitives::BlockHash &block_hash,
528  const primitives::BlockHeader &block_header) {
529  SL_TRACE(log_,
530  "Trying to add block {} into block tree",
531  primitives::BlockInfo(block_header.number, block_hash));
532 
533  auto node = tree_->getRoot().findByHash(block_hash);
534  // Check if tree doesn't have this block; if not, we skip that
535  if (node != nullptr) {
536  SL_TRACE(log_,
537  "Block {} exists in block tree",
538  primitives::BlockInfo(block_header.number, block_hash));
540  }
541 
542  auto parent = tree_->getRoot().findByHash(block_header.parent_hash);
543 
544  // Check if we know parent of this block; if not, we cannot insert it
545  if (parent == nullptr) {
546  SL_TRACE(log_,
547  "Block {} parent of {} has not found in block tree. "
548  "Trying to restore missed branch",
549  primitives::BlockInfo(block_header.number - 1,
550  block_header.parent_hash),
551  primitives::BlockInfo(block_header.number, block_hash));
552 
553  // Trying to restore missed branch
554  std::stack<std::pair<primitives::BlockHash, primitives::BlockHeader>>
555  to_add;
556 
557  for (auto hash = block_header.parent_hash;;) {
558  OUTCOME_TRY(header_opt, storage_->getBlockHeader(hash));
559  if (not header_opt.has_value()) {
561  }
562 
563  auto &header = header_opt.value();
564  SL_TRACE(log_,
565  "Block {} has found in storage and enqueued to add",
566  primitives::BlockInfo(header.number, hash));
567 
568  to_add.emplace(hash, std::move(header));
569 
570  if (tree_->getRoot().findByHash(header.parent_hash) != nullptr) {
571  SL_TRACE(log_,
572  "Block {} parent of {} has found in block tree",
573  primitives::BlockInfo(header.number - 1, header.parent_hash),
574  primitives::BlockInfo(header.number, hash));
575 
576  break;
577  }
578 
579  SL_TRACE(log_,
580  "Block {} has not found in block tree. "
581  "Trying to restore from storage",
582  primitives::BlockInfo(header.number - 1, header.parent_hash));
583 
584  hash = header.parent_hash;
585  }
586 
587  while (not to_add.empty()) {
588  const auto &[hash, header] = to_add.top();
589  OUTCOME_TRY(addExistingBlock(hash, header));
590  to_add.pop();
591  }
592 
593  parent = tree_->getRoot().findByHash(block_header.parent_hash);
594  BOOST_ASSERT_MSG(parent != nullptr,
595  "Parent must be restored at this moment");
596 
597  SL_TRACE(log_,
598  "Trying to add block {} into block tree",
599  primitives::BlockInfo(block_header.number, block_hash));
600  }
601 
602  std::optional<consensus::EpochDigest> next_epoch;
603  if (auto digest = consensus::getNextEpochDigest(block_header);
604  digest.has_value()) {
605  next_epoch.emplace(std::move(digest.value()));
606  }
607 
608  // Update local meta with the block
609  auto new_node =
610  std::make_shared<TreeNode>(block_hash, block_header.number, parent);
611 
612  tree_->updateMeta(new_node);
613 
614  OUTCOME_TRY(reorganize());
615 
616  OUTCOME_TRY(
617  storage_->setBlockTreeLeaves({tree_->getMetadata().leaves.begin(),
618  tree_->getMetadata().leaves.end()}));
619 
620  metric_known_chain_leaves_->set(tree_->getMetadata().leaves.size());
622  tree_->getMetadata().deepest_leaf.lock()->depth);
623 
624  return outcome::success();
625  }
626 
627  outcome::result<void> BlockTreeImpl::addBlockBody(
628  primitives::BlockNumber block_number,
629  const primitives::BlockHash &block_hash,
630  const primitives::BlockBody &body) {
631  primitives::BlockData block_data{.hash = block_hash, .body = body};
632  return storage_->putBlockData(block_number, block_data);
633  }
634 
635  outcome::result<void> BlockTreeImpl::finalize(
636  const primitives::BlockHash &block_hash,
637  const primitives::Justification &justification) {
638  auto node = tree_->getRoot().findByHash(block_hash);
639  if (!node) {
641  }
642  if (node->depth <= getLastFinalized().number
643  && hasDirectChain(block_hash, getLastFinalized().hash)) {
644  // block was already finalized, fine
645  return outcome::success();
646  }
647 
648  SL_DEBUG(log_,
649  "Finalizing block {}",
650  primitives::BlockInfo(node->depth, block_hash));
651 
652  OUTCOME_TRY(header_opt, storage_->getBlockHeader(node->block_hash));
653  if (!header_opt.has_value()) {
655  }
656  auto &header = header_opt.value();
657 
658  auto last_finalized_block_info =
659  tree_->getMetadata().last_finalized.lock()->getBlockInfo();
660 
661  // update our local meta
662  node->finalized = true;
663 
664  OUTCOME_TRY(prune(node));
665 
666  tree_->updateTreeRoot(node, justification);
667 
668  OUTCOME_TRY(reorganize());
669 
670  OUTCOME_TRY(
671  storage_->setBlockTreeLeaves({tree_->getMetadata().leaves.begin(),
672  tree_->getMetadata().leaves.end()}));
673 
674  chain_events_engine_->notify(
676 
677  OUTCOME_TRY(body, storage_->getBlockBody(node->block_hash));
678  if (body.has_value()) {
679  for (auto &ext : body.value()) {
680  if (auto key = extrinsic_event_key_repo_->get(
681  hasher_->blake2b_256(ext.data))) {
682  extrinsic_events_engine_->notify(
683  key.value(),
685  key.value(), block_hash));
686  }
687  }
688  }
689 
690  primitives::BlockInfo finalized_block(node->depth, block_hash);
691  log_->info("Finalized block {}", finalized_block);
692  telemetry_->notifyBlockFinalized(finalized_block);
693  metric_finalized_block_height_->set(node->depth);
694 
695  KAGOME_PROFILE_START(justification_store)
696 
697  OUTCOME_TRY(
698  storage_->putJustification(justification, block_hash, node->depth));
699  SL_DEBUG(log_,
700  "Store justification for finalized block #{} {}",
701  node->depth,
702  block_hash);
703  // we store justification for last finalized block only as long as it is
704  // last finalized (if it doesn't meet other justification storage rules,
705  // e.g. its number a multiple of 512)
706  OUTCOME_TRY(last_finalized_header_opt,
707  storage_->getBlockHeader(last_finalized_block_info.number));
708  // SAFETY: header for the last finalized block must be present
709  auto &last_finalized_header = last_finalized_header_opt.value();
710  OUTCOME_TRY(
711  shouldStoreLastFinalized,
712  justification_storage_policy_->shouldStoreFor(last_finalized_header));
713  if (!shouldStoreLastFinalized) {
714  OUTCOME_TRY(justification_opt,
715  storage_->getJustification(last_finalized_block_info.hash));
716  if (justification_opt.has_value()) {
717  SL_DEBUG(log_,
718  "Purge redundant justification for finalized block {}",
719  last_finalized_block_info);
720  OUTCOME_TRY(storage_->removeJustification(
721  last_finalized_block_info.hash, last_finalized_block_info.number));
722  }
723  }
724 
725  KAGOME_PROFILE_END(justification_store)
726 
727  return outcome::success();
728  }
729 
730  outcome::result<bool> BlockTreeImpl::hasBlockHeader(
731  const primitives::BlockId &block) const {
732  return storage_->hasBlockHeader(block);
733  }
734 
735  outcome::result<primitives::BlockHeader> BlockTreeImpl::getBlockHeader(
736  const primitives::BlockId &block) const {
737  OUTCOME_TRY(header, storage_->getBlockHeader(block));
738  if (header.has_value()) return header.value();
740  }
741 
742  outcome::result<primitives::BlockBody> BlockTreeImpl::getBlockBody(
743  const primitives::BlockId &block) const {
744  OUTCOME_TRY(body, storage_->getBlockBody(block));
745  if (body.has_value()) return body.value();
747  }
748 
749  outcome::result<primitives::Justification>
751  OUTCOME_TRY(justification, storage_->getJustification(block));
752  if (justification.has_value()) return justification.value();
754  }
755 
757  const primitives::BlockHash &block, uint64_t maximum) const {
758  auto block_number_res = header_repo_->getNumberByHash(block);
759  if (block_number_res.has_error()) {
760  log_->error("cannot retrieve block with hash {}: {}",
761  block.toHex(),
762  block_number_res.error().message());
764  }
765  auto start_block_number = block_number_res.value();
766 
767  if (maximum == 1) {
768  return std::vector{block};
769  }
770 
771  auto deepest_leaf = tree_->getMetadata().deepest_leaf.lock();
772  BOOST_ASSERT(deepest_leaf != nullptr);
773  auto current_depth = deepest_leaf->depth;
774 
775  if (start_block_number >= current_depth) {
776  return std::vector{block};
777  }
778 
779  auto count =
780  std::min<uint64_t>(current_depth - start_block_number + 1, maximum);
781 
782  primitives::BlockNumber finish_block_number =
783  start_block_number + count - 1;
784 
785  auto finish_block_hash_res =
786  header_repo_->getHashByNumber(finish_block_number);
787  if (finish_block_hash_res.has_error()) {
788  log_->error("cannot retrieve block with number {}: {}",
789  finish_block_number,
790  finish_block_hash_res.error().message());
792  }
793  const auto &finish_block_hash = finish_block_hash_res.value();
794 
795  OUTCOME_TRY(chain, getDescendingChainToBlock(finish_block_hash, count));
796  if (chain.back() != block) {
797  return std::vector{block};
798  }
799  std::reverse(chain.begin(), chain.end());
800  return std::move(chain);
801  }
802 
804  const primitives::BlockHash &to_block, uint64_t maximum) const {
805  std::vector<primitives::BlockHash> chain;
806 
807  auto hash = to_block;
808 
809  // Try to retrieve from cached tree
810  if (auto node = tree_->getRoot().findByHash(hash)) {
811  while (maximum > chain.size()) {
812  auto parent = node->parent.lock();
813  if (not parent) {
814  hash = node->block_hash;
815  break;
816  }
817  chain.emplace_back(node->block_hash);
818  node = parent;
819  }
820  }
821 
822  while (maximum > chain.size()) {
823  auto header_res = header_repo_->getBlockHeader(hash);
824  if (header_res.has_error()) {
825  if (chain.empty()) {
826  log_->error("cannot retrieve block with hash {}: {}",
827  hash,
828  header_res.error().message());
830  }
831  break;
832  }
833  const auto &header = header_res.value();
834 
835  chain.emplace_back(hash);
836 
837  if (header.number == 0) {
838  break;
839  }
840 
841  hash = header.parent_hash;
842  }
843 
844  return chain;
845  }
846 
848  const primitives::BlockHash &ancestor,
849  const primitives::BlockHash &descendant) const {
850  OUTCOME_TRY(from, header_repo_->getNumberByHash(ancestor));
851  OUTCOME_TRY(to, header_repo_->getNumberByHash(descendant));
852  if (to < from) {
854  }
855  auto count = to - from + 1;
856  OUTCOME_TRY(chain, getDescendingChainToBlock(descendant, count));
857  BOOST_ASSERT(chain.size() == count);
858  if (chain.back() != ancestor) {
860  }
861  std::reverse(chain.begin(), chain.end());
862  return std::move(chain);
863  }
864 
866  const primitives::BlockHash &ancestor,
867  const primitives::BlockHash &descendant) const {
868  auto ancestor_node_ptr = tree_->getRoot().findByHash(ancestor);
869  auto descendant_node_ptr = tree_->getRoot().findByHash(descendant);
870 
871  /*
872  * check that ancestor is above descendant
873  * optimization that prevents reading blockDB up the genesis
874  * TODO (xDimon) it could be not right place for this check
875  * or changing logic may make it obsolete
876  * block numbers may be obtained somewhere else
877  */
878  primitives::BlockNumber ancestor_depth = 0u;
879  primitives::BlockNumber descendant_depth = 0u;
880  if (ancestor_node_ptr) {
881  ancestor_depth = ancestor_node_ptr->depth;
882  } else {
883  auto number_res = header_repo_->getNumberByHash(ancestor);
884  if (!number_res) {
885  return false;
886  }
887  ancestor_depth = number_res.value();
888  }
889  if (descendant_node_ptr) {
890  descendant_depth = descendant_node_ptr->depth;
891  } else {
892  auto number_res = header_repo_->getNumberByHash(descendant);
893  if (!number_res) {
894  return false;
895  }
896  descendant_depth = number_res.value();
897  }
898  if (descendant_depth < ancestor_depth) {
899  SL_WARN(log_,
900  "Ancestor block is lower. {} in comparison with {}",
901  primitives::BlockInfo(ancestor_depth, ancestor),
902  primitives::BlockInfo(descendant_depth, descendant));
903  return false;
904  }
905 
906  // if both nodes are in our light tree, we can use this representation
907  // only
908  if (ancestor_node_ptr && descendant_node_ptr) {
909  auto current_node = descendant_node_ptr;
910  while (current_node != ancestor_node_ptr) {
911  if (current_node->depth <= ancestor_node_ptr->depth) {
912  return false;
913  }
914  if (auto parent = current_node->parent; !parent.expired()) {
915  current_node = parent.lock();
916  } else {
917  return false;
918  }
919  }
920  return true;
921  }
922 
923  // else, we need to use a database
924 
925  // Try to use optimal way, if ancestor and descendant in the finalized chain
926  if (descendant_depth <= getLastFinalized().number) {
927  auto res = header_repo_->getHashByNumber(descendant_depth);
928  BOOST_ASSERT_MSG(res.has_value(),
929  "Any finalized block must be accessible by number");
930  // Check if descendant in finalised chain
931  if (res.value() == descendant) {
932  res = header_repo_->getHashByNumber(ancestor_depth);
933  BOOST_ASSERT_MSG(res.has_value(),
934  "Any finalized block must be accessible by number");
935  if (res.value() == ancestor) {
936  // Ancestor and descendant in the finalized chain,
937  // therefore they have direct chain between each other
938  return true;
939  } else {
940  // Ancestor in the finalized chain, but descendant is not,
941  // therefore they can not have direct chain between each other
942  return false;
943  }
944  }
945  }
946 
947  auto current_hash = descendant;
948  KAGOME_PROFILE_START(search_finalized_chain)
949  while (current_hash != ancestor) {
950  auto current_header_res = header_repo_->getBlockHeader(current_hash);
951  if (!current_header_res) {
952  return false;
953  }
954  if (current_header_res.value().number <= ancestor_depth) {
955  return false;
956  }
957  current_hash = current_header_res.value().parent_hash;
958  }
959  KAGOME_PROFILE_END(search_finalized_chain)
960  return true;
961  }
962 
964  auto &&leaf = tree_->getMetadata().deepest_leaf.lock();
965  BOOST_ASSERT(leaf != nullptr);
966  return {leaf->depth, leaf->block_hash};
967  }
968 
969  outcome::result<primitives::BlockInfo> BlockTreeImpl::getBestContaining(
970  const primitives::BlockHash &target_hash,
971  const std::optional<primitives::BlockNumber> &max_number) const {
972  OUTCOME_TRY(target_header, header_repo_->getBlockHeader(target_hash));
973  if (max_number.has_value() && target_header.number > max_number.value()) {
975  }
976  OUTCOME_TRY(canon_hash,
977  header_repo_->getHashByNumber(target_header.number));
978  // if a max number is given we try to fetch the block at the
979  // given depth, if it doesn't exist or `max_number` is not
980  // provided, we continue to search from all leaves below.
981  if (canon_hash == target_hash) {
982  if (max_number.has_value()) {
983  auto header = header_repo_->getBlockHeader(max_number.value());
984  if (header) {
985  OUTCOME_TRY(hash,
986  header_repo_->getHashByNumber(header.value().number));
987  return primitives::BlockInfo{header.value().number, hash};
988  }
989  }
990  } else {
991  OUTCOME_TRY(last_finalized,
992  header_repo_->getNumberByHash(getLastFinalized().hash));
993  if (last_finalized >= target_header.number) {
995  }
996  }
997  for (auto &leaf_hash : getLeavesSorted()) {
998  auto current_hash = leaf_hash;
999  auto best_hash = current_hash;
1000  if (max_number.has_value()) {
1001  OUTCOME_TRY(hash, walkBackUntilLess(current_hash, max_number.value()));
1002  best_hash = hash;
1003  current_hash = hash;
1004  }
1005  OUTCOME_TRY(best_header, header_repo_->getBlockHeader(best_hash));
1006  primitives::BlockNumber current_block_number{};
1007  do {
1008  OUTCOME_TRY(current_header, header_repo_->getBlockHeader(current_hash));
1009  if (current_hash == target_hash) {
1010  return primitives::BlockInfo{best_header.number, best_hash};
1011  }
1012  current_block_number = current_header.number;
1013  current_hash = current_header.parent_hash;
1014  } while (current_block_number >= target_header.number);
1015  }
1016 
1017  log_->warn(
1018  "Block {} exists in chain but not found when following all leaves "
1019  "backwards. Max block number = {}",
1020  target_hash.toHex(),
1021  max_number.has_value() ? max_number.value() : -1);
1023  }
1024 
1025  std::vector<primitives::BlockHash> BlockTreeImpl::getLeaves() const {
1026  std::vector<primitives::BlockHash> result;
1027  result.reserve(tree_->getMetadata().leaves.size());
1028  std::transform(tree_->getMetadata().leaves.begin(),
1029  tree_->getMetadata().leaves.end(),
1030  std::back_inserter(result),
1031  [](const auto &hash) { return hash; });
1032  return result;
1033  }
1034 
1036  const primitives::BlockHash &block) const {
1037  if (auto node = tree_->getRoot().findByHash(block); node != nullptr) {
1038  std::vector<primitives::BlockHash> result;
1039  result.reserve(node->children.size());
1040  for (const auto &child : node->children) {
1041  result.push_back(child->block_hash);
1042  }
1043  return result;
1044  }
1045  OUTCOME_TRY(header, storage_->getBlockHeader(block));
1046  if (!header.has_value()) return BlockTreeError::HEADER_NOT_FOUND;
1047  // if node is not in tree_ it must be finalized and thus have only one child
1048  OUTCOME_TRY(child_hash,
1049  header_repo_->getHashByNumber(header.value().number + 1));
1050  return outcome::success(std::vector<primitives::BlockHash>{child_hash});
1051  }
1052 
1054  const auto &last = tree_->getMetadata().last_finalized.lock();
1055  BOOST_ASSERT(last != nullptr);
1056  return primitives::BlockInfo{last->depth, last->block_hash};
1057  }
1058 
1059  std::vector<primitives::BlockHash> BlockTreeImpl::getLeavesSorted() const {
1060  std::vector<primitives::BlockInfo> leaf_depths;
1061  auto leaves = getLeaves();
1062  leaf_depths.reserve(leaves.size());
1063  for (auto &leaf : leaves) {
1064  auto leaf_node = tree_->getRoot().findByHash(leaf);
1065  leaf_depths.emplace_back(
1066  primitives::BlockInfo{leaf_node->depth, leaf_node->block_hash});
1067  }
1068  std::sort(
1069  leaf_depths.begin(),
1070  leaf_depths.end(),
1071  [](auto const &p1, auto const &p2) { return p1.number > p2.number; });
1072  std::vector<primitives::BlockHash> leaf_hashes;
1073  leaf_hashes.reserve(leaf_depths.size());
1074  std::transform(leaf_depths.begin(),
1075  leaf_depths.end(),
1076  std::back_inserter(leaf_hashes),
1077  [](auto &p) { return p.hash; });
1078  return leaf_hashes;
1079  }
1080 
1081  outcome::result<primitives::BlockHash> BlockTreeImpl::walkBackUntilLess(
1082  const primitives::BlockHash &start,
1083  const primitives::BlockNumber &limit) const {
1084  auto current_hash = start;
1085  while (true) {
1086  OUTCOME_TRY(current_header, header_repo_->getBlockHeader(current_hash));
1087  if (current_header.number <= limit) {
1088  return current_hash;
1089  }
1090  current_hash = current_header.parent_hash;
1091  }
1092  }
1093 
1094  outcome::result<void> BlockTreeImpl::prune(
1095  const std::shared_ptr<TreeNode> &lastFinalizedNode) {
1096  std::deque<std::shared_ptr<TreeNode>> to_remove;
1097 
1098  auto following_node = lastFinalizedNode;
1099 
1100  for (auto current_node = following_node->parent.lock();
1101  current_node && !current_node->finalized;
1102  current_node = current_node->parent.lock()) {
1103  // DFS-on-deque
1104  to_remove.emplace_back(); // Waterbreak
1105  std::copy_if(current_node->children.begin(),
1106  current_node->children.end(),
1107  std::back_inserter(to_remove),
1108  [&](const auto &child) { return child != following_node; });
1109  auto last = to_remove.back();
1110  while (last != nullptr) {
1111  to_remove.pop_back();
1112  std::copy(last->children.begin(),
1113  last->children.end(),
1114  std::back_inserter(to_remove));
1115  to_remove.emplace_front(std::move(last));
1116  last = to_remove.back();
1117  }
1118  to_remove.pop_back(); // Remove waterbreak
1119 
1120  // remove (in memory) all child, except main chain block
1121  current_node->children = {following_node};
1122  following_node = current_node;
1123  }
1124 
1125  std::vector<primitives::Extrinsic> extrinsics;
1126 
1127  // remove from storage
1128  for (const auto &node : to_remove) {
1129  OUTCOME_TRY(block_body_res, storage_->getBlockBody(node->block_hash));
1130  if (block_body_res.has_value()) {
1131  extrinsics.reserve(extrinsics.size() + block_body_res.value().size());
1132  for (auto &ext : block_body_res.value()) {
1133  if (auto key = extrinsic_event_key_repo_->get(
1134  hasher_->blake2b_256(ext.data))) {
1135  extrinsic_events_engine_->notify(
1136  key.value(),
1138  key.value(), node->block_hash));
1139  }
1140  extrinsics.emplace_back(std::move(ext));
1141  }
1142  }
1143 
1144  tree_->removeFromMeta(node);
1145  OUTCOME_TRY(storage_->removeBlock({node->depth, node->block_hash}));
1146  }
1147 
1148  // trying to return extrinsics back to transaction pool
1149  for (auto &&extrinsic : extrinsics) {
1150  auto result = extrinsic_observer_->onTxMessage(extrinsic);
1151  if (result) {
1152  SL_DEBUG(log_, "Tx {} was reapplied", result.value().toHex());
1153  } else {
1154  SL_DEBUG(log_, "Tx was skipped: {}", result.error().message());
1155  }
1156  }
1157 
1158  return outcome::success();
1159  }
1160 
1161  outcome::result<void> BlockTreeImpl::reorganize() {
1162  auto block = BlockTreeImpl::deepestLeaf();
1163  if (block.number == 0) {
1164  return outcome::success();
1165  }
1166  auto hash_res = header_repo_->getHashByNumber(block.number);
1167  if (hash_res.has_error()) {
1168  if (hash_res
1169  != outcome::failure(blockchain::BlockTreeError::HEADER_NOT_FOUND)) {
1170  return hash_res.as_failure();
1171  }
1172  } else if (block.hash == hash_res.value()) {
1173  return outcome::success();
1174  }
1175 
1176  size_t count = 0;
1177  for (;;) {
1178  OUTCOME_TRY(storage_->putNumberToIndexKey(block));
1179  if (block.number == 0) break;
1180  OUTCOME_TRY(header, getBlockHeader(block.hash));
1181  auto parent_hash_res = header_repo_->getHashByNumber(block.number - 1);
1182  if (parent_hash_res.has_error()) {
1183  if (parent_hash_res
1184  != outcome::failure(blockchain::BlockTreeError::HEADER_NOT_FOUND)) {
1185  return parent_hash_res.as_failure();
1186  }
1187  } else if (header.parent_hash == parent_hash_res.value()) {
1188  break;
1189  }
1190  ++count;
1191  block = {block.number - 1, header.parent_hash};
1192  }
1193 
1194  if (count > 1) {
1195  SL_DEBUG(log_, "Best chain reorganized for {} blocks deep", count);
1196  }
1197 
1198  return outcome::success();
1199  }
1200 
1201 } // namespace kagome::blockchain
static outcome::result< std::shared_ptr< BlockTreeImpl > > create(std::shared_ptr< BlockHeaderRepository > header_repo, std::shared_ptr< BlockStorage > storage, std::shared_ptr< network::ExtrinsicObserver > extrinsic_observer, std::shared_ptr< crypto::Hasher > hasher, primitives::events::ChainSubscriptionEnginePtr chain_events_engine, primitives::events::ExtrinsicSubscriptionEnginePtr extrinsic_events_engine, std::shared_ptr< subscription::ExtrinsicEventKeyRepository > extrinsic_event_key_repo, std::shared_ptr< storage::changes_trie::ChangesTracker > changes_tracker, std::shared_ptr< const class JustificationStoragePolicy > justification_storage_policy)
Create an instance of block tree.
outcome::result< primitives::BlockHeader > getBlockHeader(const primitives::BlockId &block) const override
#define KAGOME_PROFILE_END(scope)
std::shared_ptr< BlockStorage > storage_
BlockTreeImpl(std::shared_ptr< BlockHeaderRepository > header_repo, std::shared_ptr< BlockStorage > storage, std::unique_ptr< CachedTree > cached_tree, std::shared_ptr< network::ExtrinsicObserver > extrinsic_observer, std::shared_ptr< crypto::Hasher > hasher, primitives::events::ChainSubscriptionEnginePtr chain_events_engine, primitives::events::ExtrinsicSubscriptionEnginePtr extrinsic_events_engine, std::shared_ptr< subscription::ExtrinsicEventKeyRepository > extrinsic_event_key_repo, std::shared_ptr< storage::changes_trie::ChangesTracker > changes_tracker, std::shared_ptr< const class JustificationStoragePolicy > justification_storage_policy)
BlockHashVecRes getBestChainFromBlock(const primitives::BlockHash &block, uint64_t maximum) const override
outcome::result< void > addBlockBody(primitives::BlockNumber block_number, const primitives::BlockHash &block_hash, const primitives::BlockBody &body) override
virtual void set(double val)=0
Set the gauge to the given value.
static outcome::result< void > recover(primitives::BlockId target_block, std::shared_ptr< BlockStorage > storage, std::shared_ptr< BlockHeaderRepository > header_repo, std::shared_ptr< const storage::trie::TrieStorage > trie_storage, std::shared_ptr< blockchain::BlockTree > block_tree)
Recover block tree state at provided block.
std::vector< Extrinsic > BlockBody
Definition: block.hpp:14
Block class represents polkadot block primitive.
Definition: block.hpp:19
primitives::events::ExtrinsicSubscriptionEnginePtr extrinsic_events_engine_
std::shared_ptr< crypto::Hasher > hasher_
DatabaseError
universal database interface error
std::shared_ptr< const class JustificationStoragePolicy > justification_storage_policy_
static ExtrinsicLifecycleEvent Retracted(SubscribedExtrinsicId id, Hash256Span retracted_block)
primitives::BlockInfo deepestLeaf() const override
outcome::result< EpochDigest > getNextEpochDigest(const primitives::BlockHeader &header)
outcome::result< primitives::BlockHash > walkBackUntilLess(const primitives::BlockHash &start, const primitives::BlockNumber &limit) const
std::vector< primitives::BlockHash > getLeaves() const override
outcome::result< bool > hasBlockHeader(const primitives::BlockId &block) const override
std::vector< primitives::BlockHash > getLeavesSorted() const
metrics::RegistryPtr metrics_registry_
outcome::result< void > prune(const std::shared_ptr< TreeNode > &lastFinalizedNode)
const primitives::BlockHash & getGenesisBlockHash() const override
outcome::result< void > reorganize()
primitives::BlockInfo getLastFinalized() const override
static ExtrinsicLifecycleEvent Finalized(SubscribedExtrinsicId id, Hash256Span block)
uint32_t BlockNumber
Definition: common.hpp:18
SLBuffer< std::numeric_limits< size_t >::max()> Buffer
Definition: buffer.hpp:244
std::shared_ptr< ChainSubscriptionEngine > ChainSubscriptionEnginePtr
outcome::result< void > addBlock(const primitives::Block &block) override
outcome::result< void > finalize(const primitives::BlockHash &block_hash, const primitives::Justification &justification) override
outcome::result< primitives::BlockBody > getBlockBody(const primitives::BlockId &block) const override
std::shared_ptr< soralog::Logger > Logger
Definition: logger.hpp:23
outcome::result< void > removeLeaf(const primitives::BlockHash &block_hash) override
metrics::Gauge * metric_finalized_block_height_
std::unique_ptr< CachedTree > tree_
static ExtrinsicLifecycleEvent InBlock(SubscribedExtrinsicId id, Hash256Span block)
std::shared_ptr< network::ExtrinsicObserver > extrinsic_observer_
std::shared_ptr< storage::changes_trie::ChangesTracker > trie_changes_tracker_
BlockHashVecRes getChainByBlocks(const primitives::BlockHash &ancestor, const primitives::BlockHash &descendant) const override
outcome::result< std::vector< primitives::BlockHash >> BlockHashVecRes
Definition: block_tree.hpp:32
std::optional< primitives::BlockHash > genesis_block_hash_
std::shared_ptr< ExtrinsicSubscriptionEngine > ExtrinsicSubscriptionEnginePtr
std::string toHex() const noexcept
Definition: blob.hpp:160
outcome::result< void > addBlockHeader(const primitives::BlockHeader &header) override
BlockHashVecRes getDescendingChainToBlock(const primitives::BlockHash &block, uint64_t maximum) const override
BlockHashVecRes getChildren(const primitives::BlockHash &block) const override
primitives::events::ChainSubscriptionEnginePtr chain_events_engine_
BlockNumber number
index of the block in the chain
std::shared_ptr< subscription::ExtrinsicEventKeyRepository > extrinsic_event_key_repo_
bool hasDirectChain(const primitives::BlockHash &ancestor, const primitives::BlockHash &descendant) const override
boost::variant< BlockHash, BlockNumber > BlockId
Block id is the variant over BlockHash and BlockNumber.
Definition: block_id.hpp:18
#define KAGOME_PROFILE_START(scope)
outcome::result< primitives::Justification > getBlockJustification(const primitives::BlockId &block) const override
BlockHeader header
block header
Definition: block.hpp:22
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
BlockHash parent_hash
32-byte Blake2s hash of parent header
outcome::result< primitives::BlockInfo > getBestContaining(const primitives::BlockHash &target_hash, const std::optional< primitives::BlockNumber > &max_number) const override
Get the most recent block of the best (longest) chain among those that contain a block with...
primitives::BlockHash hash
Definition: block_data.hpp:24
BlockBody body
extrinsics collection
Definition: block.hpp:23
outcome::result< void > addExistingBlock(const primitives::BlockHash &block_hash, const primitives::BlockHeader &block_header) override
std::shared_ptr< BlockHeaderRepository > header_repo_