/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2017-2020 Telegram Systems LLP */ #include "validator-session-state.h" #include "td/utils/Random.h" #include "auto/tl/ton_api.hpp" #include namespace ton { namespace validatorsession { static const SessionVoteCandidate* get_candidate(const VoteVector* vec, ValidatorSessionCandidateId id) { if (!vec) { return nullptr; } auto size = vec->size(); auto v = vec->data(); for (td::uint32 i = 0; i < size; i++) { if (v[i]->get_id() == id) { return v[i]; } } return nullptr; } // // // SessionBlockCandidateSignature // // const SessionBlockCandidateSignature* SessionBlockCandidateSignature::merge(ValidatorSessionDescription& desc, const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) { if (!l) { return r; } if (!r) { return l; } if (l == r) { return l; } if (l->as_slice() < r->as_slice()) { return l; } else { return r; } } // // // SessionBlockCandidate // // bool SessionBlockCandidate::check_block_is_approved(ValidatorSessionDescription& desc) const { ValidatorWeight w = 0; for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) { if (approved_by_->at(i)) { w += desc.get_node_weight(i); if (w >= desc.get_cutoff_weight()) { return true; } } } return false; } const SessionBlockCandidate* SessionBlockCandidate::merge(ValidatorSessionDescription& desc, const SessionBlockCandidate* l, const SessionBlockCandidate* r) { if (!l) { return r; } if (!r) { return l; } if (l == r) { return l; } CHECK(l->get_id() == r->get_id()); auto v = SessionBlockCandidateSignatureVector::merge( desc, l->approved_by_, r->approved_by_, [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) { return SessionBlockCandidateSignature::merge(desc, l, r); }); return SessionBlockCandidate::create(desc, l->block_, std::move(v)); } // // // SessionVoteCandidate // // bool SessionVoteCandidate::check_block_is_voted(ValidatorSessionDescription& desc) const { ValidatorWeight w = 0; for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) { if (voted_by_->at(i)) { w += desc.get_node_weight(i); if (w >= desc.get_cutoff_weight()) { return true; } } } return false; } const SessionVoteCandidate* SessionVoteCandidate::merge(ValidatorSessionDescription& desc, const SessionVoteCandidate* l, const SessionVoteCandidate* r) { if (!l) { return r; } if (!r) { return l; } if (l == r) { return l; } CHECK(l->get_id() == r->get_id()); auto v = CntVector::merge(desc, l->voted_by_, r->voted_by_); return SessionVoteCandidate::create(desc, l->block_, std::move(v)); } // // // ATTEMPT STATE // // const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::merge( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* left, const ValidatorSessionRoundAttemptState* right) { if (!left) { return right; } if (!right) { return left; } if (left == right) { return left; } CHECK(left->seqno_ == right->seqno_); const SentBlock* vote_for = nullptr; bool vote_for_inited = false; if (!left->vote_for_inited_) { vote_for = right->vote_for_; vote_for_inited = right->vote_for_inited_; } else if (!right->vote_for_inited_) { vote_for = left->vote_for_; vote_for_inited = left->vote_for_inited_; } else if (left->vote_for_ == right->vote_for_) { vote_for_inited = true; vote_for = left->vote_for_; } else { auto l = SentBlock::get_block_id(left->vote_for_); auto r = SentBlock::get_block_id(right->vote_for_); vote_for_inited = true; if (l < r) { vote_for = left->vote_for_; } else { vote_for = right->vote_for_; } } auto precommitted = CntVector::merge(desc, left->precommitted_, right->precommitted_); auto votes = VoteVector::merge(desc, left->votes_, right->votes_, [&](const SessionVoteCandidate* l, const SessionVoteCandidate* r) { return SessionVoteCandidate::merge(desc, l, r); }); return ValidatorSessionRoundAttemptState::create(desc, left->seqno_, std::move(votes), std::move(precommitted), vote_for, vote_for_inited); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::action( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_message_voteFor& act, const ValidatorSessionRoundState* round) { if (state->vote_for_inited_) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: duplicate VOTEFOR"; return state; } if (src_idx != desc.get_vote_for_author(att)) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: bad VOTEFOR author"; return state; } if (round->get_first_attempt(src_idx) == 0 && desc.opts().max_round_attempts > 0) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: too early for VOTEFOR"; return state; } if (round->get_first_attempt(src_idx) + desc.opts().max_round_attempts > att && desc.opts().max_round_attempts > 0) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: too early for VOTEFOR"; return state; } auto x = round->get_block(act.candidate_); if (!x) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: VOTEFOR for not submitted block"; return state; } if (!x->check_block_is_approved(desc)) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: VOTEFOR for not approved block"; return state; } return ValidatorSessionRoundAttemptState::create(desc, state->seqno_, state->votes_, state->precommitted_, x->get_block(), true); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::action( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_message_vote& act, const ValidatorSessionRoundState* round) { bool made; return make_one(desc, state, src_idx, att, round, &act, made); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::action( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_message_precommit& act, const ValidatorSessionRoundState* round) { bool made; return make_one(desc, state, src_idx, att, round, &act, made); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::action( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_message_empty& act, const ValidatorSessionRoundState* round) { bool made; return make_one(desc, state, src_idx, att, round, &act, made); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::try_vote( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ValidatorSessionRoundState* round, const ton_api::validatorSession_round_Message* act, bool& made) { made = false; if (state->check_vote_received_from(src_idx)) { return state; } auto found = false; auto block = round->choose_block_to_vote(desc, src_idx, att, state->vote_for_, state->vote_for_inited_, found); if (!found) { return state; } auto block_id = SentBlock::get_block_id(block); made = true; if (act) { if (act->get_id() != ton_api::validatorSession_message_vote::ID) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: expected VOTE(" << block_id << ")"; } else { auto x = static_cast(act); if (x->candidate_ != block_id) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: expected VOTE(" << block_id << ")"; } } } else { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "]: making implicit VOTE(" << block_id << ")"; } auto candidate = get_candidate(state->votes_, block_id); if (!candidate) { candidate = SessionVoteCandidate::create(desc, block); } candidate = SessionVoteCandidate::push(desc, candidate, src_idx); auto v = VoteVector::push(desc, state->votes_, candidate); return ValidatorSessionRoundAttemptState::create(desc, state->seqno_, std::move(v), state->precommitted_, state->vote_for_, state->vote_for_inited_); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::try_precommit( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ValidatorSessionRoundState* round, const ton_api::validatorSession_round_Message* act, bool& made) { made = false; if (state->check_precommit_received_from(src_idx)) { return state; } bool found; auto block = state->get_voted_block(desc, found); if (!found) { return state; } made = true; auto block_id = SentBlock::get_block_id(block); if (act) { if (act->get_id() != ton_api::validatorSession_message_precommit::ID) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: expected PRECOMMIT(" << block_id << ")"; } else { auto x = static_cast(act); if (x->candidate_ != block_id) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: expected PRECOMMIT(" << block_id << ")"; } } } else { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "]: making implicit PRECOMMIT(" << block_id << ")"; } auto v = CntVector::change(desc, state->precommitted_, src_idx, true); return ValidatorSessionRoundAttemptState::create(desc, state->seqno_, state->votes_, std::move(v), state->vote_for_, state->vote_for_inited_); } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::make_one( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ValidatorSessionRoundState* round, const ton_api::validatorSession_round_Message* act, bool& made) { made = false; state = try_vote(desc, state, src_idx, att, round, act, made); if (made) { return state; } state = try_precommit(desc, state, src_idx, att, round, act, made); if (made) { return state; } if (act && act->get_id() != ton_api::validatorSession_message_empty::ID) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act << "]: invalid message: expected EMPTY"; } return state; } const ValidatorSessionRoundAttemptState* ValidatorSessionRoundAttemptState::action( ValidatorSessionDescription& desc, const ValidatorSessionRoundAttemptState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_round_Message* act, const ValidatorSessionRoundState* round) { ton_api::downcast_call(*const_cast(act), [&](auto& obj) { state = action(desc, state, src_idx, att, obj, round); }); return state; } bool ValidatorSessionRoundAttemptState::check_vote_received_from(td::uint32 src_idx) const { if (!votes_) { return false; } auto size = votes_->size(); auto v = votes_->data(); for (td::uint32 i = 0; i < size; i++) { if (v[i]->check_block_is_voted_by(src_idx)) { return true; } } return false; } bool ValidatorSessionRoundAttemptState::check_precommit_received_from(td::uint32 src_idx) const { return precommitted_->at(src_idx); } const SentBlock* ValidatorSessionRoundAttemptState::get_voted_block(ValidatorSessionDescription& desc, bool& f) const { f = false; if (!votes_) { return nullptr; } auto size = votes_->size(); auto v = votes_->data(); for (td::uint32 i = 0; i < size; i++) { if (v[i]->check_block_is_voted(desc)) { f = true; return v[i]->get_block(); } } return nullptr; } bool ValidatorSessionRoundAttemptState::check_attempt_is_precommitted(ValidatorSessionDescription& desc) const { ValidatorWeight weight = 0; for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) { if (precommitted_->at(i)) { weight += desc.get_node_weight(i); if (weight >= desc.get_cutoff_weight()) { return true; } } } return false; } tl_object_ptr ValidatorSessionRoundAttemptState::create_action( ValidatorSessionDescription& desc, const ValidatorSessionRoundState* round, td::uint32 src_idx, td::uint32 att) const { if (!check_vote_received_from(src_idx)) { auto found = false; auto B = round->choose_block_to_vote(desc, src_idx, att, vote_for_, vote_for_inited_, found); if (found) { auto block_id = SentBlock::get_block_id(B); return create_tl_object(round->get_seqno(), seqno_, block_id); } } if (!check_precommit_received_from(src_idx)) { bool f = false; auto B = get_voted_block(desc, f); if (f) { auto block_id = SentBlock::get_block_id(B); return create_tl_object(round->get_seqno(), seqno_, block_id); } } return create_tl_object(round->get_seqno(), seqno_); } void ValidatorSessionRoundAttemptState::dump(ValidatorSessionDescription& desc, td::StringBuilder& sb) const { sb << "attempt=" << seqno_ << "\n"; sb << ">>>>\n"; if (vote_for_inited_) { sb << "vote_for=" << (vote_for_ ? vote_for_->get_src_idx() : std::numeric_limits::max()) << "\n"; } else { sb << "vote_for=NONE\n"; } if (votes_) { auto s = votes_->size(); sb << "votes: "; std::vector R; R.resize(desc.get_total_nodes(), -1); for (td::uint32 i = 0; i < s; i++) { const auto e = votes_->at(i); const auto& x = e->get_voters_list(); for (td::uint32 j = 0; j < desc.get_total_nodes(); j++) { if (x->at(j)) { R[j] = e->get_src_idx(); } } } for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) { sb << R[i] << " "; } sb << "\n"; } else { sb << "votes: EMPTY\n"; } sb << "precommits: "; for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) { const auto e = precommitted_->at(i); if (e) { sb << "+ "; } else { sb << "- "; } } sb << "\n"; sb << "<<<<\n"; } } // namespace validatorsession } // namespace ton