16 #include "scale/scale.hpp" 19 constexpr
auto highestGrandpaRoundMetricName =
20 "kagome_finality_grandpa_round";
23 auto toMilliseconds(D &&duration) {
24 return std::chrono::duration_cast<std::chrono::milliseconds>(duration);
33 Clock::Duration getGossipDuration(
const application::ChainSpec &chain) {
35 auto slow = chain.isVersi() || chain.isWococo() || chain.isRococo()
38 std::chrono::milliseconds{slow ? 2000 : 1000});
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,
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)},
71 BOOST_ASSERT(
clock_ !=
nullptr);
79 BOOST_ASSERT(app_state_manager !=
nullptr);
83 "Highest GRANDPA round");
90 app_state_manager->takeControl(*
this);
95 environment_->setJustificationObserver(shared_from_this());
102 if (not round_state_res.has_value()) {
104 "Can't retrieve last round data: {}. Stopping grandpa execution",
105 round_state_res.error().message());
108 auto &round_state = round_state_res.value();
111 "Grandpa will be started with round #{}",
112 round_state.round_number + 1);
116 if (not authorities_res.has_value()) {
118 "Can't retrieve authorities for block {}. Stopping grandpa execution",
119 round_state.last_finalized_block);
122 auto &authority_set = authorities_res.value();
124 auto voters = std::make_shared<VoterSet>(authority_set->id);
125 for (
const auto &authority : authority_set->authorities) {
128 if (res.has_error()) {
130 "Can't make voter set: {}. Stopping grandpa execution",
131 res.error().message());
141 "Initial round must be finalized, but it is not. " 142 "Stopping grandpa execution");
148 [wp = weak_from_this()] {
149 auto self = wp.lock();
153 BOOST_ASSERT_MSG(self->current_round_,
154 "Current round must be defiled anytime after start");
162 self->fallback_timer_handle_.reschedule(std::chrono::minutes(1));
164 std::chrono::minutes(1));
177 auto vote_graph = std::make_shared<VoteGraphImpl>(
184 ? std::make_optional(
keypair_->public_key)
187 auto vote_crypto_provider = std::make_shared<VoteCryptoProviderImpl>(
190 auto new_round = std::make_shared<VotingRoundImpl>(
195 std::move(vote_crypto_provider),
196 std::make_shared<VoteTrackerImpl>(),
197 std::make_shared<VoteTrackerImpl>(),
198 std::move(vote_graph),
209 const std::shared_ptr<VotingRound> &round) {
211 round->finalizedBlock().value_or(round->lastFinalizedBlock());
213 auto authorities_opt =
215 if (!authorities_opt) {
217 "Can't retrieve authorities for finalized block {}",
222 auto &authority_set = authorities_opt.value();
223 BOOST_ASSERT(not authority_set->authorities.empty());
225 auto voters = std::make_shared<VoterSet>(authority_set->id);
226 for (
const auto &authority : authority_set->authorities) {
229 if (res.has_error()) {
230 SL_CRITICAL(
logger_,
"Can't make voter set: {}", res.error().message());
235 const auto new_round_number =
236 round->voterSetId() == voters->id() ? (round->roundNumber() + 1) : 1;
239 std::make_shared<VoteGraphImpl>(best_block, voters,
environment_);
242 .round_number = new_round_number,
245 ? std::make_optional(
keypair_->public_key)
248 auto vote_crypto_provider = std::make_shared<VoteCryptoProviderImpl>(
251 auto new_round = std::make_shared<VotingRoundImpl>(
256 std::move(vote_crypto_provider),
257 std::make_shared<VoteTrackerImpl>(),
258 std::make_shared<VoteTrackerImpl>(),
259 std::move(vote_graph),
267 RoundNumber round_number, std::optional<VoterSetId> voter_set_id) {
270 while (round !=
nullptr) {
272 if (round->roundNumber() < round_number) {
277 if (round->roundNumber() == round_number) {
278 if (not voter_set_id.has_value()
279 or round->voterSetId() == voter_set_id.value()) {
285 round = round->getPreviousRound();
288 return round ==
nullptr ? std::nullopt : std::make_optional(round);
293 auto finalized_block =
block_tree_->getLastFinalized();
295 if (finalized_block.number == 0) {
297 .last_finalized_block = finalized_block,
299 .finalized = {finalized_block}};
302 OUTCOME_TRY(encoded_justification,
303 block_tree_->getBlockJustification(finalized_block.hash));
306 grandpa_justification,
307 scale::decode<GrandpaJustification>(encoded_justification.data));
311 .last_finalized_block = grandpa_justification.block_info,
313 .finalized = {grandpa_justification.block_info}};
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); });
324 const std::shared_ptr<VotingRound> &prev_round) {
336 round = round->getPreviousRound()) {
338 round->forgetPreviousRound();
354 if (
auto opt_round =
selectRound(round_number + 1, std::nullopt);
355 opt_round.has_value()) {
356 auto &round = opt_round.value();
367 "NeighborMessage set_id={} round={} last_finalized={} " 368 "has received from {}",
377 if (not info.has_value()
378 or (info->get().set_id.has_value()
380 or (info->get().round_number.has_value()
383 opt_round.has_value()) {
384 auto &round = opt_round.value();
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();
399 reputation_changed =
true;
407 reputation_changed =
true;
413 if (not reputation_changed) {
425 if (res.has_value()) {
430 [wp = weak_from_this()] {
431 auto self = wp.lock();
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(
441 self->pending_catchup_request_.reset();
456 if (info->get().last_finalized <=
block_tree_->deepestLeaf().number) {
458 auto last_finalized =
block_tree_->getLastFinalized();
463 [wp = weak_from_this(), last_finalized, msg](
auto res) {
464 auto self = wp.lock();
468 if (res.has_error()) {
469 SL_WARN(self->logger_,
470 "Missing justifications between blocks {} and " 471 "{} was not loaded: {}",
474 res.error().message());
476 SL_DEBUG(self->logger_,
477 "Loaded justifications for blocks in range {} - {}",
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()) {
491 "Catch-up request to round #{} received from {} was rejected: " 492 "we are not have our view about remote peer",
499 const auto &info = info_opt->get();
504 "Catch-up request to round #{} received from {} was rejected: " 505 "it is not corresponding our view about remote peer ",
532 "Catch-up request to round #{} received from {} was rejected: " 533 "it is not corresponding our view about remote peer ",
548 "Catch-up request to round #{} received from {} was rejected: " 549 "impolite, because voter set id are differ (our: {}, their: {})",
561 "Catch-up request to round #{} received from {} was rejected: " 562 "impolite, because our current round is less - {}",
573 if (!opt_round.has_value()) {
575 "Catch-up request to round #{} received from {} was rejected: " 576 "target round not found",
582 auto &round = opt_round.value();
583 if (not round->finalizedBlock().has_value()) {
585 "Catch-up request to round #{} received from {} was rejected: " 586 "round is not finalizable",
589 throw std::runtime_error(
"Need not ensure if it is correct");
593 "Catch-up request to round #{} received from {}",
596 round->doCatchUpResponse(peer_id);
604 bool need_cleanup_when_exiting_scope =
false;
608 if (not ctx->peer_id.has_value()) {
615 const auto &[remote_peer_id, catchup_request] =
618 if (peer_id != remote_peer_id) {
638 need_cleanup_when_exiting_scope =
true;
641 auto cleanup = gsl::finally([&] {
642 if (need_cleanup_when_exiting_scope) {
653 "Catch-up response (till round #{}) received from {} was rejected: " 654 "impolite, because voter set id are differ (our: {}, their: {})",
666 "Catch-up response (till round #{}) received from {} was rejected: " 667 "catching up into the past",
674 "Catch-up response (till round #{}) received from {}",
687 std::back_inserter(round_state.votes),
688 [](
auto &item) {
return item; });
691 std::back_inserter(round_state.votes),
692 [](
auto &item) {
return item; });
696 if (!authorities_opt) {
698 "Can't retrieve authorities for finalized block {}",
699 round_state.finalized.value());
702 auto &authority_set = authorities_opt.value();
704 auto voters = std::make_shared<VoterSet>(msg.
voter_set_id);
705 for (
const auto &authority : authority_set->authorities) {
706 auto res = voters->insert(
708 if (res.has_error()) {
709 SL_WARN(
logger_,
"Can't make voter set: {}", res.error().message());
716 if (not round->completable()
717 and not round->finalizedBlock().has_value()) {
719 if (ctx->unknown_voter_counter > 0) {
723 * ctx->unknown_voter_counter);
726 if (ctx->invalid_signature_counter > 0) {
730 * ctx->checked_signature_counter);
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);
748 bool is_prevotes_changed =
false;
749 bool is_precommits_changed =
false;
753 is_prevotes_changed =
true;
759 is_precommits_changed =
true;
762 if (is_prevotes_changed or is_precommits_changed) {
769 SL_DEBUG(
logger_,
"Catch-up response applied");
774 if (ctx->unknown_voter_counter > 0) {
778 * ctx->unknown_voter_counter);
781 if (ctx->invalid_signature_counter > 0) {
785 * ctx->checked_signature_counter);
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);
809 if (not info.has_value() or not info->get().set_id.has_value()
810 or not info->get().round_number.has_value()) {
813 "{} signed by {} with set_id={} in round={} has received from {} " 814 "and we are not have our view about remote peer",
833 "{} signed by {} with set_id={} in round={} has received from {} " 834 "and rejected as impolite (our set id is {})",
852 "{} signed by {} with set_id={} in round={} has received from {} " 853 "and rejected as extremely impolite (our set id is {})",
880 "{} signed by {} with set_id={} in round={} has received from {} " 881 "and rejected as impolite (our round is {})",
897 "{} signed by {} with set_id={} in round={} has received from {} " 898 "and rejected as extremely impolite (our round is {})",
910 std::optional<std::shared_ptr<VotingRound>> opt_target_round =
912 if (not opt_target_round.has_value()) {
915 "{} signed by {} with set_id={} in round={} has received from {} " 916 "and rejected (round not found)",
926 auto &target_round = opt_target_round.value();
929 "{} signed by {} with set_id={} in round={} for block {} " 930 "has received from {}",
942 bool is_prevotes_changed =
false;
943 bool is_precommits_changed =
false;
947 target_round->onProposal(msg.vote,
948 VotingRound::Propagation::REQUESTED);
951 if (target_round->onPrevote(msg.vote,
952 VotingRound::Propagation::REQUESTED)) {
953 is_prevotes_changed = true;
957 if (target_round->onPrecommit(msg.vote,
959 is_precommits_changed =
true;
966 if (ctx->invalid_signature_counter > 0) {
969 * ctx->checked_signature_counter);
973 if (ctx->unknown_voter_counter > 0) {
976 * ctx->unknown_voter_counter);
979 if (is_prevotes_changed or is_precommits_changed) {
980 target_round->update(
989 if (not target_round->finalizedBlock().has_value()) {
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);
1016 "Commit with set_id={} in round={} for block {} has received from {} " 1017 "and dropped as impolite: our voter set id is {}",
1034 if (msg.
round + kKeepRecentRounds < current_round_->roundNumber()) {
1037 "Commit with set_id={} in round={} for block {} has received from {} " 1038 "and dropped as impolite: too old commit, our round is {}",
1047 if (msg.
message.precommits.empty()
1048 or msg.
message.auth_data.size() != msg.
message.precommits.size()) {
1054 if (
auto finalized_opt = prev_round->finalizedBlock()) {
1055 if (msg.
message.target_number < finalized_opt->number) {
1065 "Commit with set_id={} in round={} for block {} has received from {} " 1066 "and dropped as fulfilled",
1075 "Commit with set_id={} in round={} for block {} " 1076 "has received from {}",
1086 for (
size_t i = 0; i < msg.
message.precommits.size(); ++i) {
1090 commit.
id = msg.
message.auth_data[i].second;
1091 justification.items.emplace_back(std::move(commit));
1096 ctx->peer_id.emplace(peer_id);
1097 ctx->commit.emplace(msg);
1100 if (not res.has_value()) {
1103 "Commit with set_id={} in round={} for block {} has received from {} " 1104 "and has not applied: {}",
1109 res.error().message());
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());
1130 auto prev_round_opt =
1133 if (prev_round_opt.has_value()) {
1134 const auto &prev_round = prev_round_opt.value();
1136 need_to_make_round_current =
true;
1137 BOOST_ASSERT(round);
1140 "Hop grandpa to round #{} by received justification",
1147 .finalized = block_info};
1151 if (!authorities_opt) {
1153 "Can't retrieve authorities to apply a justification " 1158 auto &authority_set = authorities_opt.value();
1160 "Apply justification for block {} with voter set id {}",
1164 "authorities->id: {}, current_round_->voterSetId(): {}, " 1165 "justification.round_number: {}, " 1166 "current_round_->roundNumber(): {}",
1183 "Authority set on block {} with justification has id {}, " 1184 "while the current round set id is {} (difference must be 1)",
1188 auto voters = std::make_shared<VoterSet>(authority_set->id);
1189 for (
const auto &authority : authority_set->authorities) {
1190 auto res = voters->insert(
1192 if (res.has_error()) {
1194 logger_,
"Can't make voter set: {}", res.error().message());
1195 return res.as_failure();
1200 need_to_make_round_current =
true;
1201 BOOST_ASSERT(round);
1204 "Rewind grandpa till round #{} by received justification",
1209 OUTCOME_TRY(round->applyJustification(block_info, justification));
1211 if (need_to_make_round_current) {
1220 return outcome::success();
1227 if (not ctx->peer_id.has_value()) {
1231 if (ctx->missing_blocks.empty()) {
1235 auto final = [wp = weak_from_this(), ctx] {
1236 if (
auto self = wp.lock()) {
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());
1249 auto do_request_ptr = std::make_shared<std::function<void()>>();
1250 auto &do_request = *do_request_ptr;
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(
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);
1276 do_request_ptr.reset();
BlockNumber last_finalized
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
const ReputationChange ROUND_MESSAGE
std::shared_ptr< network::Synchronizer > synchronizer_
std::vector< SignedPrevote > prevote_justification
std::shared_ptr< authority::AuthorityManager > authority_manager_
primitives::BlockInfo BlockInfo
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'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
void sendNeighborMessage()
libp2p::basic::Scheduler::Handle catchup_request_timer_handle_
std::shared_ptr< crypto::Ed25519Provider > crypto_provider_
const ReputationChange NEIGHBOR_MESSAGE
BlockInfo getBlockInfo() const
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
typename ClockType::duration Duration
BlockInfo last_finalized_block
void onCommitMessage(const libp2p::peer::PeerId &peer_id, const network::FullCommitMessage &msg) override
std::shared_ptr< Clock > clock_
const ReputationChange MALFORMED_CATCH_UP
const ReputationChange HONEST_OUT_OF_SCOPE_CATCH_UP
std::vector< SignedPrecommit > precommit_justification
BlockInfo best_final_candidate
std::optional< std::shared_ptr< VotingRound > > selectRound(RoundNumber round_number, std::optional< VoterSetId > voter_set_id)
metrics::Gauge * metric_highest_round_
std::shared_ptr< runtime::GrandpaApi > grandpa_api_
static const size_t kKeepRecentRounds
Maximum number of rounds we are keep to communication.
static const size_t kCatchUpThreshold
metrics::RegistryPtr metrics_registry_
const Clock::Duration round_time_factor_
std::shared_ptr< libp2p::basic::Scheduler > scheduler_
Tagged< bool, struct IsBlockFinalizedTag > IsBlockFinalized