Kagome
Polkadot Runtime Engine in C++17
block_appender_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <chrono>
9 
19 #include "primitives/common.hpp"
20 #include "scale/scale.hpp"
21 
24  switch (e) {
25  case E::INVALID_BLOCK:
26  return "Invalid block";
27  case E::PARENT_NOT_FOUND:
28  return "Parent not found";
29  }
30  return "Unknown error";
31 }
32 
33 namespace kagome::consensus {
34 
36  std::shared_ptr<blockchain::BlockTree> block_tree,
37  std::shared_ptr<consensus::babe::BabeConfigRepository> babe_config_repo,
38  std::shared_ptr<BlockValidator> block_validator,
39  std::shared_ptr<grandpa::Environment> grandpa_environment,
40  std::shared_ptr<crypto::Hasher> hasher,
41  std::shared_ptr<blockchain::DigestTracker> digest_tracker,
42  std::shared_ptr<BabeUtil> babe_util,
43  std::shared_ptr<babe::ConsistencyKeeper> consistency_keeper)
44  : block_tree_{std::move(block_tree)},
45  babe_config_repo_{std::move(babe_config_repo)},
46  block_validator_{std::move(block_validator)},
47  grandpa_environment_{std::move(grandpa_environment)},
48  hasher_{std::move(hasher)},
49  digest_tracker_(std::move(digest_tracker)),
50  babe_util_(std::move(babe_util)),
51  consistency_keeper_(std::move(consistency_keeper)),
52  logger_{log::createLogger("BlockAppender", "block_appender")} {
53  BOOST_ASSERT(block_tree_ != nullptr);
54  BOOST_ASSERT(babe_config_repo_ != nullptr);
55  BOOST_ASSERT(block_validator_ != nullptr);
56  BOOST_ASSERT(grandpa_environment_ != nullptr);
57  BOOST_ASSERT(hasher_ != nullptr);
58  BOOST_ASSERT(digest_tracker_ != nullptr);
59  BOOST_ASSERT(babe_util_ != nullptr);
60  BOOST_ASSERT(consistency_keeper_ != nullptr);
61  BOOST_ASSERT(logger_ != nullptr);
62  }
63 
64  outcome::result<void> BlockAppenderImpl::appendBlock(
66  if (not b.header.has_value()) {
67  logger_->warn("Skipping a block without header");
68  return Error::INVALID_BLOCK;
69  }
70  auto &header = b.header.value();
71 
72  auto block_hash = hasher_->blake2b_256(scale::encode(header).value());
73 
74  primitives::BlockInfo block_info(header.number, block_hash);
75 
76  if (last_appended_.has_value()) {
77  if (last_appended_->number > block_info.number) {
78  SL_TRACE(
79  logger_, "Skip early appended header of block: {}", block_info);
80  return outcome::success();
81  }
82  if (last_appended_.value() == block_info) {
83  SL_TRACE(logger_, "Skip just appended header of block: {}", block_info);
84  return outcome::success();
85  }
86  }
87 
88  if (last_appended_
89  != primitives::BlockInfo(header.number - 1, header.parent_hash)) {
90  if (auto header_res = block_tree_->getBlockHeader(header.parent_hash);
91  header_res.has_error()
92  && header_res.error()
94  logger_->warn("Skipping a block {} with unknown parent", block_info);
96  } else if (header_res.has_error()) {
97  return header_res.as_failure();
98  }
99  }
100 
101  // get current time to measure performance if block execution
102  auto t_start = std::chrono::high_resolution_clock::now();
103 
104  primitives::Block block{.header = std::move(header)};
105 
106  // check if block header already exists. If so, do not append
107  if (auto header_res = block_tree_->getBlockHeader(block_hash);
108  header_res.has_value()) {
109  SL_DEBUG(logger_, "Skip existing header of block: {}", block_info);
110 
111  OUTCOME_TRY(block_tree_->addExistingBlock(block_hash, block.header));
112  } else if (header_res.error()
114  return header_res.as_failure();
115  } else {
116  OUTCOME_TRY(block_tree_->addBlockHeader(block.header));
117  }
118 
119  OUTCOME_TRY(babe_digests, getBabeDigests(block.header));
120 
121  const auto &babe_header = babe_digests.second;
122 
123  auto slot_number = babe_header.slot_number;
124 
125  babe_util_->syncEpoch([&] {
126  auto res = block_tree_->getBlockHeader(primitives::BlockNumber(1));
127  if (res.has_error()) {
128  if (block.header.number == 1) {
129  SL_TRACE(logger_,
130  "First block slot is {}: it is first block (at executing)",
131  slot_number);
132  return std::tuple(slot_number, false);
133  } else {
134  SL_TRACE(logger_,
135  "First block slot is {}: no first block (at executing)",
136  babe_util_->getCurrentSlot());
137  return std::tuple(babe_util_->getCurrentSlot(), false);
138  }
139  }
140 
141  const auto &first_block_header = res.value();
142  auto babe_digest_res = consensus::getBabeDigests(first_block_header);
143  BOOST_ASSERT_MSG(babe_digest_res.has_value(),
144  "Any non genesis block must contain babe digest");
145  auto first_slot_number = babe_digest_res.value().second.slot_number;
146 
147  auto is_first_block_finalized =
148  block_tree_->getLastFinalized().number > 0;
149 
150  SL_TRACE(
151  logger_,
152  "First block slot is {}: by {}finalized first block (at executing)",
153  first_slot_number,
154  is_first_block_finalized ? "" : "non-");
155  return std::tuple(first_slot_number, is_first_block_finalized);
156  });
157 
158  auto epoch_number = babe_util_->slotToEpoch(slot_number);
159 
160  SL_VERBOSE(
161  logger_,
162  "Appending header of block {} ({} in slot {}, epoch {}, authority #{})",
163  block_info,
164  to_string(babe_header.slotType()),
165  slot_number,
166  epoch_number,
167  babe_header.authority_index);
168 
169  auto consistency_guard = consistency_keeper_->start(block_info);
170 
171  // observe digest of block
172  // (must be done strictly after block will be added)
173  auto digest_tracking_res =
174  digest_tracker_->onDigest(block_info, block.header.digest);
175  if (digest_tracking_res.has_error()) {
176  SL_ERROR(logger_,
177  "Error while tracking digest of block {}: {}",
178  block_info,
179  digest_tracking_res.error().message());
180  return digest_tracking_res.as_failure();
181  }
182 
183  auto babe_config = babe_config_repo_->config(block_info, epoch_number);
184  if (babe_config == nullptr) {
185  return Error::INVALID_BLOCK; // TODO Change to more appropriate error
186  }
187 
188  SL_TRACE(logger_,
189  "Actual epoch digest to apply block {} (slot {}, epoch {}). "
190  "Randomness: {}",
191  block_info,
192  slot_number,
193  epoch_number,
194  babe_config->randomness);
195 
196  auto threshold = calculateThreshold(babe_config->leadership_rate,
197  babe_config->authorities,
198  babe_header.authority_index);
199 
200  OUTCOME_TRY(block_validator_->validateHeader(
201  block.header,
202  epoch_number,
203  babe_config->authorities[babe_header.authority_index].id,
204  threshold,
205  *babe_config));
206 
207  // apply justification if any (must be done strictly after block will be
208  // added and his consensus-digests will be handled)
209  if (b.justification.has_value()) {
210  SL_VERBOSE(logger_, "Justification received for block {}", block_info);
211 
212  // try to apply left in justification store values first
213  if (not justifications_.empty()) {
214  std::vector<primitives::BlockInfo> to_remove;
215  for (const auto &[block_justified_for, justification] :
216  justifications_) {
217  auto res = applyJustification(block_justified_for, justification);
218  if (res) {
219  to_remove.push_back(block_justified_for);
220  }
221  }
222  if (not to_remove.empty()) {
223  for (const auto &item : to_remove) {
224  justifications_.erase(item);
225  }
226  }
227  }
228 
229  auto res = applyJustification(block_info, b.justification.value());
230  if (res.has_error()) {
231  if (res
232  == outcome::failure(grandpa::VotingRoundError::NOT_ENOUGH_WEIGHT)) {
233  justifications_.emplace(block_info, b.justification.value());
234  } else {
235  SL_ERROR(logger_,
236  "Error while applying of block {} justification: {}",
237  block_info,
238  res.error().message());
239  return res.as_failure();
240  }
241  } else {
242  // safely could be remove if current justification applied successfully
243  justifications_.clear();
244  }
245  }
246 
247  auto now = std::chrono::high_resolution_clock::now();
248 
249  SL_DEBUG(
250  logger_,
251  "Imported header of block {} within {} us",
252  block_info,
253  std::chrono::duration_cast<std::chrono::microseconds>(now - t_start)
254  .count());
255 
256  auto block_delta = block_info.number - speed_data_.block_number;
257  auto time_delta = now - speed_data_.time;
258  if (block_delta >= 10000 or time_delta >= std::chrono::minutes(1)) {
259  SL_INFO(logger_,
260  "Imported {} more headers of blocks. Average speed is {} bps",
261  block_delta,
262  block_delta
263  / std::chrono::duration_cast<std::chrono::seconds>(time_delta)
264  .count());
265  speed_data_.block_number = block_info.number;
266  speed_data_.time = now;
267  }
268 
269  consistency_guard.commit();
270 
271  last_appended_.emplace(std::move(block_info));
272 
273  return outcome::success();
274  }
275 
277  const primitives::BlockInfo &block_info,
278  const primitives::Justification &justification) {
279  return grandpa_environment_->applyJustification(block_info, justification);
280  }
281 
282 } // namespace kagome::consensus
std::shared_ptr< BabeUtil > babe_util_
BlockAppenderImpl(std::shared_ptr< blockchain::BlockTree > block_tree, std::shared_ptr< consensus::babe::BabeConfigRepository > babe_config_repo, std::shared_ptr< BlockValidator > block_validator, std::shared_ptr< grandpa::Environment > grandpa_environment, std::shared_ptr< crypto::Hasher > hasher, std::shared_ptr< blockchain::DigestTracker > digest_tracker, std::shared_ptr< BabeUtil > babe_util, std::shared_ptr< babe::ConsistencyKeeper > consistency_keeper)
outcome::result< std::pair< Seal, BabeBlockHeader > > getBabeDigests(const primitives::BlockHeader &block_header)
Block class represents polkadot block primitive.
Definition: block.hpp:19
std::string_view to_string(SlotType s)
Definition: slot.hpp:22
std::map< primitives::BlockInfo, primitives::Justification > justifications_
Threshold calculateThreshold(const std::pair< uint64_t, uint64_t > &ratio, const primitives::AuthorityList &authorities, primitives::AuthorityIndex authority_index)
OUTCOME_CPP_DEFINE_CATEGORY(kagome::consensus, BlockAppenderImpl::Error, e)
uint32_t BlockNumber
Definition: common.hpp:18
std::shared_ptr< crypto::Hasher > hasher_
std::shared_ptr< grandpa::Environment > grandpa_environment_
std::shared_ptr< blockchain::BlockTree > block_tree_
std::optional< primitives::BlockInfo > last_appended_
std::shared_ptr< consensus::babe::BabeConfigRepository > babe_config_repo_
outcome::result< void > appendBlock(primitives::BlockData &&b) override
std::shared_ptr< BlockValidator > block_validator_
BlockHeader header
block header
Definition: block.hpp:22
Logger createLogger(const std::string &tag)
Definition: logger.cpp:112
outcome::result< void > applyJustification(const primitives::BlockInfo &block_info, const primitives::Justification &justification) override
std::shared_ptr< babe::ConsistencyKeeper > consistency_keeper_
struct kagome::consensus::BlockAppenderImpl::@2 speed_data_
std::shared_ptr< blockchain::DigestTracker > digest_tracker_