Kagome
Polkadot Runtime Engine in C++17
grandpa_impl.cpp
Go to the documentation of this file.
1 
7 
16 #include "scale/scale.hpp"
17 
18 namespace {
19  constexpr auto highestGrandpaRoundMetricName =
20  "kagome_finality_grandpa_round";
21 
22  template <typename D>
23  auto toMilliseconds(D &&duration) {
24  return std::chrono::duration_cast<std::chrono::milliseconds>(duration);
25  }
26 } // namespace
27 
29 
31 
32  namespace {
33  Clock::Duration getGossipDuration(const application::ChainSpec &chain) {
34  // https://github.com/paritytech/polkadot/pull/5448
35  auto slow = chain.isVersi() || chain.isWococo() || chain.isRococo()
36  || chain.isKusama();
37  return std::chrono::duration_cast<Clock::Duration>(
38  std::chrono::milliseconds{slow ? 2000 : 1000});
39  }
40  } // namespace
41 
43  std::shared_ptr<application::AppStateManager> app_state_manager,
44  std::shared_ptr<Environment> environment,
45  std::shared_ptr<crypto::Ed25519Provider> crypto_provider,
46  std::shared_ptr<runtime::GrandpaApi> grandpa_api,
47  const std::shared_ptr<crypto::Ed25519Keypair> &keypair,
48  const application::ChainSpec &chain_spec,
49  std::shared_ptr<Clock> clock,
50  std::shared_ptr<libp2p::basic::Scheduler> scheduler,
51  std::shared_ptr<authority::AuthorityManager> authority_manager,
52  std::shared_ptr<network::Synchronizer> synchronizer,
53  std::shared_ptr<network::PeerManager> peer_manager,
54  std::shared_ptr<blockchain::BlockTree> block_tree,
55  std::shared_ptr<network::ReputationRepository> reputation_repository)
56  : round_time_factor_{getGossipDuration(chain_spec)},
57  environment_{std::move(environment)},
58  crypto_provider_{std::move(crypto_provider)},
59  grandpa_api_{std::move(grandpa_api)},
60  keypair_{keypair},
61  clock_{std::move(clock)},
62  scheduler_{std::move(scheduler)},
63  authority_manager_(std::move(authority_manager)),
64  synchronizer_(std::move(synchronizer)),
65  peer_manager_(std::move(peer_manager)),
66  block_tree_(std::move(block_tree)),
67  reputation_repository_(std::move(reputation_repository)) {
68  BOOST_ASSERT(environment_ != nullptr);
69  BOOST_ASSERT(crypto_provider_ != nullptr);
70  BOOST_ASSERT(grandpa_api_ != nullptr);
71  BOOST_ASSERT(clock_ != nullptr);
72  BOOST_ASSERT(scheduler_ != nullptr);
73  BOOST_ASSERT(authority_manager_ != nullptr);
74  BOOST_ASSERT(synchronizer_ != nullptr);
75  BOOST_ASSERT(peer_manager_ != nullptr);
76  BOOST_ASSERT(block_tree_ != nullptr);
77  BOOST_ASSERT(reputation_repository_ != nullptr);
78 
79  BOOST_ASSERT(app_state_manager != nullptr);
80 
81  // Register metrics
82  metrics_registry_->registerGaugeFamily(highestGrandpaRoundMetricName,
83  "Highest GRANDPA round");
85  metrics_registry_->registerGaugeMetric(highestGrandpaRoundMetricName);
87 
88  // allow app state manager to prepare, start and stop grandpa consensus
89  // pipeline
90  app_state_manager->takeControl(*this);
91  }
92 
94  // Set themselves in environment
95  environment_->setJustificationObserver(shared_from_this());
96  return true;
97  }
98 
100  // Obtain last completed round
101  auto round_state_res = getLastCompletedRound();
102  if (not round_state_res.has_value()) {
103  logger_->critical(
104  "Can't retrieve last round data: {}. Stopping grandpa execution",
105  round_state_res.error().message());
106  return false;
107  }
108  auto &round_state = round_state_res.value();
109 
110  SL_DEBUG(logger_,
111  "Grandpa will be started with round #{}",
112  round_state.round_number + 1);
113 
114  auto authorities_res = authority_manager_->authorities(
115  round_state.last_finalized_block, IsBlockFinalized{false});
116  if (not authorities_res.has_value()) {
117  logger_->critical(
118  "Can't retrieve authorities for block {}. Stopping grandpa execution",
119  round_state.last_finalized_block);
120  return false;
121  }
122  auto &authority_set = authorities_res.value();
123 
124  auto voters = std::make_shared<VoterSet>(authority_set->id);
125  for (const auto &authority : authority_set->authorities) {
126  auto res = voters->insert(primitives::GrandpaSessionKey(authority.id.id),
127  authority.weight);
128  if (res.has_error()) {
129  logger_->critical(
130  "Can't make voter set: {}. Stopping grandpa execution",
131  res.error().message());
132  return false;
133  }
134  }
135 
136  current_round_ = makeInitialRound(round_state, std::move(voters));
137  BOOST_ASSERT(current_round_ != nullptr);
138 
139  if (not current_round_->finalizedBlock().has_value()) {
140  logger_->critical(
141  "Initial round must be finalized, but it is not. "
142  "Stopping grandpa execution");
143  return false;
144  }
145 
146  // Timer to send neighbor message if round does not change long time (1 min)
147  fallback_timer_handle_ = scheduler_->scheduleWithHandle(
148  [wp = weak_from_this()] {
149  auto self = wp.lock();
150  if (not self) {
151  return;
152  }
153  BOOST_ASSERT_MSG(self->current_round_,
154  "Current round must be defiled anytime after start");
155  auto round =
156  std::dynamic_pointer_cast<VotingRoundImpl>(self->current_round_);
157  if (round) {
158  round->sendNeighborMessage();
159  }
160 
161  std::ignore =
162  self->fallback_timer_handle_.reschedule(std::chrono::minutes(1));
163  },
164  std::chrono::minutes(1));
165 
167 
168  return true;
169  }
170 
172  fallback_timer_handle_.cancel();
173  }
174 
175  std::shared_ptr<VotingRound> GrandpaImpl::makeInitialRound(
176  const MovableRoundState &round_state, std::shared_ptr<VoterSet> voters) {
177  auto vote_graph = std::make_shared<VoteGraphImpl>(
178  round_state.last_finalized_block, voters, environment_);
179 
180  GrandpaConfig config{.voters = std::move(voters),
181  .round_number = round_state.round_number,
182  .duration = round_time_factor_,
183  .id = keypair_
184  ? std::make_optional(keypair_->public_key)
185  : std::nullopt};
186 
187  auto vote_crypto_provider = std::make_shared<VoteCryptoProviderImpl>(
188  keypair_, crypto_provider_, round_state.round_number, config.voters);
189 
190  auto new_round = std::make_shared<VotingRoundImpl>(
191  shared_from_this(),
192  std::move(config),
194  environment_,
195  std::move(vote_crypto_provider),
196  std::make_shared<VoteTrackerImpl>(), // Prevote tracker
197  std::make_shared<VoteTrackerImpl>(), // Precommit tracker
198  std::move(vote_graph),
199  clock_,
200  scheduler_,
201  round_state);
202 
203  new_round->end(); // it is okay, because we do not want to actually execute
204  // this round
205  return new_round;
206  }
207 
208  std::shared_ptr<VotingRound> GrandpaImpl::makeNextRound(
209  const std::shared_ptr<VotingRound> &round) {
210  BlockInfo best_block =
211  round->finalizedBlock().value_or(round->lastFinalizedBlock());
212 
213  auto authorities_opt =
214  authority_manager_->authorities(best_block, IsBlockFinalized{true});
215  if (!authorities_opt) {
216  SL_CRITICAL(logger_,
217  "Can't retrieve authorities for finalized block {}",
218  best_block);
219  std::abort();
220  }
221 
222  auto &authority_set = authorities_opt.value();
223  BOOST_ASSERT(not authority_set->authorities.empty());
224 
225  auto voters = std::make_shared<VoterSet>(authority_set->id);
226  for (const auto &authority : authority_set->authorities) {
227  auto res = voters->insert(primitives::GrandpaSessionKey(authority.id.id),
228  authority.weight);
229  if (res.has_error()) {
230  SL_CRITICAL(logger_, "Can't make voter set: {}", res.error().message());
231  std::abort();
232  }
233  }
234 
235  const auto new_round_number =
236  round->voterSetId() == voters->id() ? (round->roundNumber() + 1) : 1;
237 
238  auto vote_graph =
239  std::make_shared<VoteGraphImpl>(best_block, voters, environment_);
240 
241  GrandpaConfig config{.voters = std::move(voters),
242  .round_number = new_round_number,
243  .duration = round_time_factor_,
244  .id = keypair_
245  ? std::make_optional(keypair_->public_key)
246  : std::nullopt};
247 
248  auto vote_crypto_provider = std::make_shared<VoteCryptoProviderImpl>(
249  keypair_, crypto_provider_, new_round_number, config.voters);
250 
251  auto new_round = std::make_shared<VotingRoundImpl>(
252  shared_from_this(),
253  std::move(config),
255  environment_,
256  std::move(vote_crypto_provider),
257  std::make_shared<VoteTrackerImpl>(), // Prevote tracker
258  std::make_shared<VoteTrackerImpl>(), // Precommit tracker
259  std::move(vote_graph),
260  clock_,
261  scheduler_,
262  round);
263  return new_round;
264  }
265 
266  std::optional<std::shared_ptr<VotingRound>> GrandpaImpl::selectRound(
267  RoundNumber round_number, std::optional<VoterSetId> voter_set_id) {
268  std::shared_ptr<VotingRound> round = current_round_;
269 
270  while (round != nullptr) {
271  // Probably came to the round with previous voter set
272  if (round->roundNumber() < round_number) {
273  return std::nullopt;
274  }
275 
276  // Round found; check voter set
277  if (round->roundNumber() == round_number) {
278  if (not voter_set_id.has_value()
279  or round->voterSetId() == voter_set_id.value()) {
280  break;
281  }
282  }
283 
284  // Go to the previous round
285  round = round->getPreviousRound();
286  }
287 
288  return round == nullptr ? std::nullopt : std::make_optional(round);
289  }
290 
291  outcome::result<MovableRoundState> GrandpaImpl::getLastCompletedRound()
292  const {
293  auto finalized_block = block_tree_->getLastFinalized();
294 
295  if (finalized_block.number == 0) {
296  return MovableRoundState{.round_number = 0,
297  .last_finalized_block = finalized_block,
298  .votes = {},
299  .finalized = {finalized_block}};
300  }
301 
302  OUTCOME_TRY(encoded_justification,
303  block_tree_->getBlockJustification(finalized_block.hash));
304 
305  OUTCOME_TRY(
306  grandpa_justification,
307  scale::decode<GrandpaJustification>(encoded_justification.data));
308 
309  MovableRoundState round_state{
310  .round_number = grandpa_justification.round_number,
311  .last_finalized_block = grandpa_justification.block_info,
312  .votes = {},
313  .finalized = {grandpa_justification.block_info}};
314 
315  std::transform(std::move_iterator(grandpa_justification.items.begin()),
316  std::move_iterator(grandpa_justification.items.end()),
317  std::back_inserter(round_state.votes),
318  [](auto &&item) { return std::forward<VoteVariant>(item); });
319 
320  return round_state;
321  }
322 
324  const std::shared_ptr<VotingRound> &prev_round) {
325  if (current_round_ != prev_round) {
326  return;
327  }
328 
330 
331  std::ignore = fallback_timer_handle_.reschedule(std::chrono::minutes(1));
332 
333  // Truncate chain of rounds
334  size_t i = 0;
335  for (auto round = current_round_; round != nullptr;
336  round = round->getPreviousRound()) {
337  if (++i >= kKeepRecentRounds) {
338  round->forgetPreviousRound();
339  }
340  }
341 
342  metric_highest_round_->set(current_round_->roundNumber());
343  if (keypair_) {
344  current_round_->play();
345  } else {
346  auto round = std::dynamic_pointer_cast<VotingRoundImpl>(current_round_);
347  if (round) {
348  round->sendNeighborMessage();
349  }
350  }
351  }
352 
354  if (auto opt_round = selectRound(round_number + 1, std::nullopt);
355  opt_round.has_value()) {
356  auto &round = opt_round.value();
357  round->update(VotingRound::IsPreviousRoundChanged{true},
360  }
361  }
362 
364  const libp2p::peer::PeerId &peer_id,
365  const network::GrandpaNeighborMessage &msg) {
366  SL_DEBUG(logger_,
367  "NeighborMessage set_id={} round={} last_finalized={} "
368  "has received from {}",
369  msg.voter_set_id,
370  msg.round_number,
371  msg.last_finalized,
372  peer_id);
373 
374  auto info = peer_manager_->getPeerState(peer_id);
375 
376  // Iff peer just reached one of recent round, then share known votes
377  if (not info.has_value()
378  or (info->get().set_id.has_value()
379  and msg.voter_set_id != info->get().set_id)
380  or (info->get().round_number.has_value()
381  and msg.round_number > info->get().round_number)) {
382  if (auto opt_round = selectRound(msg.round_number, msg.voter_set_id);
383  opt_round.has_value()) {
384  auto &round = opt_round.value();
385  environment_->sendState(peer_id, round->state(), msg.voter_set_id);
386  }
387  }
388 
389  bool reputation_changed = false;
390  if (info.has_value() and info->get().set_id.has_value()
391  and info->get().round_number.has_value()) {
392  const auto prev_set_id = info->get().set_id.value();
393  const auto prev_round_number = info->get().round_number.value();
394 
395  // bad order of set id
396  if (msg.voter_set_id < prev_set_id) {
397  reputation_repository_->change(
399  reputation_changed = true;
400  }
401 
402  // bad order of round number
403  if (msg.voter_set_id == prev_set_id
404  and msg.round_number < prev_round_number) {
405  reputation_repository_->change(
407  reputation_changed = true;
408  }
409  }
410 
411  peer_manager_->updatePeerState(peer_id, msg);
412 
413  if (not reputation_changed) {
414  reputation_repository_->change(
416  }
417 
418  // If peer has the same voter set id
419  if (msg.voter_set_id == current_round_->voterSetId()) {
420  // Check if needed to catch-up peer, then do that
421  if (msg.round_number
422  >= current_round_->roundNumber() + kCatchUpThreshold) {
423  auto res = environment_->onCatchUpRequested(
424  peer_id, msg.voter_set_id, msg.round_number - 1);
425  if (res.has_value()) {
426  pending_catchup_request_.emplace(
427  peer_id,
429  catchup_request_timer_handle_ = scheduler_->scheduleWithHandle(
430  [wp = weak_from_this()] {
431  auto self = wp.lock();
432  if (not self) {
433  return;
434  }
435  if (self->pending_catchup_request_.has_value()) {
436  const auto &peer_id =
437  std::get<0>(self->pending_catchup_request_.value());
438  self->reputation_repository_->change(
439  peer_id,
441  self->pending_catchup_request_.reset();
442  }
443  },
444  toMilliseconds(kCatchupRequestTimeout));
445  }
446  }
447 
448  return;
449  }
450 
451  // Ignore peer whose voter set id lower than our current
452  if (msg.voter_set_id < current_round_->voterSetId()) {
453  return;
454  }
455 
456  if (info->get().last_finalized <= block_tree_->deepestLeaf().number) {
457  // Trying to substitute with justifications' request only
458  auto last_finalized = block_tree_->getLastFinalized();
459  synchronizer_->syncMissingJustifications(
460  peer_id,
461  last_finalized,
462  std::nullopt,
463  [wp = weak_from_this(), last_finalized, msg](auto res) {
464  auto self = wp.lock();
465  if (not self) {
466  return;
467  }
468  if (res.has_error()) {
469  SL_WARN(self->logger_,
470  "Missing justifications between blocks {} and "
471  "{} was not loaded: {}",
472  last_finalized,
473  msg.last_finalized,
474  res.error().message());
475  } else {
476  SL_DEBUG(self->logger_,
477  "Loaded justifications for blocks in range {} - {}",
478  last_finalized,
479  res.value());
480  }
481  });
482  }
483  }
484 
486  const network::CatchUpRequest &msg) {
487  auto info_opt = peer_manager_->getPeerState(peer_id);
488  if (not info_opt.has_value() or not info_opt->get().set_id.has_value()
489  or not info_opt->get().round_number.has_value()) {
490  SL_DEBUG(logger_,
491  "Catch-up request to round #{} received from {} was rejected: "
492  "we are not have our view about remote peer",
493  msg.round_number,
494  peer_id);
495  reputation_repository_->change(
497  return;
498  }
499  const auto &info = info_opt->get();
500 
501  // Check if request is corresponding our view about remote peer by set id
502  if (msg.voter_set_id != info.set_id.value()) {
503  SL_DEBUG(logger_,
504  "Catch-up request to round #{} received from {} was rejected: "
505  "it is not corresponding our view about remote peer ",
506  msg.round_number,
507  peer_id,
508  current_round_->voterSetId(),
509  msg.voter_set_id);
510 
511  // NOTE: When we're close to a set change there is potentially a
512  // race where the peer sent us the request before it observed that
513  // we had transitioned to a new set. In this case we charge a lower
514  // cost.
515  if (msg.voter_set_id == info.set_id.value()
516  and msg.round_number
517  < info.round_number.value() + kCatchUpThreshold) {
518  reputation_repository_->change(
520  return;
521  }
522 
523  reputation_repository_->change(
525  return;
526  }
527 
528  // Check if request is corresponding our view about remote peer by round
529  // number
530  if (msg.round_number <= info.round_number.value()) {
531  SL_DEBUG(logger_,
532  "Catch-up request to round #{} received from {} was rejected: "
533  "it is not corresponding our view about remote peer ",
534  msg.round_number,
535  peer_id,
536  current_round_->voterSetId(),
537  msg.voter_set_id);
538 
539  reputation_repository_->change(
541  return;
542  }
543 
544  // It is also impolite to send a catch-up request to a peer in a new
545  // different Set ID.
546  if (msg.voter_set_id != current_round_->voterSetId()) {
547  SL_DEBUG(logger_,
548  "Catch-up request to round #{} received from {} was rejected: "
549  "impolite, because voter set id are differ (our: {}, their: {})",
550  msg.round_number,
551  peer_id,
552  current_round_->voterSetId(),
553  msg.voter_set_id);
554  return;
555  }
556 
557  // It is impolite to send a catch-up request for a round `R` to a peer whose
558  // announced view is behind `R`.
559  if (msg.round_number > current_round_->roundNumber()) {
560  SL_DEBUG(logger_,
561  "Catch-up request to round #{} received from {} was rejected: "
562  "impolite, because our current round is less - {}",
563  msg.round_number,
564  peer_id,
565  current_round_->roundNumber());
566 
567  reputation_repository_->change(
569  return;
570  }
571 
572  auto opt_round = selectRound(msg.round_number, msg.voter_set_id);
573  if (!opt_round.has_value()) {
574  SL_DEBUG(logger_,
575  "Catch-up request to round #{} received from {} was rejected: "
576  "target round not found",
577  msg.round_number,
578  peer_id);
579  return;
580  }
581 
582  auto &round = opt_round.value();
583  if (not round->finalizedBlock().has_value()) {
584  SL_DEBUG(logger_,
585  "Catch-up request to round #{} received from {} was rejected: "
586  "round is not finalizable",
587  msg.round_number,
588  peer_id);
589  throw std::runtime_error("Need not ensure if it is correct");
590  }
591 
592  SL_DEBUG(logger_,
593  "Catch-up request to round #{} received from {}",
594  msg.round_number,
595  peer_id);
596  round->doCatchUpResponse(peer_id);
597 
598  reputation_repository_->change(peer_id,
600  }
601 
603  const network::CatchUpResponse &msg) {
604  bool need_cleanup_when_exiting_scope = false;
606 
607  auto ctx = GrandpaContext::get().value();
608  if (not ctx->peer_id.has_value()) {
609  if (not pending_catchup_request_.has_value()) {
610  reputation_repository_->change(
612  return;
613  }
614 
615  const auto &[remote_peer_id, catchup_request] =
616  pending_catchup_request_.value();
617 
618  if (peer_id != remote_peer_id) {
619  reputation_repository_->change(
621  return;
622  }
623 
624  if (msg.voter_set_id != catchup_request.voter_set_id
625  or msg.round_number != catchup_request.round_number) {
626  reputation_repository_->change(
628  return;
629  }
630 
631  if (msg.prevote_justification.empty()
632  or msg.precommit_justification.empty()) {
633  reputation_repository_->change(
635  return;
636  }
637 
638  need_cleanup_when_exiting_scope = true;
639  }
640 
641  auto cleanup = gsl::finally([&] {
642  if (need_cleanup_when_exiting_scope) {
644  pending_catchup_request_.reset();
645  }
646  });
647 
648  BOOST_ASSERT(current_round_ != nullptr);
649  // Ignore message of peer whose round in different voter set
650  if (msg.voter_set_id != current_round_->voterSetId()) {
651  SL_DEBUG(
652  logger_,
653  "Catch-up response (till round #{}) received from {} was rejected: "
654  "impolite, because voter set id are differ (our: {}, their: {})",
655  msg.round_number,
656  peer_id,
657  current_round_->voterSetId(),
658  msg.voter_set_id);
659  return;
660  }
661 
662  if (msg.round_number < current_round_->roundNumber()) {
663  // Catching up in to the past
664  SL_DEBUG(
665  logger_,
666  "Catch-up response (till round #{}) received from {} was rejected: "
667  "catching up into the past",
668  msg.round_number,
669  peer_id);
670  return;
671  }
672 
673  SL_DEBUG(logger_,
674  "Catch-up response (till round #{}) received from {}",
675  msg.round_number,
676  peer_id);
677 
678  if (msg.round_number > current_round_->roundNumber()) {
679  MovableRoundState round_state{
680  .round_number = msg.round_number,
681  .last_finalized_block = current_round_->lastFinalizedBlock(),
682  .votes = {},
683  .finalized = msg.best_final_candidate};
684 
685  std::transform(msg.prevote_justification.begin(),
686  msg.prevote_justification.end(),
687  std::back_inserter(round_state.votes),
688  [](auto &item) { return item; });
689  std::transform(msg.precommit_justification.begin(),
690  msg.precommit_justification.end(),
691  std::back_inserter(round_state.votes),
692  [](auto &item) { return item; });
693 
694  auto authorities_opt = authority_manager_->authorities(
695  round_state.finalized.value(), IsBlockFinalized{false});
696  if (!authorities_opt) {
697  SL_WARN(logger_,
698  "Can't retrieve authorities for finalized block {}",
699  round_state.finalized.value());
700  return;
701  }
702  auto &authority_set = authorities_opt.value();
703 
704  auto voters = std::make_shared<VoterSet>(msg.voter_set_id);
705  for (const auto &authority : authority_set->authorities) {
706  auto res = voters->insert(
707  primitives::GrandpaSessionKey(authority.id.id), authority.weight);
708  if (res.has_error()) {
709  SL_WARN(logger_, "Can't make voter set: {}", res.error().message());
710  return;
711  }
712  }
713 
714  auto round = makeInitialRound(round_state, std::move(voters));
715 
716  if (not round->completable()
717  and not round->finalizedBlock().has_value()) {
718  // Met unknown voter - cost reputation
719  if (ctx->unknown_voter_counter > 0) {
720  reputation_repository_->change(
721  peer_id,
723  * ctx->unknown_voter_counter);
724  }
725  // Met invalid signature - cost reputation
726  if (ctx->invalid_signature_counter > 0) {
727  reputation_repository_->change(
728  peer_id,
730  * ctx->checked_signature_counter);
731  }
732  // Check if missed block are detected and if this is first attempt
733  // (considering by definition peer id in context)
734  if (not ctx->missing_blocks.empty()) {
735  if (not ctx->peer_id.has_value()) {
736  ctx->peer_id.emplace(peer_id);
737  ctx->catch_up_response.emplace(msg);
739  }
740  }
741  return;
742  }
743 
744  current_round_->end();
745  current_round_ = std::move(round);
746 
747  } else {
748  bool is_prevotes_changed = false;
749  bool is_precommits_changed = false;
750  for (auto &vote : msg.prevote_justification) {
751  if (current_round_->onPrevote(vote,
753  is_prevotes_changed = true;
754  }
755  }
756  for (auto &vote : msg.precommit_justification) {
757  if (current_round_->onPrecommit(vote,
759  is_precommits_changed = true;
760  }
761  }
762  if (is_prevotes_changed or is_precommits_changed) {
763  current_round_->update(
765  VotingRound::IsPrevotesChanged{is_prevotes_changed},
766  VotingRound::IsPrecommitsChanged{is_precommits_changed});
767  }
768 
769  SL_DEBUG(logger_, "Catch-up response applied");
770 
771  // Check if catch-up round is not completable
772  if (not current_round_->completable()) {
773  // Met unknown voter - cost reputation
774  if (ctx->unknown_voter_counter > 0) {
775  reputation_repository_->change(
776  peer_id,
778  * ctx->unknown_voter_counter);
779  }
780  // Met invalid signature - cost reputation
781  if (ctx->invalid_signature_counter > 0) {
782  reputation_repository_->change(
783  peer_id,
785  * ctx->checked_signature_counter);
786  }
787  // Check if missed block are detected and if this is first attempt
788  // (considering by definition peer id in context)
789  if (not ctx->missing_blocks.empty()) {
790  if (not ctx->peer_id.has_value()) {
791  ctx->peer_id.emplace(peer_id);
792  ctx->catch_up_response.emplace(msg);
794  }
795  }
796  return;
797  }
798  }
799 
801 
802  reputation_repository_->change(
804  }
805 
807  const VoteMessage &msg) {
808  auto info = peer_manager_->getPeerState(peer_id);
809  if (not info.has_value() or not info->get().set_id.has_value()
810  or not info->get().round_number.has_value()) {
811  SL_DEBUG(
812  logger_,
813  "{} signed by {} with set_id={} in round={} has received from {} "
814  "and we are not have our view about remote peer",
815  msg.vote.is<Prevote>() ? "Prevote"
816  : msg.vote.is<Precommit>() ? "Precommit"
817  : "PrimaryPropose",
818  msg.id(),
819  msg.counter,
820  msg.round_number,
821  peer_id,
822  current_round_->voterSetId());
823  reputation_repository_->change(
825  return;
826  }
827 
828  // If a peer is at a given voter set, it is impolite to send messages from
829  // an earlier voter set.
830  if (msg.counter < current_round_->voterSetId()) {
831  SL_DEBUG(
832  logger_,
833  "{} signed by {} with set_id={} in round={} has received from {} "
834  "and rejected as impolite (our set id is {})",
835  msg.vote.is<Prevote>() ? "Prevote"
836  : msg.vote.is<Precommit>() ? "Precommit"
837  : "PrimaryPropose",
838  msg.id(),
839  msg.counter,
840  msg.round_number,
841  peer_id,
842  current_round_->voterSetId());
843  reputation_repository_->change(peer_id,
845  return;
846  }
847 
848  // It is extremely impolite to send messages from a future voter set.
849  // "future-set" messages can be dropped and ignored.
850  if (msg.counter > current_round_->voterSetId()) {
851  SL_WARN(logger_,
852  "{} signed by {} with set_id={} in round={} has received from {} "
853  "and rejected as extremely impolite (our set id is {})",
854  msg.vote.is<Prevote>() ? "Prevote"
855  : msg.vote.is<Precommit>() ? "Precommit"
856  : "PrimaryPropose",
857  msg.id(),
858  msg.counter,
859  msg.round_number,
860  peer_id,
861  current_round_->voterSetId());
862  reputation_repository_->change(peer_id,
864  return;
865  }
866 
867  if (msg.round_number > current_round_->roundNumber() + 1) {
868  reputation_repository_->change(peer_id,
870  } else if (msg.round_number < current_round_->roundNumber() - 1) {
871  reputation_repository_->change(peer_id,
873  }
874 
875  // If the current peer is at round r, it is impolite to receive messages
876  // about r-2 or earlier
877  if (msg.round_number + 2 < current_round_->roundNumber()) {
878  SL_DEBUG(
879  logger_,
880  "{} signed by {} with set_id={} in round={} has received from {} "
881  "and rejected as impolite (our round is {})",
882  msg.vote.is<Prevote>() ? "Prevote"
883  : msg.vote.is<Precommit>() ? "Precommit"
884  : "PrimaryPropose",
885  msg.id(),
886  msg.counter,
887  msg.round_number,
888  peer_id,
889  current_round_->roundNumber());
890  return;
891  }
892 
893  // If a peer is at round r, is extremely impolite to send messages about r+1
894  // or later. "future-round" messages can be dropped and ignored.
895  if (msg.round_number >= current_round_->roundNumber() + 1) {
896  SL_WARN(logger_,
897  "{} signed by {} with set_id={} in round={} has received from {} "
898  "and rejected as extremely impolite (our round is {})",
899  msg.vote.is<Prevote>() ? "Prevote"
900  : msg.vote.is<Precommit>() ? "Precommit"
901  : "PrimaryPropose",
902  msg.id(),
903  msg.counter,
904  msg.round_number,
905  peer_id,
906  current_round_->roundNumber());
907  return;
908  }
909 
910  std::optional<std::shared_ptr<VotingRound>> opt_target_round =
911  selectRound(msg.round_number, msg.counter);
912  if (not opt_target_round.has_value()) {
913  SL_DEBUG(
914  logger_,
915  "{} signed by {} with set_id={} in round={} has received from {} "
916  "and rejected (round not found)",
917  msg.vote.is<Prevote>() ? "Prevote"
918  : msg.vote.is<Precommit>() ? "Precommit"
919  : "PrimaryPropose",
920  msg.id(),
921  msg.counter,
922  msg.round_number,
923  peer_id);
924  return;
925  }
926  auto &target_round = opt_target_round.value();
927 
928  SL_DEBUG(logger_,
929  "{} signed by {} with set_id={} in round={} for block {} "
930  "has received from {}",
931  msg.vote.is<Prevote>() ? "Prevote"
932  : msg.vote.is<Precommit>() ? "Precommit"
933  : "PrimaryPropose",
934  msg.id(),
935  msg.counter,
936  msg.round_number,
937  msg.vote.getBlockInfo(),
938  peer_id);
939 
941 
942  bool is_prevotes_changed = false;
943  bool is_precommits_changed = false;
944  visit_in_place(
945  msg.vote.message,
946  [&](const PrimaryPropose &) {
947  target_round->onProposal(msg.vote,
948  VotingRound::Propagation::REQUESTED);
949  },
950  [&](const Prevote &) {
951  if (target_round->onPrevote(msg.vote,
952  VotingRound::Propagation::REQUESTED)) {
953  is_prevotes_changed = true;
954  }
955  },
956  [&](const Precommit &) {
957  if (target_round->onPrecommit(msg.vote,
959  is_precommits_changed = true;
960  }
961  });
962 
963  auto ctx = GrandpaContext::get().value();
964 
965  // Met invalid signature - cost reputation
966  if (ctx->invalid_signature_counter > 0) {
967  reputation_repository_->change(peer_id,
969  * ctx->checked_signature_counter);
970  }
971 
972  // Met unknown voter - cost reputation
973  if (ctx->unknown_voter_counter > 0) {
974  reputation_repository_->change(peer_id,
976  * ctx->unknown_voter_counter);
977  }
978 
979  if (is_prevotes_changed or is_precommits_changed) {
980  target_round->update(
982  VotingRound::IsPrevotesChanged{is_prevotes_changed},
983  VotingRound::IsPrecommitsChanged{is_precommits_changed});
984 
985  reputation_repository_->change(
987  }
988 
989  if (not target_round->finalizedBlock().has_value()) {
990  // Check if missed block are detected and if this is first attempt
991  // (considering by definition peer id in context)
992  if (not ctx->missing_blocks.empty()) {
993  if (not ctx->peer_id.has_value()) {
994  ctx->peer_id.emplace(peer_id);
995  ctx->vote.emplace(msg);
997  }
998  }
999  return;
1000  }
1001  }
1002 
1004  const network::FullCommitMessage &msg) {
1005  // TODO check if height of commit less then previous one
1006  // if (new_commit_height < last_commit_height) {
1007  // reputation_repository_->change(
1008  // peer_id, network::reputation::cost::INVALID_VIEW_CHANGE);
1009  // }
1010 
1011  // It is especially impolite to send commits which are invalid, or from
1012  // a different Set ID than the receiving peer has indicated
1013  if (msg.set_id != current_round_->voterSetId()) {
1014  SL_DEBUG(
1015  logger_,
1016  "Commit with set_id={} in round={} for block {} has received from {} "
1017  "and dropped as impolite: our voter set id is {}",
1018  msg.set_id,
1019  msg.round,
1020  BlockInfo(msg.message.target_number, msg.message.target_hash),
1021  peer_id,
1022  current_round_->voterSetId());
1023 
1024  reputation_repository_->change(
1025  peer_id,
1026  msg.set_id < current_round_->voterSetId()
1029  return;
1030  }
1031 
1032  // It is impolite to send commits which are earlier than the last commit
1033  // sent
1034  if (msg.round + kKeepRecentRounds < current_round_->roundNumber()) {
1035  SL_DEBUG(
1036  logger_,
1037  "Commit with set_id={} in round={} for block {} has received from {} "
1038  "and dropped as impolite: too old commit, our round is {}",
1039  msg.set_id,
1040  msg.round,
1041  BlockInfo(msg.message.target_number, msg.message.target_hash),
1042  peer_id,
1043  current_round_->roundNumber());
1044  return;
1045  }
1046 
1047  if (msg.message.precommits.empty()
1048  or msg.message.auth_data.size() != msg.message.precommits.size()) {
1049  reputation_repository_->change(
1051  }
1052 
1053  if (auto prev_round = current_round_->getPreviousRound()) {
1054  if (auto finalized_opt = prev_round->finalizedBlock()) {
1055  if (msg.message.target_number < finalized_opt->number) {
1056  reputation_repository_->change(
1058  }
1059  }
1060  }
1061 
1062  if (msg.round < current_round_->roundNumber()) {
1063  SL_DEBUG(
1064  logger_,
1065  "Commit with set_id={} in round={} for block {} has received from {} "
1066  "and dropped as fulfilled",
1067  msg.set_id,
1068  msg.round,
1069  BlockInfo(msg.message.target_number, msg.message.target_hash),
1070  peer_id);
1071  return;
1072  }
1073 
1074  SL_DEBUG(logger_,
1075  "Commit with set_id={} in round={} for block {} "
1076  "has received from {}",
1077  msg.set_id,
1078  msg.round,
1079  BlockInfo(msg.message.target_number, msg.message.target_hash),
1080  peer_id);
1081 
1082  GrandpaJustification justification{
1083  .round_number = msg.round,
1084  .block_info =
1085  BlockInfo(msg.message.target_number, msg.message.target_hash)};
1086  for (size_t i = 0; i < msg.message.precommits.size(); ++i) {
1087  SignedPrecommit commit;
1088  commit.message = msg.message.precommits[i];
1089  commit.signature = msg.message.auth_data[i].first;
1090  commit.id = msg.message.auth_data[i].second;
1091  justification.items.emplace_back(std::move(commit));
1092  }
1093 
1095  auto ctx = GrandpaContext::get().value();
1096  ctx->peer_id.emplace(peer_id);
1097  ctx->commit.emplace(msg);
1098 
1099  auto res = applyJustification(justification.block_info, justification);
1100  if (not res.has_value()) {
1101  SL_WARN(
1102  logger_,
1103  "Commit with set_id={} in round={} for block {} has received from {} "
1104  "and has not applied: {}",
1105  msg.set_id,
1106  msg.round,
1107  BlockInfo(msg.message.target_number, msg.message.target_hash),
1108  peer_id,
1109  res.error().message());
1110  return;
1111  }
1112 
1113  reputation_repository_->change(
1115  }
1116 
1117  outcome::result<void> GrandpaImpl::applyJustification(
1118  const BlockInfo &block_info, const GrandpaJustification &justification) {
1119  auto round_opt = selectRound(justification.round_number, std::nullopt);
1120  std::shared_ptr<VotingRound> round;
1121  bool need_to_make_round_current = false;
1122  if (round_opt.has_value()) {
1123  round = std::move(round_opt.value());
1124  } else {
1125  // This is justification for already finalized block
1126  if (current_round_->lastFinalizedBlock().number > block_info.number) {
1128  }
1129 
1130  auto prev_round_opt =
1131  selectRound(justification.round_number - 1, std::nullopt);
1132 
1133  if (prev_round_opt.has_value()) {
1134  const auto &prev_round = prev_round_opt.value();
1135  round = makeNextRound(prev_round);
1136  need_to_make_round_current = true;
1137  BOOST_ASSERT(round);
1138 
1139  SL_DEBUG(logger_,
1140  "Hop grandpa to round #{} by received justification",
1141  justification.round_number);
1142  } else {
1143  MovableRoundState round_state{
1144  .round_number = justification.round_number,
1145  .last_finalized_block = current_round_->lastFinalizedBlock(),
1146  .votes = {},
1147  .finalized = block_info};
1148 
1149  auto authorities_opt = authority_manager_->authorities(
1150  block_info, IsBlockFinalized{false});
1151  if (!authorities_opt) {
1152  SL_WARN(logger_,
1153  "Can't retrieve authorities to apply a justification "
1154  "at block {}",
1155  block_info);
1157  }
1158  auto &authority_set = authorities_opt.value();
1159  SL_INFO(logger_,
1160  "Apply justification for block {} with voter set id {}",
1161  block_info,
1162  authority_set->id);
1163  SL_INFO(logger_,
1164  "authorities->id: {}, current_round_->voterSetId(): {}, "
1165  "justification.round_number: {}, "
1166  "current_round_->roundNumber(): {}",
1167  authority_set->id,
1168  current_round_->voterSetId(),
1169  justification.round_number,
1170  current_round_->roundNumber());
1171 
1172  // This is justification for non-actual round
1173  if (authority_set->id < current_round_->voterSetId()) {
1175  }
1176  if (authority_set->id == current_round_->voterSetId()
1177  && justification.round_number < current_round_->roundNumber()) {
1179  }
1180 
1181  if (authority_set->id > current_round_->voterSetId() + 1) {
1182  SL_WARN(logger_,
1183  "Authority set on block {} with justification has id {}, "
1184  "while the current round set id is {} (difference must be 1)",
1185  block_info, authority_set->id, current_round_->voterSetId());
1186  }
1187 
1188  auto voters = std::make_shared<VoterSet>(authority_set->id);
1189  for (const auto &authority : authority_set->authorities) {
1190  auto res = voters->insert(
1191  primitives::GrandpaSessionKey(authority.id.id), authority.weight);
1192  if (res.has_error()) {
1193  SL_CRITICAL(
1194  logger_, "Can't make voter set: {}", res.error().message());
1195  return res.as_failure();
1196  }
1197  }
1198 
1199  round = makeInitialRound(round_state, std::move(voters));
1200  need_to_make_round_current = true;
1201  BOOST_ASSERT(round);
1202 
1203  SL_DEBUG(logger_,
1204  "Rewind grandpa till round #{} by received justification",
1205  justification.round_number);
1206  }
1207  }
1208 
1209  OUTCOME_TRY(round->applyJustification(block_info, justification));
1210 
1211  if (need_to_make_round_current) {
1212  current_round_->end();
1213  current_round_ = round;
1214  }
1215 
1216  tryExecuteNextRound(round);
1217 
1218  // if round == current round, then execution of the next round will be
1219  // elsewhere
1220  return outcome::success();
1221  }
1222 
1224  auto ctx = GrandpaContext::get().value();
1225  BOOST_ASSERT(ctx);
1226 
1227  if (not ctx->peer_id.has_value()) {
1228  return;
1229  }
1230 
1231  if (ctx->missing_blocks.empty()) {
1232  return;
1233  }
1234 
1235  auto final = [wp = weak_from_this(), ctx] {
1236  if (auto self = wp.lock()) {
1237  GrandpaContext::set(ctx);
1238  if (ctx->vote.has_value()) {
1239  self->onVoteMessage(ctx->peer_id.value(), ctx->vote.value());
1240  } else if (ctx->catch_up_response.has_value()) {
1241  self->onCatchUpResponse(ctx->peer_id.value(),
1242  ctx->catch_up_response.value());
1243  } else if (ctx->commit.has_value()) {
1244  self->onCommitMessage(ctx->peer_id.value(), ctx->commit.value());
1245  }
1246  }
1247  };
1248 
1249  auto do_request_ptr = std::make_shared<std::function<void()>>();
1250  auto &do_request = *do_request_ptr;
1251 
1252  do_request = [wp = weak_from_this(),
1253  ctx = std::move(ctx),
1254  do_request_ptr = std::move(do_request_ptr),
1255  final = std::move(final)]() mutable {
1256  if (auto self = wp.lock()) {
1257  auto &peer_id = ctx->peer_id.value();
1258  auto &blocks = ctx->missing_blocks;
1259  if (not blocks.empty()) {
1260  auto it = blocks.rbegin();
1261  auto node = blocks.extract((++it).base());
1262  auto block = node.value();
1263  self->synchronizer_->syncByBlockInfo(
1264  block,
1265  peer_id,
1266  [wp, ctx, do_request_ptr = std::move(do_request_ptr)](auto res) {
1267  if (do_request_ptr != nullptr) {
1268  auto do_request = std::move(*do_request_ptr);
1269  do_request();
1270  }
1271  },
1272  true);
1273  return;
1274  }
1275  final();
1276  do_request_ptr.reset();
1277  }
1278  };
1279 
1280  do_request();
1281  }
1282 } // namespace kagome::consensus::grandpa
const ReputationChange OUT_OF_SCOPE_MESSAGE
const ReputationChange CATCH_UP_REQUEST_TIMEOUT
const ReputationChange UNKNOWN_VOTER
virtual void set(double val)=0
Set the gauge to the given value.
Tagged< bool, struct IsBlockFinalizedTag > IsBlockFinalized
outcome::result< MovableRoundState > getLastCompletedRound() const
const ReputationChange BAD_CATCHUP_RESPONSE
static void set(std::shared_ptr< GrandpaContext > context)
const ReputationChange PAST_REJECTION
const ReputationChange INVALID_VIEW_CHANGE
std::shared_ptr< network::PeerManager > peer_manager_
const ReputationChange MALFORMED_COMMIT
std::shared_ptr< network::Synchronizer > synchronizer_
std::vector< SignedPrevote > prevote_justification
std::shared_ptr< authority::AuthorityManager > authority_manager_
primitives::BlockInfo BlockInfo
Definition: structs.hpp:29
const ReputationChange BASIC_VALIDATED_COMMIT
libp2p::basic::Scheduler::Handle fallback_timer_handle_
std::shared_ptr< VotingRound > current_round_
void onVoteMessage(const libp2p::peer::PeerId &peer_id, const network::VoteMessage &msg) override
const ReputationChange BASIC_VALIDATED_CATCH_UP
std::shared_ptr< VotingRound > makeInitialRound(const MovableRoundState &round_state, std::shared_ptr< VoterSet > voters)
GrandpaImpl(std::shared_ptr< application::AppStateManager > app_state_manager, std::shared_ptr< Environment > environment, std::shared_ptr< crypto::Ed25519Provider > crypto_provider, std::shared_ptr< runtime::GrandpaApi > grandpa_api, const std::shared_ptr< crypto::Ed25519Keypair > &keypair, const application::ChainSpec &chain_spec, std::shared_ptr< Clock > clock, std::shared_ptr< libp2p::basic::Scheduler > scheduler, std::shared_ptr< authority::AuthorityManager > authority_manager, std::shared_ptr< network::Synchronizer > synchronizer, std::shared_ptr< network::PeerManager > peer_manager, std::shared_ptr< blockchain::BlockTree > block_tree, std::shared_ptr< network::ReputationRepository > reputation_repository)
std::shared_ptr< VoterSet > voters
Current round&#39;s authorities.
outcome::result< void > applyJustification(const BlockInfo &block_info, const GrandpaJustification &justification) override
std::shared_ptr< blockchain::BlockTree > block_tree_
void tryExecuteNextRound(const std::shared_ptr< VotingRound > &round) override
libp2p::peer::PeerId PeerId
static constexpr Clock::Duration kCatchupRequestTimeout
Timeout of waiting catchup response for request.
static std::optional< std::shared_ptr< GrandpaContext > > get()
void onCatchUpRequest(const libp2p::peer::PeerId &peer_id, const network::CatchUpRequest &msg) override
void onCatchUpResponse(const libp2p::peer::PeerId &peer_id, const network::CatchUpResponse &msg) override
std::optional< const std::tuple< libp2p::peer::PeerId, network::CatchUpRequest > > pending_catchup_request_
const ReputationChange BAD_SIGNATURE
libp2p::basic::Scheduler::Handle catchup_request_timer_handle_
std::shared_ptr< crypto::Ed25519Provider > crypto_provider_
std::shared_ptr< network::ReputationRepository > reputation_repository_
const ReputationChange CATCH_UP_REPLY
void onNeighborMessage(const libp2p::peer::PeerId &peer_id, const network::GrandpaNeighborMessage &msg) override
void updateNextRound(RoundNumber round_number) override
Stores the current state of the round.
const std::shared_ptr< crypto::Ed25519Keypair > & keypair_
const ReputationChange FUTURE_MESSAGE
std::shared_ptr< Environment > environment_
std::shared_ptr< VotingRound > makeNextRound(const std::shared_ptr< VotingRound > &previous_round)
crypto::Ed25519PublicKey GrandpaSessionKey
Definition: session_key.hpp:18
typename ClockType::duration Duration
Definition: clock.hpp:23
void onCommitMessage(const libp2p::peer::PeerId &peer_id, const network::FullCommitMessage &msg) override
const ReputationChange MALFORMED_CATCH_UP
const ReputationChange HONEST_OUT_OF_SCOPE_CATCH_UP
std::vector< SignedPrecommit > precommit_justification
std::optional< std::shared_ptr< VotingRound > > selectRound(RoundNumber round_number, std::optional< VoterSetId > voter_set_id)
std::shared_ptr< runtime::GrandpaApi > grandpa_api_
static const size_t kKeepRecentRounds
Maximum number of rounds we are keep to communication.
std::shared_ptr< libp2p::basic::Scheduler > scheduler_
Tagged< bool, struct IsBlockFinalizedTag > IsBlockFinalized