Kagome
Polkadot Runtime Engine in C++17
voting_round_impl.cpp
Go to the documentation of this file.
1 
7 
8 #include <unordered_set>
9 
11 #include "common/visitor.hpp"
15 
17 
18  namespace {
19  template <typename T>
20  auto convertToPrimaryPropose(T &&vote) {
21  return PrimaryPropose(vote.number, vote.hash);
22  }
23 
24  template <typename T>
25  auto convertToPrevote(T &&vote) {
26  return Prevote(vote.number, vote.hash);
27  }
28 
29  template <typename T>
30  auto convertToPrecommit(T &&vote) {
31  return Precommit(vote.number, vote.hash);
32  }
33 
34  template <typename T>
35  auto convertToBlockInfo(T &&vote) {
36  return BlockInfo(vote.number, vote.hash);
37  }
38 
39  template <typename D>
40  auto toMilliseconds(D &&duration) {
41  return std::chrono::duration_cast<std::chrono::milliseconds>(duration);
42  }
43  } // namespace
44 
46  const std::shared_ptr<Grandpa> &grandpa,
47  const GrandpaConfig &config,
48  std::shared_ptr<authority::AuthorityManager> authority_manager,
49  std::shared_ptr<Environment> env,
50  std::shared_ptr<VoteCryptoProvider> vote_crypto_provider,
51  std::shared_ptr<VoteTracker> prevotes,
52  std::shared_ptr<VoteTracker> precommits,
53  std::shared_ptr<VoteGraph> vote_graph,
54  std::shared_ptr<Clock> clock,
55  std::shared_ptr<libp2p::basic::Scheduler> scheduler)
56  : voter_set_{std::move(config.voters)},
57  round_number_{config.round_number},
58  duration_{config.duration},
59  id_{config.id},
60  grandpa_(grandpa),
61  authority_manager_{std::move(authority_manager)},
62  env_{std::move(env)},
63  vote_crypto_provider_{std::move(vote_crypto_provider)},
64  graph_{std::move(vote_graph)},
65  scheduler_{std::move(scheduler)},
66  prevotes_{std::move(prevotes)},
67  precommits_{std::move(precommits)} {
68  BOOST_ASSERT(not grandpa_.expired());
69  BOOST_ASSERT(authority_manager_ != nullptr);
70  BOOST_ASSERT(vote_crypto_provider_ != nullptr);
71  BOOST_ASSERT(prevotes_ != nullptr);
72  BOOST_ASSERT(precommits_ != nullptr);
73  BOOST_ASSERT(env_ != nullptr);
74  BOOST_ASSERT(graph_ != nullptr);
75  BOOST_ASSERT(scheduler_ != nullptr);
76 
77  // calculate super-majority
78  auto faulty = (voter_set_->totalWeight() - 1) / 3;
79  threshold_ = voter_set_->totalWeight() - faulty;
80 
81  // Check if node is primary
82  auto index = round_number_ % voter_set_->size();
83  isPrimary_ = voter_set_->voterId(index) == outcome::success(id_);
84 
85  prevote_equivocators_.resize(voter_set_->size(), false);
86  precommit_equivocators_.resize(voter_set_->size(), false);
87 
88  SL_DEBUG(logger_,
89  "Round #{}: Created with voter set #{}",
91  voter_set_->id());
92  }
93 
95  const std::shared_ptr<Grandpa> &grandpa,
96  const GrandpaConfig &config,
97  const std::shared_ptr<authority::AuthorityManager> authority_manager,
98  const std::shared_ptr<Environment> &env,
99  const std::shared_ptr<VoteCryptoProvider> &vote_crypto_provider,
100  const std::shared_ptr<VoteTracker> &prevotes,
101  const std::shared_ptr<VoteTracker> &precommits,
102  const std::shared_ptr<VoteGraph> &vote_graph,
103  const std::shared_ptr<Clock> &clock,
104  const std::shared_ptr<libp2p::basic::Scheduler> &scheduler,
105  const std::shared_ptr<VotingRound> &previous_round)
106  : VotingRoundImpl(grandpa,
107  config,
108  authority_manager,
109  env,
110  vote_crypto_provider,
111  prevotes,
112  precommits,
113  vote_graph,
114  clock,
115  scheduler) {
116  BOOST_ASSERT(previous_round != nullptr);
117 
118  previous_round_ = previous_round;
119  last_finalized_block_ = previous_round->finalizedBlock().value_or(
120  previous_round->lastFinalizedBlock());
121  }
122 
124  const std::shared_ptr<Grandpa> &grandpa,
125  const GrandpaConfig &config,
126  const std::shared_ptr<authority::AuthorityManager> authority_manager,
127  const std::shared_ptr<Environment> &env,
128  const std::shared_ptr<VoteCryptoProvider> &vote_crypto_provider,
129  const std::shared_ptr<VoteTracker> &prevotes,
130  const std::shared_ptr<VoteTracker> &precommits,
131  const std::shared_ptr<VoteGraph> &vote_graph,
132  const std::shared_ptr<Clock> &clock,
133  const std::shared_ptr<libp2p::basic::Scheduler> &scheduler,
134  const MovableRoundState &round_state)
135  : VotingRoundImpl(grandpa,
136  config,
137  authority_manager,
138  env,
139  vote_crypto_provider,
140  prevotes,
141  precommits,
142  vote_graph,
143  clock,
144  scheduler) {
146 
147  if (round_number_ != 0) {
148  bool is_prevotes_changed = false;
149  bool is_precommits_changed = false;
150 
151  // Apply stored votes
152  auto apply = [&](const auto &vote) {
153  visit_in_place(
154  vote.message,
155  [&](const Prevote &) {
156  if (VotingRoundImpl::onPrevote(vote, Propagation::NEEDLESS)) {
157  is_prevotes_changed = true;
158  }
159  },
160  [&](const Precommit &) {
162  is_precommits_changed = true;
163  }
164  },
165  [](auto...) {});
166  };
167 
168  for (auto &vote_variant : round_state.votes) {
169  visit_in_place(
170  vote_variant,
171  [&](const SignedMessage &vote) { apply(vote); },
172  [&](const EquivocatorySignedMessage &pair) {
173  apply(pair.first);
174  apply(pair.second);
175  });
176  }
177 
178  if (is_prevotes_changed or is_precommits_changed) {
180  IsPrevotesChanged{is_prevotes_changed},
181  IsPrecommitsChanged{is_precommits_changed});
182  }
183 
184  // Round might be no finalized, if provided state has not enough prevotes
185  // (i.e. state was made by justification in commit). In this case we have
186  // fallback way to finalize than basing on supermajority of precommits. It
187  // is enough to be finalized, but no completable.
188  if (not finalized_.has_value()) {
189  if (precommits_->getTotalWeight() >= threshold_) {
190  auto possible_to_finalize = [&](const VoteWeight &weight) {
191  return weight.total(VoteType::Precommit,
193  *voter_set_)
194  >= threshold_;
195  };
196 
197  finalized_ = graph_->findAncestor(VoteType::Precommit,
199  std::move(possible_to_finalize));
200 
201  BOOST_ASSERT(finalized_.has_value());
202  }
203  }
204 
205  } else {
206  // Zero-round is always self-finalized
208  completable_ = true;
209  }
210  }
211 
213  if (stage_ != Stage::INIT) {
214  return;
215  }
216 
218 
219  SL_DEBUG(logger_, "Round #{}: Start round", round_number_);
220 
222  scheduler_->scheduleWithHandle([&] { pending(); }, pending_interval_);
223 
225 
226  // Current local time (Tstart)
227  start_time_ = scheduler_->now();
228 
229  // Derive-Primary
230  // see ctor
231 
232  if (isPrimary_) {
233  SL_DEBUG(logger_, "Node is primary proposer at round #{}", round_number_);
234 
235  BOOST_ASSERT(previous_round_ != nullptr);
236 
237  // Broadcast Commit-message with previous round best final candidate
238  // (or last finalized otherwise)
239  // spec: Broadcast(M vr ¡1;Fin (Best-Final-Candidate(r-1)))
240  previous_round_->doCommit();
241 
242  // if Best-Final-Candidate greater than Last-Finalized-Block
243  // spec: if Best-Final-Candidate(r ¡ 1) > Last-Finalized-Block
244  if (previous_round_->bestFinalCandidate().number
246  doProposal();
247  }
248  }
249 
251  }
252 
254  if (stage_ == Stage::COMPLETED) {
255  return;
256  }
257  BOOST_ASSERT(stage_ == Stage::START);
258 
260 
261  SL_DEBUG(logger_, "Round #{}: Start prevote stage", round_number_);
262 
263  // Continue to receive messages
264  // until T>=Tstart + 2 * Duration or round is completable
265  // spec: Receive-Messages(until Time>=Tr+2T or r is completable)
266 
267  if (completable()) {
268  SL_DEBUG(logger_, "Round #{} is already completable", round_number_);
270  endPrevoteStage();
271  return;
272  }
273 
274  stage_timer_handle_ = scheduler_->scheduleWithHandle(
275  [&] {
276  if (stage_ == Stage::PREVOTE_RUNS) {
277  SL_DEBUG(logger_,
278  "Round #{}: Time of prevote stage is out",
279  round_number_);
280  endPrevoteStage();
281  }
282  },
283  toMilliseconds(duration_ * 2 - (scheduler_->now() - start_time_)));
284 
285  on_complete_handler_ = [this] {
286  if (stage_ == Stage::PREVOTE_RUNS) {
287  SL_DEBUG(logger_, "Round #{}: Became completable", round_number_);
288  endPrevoteStage();
289  }
290  };
291 
293  }
294 
296  if (stage_ == Stage::COMPLETED) {
297  return;
298  }
299  BOOST_ASSERT(stage_ == Stage::PREVOTE_RUNS);
300 
301  stage_timer_handle_.cancel();
302  on_complete_handler_ = nullptr;
303 
305 
306  SL_DEBUG(logger_, "Round #{}: End prevote stage", round_number_);
307 
308  // Broadcast vote for prevote stage
309  doPrevote();
310 
312  }
313 
315  if (stage_ == Stage::COMPLETED) {
316  return;
317  }
318  BOOST_ASSERT(stage_ == Stage::END_PREVOTE);
319 
321 
322  SL_DEBUG(logger_, "Round #{}: Start precommit stage", round_number_);
323 
324  // Continue to receive messages
325  // until T>=Tstart + 4 * Duration or round is completable
326 
327  // spec: Receive-Messages(
328  // until Bpv>=Best-Final-Candidate(r-1)
329  // and (Time>=Tr+4T or r is completable)
330  // )
331 
332  if (completable()) {
333  SL_DEBUG(logger_, "Round #{} is already completable", round_number_);
336  return;
337  }
338 
339  stage_timer_handle_ = scheduler_->scheduleWithHandle(
340  [&] {
341  if (stage_ == Stage::PRECOMMIT_RUNS) {
342  SL_DEBUG(logger_,
343  "Round #{}: Time of precommit stage is out",
344  round_number_);
346  }
347  },
348  toMilliseconds(duration_ * 4 - (scheduler_->now() - start_time_)));
349 
350  on_complete_handler_ = [this] {
351  if (stage_ == Stage::PRECOMMIT_RUNS) {
352  SL_DEBUG(logger_, "Round #{}: Became completable", round_number_);
354  }
355  };
356 
358  }
359 
361  if (stage_ == Stage::COMPLETED) {
362  return;
363  }
364  BOOST_ASSERT(stage_ == Stage::PRECOMMIT_RUNS);
365 
366  stage_timer_handle_.cancel();
367  on_complete_handler_ = nullptr;
368 
370 
371  SL_DEBUG(logger_, "Round #{}: End precommit stage", round_number_);
372 
373  // Broadcast vote for precommit stage
374  doPrecommit();
375 
377  }
378 
380  if (stage_ == Stage::COMPLETED) {
381  return;
382  }
383  BOOST_ASSERT(stage_ == Stage::END_PRECOMMIT);
384 
386 
387  // Continue to receive messages until current round is completable and
388  // previous one is finalizable and last finalized better than the best final
389  // candidate of previous round
390 
391  // spec: Receive-Messages(
392  // until r is completable
393  // and Finalizable(r-1)
394  // and Last-Finalized-Block>Best-Final-Candidate(r-1)
395  // )
396 
397  const bool is_ready_to_end =
398  finalized_.has_value()
399  and finalized_->number
400  >= (previous_round_
401  ? previous_round_->bestFinalCandidate().number
403 
404  if (is_ready_to_end) {
405  SL_DEBUG(logger_,
406  "Round #{}: Conditions for final stage are satisfied already",
407  round_number_);
409  endWaitingStage();
410  return;
411  }
412 
413  SL_DEBUG(logger_, "Round #{}: Start final stage", round_number_);
414 
415  on_complete_handler_ = [&] {
416  const bool is_ready_to_end =
417  finalized_.has_value()
418  and finalized_->number
419  >= (previous_round_
420  ? previous_round_->bestFinalCandidate().number
422 
423  if (is_ready_to_end) {
424  SL_DEBUG(logger_,
425  "Round #{}: Conditions for final stage are met",
426  round_number_);
427  endWaitingStage();
428  }
429  };
430 
432  }
433 
435  if (stage_ == Stage::COMPLETED) {
436  return;
437  }
438  BOOST_ASSERT(stage_ == Stage::WAITING_RUNS);
439 
440  stage_timer_handle_.cancel();
441  on_complete_handler_ = nullptr;
442 
443  // Final attempt to finalize round what should be success
444  BOOST_ASSERT(finalized_.has_value());
446 
447  end();
448  }
449 
451  if (stage_ != Stage::COMPLETED) {
452  SL_DEBUG(logger_, "Round #{}: End round", round_number_);
453  on_complete_handler_ = nullptr;
454  stage_timer_handle_.cancel();
455  pending_timer_handle_.cancel();
457  }
458  }
459 
461  // Doing primary proposal is no longer actual
462  if (not previous_round_) {
463  return;
464  }
465 
466  // Don't change early defined primary vote
467  if (primary_vote_.has_value()) {
468  sendProposal(convertToPrimaryPropose(primary_vote_.value()));
469  return;
470  }
471 
472  // Send primary propose
473  // @spec Broadcast(M vr ¡1;Prim (Best-Final-Candidate(r-1)))
474 
475  BOOST_ASSERT_MSG(not primary_vote_.has_value(),
476  "Primary proposal must be once for a round");
477 
478  primary_vote_ = previous_round_->bestFinalCandidate();
479 
480  sendProposal(convertToPrimaryPropose(primary_vote_.value()));
481  }
482 
483  void VotingRoundImpl::sendProposal(const PrimaryPropose &primary_proposal) {
484  SL_DEBUG(logger_,
485  "Round #{}: Sending primary proposal of block {}",
487  primary_proposal);
488 
489  auto signed_primary_proposal_opt =
490  vote_crypto_provider_->signPrimaryPropose(primary_proposal);
491 
492  if (not signed_primary_proposal_opt.has_value()) {
493  logger_->error(
494  "Round #{}: Primary proposal was not sent: Can't sign message",
495  round_number_);
496  }
497  const auto &signed_primary_proposal = signed_primary_proposal_opt.value();
498 
499  auto res =
500  env_->onVoted(round_number_, voter_set_->id(), signed_primary_proposal);
501  if (not res) {
502  logger_->error("Round #{}: Primary proposal was not sent: {}",
504  res.error().message());
505  }
506  }
507 
509  // Doing prevote is no longer actual
510  if (not previous_round_) {
511  return;
512  }
513 
514  // Don't change defined vote to avoid equivocation
515  if (prevote_.has_value()) {
516  sendPrevote(convertToPrevote(prevote_.value()));
517  return;
518  }
519 
520  // spec: L <- Best-Final-Candidate(r-1)
521  const auto best_final_candicate = previous_round_->bestFinalCandidate();
522 
523  // spec: Bpv <- GRANDPA-GHOST(r)
524  const auto best_chain =
525  env_->bestChainContaining(best_final_candicate.hash, voter_set_->id());
526  const auto best_prevote_candidate =
527  best_chain.has_value() ? convertToBlockInfo(best_chain.value())
529 
530  // spec: N <- Bpv
531  prevote_ = best_prevote_candidate;
532 
533  // spec: if Received(Bprim) and Bpv >= Bprim > L
534  if (primary_vote_.has_value()) {
535  const auto &primary = primary_vote_.value();
536 
537  if (best_prevote_candidate.number >= primary.number
538  and primary.number > best_final_candicate.number) {
539  // spec: N <- Bprim
540  prevote_ = primary;
541  }
542  }
543 
544  // Broadcast vote for prevote stage
545  // spec: Broadcast(Bpv)
546  sendPrevote(convertToPrevote(prevote_.value()));
547  }
548 
549  void VotingRoundImpl::sendPrevote(const Prevote &prevote) {
550  SL_DEBUG(logger_,
551  "Round #{}: Sending prevote for block {}",
553  prevote);
554 
555  auto signed_prevote_opt = vote_crypto_provider_->signPrevote(prevote);
556  if (not signed_prevote_opt.has_value()) {
557  logger_->error("Round #{}: Prevote was not sent: Can't sign message",
558  round_number_);
559  return;
560  }
561  auto &signed_prevote = signed_prevote_opt.value();
562 
563  auto res = env_->onVoted(round_number_, voter_set_->id(), signed_prevote);
564  if (not res) {
565  logger_->error("Round #{}: Prevote was not sent: {}",
567  res.error().message());
568  }
569  }
570 
572  // Doing precommit is no longer actual
573  if (not previous_round_) {
574  return;
575  }
576 
577  // Don't change defined vote to avoid equivocation
578  if (precommit_.has_value()) {
579  sendPrecommit(convertToPrecommit(precommit_.value()));
580  return;
581  }
582 
583  // we wait for the last round's estimate to be equal to or
584  // the ancestor of the current round's p-Ghost before precommitting.
585 
586  const auto &prevote_ghost =
587  prevote_ghost_.value_or(previous_round_->bestFinalCandidate());
588 
589  auto last_round_estimate = previous_round_->bestFinalCandidate();
590 
591  // We should precommit if current state contains prevote, and it is
592  // either equal to the last round estimate or is descendant of it
593  const bool should_precommit =
594  prevote_ghost == last_round_estimate
595  or env_->isEqualOrDescendOf(last_round_estimate.hash,
596  prevote_ghost.hash);
597 
598  if (should_precommit) {
599  precommit_ = prevote_ghost;
600 
601  sendPrecommit(convertToPrecommit(precommit_.value()));
602  return;
603  }
604  }
605 
606  void VotingRoundImpl::sendPrecommit(const Precommit &precommit) {
607  SL_DEBUG(logger_,
608  "Round #{}: Sending precommit for block {}",
610  precommit);
611 
612  auto signed_precommit_opt = vote_crypto_provider_->signPrecommit(precommit);
613  if (not signed_precommit_opt.has_value()) {
614  logger_->error("Round #{}: Precommit was not sent: Can't sign message",
615  round_number_);
616  return;
617  }
618  auto &signed_precommit = signed_precommit_opt.value();
619 
620  auto res = env_->onVoted(round_number_, voter_set_->id(), signed_precommit);
621  if (not res) {
622  logger_->error("Round #{}: Precommit was not sent: {}",
624  res.error().message());
625  }
626  }
627 
629  BOOST_ASSERT(finalized_.has_value());
630 
631  const auto &block = finalized_.value();
632 
633  SL_DEBUG(
634  logger_, "Round #{}: Finalizing on block {}", round_number_, block);
635 
636  GrandpaJustification justification{
638  .block_info = block,
639  .items = getPrecommitJustification(block, precommits_->getMessages())};
640 
641  auto res = env_->finalize(voter_set_->id(), justification);
642  if (res.has_error()) {
643  SL_WARN(logger_,
644  "Round #{}: Finalizing on block {} is failed: {}",
646  block,
647  res.error().message());
648  }
649  }
650 
652  if (not finalized_.has_value()) {
653  return;
654  }
655 
656  auto block = finalized_.value();
657  GrandpaJustification justification{
659  .block_info = block,
660  .items = getPrecommitJustification(block, precommits_->getMessages())};
661 
662  SL_DEBUG(logger_,
663  "Round #{}: Sending commit message for block {}",
665  block);
666 
667  auto committed = env_->onCommitted(
668  round_number_, voter_set_->id(), block, justification);
669  if (not committed) {
670  logger_->error("Round #{}: Commit message was not sent: {}",
672  committed.error().message());
673  return;
674  }
675  }
676 
677  bool VotingRoundImpl::isPrimary(const Id &id) const {
678  auto index = round_number_ % voter_set_->size();
679  return voter_set_->voterId(index) == outcome::success(id);
680  }
681 
682  outcome::result<void> VotingRoundImpl::applyJustification(
683  const BlockInfo &block_info, const GrandpaJustification &justification) {
684  // validate message
685  OUTCOME_TRY(validatePrecommitJustification(block_info, justification));
686 
687  SL_DEBUG(logger_,
688  "Round #{}: Finalisation of round is received for block {}",
690  block_info);
691 
692  bool is_prevotes_changed = false;
693  bool is_precommits_changed = false;
694 
695  for (auto &vote : justification.items) {
696  visit_in_place(
697  vote.message,
698  [&](const Prevote &) {
699  if (VotingRoundImpl::onPrevote(vote, Propagation::NEEDLESS)) {
700  is_prevotes_changed = true;
701  }
702  },
703  [&](const Precommit &) {
705  is_precommits_changed = true;
706  }
707  },
708  [](auto...) {});
709  }
710 
711  if (is_prevotes_changed or is_precommits_changed) {
713  IsPrevotesChanged{is_prevotes_changed},
714  IsPrecommitsChanged{is_precommits_changed});
715  }
716 
717  if (not finalized_.has_value()) {
718  if (precommits_->getTotalWeight() >= threshold_) {
719  auto possible_to_finalize = [&](const VoteWeight &weight) {
720  return weight.total(
722  >= threshold_;
723  };
724 
725  finalized_ = graph_->findAncestor(
726  VoteType::Precommit, block_info, std::move(possible_to_finalize));
727 
728  BOOST_ASSERT(finalized_.has_value());
729  } else {
731  }
732  }
733 
734  if (not env_->isEqualOrDescendOf(block_info.hash,
735  finalized_.value().hash)) {
736  return VotingRoundError::
738  }
739 
740  auto finalized = env_->finalize(voter_set_->id(), justification);
741  if (not finalized) {
742  return finalized.as_failure();
743  }
744 
746 
747  return outcome::success();
748  }
749 
751  const BlockInfo &vote, const GrandpaJustification &justification) const {
752  size_t total_weight = 0;
753 
754  auto threshold = threshold_;
755  std::unordered_map<Id, BlockHash> validators;
756  std::unordered_set<Id> equivocators;
757 
758  for (const auto &signed_precommit : justification.items) {
759  // Skip known equivocators
760  if (auto index = voter_set_->voterIndex(signed_precommit.id);
761  index.has_value()) {
762  if (precommit_equivocators_.at(index.value())) {
763  continue;
764  }
765  }
766 
767  // Verify signatures
768  if (not vote_crypto_provider_->verifyPrecommit(signed_precommit)) {
769  SL_WARN(
770  logger_,
771  "Round #{}: Precommit signed by {} was rejected: invalid signature",
773  signed_precommit.id);
775  }
776 
777  // check that every signed precommit corresponds to the vote (i.e.
778  // signed_precommits are descendants of the vote). If so add weight of
779  // that voter to the total weight
780 
781  if (auto [it, success] = validators.emplace(
782  signed_precommit.id, signed_precommit.getBlockHash());
783  success) {
784  // New vote
785  auto weight_opt = voter_set_->voterWeight(signed_precommit.id);
786  if (!weight_opt) {
787  SL_DEBUG(logger_, "Voter {} is not in the current voter set", signed_precommit.id.toHex());
788  continue;
789  }
790  if (env_->hasAncestry(vote.hash, signed_precommit.getBlockHash())) {
791  total_weight += weight_opt.value();
792  } else {
793  SL_DEBUG(logger_,
794  "Vote does not have ancestry with target block: "
795  "vote={} target={}",
796  vote.hash,
797  signed_precommit.getBlockHash());
798  }
799 
800  } else if (equivocators.emplace(signed_precommit.id).second) {
801  // Detected equivocation
802  if (env_->hasAncestry(vote.hash, it->second)) {
803  auto weight = voter_set_->voterWeight(signed_precommit.id).value();
804  total_weight -= weight;
805  threshold -= weight;
806  } else {
807  SL_DEBUG(logger_,
808  "Vote does not have ancestry with target block: "
809  "vote={} target={}",
810  vote.hash.toHex(),
811  signed_precommit.getBlockHash());
812  }
813 
814  } else {
815  // Detected duplicate of equivotation
816  SL_WARN(
817  logger_,
818  "Round #{}: Received third precommit of caught equivocator from {}",
820  signed_precommit.id);
822  }
823  }
824 
825  if (total_weight < threshold) {
826  SL_WARN(logger_,
827  "Round #{}: Received justification does not have super-majority: "
828  "total_weight={} < threshold={}",
830  total_weight,
831  threshold);
833  }
834 
835  return outcome::success();
836  }
837 
839  if (stage_ == Stage::COMPLETED) {
840  return;
841  }
842 
843  if (finalized_.has_value()) {
844  doFinalize();
845  if (on_complete_handler_) {
847  }
848  return;
849  }
850 
851  if (not completable_) {
852  SL_DEBUG(logger_,
853  "Round #{}: Round not finalized yet: not completable",
854  round_number_);
855  } else if (not finalized_.has_value()) {
856  SL_DEBUG(logger_,
857  "Round #{}: Round not finalized yet: not finalizable",
858  round_number_);
859  } else {
860  SL_DEBUG(logger_,
861  "Round #{}: Round not finalized yet: unknown reason",
862  round_number_);
863  }
864  }
865 
867  return round_number_;
868  }
869 
871  return voter_set_->id();
872  }
873 
875  Propagation propagation) {
876  if (not isPrimary(proposal.id)) {
877  logger_->warn(
878  "Round #{}: Proposal signed by {} was rejected: "
879  "voter is not primary",
881  proposal.id);
882  return;
883  }
884 
885  if (auto ctx_opt = GrandpaContext::get()) {
886  const auto &ctx = ctx_opt.value();
887  ctx->checked_signature_counter++;
888  }
889 
890  bool isValid = vote_crypto_provider_->verifyPrimaryPropose(proposal);
891  if (not isValid) {
892  logger_->warn(
893  "Round #{}: Proposal signed by {} was rejected: "
894  "invalid signature",
896  proposal.id);
897 
898  if (auto ctx_opt = GrandpaContext::get()) {
899  const auto &ctx = ctx_opt.value();
900  ctx->invalid_signature_counter++;
901  }
902 
903  return;
904  }
905 
906  auto result = voter_set_->indexAndWeight(proposal.id);
907  if (!result) {
908  if (auto ctx_opt = GrandpaContext::get()) {
909  const auto &ctx = ctx_opt.value();
910  ctx->unknown_voter_counter++;
911  }
912  }
913 
914  SL_DEBUG(logger_,
915  "Round #{}: Proposal signed by {} was accepted for block {}",
917  proposal.id,
918  proposal.getBlockInfo());
919 
920  if (primary_vote_.has_value()) {
921  propagation = Propagation::NEEDLESS;
922  } else {
923  // Check if node hasn't block
924  auto res = env_->hasBlock(proposal.getBlockHash());
925  if (res.has_value() and not res.value()) {
926  if (auto ctx_opt = GrandpaContext::get(); ctx_opt.has_value()) {
927  auto ctx = ctx_opt.value();
928  ctx->missing_blocks.emplace(proposal.getBlockInfo());
929  }
930  return;
931  }
932  }
933 
934  primary_vote_.emplace(proposal.getBlockInfo());
935 
936  if (propagation == Propagation::REQUESTED) {
937  auto res = env_->onVoted(round_number_, voter_set_->id(), proposal);
938  if (not res) {
939  logger_->error("Round #{}: Primary proposal was not propagated: {}",
941  res.error().message());
942  }
943  }
944  }
945 
947  Propagation propagation) {
948  if (auto ctx_opt = GrandpaContext::get()) {
949  const auto &ctx = ctx_opt.value();
950  ctx->checked_signature_counter++;
951  }
952 
953  bool isValid = vote_crypto_provider_->verifyPrevote(prevote);
954  if (not isValid) {
955  logger_->warn(
956  "Round #{}: Prevote signed by {} was rejected: invalid signature",
958  prevote.id);
959 
960  if (auto ctx_opt = GrandpaContext::get()) {
961  const auto &ctx = ctx_opt.value();
962  ctx->invalid_signature_counter++;
963  }
964 
965  return false;
966  }
967 
968  if (auto result = onSigned<Prevote>(prevote); result.has_failure()) {
969  if (result == outcome::failure(VotingRoundError::DUPLICATED_VOTE)) {
970  return false;
971  }
972  if (result
973  == outcome::failure(VotingRoundError::VOTE_OF_KNOWN_EQUIVOCATOR)) {
974  return false;
975  }
976  if (result == outcome::failure(VotingRoundError::UNKNOWN_VOTER)) {
977  if (auto ctx_opt = GrandpaContext::get()) {
978  const auto &ctx = ctx_opt.value();
979  ctx->unknown_voter_counter++;
980  }
981  }
982  if (result != outcome::failure(VotingRoundError::EQUIVOCATED_VOTE)) {
983  logger_->warn("Round #{}: Prevote signed by {} was rejected: {}",
985  prevote.id,
986  result.error().message());
987  return false;
988  }
989  }
990 
991  SL_DEBUG(logger_,
992  "Round #{}: Prevote signed by {} was accepted for block {}",
994  prevote.id,
995  prevote.getBlockInfo());
996 
997  if (id_ == prevote.id) {
998  if (not prevote_.has_value()) {
999  prevote_.emplace(prevote.getBlockInfo());
1000  SL_DEBUG(logger_, "Round #{}: Own prevote was restored", round_number_);
1001  }
1002  propagation = Propagation::NEEDLESS;
1003  }
1004 
1005  if (propagation == Propagation::REQUESTED) {
1006  auto res = env_->onVoted(round_number_, voter_set_->id(), prevote);
1007  if (not res) {
1008  logger_->error("Round #{}: Prevote was not propagated: {}",
1009  round_number_,
1010  res.error().message());
1011  }
1012  }
1013 
1014  return true;
1015  }
1016 
1018  Propagation propagation) {
1019  if (auto ctx_opt = GrandpaContext::get()) {
1020  const auto &ctx = ctx_opt.value();
1021  ctx->checked_signature_counter++;
1022  }
1023 
1024  bool isValid = vote_crypto_provider_->verifyPrecommit(precommit);
1025  if (not isValid) {
1026  logger_->warn(
1027  "Round #{}: Precommit signed by {} was rejected: "
1028  "invalid signature",
1029  round_number_,
1030  precommit.id);
1031 
1032  if (auto ctx_opt = GrandpaContext::get()) {
1033  const auto &ctx = ctx_opt.value();
1034  ctx->invalid_signature_counter++;
1035  }
1036 
1037  return false;
1038  }
1039 
1040  if (auto result = onSigned<Precommit>(precommit); result.has_failure()) {
1041  if (result == outcome::failure(VotingRoundError::DUPLICATED_VOTE)) {
1042  return false;
1043  }
1044  if (result
1045  == outcome::failure(VotingRoundError::VOTE_OF_KNOWN_EQUIVOCATOR)) {
1046  return false;
1047  }
1048  if (result != outcome::failure(VotingRoundError::EQUIVOCATED_VOTE)) {
1049  logger_->warn("Round #{}: Precommit signed by {} was rejected: {}",
1050  round_number_,
1051  precommit.id,
1052  result.error().message());
1053  return false;
1054  }
1055  }
1056 
1057  SL_DEBUG(logger_,
1058  "Round #{}: Precommit signed by {} was accepted for block {}",
1059  round_number_,
1060  precommit.id,
1061  precommit.getBlockInfo());
1062 
1063  if (id_ == precommit.id) {
1064  if (not precommit_.has_value()) {
1065  precommit_.emplace(precommit.getBlockInfo());
1066  SL_DEBUG(
1067  logger_, "Round #{}: Own precommit was restored", round_number_);
1068  }
1069  propagation = Propagation::NEEDLESS;
1070  }
1071 
1072  if (propagation == Propagation::REQUESTED) {
1073  auto res = env_->onVoted(round_number_, voter_set_->id(), precommit);
1074  if (not res) {
1075  logger_->error("Round #{}: Precommit was not propagated: {}",
1076  round_number_,
1077  res.error().message());
1078  }
1079  }
1080 
1081  return true;
1082  }
1083 
1084  void VotingRoundImpl::update(IsPreviousRoundChanged is_previous_round_changed,
1085  IsPrevotesChanged is_prevotes_changed,
1086  IsPrecommitsChanged is_precommits_changed) {
1087  bool need_to_update_grandpa_ghost =
1088  is_previous_round_changed or is_prevotes_changed;
1089 
1090  bool need_to_update_estimate =
1091  is_precommits_changed or need_to_update_grandpa_ghost;
1092 
1093  if (need_to_update_grandpa_ghost) {
1094  if (updateGrandpaGhost()) {
1095  need_to_update_estimate = true;
1096  }
1097  }
1098 
1099  if (need_to_update_estimate) {
1100  if (updateEstimate()) {
1102 
1103  if (auto grandpa = grandpa_.lock()) {
1104  grandpa->updateNextRound(round_number_);
1105  }
1106  }
1107  }
1108 
1109  bool can_start_next_round;
1110 
1111  // Start next round only when previous round estimate is finalized
1112  if (previous_round_) {
1113  // Either it was already finalized in the previous round or it must be
1114  // finalized in the current round
1115  can_start_next_round = previous_round_->finalizedBlock().has_value();
1116  } else {
1117  // When we catch up to a round we complete the round without any last
1118  // round state. in this case we already started a new round after we
1119  // caught up so this guard is unneeded.
1120  can_start_next_round = true;
1121  }
1122 
1123  // Start next round only when current round is completable
1124  can_start_next_round = can_start_next_round and completable_;
1125 
1126  // Play new round
1127  // spec: Play-Grandpa-round(r + 1);
1128 
1129  if (can_start_next_round) {
1130  scheduler_->schedule(
1131  [grandpa_wp = std::move(grandpa_), round_wp = weak_from_this()] {
1132  if (auto grandpa = grandpa_wp.lock()) {
1133  if (auto round = round_wp.lock()) {
1134  grandpa->tryExecuteNextRound(round);
1135  }
1136  }
1137  });
1138  }
1139  }
1140 
1141  template <typename T>
1142  outcome::result<void> VotingRoundImpl::onSigned(const SignedMessage &vote) {
1143  BOOST_ASSERT(vote.is<T>());
1144 
1145  // Check if voter is contained in current voter set
1146  auto index_and_weight_opt = voter_set_->indexAndWeight(vote.id);
1147  if (!index_and_weight_opt) {
1148  SL_DEBUG(logger_, "Voter {} is not in the current voter set", vote.id.toHex());
1150  }
1151  const auto &[index, weight] = index_and_weight_opt.value();
1152 
1153  auto [type, type_str_, equivocators, tracker] =
1154  [&]() -> std::tuple<VoteType,
1155  const char *const,
1156  std::vector<bool> &,
1157  VoteTracker &> {
1158  if constexpr (std::is_same_v<T, Prevote>) {
1159  return {
1161  }
1162  if constexpr (std::is_same_v<T, Precommit>) {
1163  return {VoteType::Precommit,
1164  "Precommit",
1166  *precommits_};
1167  }
1168  }();
1169  auto &type_str = type_str_; // Reference to binding for capturing in lambda
1170 
1171  // Ignore known equivocators
1172  if (equivocators[index]) {
1174  }
1175 
1176  // Ignore zero-weight voter
1177  if (weight == 0) {
1179  }
1180 
1181  auto push_res = tracker.push(vote, weight);
1182  switch (push_res) {
1184  auto result = graph_->insert(type, vote.getBlockInfo(), vote.id);
1185  if (result.has_error()) {
1186  tracker.unpush(vote, weight);
1187  auto log_lvl = log::Level::WARN;
1188  // TODO(Harrm): this looks like a kind of a crutch,
1189  // think of a better way to pass this information
1190  if (result
1191  == outcome::failure(
1193  if (auto ctx_opt = GrandpaContext::get(); ctx_opt.has_value()) {
1194  auto &ctx = ctx_opt.value();
1195  ctx->missing_blocks.emplace(vote.getBlockInfo());
1196  log_lvl = log::Level::DEBUG;
1197  }
1198  }
1199  SL_LOG(logger_,
1200  log_lvl,
1201  "{} from {} for block {} was not inserted with error: {}",
1202  type_str,
1203  vote.id.toHex(),
1204  vote.getBlockInfo(),
1205  result.error().message());
1206  return result.as_failure();
1207  }
1208  return outcome::success();
1209  }
1212  }
1214  equivocators[index] = true;
1215  graph_->remove(type, vote.id);
1217  }
1218  default:
1219  BOOST_UNREACHABLE_RETURN({});
1220  }
1221  }
1222 
1223  template outcome::result<void> VotingRoundImpl::onSigned<Prevote>(
1224  const SignedMessage &vote);
1225 
1226  template outcome::result<void> VotingRoundImpl::onSigned<Precommit>(
1227  const SignedMessage &vote);
1228 
1230  if (prevotes_->getTotalWeight() < threshold_) {
1231  SL_TRACE(logger_,
1232  "Round #{}: updateGrandpaGhost->false "
1233  "(total prevote weight={} < threshold={})",
1234  round_number_,
1235  prevotes_->getTotalWeight(),
1236  threshold_);
1237  return false;
1238  }
1239 
1240  auto current_best = previous_round_ ? previous_round_->bestFinalCandidate()
1242 
1243  auto possible_to_prevote = [this](const VoteWeight &weight) {
1244  return weight.total(VoteType::Prevote, prevote_equivocators_, *voter_set_)
1245  >= threshold_;
1246  };
1247 
1249  auto new_prevote_ghost =
1250  graph_->findGhost(VoteType::Prevote, current_best, possible_to_prevote);
1251 
1252  if (new_prevote_ghost.has_value()) {
1253  bool changed = new_prevote_ghost != prevote_ghost_;
1254 
1255  prevote_ghost_ = new_prevote_ghost.value();
1256 
1257  if (changed) {
1258  SL_TRACE(logger_,
1259  "Round #{}: updateGrandpaGhost->true "
1260  "(prevote ghost was changed to block {})",
1261  round_number_,
1262  prevote_ghost_.value());
1263  } else {
1264  SL_TRACE(logger_,
1265  "Round #{}: updateGrandpaGhost->false "
1266  "(prevote ghost was not changed)",
1267  round_number_);
1268  }
1269  return changed || new_prevote_ghost == last_finalized_block_;
1270  }
1271 
1272  SL_TRACE(logger_,
1273  "Round #{}: updateGrandpaGhost->false (no new prevote ghost)",
1274  round_number_);
1275  return false;
1276  }
1277 
1279  if (prevotes_->getTotalWeight() < threshold_) {
1280  SL_TRACE(logger_,
1281  "Round #{}: updateEstimate->false "
1282  "(total prevote weight={} < threshold={})",
1283  round_number_,
1284  prevotes_->getTotalWeight(),
1285  threshold_);
1286  return false;
1287  }
1288 
1289  if (not prevote_ghost_) {
1290  return false;
1291  }
1292  const auto &prevote_ghost = prevote_ghost_.value();
1293 
1294  // anything new finalized? finalized blocks are those which have both
1295  // 2/3+ prevote and precommit weight.
1296  if (precommits_->getTotalWeight() >= threshold_) {
1297  auto possible_to_finalize = [&](const VoteWeight &weight) {
1298  return weight.total(
1300  >= threshold_;
1301  };
1302 
1303  finalized_ = graph_->findAncestor(
1304  VoteType::Precommit, prevote_ghost, std::move(possible_to_finalize));
1305 
1306  BOOST_ASSERT(finalized_.has_value());
1307  }
1308 
1309  // find how many more equivocations we could still get.
1310  //
1311  // it is only important to consider the voters whose votes
1312  // we have already seen, because we are assuming any votes we
1313  // haven't seen will target this block.
1314  const auto tolerated_equivocations = voter_set_->totalWeight() - threshold_;
1315 
1316  // get total weight of all equivocators
1317  const auto current_equivocations = std::accumulate(
1318  precommit_equivocators_.begin(),
1320  0ul,
1321  [this, index = 0ul](size_t sum, auto isEquivocator) mutable {
1322  if (not isEquivocator) {
1323  ++index;
1324  return sum;
1325  }
1326  return sum + voter_set_->voterWeight(index++).value();
1327  });
1328 
1329  const auto additional_equivocations =
1330  tolerated_equivocations - current_equivocations;
1331 
1332  const auto remaining_commit_votes =
1333  voter_set_->totalWeight() - precommits_->getTotalWeight();
1334 
1335  // figuring out whether a block can still be committed for is
1336  // not straightforward because we have to account for all possible future
1337  // equivocations and thus cannot discount weight from validators who
1338  // have already voted.
1339  auto possible_to_precommit = [&](const VoteWeight &weight) {
1340  // total precommits for this block, including equivocations.
1341  auto precommited_for = weight.total(
1343 
1344  // equivocations we could still get are out of those who
1345  // have already voted, but not on this block.
1346  auto possible_equivocations =
1347  std::min<size_t>(precommits_->getTotalWeight() - precommited_for,
1348  additional_equivocations);
1349 
1350  // all the votes already applied on this block,
1351  // assuming all remaining actors commit to this block,
1352  // and that we get further equivocations
1353  auto full_possible_weight =
1354  precommited_for + remaining_commit_votes + possible_equivocations;
1355 
1356  return full_possible_weight >= threshold_;
1357  };
1358 
1359  // until we have threshold precommits, any new block could get supermajority
1360  // precommits because there are at least f + 1 precommits remaining and then
1361  // f equivocations.
1362  //
1363  // once it's at least that level, we only need to consider blocks already
1364  // referenced in the graph, because no new leaf nodes could ever have enough
1365  // precommits.
1366  //
1367  // the round-estimate is the highest block in the chain with head
1368  // `prevote_ghost` that could have supermajority-commits.
1369  if (precommits_->getTotalWeight() < threshold_) {
1370  estimate_ = prevote_ghost;
1371  SL_TRACE(logger_,
1372  "Round #{}: updateEstimate->false: pc weight not enough => "
1373  "estimate=pv_ghost",
1374  round_number_);
1375  return false;
1376  }
1377 
1378  estimate_ = graph_->findAncestor(
1379  VoteType::Precommit, prevote_ghost, possible_to_precommit);
1380 
1381  if (not estimate_.has_value()) {
1382  completable_ = false;
1383  SL_TRACE(logger_,
1384  "Round #{}: updateEstimate: no estimate => completable=false",
1385  round_number_);
1386  } else {
1387  const auto &estimate = estimate_.value();
1388 
1389  if (estimate != prevote_ghost) {
1390  completable_ = true;
1391  SL_TRACE(
1392  logger_,
1393  "Round #{}: updateEstimate: estimate!=pv_ghost => completable=true",
1394  round_number_);
1395  } else {
1396  auto ghost_opt = graph_->findGhost(
1397  VoteType::Precommit, estimate, possible_to_precommit);
1398 
1399  if (not ghost_opt.has_value()) {
1400  completable_ = true;
1401  SL_TRACE(logger_,
1402  "Round #{}: updateEstimate: no pc_ghost => completable=true",
1403  round_number_);
1404  } else {
1405  const auto &ghost = ghost_opt.value();
1406 
1407  // round-estimate is the same as the prevote-ghost.
1408  // this round is still completable if no further blocks could have
1409  // commit-supermajority.
1410 
1411  if (ghost == estimate) {
1412  completable_ = true;
1413  SL_TRACE(logger_,
1414  "Round #{}: updateEstimate: estimate==pc_ghost => "
1415  "completable=true",
1416  round_number_);
1417  } else {
1418  completable_ = false;
1419  SL_TRACE(logger_,
1420  "Round #{}: updateEstimate: estimate!=pc_ghost => "
1421  "completable=false",
1422  round_number_);
1423  }
1424  }
1425  }
1426  }
1427 
1428  return true;
1429  }
1430 
1432  return completable_;
1433  }
1434 
1436  if (prevote_.has_value()) {
1437  return prevote_.value();
1438  }
1439 
1440  // spec: L <- Best-Final-Candidate(r-1)
1441  auto best_final_candidate = previous_round_
1442  ? previous_round_->bestFinalCandidate()
1444 
1445  // spec: Bpv <- GRANDPA-GHOST(r)
1446  auto best_chain =
1447  env_->bestChainContaining(best_final_candidate.hash, voter_set_->id());
1448  auto best_prevote_candidate = best_chain.has_value()
1449  ? convertToBlockInfo(best_chain.value())
1451 
1452  // spec: N <- Bpv
1453  prevote_ = best_prevote_candidate;
1454 
1455  // spec: if Received(Bprim) and Bpv >= Bprim > L
1456  if (primary_vote_.has_value()) {
1457  auto &primary = primary_vote_.value();
1458 
1459  if (best_prevote_candidate.number >= primary.number
1460  and primary.number > best_final_candidate.number) {
1461  // spec: N <- Bprim
1462  prevote_ = primary;
1463  }
1464  }
1465 
1466  return prevote_.value();
1467  }
1468 
1470  return estimate_.value_or(finalized_.value_or(last_finalized_block_));
1471  }
1472 
1475  .last_finalized_block = last_finalized_block_,
1476  .votes = prevotes_->getMessages(),
1477  .finalized = finalized_};
1478 
1479  auto precommits = precommits_->getMessages();
1480  round_state.votes.reserve(round_state.votes.size() + precommits.size());
1481  std::move(precommits.begin(),
1482  precommits.end(),
1483  std::back_inserter(round_state.votes));
1484 
1485  return round_state;
1486  }
1487 
1488  std::vector<SignedPrevote> VotingRoundImpl::getPrevoteJustification(
1489  const BlockInfo &estimate, const std::vector<VoteVariant> &votes) const {
1490  auto result = std::accumulate(
1491  votes.begin(),
1492  votes.end(),
1493  std::vector<SignedPrevote>(),
1494  [this, &estimate](auto &prevotes, const auto &voting_variant) {
1495  visit_in_place(
1496  voting_variant,
1497  [this, &prevotes, &estimate](
1498  const SignedMessage &voting_message) {
1499  if (voting_message.is<Prevote>()) {
1500  if (env_->isEqualOrDescendOf(estimate.hash,
1501  voting_message.getBlockHash())) {
1502  prevotes.push_back(
1503  static_cast<const SignedPrevote &>(voting_message));
1504  }
1505  }
1506  },
1507  [&prevotes](const EquivocatorySignedMessage
1508  &equivocatory_voting_message) {
1509  prevotes.push_back(static_cast<const SignedPrevote &>(
1510  equivocatory_voting_message.first));
1511  prevotes.push_back(static_cast<const SignedPrevote &>(
1512  equivocatory_voting_message.second));
1513  });
1514  return prevotes;
1515  });
1516  return result;
1517  }
1518 
1519  std::vector<SignedPrecommit> VotingRoundImpl::getPrecommitJustification(
1520  const BlockInfo &estimate, const std::vector<VoteVariant> &votes) const {
1521  auto result = std::accumulate(
1522  votes.begin(),
1523  votes.end(),
1524  std::vector<SignedPrecommit>(),
1525  [this, &estimate](auto &precommits, const auto &voting_variant) {
1526  visit_in_place(
1527  voting_variant,
1528  [this, &precommits, &estimate](
1529  const SignedMessage &voting_message) {
1530  if (voting_message.is<Precommit>()) {
1531  if (env_->isEqualOrDescendOf(estimate.hash,
1532  voting_message.getBlockHash())) {
1533  precommits.push_back(
1534  static_cast<const SignedPrecommit &>(voting_message));
1535  }
1536  }
1537  },
1538  [&precommits](const EquivocatorySignedMessage
1539  &equivocatory_voting_message) {
1540  precommits.push_back(static_cast<const SignedPrecommit &>(
1541  equivocatory_voting_message.first));
1542  precommits.push_back(static_cast<const SignedPrecommit &>(
1543  equivocatory_voting_message.second));
1544  });
1545  return precommits;
1546  });
1547  return result;
1548  }
1549 
1551  BOOST_ASSERT(finalized_.has_value());
1552  const auto &finalized_block = finalized_.value();
1553 
1554  auto estimate = estimate_.value_or(last_finalized_block_);
1555  auto prevote_justification =
1556  getPrevoteJustification(estimate, prevotes_->getMessages());
1557 
1558  auto precommit_justification =
1559  getPrecommitJustification(finalized_block, precommits_->getMessages());
1560 
1561  auto result = env_->onCatchUpRespond(peer_id,
1562  voter_set_->id(),
1563  round_number_,
1564  std::move(prevote_justification),
1565  std::move(precommit_justification),
1566  finalized_block);
1567  if (not result) {
1568  logger_->warn("Catch-Up-Response was not sent: {}",
1569  result.error().message());
1570  }
1571  }
1572 
1574  auto res = env_->onNeighborMessageSent(
1575  round_number_,
1576  voter_set_->id(),
1577  finalized_.value_or(last_finalized_block_).number);
1578  if (res.has_error()) {
1579  logger_->warn("Neighbor message was not sent: {}", res.error().message());
1580  }
1581  }
1582 
1584  SL_DEBUG(logger_, "Round #{}: Pending...", round_number_);
1585 
1587 
1588  std::function<void(std::shared_ptr<VotingRound>)> resend =
1589  [&](std::shared_ptr<VotingRound> round_arg) mutable {
1590  auto round = std::dynamic_pointer_cast<VotingRoundImpl>(round_arg);
1591  BOOST_ASSERT_MSG(round != nullptr, "Expected single implementation");
1592  if (auto prev_round = round->getPreviousRound()) {
1593  resend(std::move(prev_round));
1594  }
1595  auto r = round->roundNumber();
1596  auto s = round->voterSetId();
1597  if (r == 0) return;
1598  SL_DEBUG(logger_, "Round #{}: resend votes", r);
1599  for (const auto &graph : {round->prevotes_, round->precommits_}) {
1600  for (const auto &vote_variant : graph->getMessages()) {
1601  visit_in_place(
1602  vote_variant,
1603  [&](const SignedMessage &vote) {
1604  std::ignore = env_->onVoted(r, s, vote);
1605  },
1606  [&](const EquivocatorySignedMessage &pair) {
1607  std::ignore = env_->onVoted(r, s, pair.first);
1608  std::ignore = env_->onVoted(r, s, pair.second);
1609  });
1610  }
1611  }
1612  };
1613 
1614  SL_DEBUG(logger_, "Resend votes of recent rounds");
1615  resend(shared_from_this());
1616 
1618  scheduler_->scheduleWithHandle([&] { pending(); }, pending_interval_);
1619  }
1620 } // namespace kagome::consensus::grandpa
libp2p::basic::Scheduler::Handle pending_timer_handle_
std::shared_ptr< libp2p::basic::Scheduler > scheduler_
std::shared_ptr< VotingRound > previous_round_
void doPrecommit() override
Calculate precommit and broadcast signed precommit message.
libp2p::basic::Scheduler::Handle stage_timer_handle_
void sendProposal(const PrimaryPropose &primary_proposal)
primitives::detail::BlockInfoT< struct PrimaryProposeTag > PrimaryPropose
Definition: structs.hpp:33
std::vector< SignedPrecommit > items
Definition: structs.hpp:156
std::shared_ptr< VoteTracker > precommits_
primitives::detail::BlockInfoT< struct PrecommitTag > Precommit
Definition: structs.hpp:30
primitives::BlockInfo BlockInfo
Definition: structs.hpp:29
const std::chrono::milliseconds pending_interval_
outcome::result< void > validatePrecommitJustification(const BlockInfo &vote, const GrandpaJustification &justification) const
crypto::Ed25519PublicKey Id
Definition: common.hpp:19
MovableRoundState state() const override
std::shared_ptr< VoterSet > voters
Current round&#39;s authorities.
libp2p::peer::PeerId PeerId
outcome::result< void > onSigned(const SignedMessage &vote)
Triggered when we receive {.
static std::optional< std::shared_ptr< GrandpaContext > > get()
void sendPrecommit(const Precommit &precommit)
void update(IsPreviousRoundChanged is_previous_round_changed, IsPrevotesChanged is_prevotes_changed, IsPrecommitsChanged is_precommits_changed) override
outcome::result< void > applyJustification(const BlockInfo &block_info, const GrandpaJustification &justification) override
std::shared_ptr< authority::AuthorityManager > authority_manager_
void doPrevote() override
Calculate prevote and broadcast signed prevote message.
void doCatchUpResponse(const libp2p::peer::PeerId &peer_id) override
std::pair< SignedMessage, SignedMessage > EquivocatorySignedMessage
Definition: structs.hpp:94
std::string toHex() const noexcept
Definition: blob.hpp:160
Stores the current state of the round.
void doFinalize() override
Collect and save justifications finalizing this round.
bool onPrevote(const SignedMessage &prevote, Propagation propagation) override
bool isPrimary(const Id &id) const
Check if peer.
std::vector< SignedPrevote > getPrevoteJustification(const BlockInfo &estimate, const std::vector< VoteVariant > &votes) const
void onProposal(const SignedMessage &proposal, Propagation propagation) override
bool onPrecommit(const SignedMessage &precommit, Propagation propagation) override
primitives::detail::BlockInfoT< struct PrevoteTag > Prevote
Definition: structs.hpp:31
void doCommit() override
Broadcast commit message.
std::shared_ptr< VoteCryptoProvider > vote_crypto_provider_
std::vector< SignedPrecommit > getPrecommitJustification(const BlockInfo &precommits, const std::vector< VoteVariant > &votes) const