mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 19:22:37 +00:00
* Fix "sgn" in fift; fix marking infinite loops as noterurn in func * TON-P1-1: Remove unused catchain queries * TON-P1-15: Avoid synchronization with self * TON-P1-3, TON-P1-17: Disallow more than one candidate per src per round (to prevent flood), add checks to process_broadcast * TON-P1-10: Fix fast/slow attempts * TON-P1-14: Add named constants * TON-P1-18, TON-P1-19: Alloc temporary memory in the same way as persistent memory * TON-P1-20: Add comment to choose_blocks_to_approve * TON-P1-16: Avoid creating two catchain blocks on need_new_block * TON-P1-8: Add some validation to validator-engine parameters * TON-P1-6: Don't allow sending the same block many times Many requests for the same block are not unusual (however, there's no need to answer them all) * TON-P1-2: Enable prohibiting dependencies from blamed nodes (2.7.5 in CatChain doc), fix processing blame proofs * Best practices bp-6: Fix incorrect warning bp-7: Remove unused code bp-8: Bring back PerfWarningTimer logging (only when no callback) bp-9: Remove unnecessary condition bp-11: Remove commented-out code bp-13: Divide code in validator-session-state Adherence to Specification: Fix typo
486 lines
17 KiB
C++
486 lines
17 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
Copyright 2017-2020 Telegram Systems LLP
|
|
*/
|
|
#include "validator-session-state.h"
|
|
#include "td/utils/Random.h"
|
|
#include "auto/tl/ton_api.hpp"
|
|
|
|
#include <set>
|
|
|
|
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<bool>::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<bool>::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<const ton_api::validatorSession_message_vote*>(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<const ton_api::validatorSession_message_precommit*>(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<bool>::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<ton_api::validatorSession_round_Message*>(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<ton_api::validatorSession_round_Message> 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<ton_api::validatorSession_message_vote>(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<ton_api::validatorSession_message_precommit>(round->get_seqno(), seqno_, block_id);
|
|
}
|
|
}
|
|
|
|
return create_tl_object<ton_api::validatorSession_message_empty>(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<td::uint32>::max()) << "\n";
|
|
} else {
|
|
sb << "vote_for=NONE\n";
|
|
}
|
|
|
|
if (votes_) {
|
|
auto s = votes_->size();
|
|
sb << "votes: ";
|
|
std::vector<td::int32> 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
|