/*
    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-2019 Telegram Systems LLP
*/
#include "validator-session-state.h"
#include "td/utils/overloaded.h"
#include "td/utils/Random.h"
#include "auto/tl/ton_api.hpp"
#include 
namespace td {
td::StringBuilder& operator<<(td::StringBuilder& sb, const ton::ton_api::validatorSession_round_Message& message) {
  ton::ton_api::downcast_call(
      const_cast(message),
      td::overloaded(
          [&](const ton::ton_api::validatorSession_message_submittedBlock& obj) {
            sb << "SUBMIT(" << obj.round_ << "," << obj.root_hash_ << "," << obj.file_hash_
               << obj.collated_data_file_hash_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_approvedBlock& obj) {
            sb << "APPROVE(" << obj.round_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_rejectedBlock& obj) {
            sb << "COMMIT(" << obj.round_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_commit& obj) {
            sb << "COMMIT(" << obj.round_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_vote& obj) {
            sb << "VOTE(" << obj.round_ << "," << obj.attempt_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_voteFor& obj) {
            sb << "VOTEFOR(" << obj.round_ << "," << obj.attempt_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_precommit& obj) {
            sb << "PRECOMMIT(" << obj.round_ << "," << obj.attempt_ << "," << obj.candidate_ << ")";
          },
          [&](const ton::ton_api::validatorSession_message_empty& obj) {
            sb << "EMPTY(" << obj.round_ << "," << obj.attempt_ << ")";
          }));
  return sb;
}
td::StringBuilder& operator<<(td::StringBuilder& sb, const ton::ton_api::validatorSession_round_Message* message) {
  return sb << *message;
}
}  // namespace td
namespace ton {
namespace validatorsession {
static td::uint32 get_round_id(const ton_api::validatorSession_round_Message* message) {
  td::uint32 round = 0;
  bool is_called = ton_api::downcast_call(*const_cast(message),
                                          [&](auto& obj) { round = obj.round_; });
  CHECK(is_called);
  return round;
}
static const ValidatorSessionRoundAttemptState* get_attempt(const AttemptVector* vec, td::uint32 seqno) {
  if (!vec) {
    return nullptr;
  }
  td::int32 l = -1;
  td::int32 r = vec->size();
  while (r - l > 1) {
    auto x = (r + l) / 2;
    if (vec->at(x)->get_seqno() < seqno) {
      l = x;
    } else if (vec->at(x)->get_seqno() > seqno) {
      r = x;
    } else {
      return vec->at(x);
    }
  }
  return nullptr;
}
static const SessionBlockCandidate* get_candidate(const ApproveVector* 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;
}
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));
}
//
//
// OLD ROUND STATE
//
//
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::create(ValidatorSessionDescription& desc,
                                                                           const ValidatorSessionRoundState* round) {
  bool found;
  auto B = round->get_precommitted_block(found);
  CHECK(found);
  CHECK(round->check_block_is_signed(desc));
  auto E = round->get_block(SentBlock::get_block_id(B));
  CHECK(E);
  return create(desc, round->get_seqno(), B, round->get_signatures(), E->get_approvers_list());
}
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::merge(ValidatorSessionDescription& desc,
                                                                          const ValidatorSessionOldRoundState* left,
                                                                          const ValidatorSessionOldRoundState* right) {
  if (!left) {
    return right;
  }
  if (!right) {
    return left;
  }
  if (left == right) {
    return left;
  }
  CHECK(left->seqno_ == right->seqno_);
  auto signs = SessionBlockCandidateSignatureVector::merge(
      desc, left->signatures_, right->signatures_,
      [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) {
        return SessionBlockCandidateSignature::merge(desc, l, r);
      });
  auto approve_signs = SessionBlockCandidateSignatureVector::merge(
      desc, left->approve_signatures_, right->approve_signatures_,
      [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) {
        return SessionBlockCandidateSignature::merge(desc, l, r);
      });
  return ValidatorSessionOldRoundState::create(desc, left->seqno_, left->block_, std::move(signs),
                                               std::move(approve_signs));
}
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::merge(ValidatorSessionDescription& desc,
                                                                          const ValidatorSessionOldRoundState* left,
                                                                          const ValidatorSessionRoundState* right) {
  CHECK(left->seqno_ == right->get_seqno());
  auto signs = SessionBlockCandidateSignatureVector::merge(
      desc, left->signatures_, right->get_signatures(),
      [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) {
        return SessionBlockCandidateSignature::merge(desc, l, r);
      });
  auto C = right->get_block(left->get_block_id());
  auto approve_signs = C ? SessionBlockCandidateSignatureVector::merge(
                               desc, left->approve_signatures_, C->get_approvers_list(),
                               [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) {
                                 return SessionBlockCandidateSignature::merge(desc, l, r);
                               })
                         : left->approve_signatures_;
  return ValidatorSessionOldRoundState::create(desc, left->seqno_, left->block_, std::move(signs),
                                               std::move(approve_signs));
}
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionOldRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_approvedBlock& act) {
  if (act.candidate_ != state->get_block_id()) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: approved not committed block in old round. Ignoring";
    return state;
  }
  if (state->approve_signatures_->at(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: double approve. Ignoring";
    return state;
  }
  if (act.candidate_ == skip_round_candidate_id()) {
    if (act.signature_.size() != 0) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: bad signature in APPROVE. Ignoring";
      return state;
    }
  } else {
    auto S = desc.check_approve_signature(state->get_block()->get_root_hash(), state->get_block()->get_file_hash(),
                                          src_idx, act.signature_.as_slice());
    if (S.is_error()) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: bad signature in APPROVE. Ignoring: " << S;
      return state;
    }
  }
  auto sig = SessionBlockCandidateSignature::create(desc, act.signature_.clone());
  auto approve = SessionBlockCandidateSignatureVector::change(desc, state->approve_signatures_, src_idx, sig);
  return ValidatorSessionOldRoundState::create(desc, state->seqno_, state->get_block(), state->signatures_,
                                               std::move(approve));
}
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionOldRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_commit& act) {
  auto block_id = state->get_block_id();
  if (act.candidate_ != block_id) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: signed wrong block "
                                    << "should be " << block_id;
    return state;
  }
  if (act.candidate_ == skip_round_candidate_id()) {
    if (act.signature_.size() != 0) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: invalid skip block signature";
      return state;
    }
  } else {
    auto S = desc.check_signature(state->get_block()->get_root_hash(), state->get_block()->get_file_hash(), src_idx,
                                  act.signature_.as_slice());
    if (S.is_error()) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: invalid message: bad signature: " << S.move_as_error();
      return state;
    }
  }
  if (state->check_block_is_signed_by(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: duplicate signature";
    return state;
  }
  auto signs = SessionBlockCandidateSignatureVector::change(
      desc, state->get_signatures(), src_idx, SessionBlockCandidateSignature::create(desc, act.signature_.clone()));
  return ValidatorSessionOldRoundState::create(desc, state->seqno_, state->get_block(), std::move(signs),
                                               state->approve_signatures_);
}
const ValidatorSessionOldRoundState* ValidatorSessionOldRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionOldRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_round_Message* act) {
  ton_api::downcast_call(*const_cast(act),
                         [&](const auto& obj) { state = action(desc, state, src_idx, att, obj); });
  return state;
}
//
//
// 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 approved 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";
}
//
//
// ROUND STATE
//
//
const ValidatorSessionRoundState* ValidatorSessionRoundState::merge(ValidatorSessionDescription& desc,
                                                                    const ValidatorSessionRoundState* left,
                                                                    const ValidatorSessionRoundState* right) {
  if (!left) {
    return right;
  }
  if (!right) {
    return left;
  }
  if (left == right) {
    return left;
  }
  CHECK(left->seqno_ == right->seqno_);
  if (left->precommitted_ && right->precommitted_) {
    CHECK(SentBlock::get_block_id(left->precommitted_block_) == SentBlock::get_block_id(right->precommitted_block_));
  }
  const SentBlock* precommitted_block =
      left->precommitted_block_ ? left->precommitted_block_ : right->precommitted_block_;
  bool precommitted = left->precommitted_ || right->precommitted_;
  auto first_attempt =
      CntVector::merge(desc, left->first_attempt_, right->first_attempt_, [&](td::uint32 l, td::uint32 r) {
        if (l == 0) {
          return r;
        }
        if (r == 0) {
          return l;
        }
        return std::min(l, r);
      });
  auto att_vec =
      AttemptVector::merge(desc, left->attempts_, right->attempts_,
                           [&](const ValidatorSessionRoundAttemptState* l, const ValidatorSessionRoundAttemptState* r) {
                             return ValidatorSessionRoundAttemptState::merge(desc, l, r);
                           });
  const SentBlock* f[2];
  td::uint32 f_att[2] = {};
  td::uint32 f_cnt = 0;
  for (td::uint32 i = att_vec->size(); i > 0; i--) {
    auto b = att_vec->at(i - 1);
    if (f_cnt <= 1) {
      bool found;
      auto B = b->get_voted_block(desc, found);
      if (found) {
        if (f_cnt == 1) {
          auto l = SentBlock::get_block_id(f[0]);
          auto r = SentBlock::get_block_id(B);
          if (l == r) {
            found = false;
          }
        }
        if (found) {
          f[f_cnt] = B;
          f_att[f_cnt] = b->get_seqno();
          f_cnt++;
        }
      }
    }
    if (!precommitted && b->check_attempt_is_precommitted(desc)) {
      precommitted = true;
      bool found;
      precommitted_block = b->get_voted_block(desc, found);
      CHECK(found);
    }
    if (precommitted && f_cnt == 2) {
      break;
    }
  }
  if (f_cnt >= 1) {
    CHECK(f_att[0] > f_att[1]);
  }
  auto last_precommit = CntVector::merge(
      desc, left->last_precommit_, right->last_precommit_,
      [&](td::uint32 l, td::uint32 r) -> td::uint32 {
        auto x = std::max(l, r);
        if (f_cnt == 0) {
          CHECK(x == 0);
          return x;
        }
        if (x > f_att[1]) {
          return x;
        } else {
          return 0;
        }
      },
      true);
  auto signs = CntVector::merge(
      desc, left->signatures_, right->signatures_,
      [&](const SessionBlockCandidateSignature* l, const SessionBlockCandidateSignature* r) {
        return SessionBlockCandidateSignature::merge(desc, l, r);
      });
  //auto sent_vec = SentBlockVector::merge(desc, left->sent_blocks_, right->sent_blocks_);
  auto sent = ApproveVector::merge(desc, left->sent_blocks_, right->sent_blocks_,
                                   [&](const SessionBlockCandidate* l, const SessionBlockCandidate* r) {
                                     return SessionBlockCandidate::merge(desc, l, r);
                                   });
  return ValidatorSessionRoundState::create(desc, precommitted_block, left->seqno_, precommitted,
                                            std::move(first_attempt), last_precommit, std::move(sent), std::move(signs),
                                            std::move(att_vec));
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_submittedBlock& act) {
  if (desc.get_node_priority(src_idx, state->seqno_) < 0) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: cannot propose blocks on this round";
    return state;
  }
  if (state->check_block_is_sent_by(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: duplicate block propose";
    return state;
  }
  auto b = SentBlock::create(desc, src_idx, act.root_hash_, act.file_hash_, act.collated_data_file_hash_);
  auto a = SessionBlockCandidate::create(desc, b);
  auto sent = ApproveVector::push(desc, state->sent_blocks_, a);
  return ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                            state->first_attempt_, state->last_precommit_, sent, state->signatures_,
                                            state->attempts_);
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_approvedBlock& act) {
  auto sent_block = state->get_block(act.candidate_);
  if (act.candidate_ != skip_round_candidate_id() && !sent_block) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: approved unknown block " << act.candidate_;
    return state;
  }
  if (sent_block && sent_block->check_block_is_approved_by(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: duplicate block " << act.candidate_ << " approve";
    return state;
  }
  if (act.candidate_ != skip_round_candidate_id()) {
    auto v = state->sent_blocks_->data();
    auto s = state->sent_blocks_->size();
    for (td::uint32 i = 0; i < s; i++) {
      auto B = v[i];
      if (B->get_src_idx() == sent_block->get_src_idx() && B->check_block_is_approved_by(src_idx)) {
        VLOG(VALIDATOR_SESSION_WARNING)
            << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
            << "]: invalid message: approved block, but already approved another from this node";
        return state;
      }
    }
  }
  if (act.candidate_ == skip_round_candidate_id()) {
    if (act.signature_.size() != 0) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: bad signature in APPROVE. Ignoring";
      return state;
    }
  } else {
    auto S = desc.check_approve_signature(sent_block->get_block()->get_root_hash(),
                                          sent_block->get_block()->get_file_hash(), src_idx, act.signature_.as_slice());
    if (S.is_error()) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: bad signature in APPROVE. Ignoring: " << S;
      return state;
    }
  }
  if (!sent_block) {
    CHECK(act.candidate_ == skip_round_candidate_id());
    sent_block = SessionBlockCandidate::create(desc, nullptr);
  }
  sent_block = SessionBlockCandidate::push(desc, sent_block, src_idx,
                                           SessionBlockCandidateSignature::create(desc, act.signature_.clone()));
  auto v = ApproveVector::push(desc, state->sent_blocks_, sent_block);
  return ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                            state->first_attempt_, state->last_precommit_, std::move(v),
                                            state->signatures_, state->attempts_);
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_rejectedBlock& act) {
  LOG(ERROR) << "VALIDATOR SESSION: NODE " << desc.get_source_id(src_idx) << " REJECTED CANDIDATE " << act.candidate_
             << " WITH REASON " << act.reason_.as_slice();
  return state;
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_message_commit& act) {
  if (!state->precommitted_) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: committing not precommitted block";
    return state;
  }
  auto block_id = SentBlock::get_block_id(state->precommitted_block_);
  if (block_id != act.candidate_) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: committing wrong block " << act.candidate_;
    return state;
  }
  if (state->signatures_->at(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                    << "]: invalid message: duplicate signature";
    return state;
  }
  if (act.candidate_ == skip_round_candidate_id()) {
    if (act.signature_.size() != 0) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: bad signature in COMMIT. Ignoring";
      return state;
    }
  } else {
    auto S = desc.check_signature(state->precommitted_block_->get_root_hash(),
                                  state->precommitted_block_->get_file_hash(), src_idx, act.signature_.as_slice());
    if (S.is_error()) {
      VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << act
                                      << "]: invalid message: bad signature: " << S.move_as_error();
      return state;
    }
  }
  auto s = CntVector::change(
      desc, state->signatures_, src_idx, SessionBlockCandidateSignature::create(desc, act.signature_.clone()));
  return ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                            state->first_attempt_, state->last_precommit_, state->sent_blocks_,
                                            std::move(s), state->attempts_);
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::forward_action_to_attempt(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_round_Message* act) {
  if (state->precommitted_) {
    if (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 in precommitted round: expected EMPTY";
    }
    return state;
  }
  auto attempt = get_attempt(state->attempts_, att);
  if (!attempt) {
    attempt = ValidatorSessionRoundAttemptState::create(desc, att);
  }
  bool had_voted_block;
  attempt->get_voted_block(desc, had_voted_block);
  ton_api::downcast_call(*const_cast(act), [&](auto& obj) {
    attempt = ValidatorSessionRoundAttemptState::action(desc, attempt, src_idx, att, obj, state);
  });
  bool has_voted_block;
  auto block = attempt->get_voted_block(desc, has_voted_block);
  auto precommitted = state->precommitted_;
  auto precommitted_block = state->precommitted_block_;
  if (!precommitted && attempt->check_attempt_is_precommitted(desc)) {
    precommitted = true;
    CHECK(has_voted_block);
    precommitted_block = block;
  }
  auto last_precommit = state->last_precommit_;
  td::uint32 e = 0;
  for (td::uint32 i = 0; i < last_precommit->size(); i++) {
    e = std::max(e, last_precommit->at(i));
  }
  bool eq = true;
  if (e && has_voted_block) {
    auto attempt = get_attempt(state->attempts_, e);
    CHECK(attempt);
    bool f;
    auto B = attempt->get_voted_block(desc, f);
    CHECK(f);
    auto l = SentBlock::get_block_id(block);
    auto r = SentBlock::get_block_id(B);
    eq = (l == r);
    if (!eq) {
      last_precommit = CntVector::modify(desc, last_precommit, [&](td::uint32 a) -> td::uint32 {
        if (a > att) {
          return a;
        } else {
          return 0;
        }
      });
    }
  }
  if (attempt->check_precommit_received_from(src_idx) && last_precommit->at(src_idx) < att && (att > e || eq)) {
    CHECK(has_voted_block);
    last_precommit = CntVector::change(desc, last_precommit, src_idx, att);
  }
  auto vec = AttemptVector::push(desc, state->attempts_, std::move(attempt));
  return ValidatorSessionRoundState::create(desc, precommitted_block, state->seqno_, precommitted,
                                            state->first_attempt_, last_precommit, state->sent_blocks_,
                                            state->signatures_, std::move(vec));
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::action(
    ValidatorSessionDescription& desc, const ValidatorSessionRoundState* state, td::uint32 src_idx, td::uint32 att,
    const ton_api::validatorSession_round_Message* act) {
  auto first_attempt = state->first_attempt_;
  if (first_attempt->at(src_idx) == 0 || first_attempt->at(src_idx) > att) {
    first_attempt = CntVector::change(desc, first_attempt, src_idx, att);
    state = ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                               first_attempt, state->last_precommit_, state->sent_blocks_,
                                               state->signatures_, state->attempts_);
  }
  ton_api::downcast_call(*const_cast(act),
                         [&](auto& obj) { state = action(desc, state, src_idx, att, obj); });
  return state;
}
bool ValidatorSessionRoundState::check_block_is_sent_by(td::uint32 src_idx) const {
  if (!sent_blocks_) {
    return false;
  }
  auto v = sent_blocks_->data();
  auto size = sent_blocks_->size();
  for (td::uint32 i = 0; i < size; i++) {
    auto B = v[i]->get_block();
    if (B && B->get_src_idx() == src_idx) {
      return true;
    }
  }
  return false;
}
bool ValidatorSessionRoundState::check_block_is_signed(ValidatorSessionDescription& desc) const {
  ValidatorWeight weight = 0;
  for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) {
    if (signatures_->at(i)) {
      weight += desc.get_node_weight(i);
      if (weight >= desc.get_cutoff_weight()) {
        return true;
      }
    }
  }
  return false;
}
bool ValidatorSessionRoundState::check_need_generate_vote_for(ValidatorSessionDescription& desc, td::uint32 src_idx,
                                                              td::uint32 att) const {
  if (src_idx != desc.get_vote_for_author(att)) {
    return false;
  }
  if (precommitted_) {
    return false;
  }
  if (get_first_attempt(src_idx) == 0 && desc.opts().max_round_attempts > 0) {
    return false;
  }
  if (get_first_attempt(src_idx) + desc.opts().max_round_attempts > att && desc.opts().max_round_attempts > 0) {
    return false;
  }
  auto attempt = get_attempt(attempts_, att);
  if (attempt) {
    bool vote_for_inited;
    attempt->get_vote_for_block(desc, vote_for_inited);
    if (attempt && vote_for_inited) {
      return false;
    }
  }
  if (!sent_blocks_) {
    return false;
  }
  for (td::uint32 i = 0; i < sent_blocks_->size(); i++) {
    auto B = sent_blocks_->at(i);
    if (B->check_block_is_approved(desc)) {
      return true;
    }
  }
  return false;
}
tl_object_ptr ValidatorSessionRoundState::generate_vote_for(
    ValidatorSessionDescription& desc, td::uint32 src_idx, td::uint32 att) const {
  CHECK(src_idx == desc.get_vote_for_author(att));
  std::vector v;
  for (td::uint32 i = 0; i < sent_blocks_->size(); i++) {
    auto B = sent_blocks_->at(i);
    if (B->check_block_is_approved(desc)) {
      v.push_back(B->get_id());
    }
  }
  CHECK(v.size() > 0);
  td::uint32 x = td::Random::secure_uint32();
  return create_tl_object(get_seqno(), static_cast(att),
                                                                     v[x % v.size()]);
}
const SentBlock* ValidatorSessionRoundState::choose_block_to_vote(ValidatorSessionDescription& desc, td::uint32 src_idx,
                                                                  td::uint32 att, const SentBlock* vote_for,
                                                                  bool vote_for_inited, bool& found) const {
  found = false;
  if (!sent_blocks_) {
    return nullptr;
  }
  if (last_precommit_->at(src_idx) > 0) {
    auto attempt = get_attempt(attempts_, last_precommit_->at(src_idx));
    CHECK(attempt);
    bool f;
    auto B = attempt->get_voted_block(desc, f);
    CHECK(f);
    found = true;
    return B;
  }
  auto t = first_attempt_->at(src_idx);
  auto slow_mode = (t > 0 && t + desc.opts().max_round_attempts <= att) || desc.opts().max_round_attempts == 0;
  if (!slow_mode) {
    bool f = false;
    const SentBlock* block = nullptr;
    auto size = attempts_ ? attempts_->size() : 0;
    for (td::uint32 i = size; i > 0; i--) {
      auto attempt = attempts_->at(i - 1);
      CHECK(attempt);
      block = attempt->get_voted_block(desc, f);
      if (f) {
        break;
      }
    }
    if (f) {
      found = true;
      return block;
    }
    td::int32 min_priority = desc.get_max_priority() + 2;
    for (td::uint32 i = 0; i < sent_blocks_->size(); i++) {
      auto B = sent_blocks_->at(i);
      if (!B->check_block_is_approved(desc)) {
        continue;
      }
      td::int32 e = B->get_block() ? desc.get_node_priority(B->get_src_idx(), seqno_) : desc.get_max_priority() + 1;
      CHECK(e >= 0);
      if (e < min_priority) {
        min_priority = e;
        block = B->get_block();
      }
    }
    found = min_priority < static_cast(desc.get_max_priority() + 2);
    return block;
  }
  found = vote_for_inited;
  return vote_for;
}
bool ValidatorSessionRoundState::check_block_is_approved_by(td::uint32 src_idx,
                                                            ValidatorSessionCandidateId block_id) const {
  if (!sent_blocks_) {
    return false;
  }
  auto size = sent_blocks_->size();
  auto v = sent_blocks_->data();
  for (td::uint32 i = 0; i < size; i++) {
    if (v[i]->get_id() == block_id) {
      return v[i]->check_block_is_approved_by(src_idx);
    }
  }
  return false;
}
tl_object_ptr ValidatorSessionRoundState::create_action(
    ValidatorSessionDescription& desc, td::uint32 src_idx, td::uint32 att) const {
  if (precommitted_) {
    return create_tl_object(seqno_, att);
  }
  auto attempt = get_attempt(attempts_, att);
  if (attempt) {
    return attempt->create_action(desc, this, src_idx, att);
  }
  bool found = false;
  auto block = choose_block_to_vote(desc, src_idx, att, nullptr, false, found);
  if (!found) {
    return create_tl_object(seqno_, att);
  }
  auto block_id = SentBlock::get_block_id(block);
  return create_tl_object(seqno_, att, block_id);
}
const SentBlock* ValidatorSessionRoundState::choose_block_to_sign(ValidatorSessionDescription& desc, td::uint32 src_idx,
                                                                  bool& found) const {
  found = false;
  if (!precommitted_) {
    return nullptr;
  }
  found = true;
  return precommitted_block_;
}
const ValidatorSessionRoundState* ValidatorSessionRoundState::make_one(ValidatorSessionDescription& desc,
                                                                       const ValidatorSessionRoundState* state,
                                                                       td::uint32 src_idx, td::uint32 att, bool& made) {
  made = false;
  auto first_attempt = state->first_attempt_;
  if (first_attempt->at(src_idx) == 0 || first_attempt->at(src_idx) > att) {
    made = true;
    first_attempt = CntVector::change(desc, state->first_attempt_, src_idx, att);
    state = ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                               first_attempt, state->last_precommit_, state->sent_blocks_,
                                               state->signatures_, state->attempts_);
  }
  if (state->precommitted_) {
    return state;
  }
  auto attempt = get_attempt(state->attempts_, att);
  if (!attempt) {
    attempt = ValidatorSessionRoundAttemptState::create(desc, att);
  }
  bool m;
  auto a = ValidatorSessionRoundAttemptState::make_one(desc, attempt, src_idx, att, state, nullptr, m);
  if (!m) {
    return state;
  }
  made = true;
  auto att_vec = AttemptVector::push(desc, state->attempts_, std::move(a));
  return ValidatorSessionRoundState::create(desc, state->precommitted_block_, state->seqno_, state->precommitted_,
                                            state->first_attempt_, state->last_precommit_, state->sent_blocks_,
                                            state->signatures_, std::move(att_vec));
}
std::vector ValidatorSessionRoundState::choose_blocks_to_approve(ValidatorSessionDescription& desc,
                                                                                   td::uint32 src_idx) const {
  if (!sent_blocks_) {
    return {nullptr};
  }
  std::set was_source;
  std::vector x;
  x.resize(desc.get_max_priority() + 2, nullptr);
  bool was_empty = false;
  for (td::uint32 i = 0; i < sent_blocks_->size(); i++) {
    auto B = sent_blocks_->at(i);
    if (!B->get_block()) {
      was_empty = B->check_block_is_approved_by(src_idx);
      continue;
    }
    td::int32 prio = desc.get_node_priority(B->get_src_idx(), seqno_);
    CHECK(prio >= 0);
    td::uint32 blk_src_idx = B->get_src_idx();
    if (was_source.count(blk_src_idx) > 0) {
      x[prio] = nullptr;
    } else {
      was_source.insert(blk_src_idx);
      if (!B->check_block_is_approved_by(src_idx)) {
        x[prio] = B;
      }
    }
  }
  std::vector res;
  for (auto& B : x) {
    if (B) {
      res.push_back(B->get_block());
    }
  }
  if (!was_empty) {
    res.push_back(nullptr);
  }
  return res;
}
const SessionBlockCandidate* ValidatorSessionRoundState::get_block(ValidatorSessionCandidateId block_hash) const {
  if (!sent_blocks_) {
    return nullptr;
  }
  return get_candidate(sent_blocks_, block_hash);
}
std::vector ValidatorSessionRoundState::get_blocks_approved_by(ValidatorSessionDescription& desc,
                                                                                 td::uint32 src_idx) const {
  if (!sent_blocks_) {
    return {};
  }
  std::vector res;
  for (td::uint32 i = 0; i < sent_blocks_->size(); i++) {
    auto B = sent_blocks_->at(i);
    if (B->check_block_is_approved_by(src_idx)) {
      res.push_back(B->get_block());
    }
  }
  return res;
}
std::vector ValidatorSessionRoundState::get_block_approvers(ValidatorSessionDescription& desc,
                                                                        ValidatorSessionCandidateId block) const {
  auto B = get_candidate(sent_blocks_, block);
  if (!B) {
    return {};
  }
  std::vector v;
  for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) {
    if (B->check_block_is_approved_by(i)) {
      v.push_back(i);
    }
  }
  return v;
}
//
//
// STATE
//
//
const ValidatorSessionState* ValidatorSessionState::merge(ValidatorSessionDescription& desc,
                                                          const ValidatorSessionState* left,
                                                          const ValidatorSessionState* right) {
  if (!left) {
    return right;
  }
  if (!right) {
    return left;
  }
  if (left == right) {
    return left;
  }
  CHECK(left->att_->size() == desc.get_total_nodes());
  CHECK(right->att_->size() == desc.get_total_nodes());
  auto ts = CntVector::merge(desc, left->att_, right->att_,
                                         [](td::uint32 l, td::uint32 r) { return std::max(l, r); });
  auto old = CntVector::merge(
      desc, left->old_rounds_, right->old_rounds_,
      [&](const ValidatorSessionOldRoundState* l, const ValidatorSessionOldRoundState* r) {
        return ValidatorSessionOldRoundState::merge(desc, l, r);
      });
  const ValidatorSessionRoundState* round;
  if (left->cur_round_->get_seqno() < right->cur_round_->get_seqno()) {
    auto r = old->at(left->cur_round_->get_seqno());
    old = CntVector::change(
        desc, old, left->cur_round_->get_seqno(), ValidatorSessionOldRoundState::merge(desc, r, left->cur_round_));
    round = right->cur_round_;
  } else if (left->cur_round_->get_seqno() > right->cur_round_->get_seqno()) {
    auto r = old->at(right->cur_round_->get_seqno());
    old = CntVector::change(
        desc, old, right->cur_round_->get_seqno(), ValidatorSessionOldRoundState::merge(desc, r, right->cur_round_));
    round = left->cur_round_;
  } else {
    round = ValidatorSessionRoundState::merge(desc, left->cur_round_, right->cur_round_);
  }
  if (round->check_block_is_signed(desc)) {
    old = CntVector::push(desc, old, round->get_seqno(),
                                                                ValidatorSessionOldRoundState::create(desc, round));
    round = ValidatorSessionRoundState::create(desc, round->get_seqno() + 1);
  }
  return ValidatorSessionState::create(desc, std::move(ts), std::move(old), std::move(round));
}
const ValidatorSessionState* ValidatorSessionState::action(ValidatorSessionDescription& desc,
                                                           const ValidatorSessionState* state, td::uint32 src_idx,
                                                           td::uint32 att,
                                                           const ton_api::validatorSession_round_Message* action) {
  if (att < state->att_->at(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << action
                                    << "]: invalid message: bad ts: times goes back: " << state->att_->at(src_idx)
                                    << "->" << att;
    att = state->att_->at(src_idx);
  }
  if (att < 1024) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << action
                                    << "]: invalid message: bad ts: too small: " << att;
    att = 1024;
  }
  auto ts_vec = CntVector::change(desc, state->att_, src_idx, att);
  auto round_id = get_round_id(action);
  if (round_id > state->cur_round_->get_seqno()) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << action
                                    << "]: too big round id";
    return ValidatorSessionState::create(desc, std::move(ts_vec), state->old_rounds_, state->cur_round_);
  }
  if (round_id == state->cur_round_->get_seqno()) {
    auto round = ValidatorSessionRoundState::action(desc, state->cur_round_, src_idx, att, std::move(action));
    auto old = state->old_rounds_;
    if (round->check_block_is_signed(desc)) {
      old = CntVector::push(desc, old, round->get_seqno(),
                                                                  ValidatorSessionOldRoundState::create(desc, round));
      round = ValidatorSessionRoundState::create(desc, round->get_seqno() + 1);
    }
    return ValidatorSessionState::create(desc, std::move(ts_vec), std::move(old), std::move(round));
  } else {
    auto old = state->old_rounds_;
    old = CntVector::change(
        desc, old, round_id,
        ValidatorSessionOldRoundState::action(desc, old->at(round_id), src_idx, att, std::move(action)));
    return ValidatorSessionState::create(desc, std::move(ts_vec), std::move(old), state->cur_round_);
  }
}
tl_object_ptr ValidatorSessionState::create_action(
    ValidatorSessionDescription& desc, td::uint32 src_idx, td::uint32 att) const {
  return cur_round_->create_action(desc, src_idx, att);
}
const SentBlock* ValidatorSessionState::choose_block_to_sign(ValidatorSessionDescription& desc, td::uint32 src_idx,
                                                             bool& found) const {
  found = false;
  if (cur_round_->check_block_is_signed_by(src_idx)) {
    return nullptr;
  }
  return cur_round_->choose_block_to_sign(desc, src_idx, found);
}
const ValidatorSessionState* ValidatorSessionState::make_one(ValidatorSessionDescription& desc,
                                                             const ValidatorSessionState* state, td::uint32 src_idx,
                                                             td::uint32 att, bool& made) {
  made = false;
  if (att < state->att_->at(src_idx)) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx)
                                    << "]: time goes back, using old (bigger) value ";
    att = state->att_->at(src_idx);
  }
  if (att < 1024) {
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx)
                                    << "] bad ts: too small: " << att;
    att = 1024;
  }
  bool upd_time = false;
  auto ts_vec = state->att_;
  if (ts_vec->at(src_idx) < att) {
    upd_time = true;
    ts_vec = CntVector::change(desc, ts_vec, src_idx, att);
    VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx)
                                    << "]: updating time in make_all()";
  }
  auto round = ValidatorSessionRoundState::make_one(desc, state->cur_round_, src_idx, att, made);
  if (!made && !upd_time) {
    return state;
  }
  made = true;
  auto old = state->old_rounds_;
  if (made && round->check_block_is_signed(desc)) {
    old = CntVector::push(desc, old, round->get_seqno(),
                                                                ValidatorSessionOldRoundState::create(desc, round));
    round = ValidatorSessionRoundState::create(desc, round->get_seqno());
  }
  return ValidatorSessionState::create(desc, std::move(ts_vec), std::move(old), std::move(round));
}
const SentBlock* ValidatorSessionState::get_committed_block(ValidatorSessionDescription& desc, td::uint32 seqno) const {
  if (seqno < old_rounds_->size()) {
    return old_rounds_->at(seqno)->get_block();
  } else {
    return nullptr;
  }
}
const SentBlock* ValidatorSessionState::get_block(ValidatorSessionDescription& desc, ValidatorSessionCandidateId id,
                                                  bool& found) const {
  auto B = cur_round_->get_block(id);
  if (!B) {
    found = false;
    return nullptr;
  } else {
    found = true;
    return B->get_block();
  }
}
std::vector ValidatorSessionState::get_blocks_approved_by(ValidatorSessionDescription& desc,
                                                                            td::uint32 src_idx) const {
  return cur_round_->get_blocks_approved_by(desc, src_idx);
}
const CntVector* ValidatorSessionState::get_committed_block_signatures(
    ValidatorSessionDescription& desc, td::uint32 seqno) const {
  if (seqno < old_rounds_->size()) {
    return old_rounds_->at(seqno)->get_signatures();
  } else {
    return nullptr;
  }
}
const CntVector* ValidatorSessionState::get_committed_block_approve_signatures(
    ValidatorSessionDescription& desc, td::uint32 seqno) const {
  if (seqno < old_rounds_->size()) {
    return old_rounds_->at(seqno)->get_approve_signatures();
  } else {
    return nullptr;
  }
}
void ValidatorSessionState::dump(ValidatorSessionDescription& desc, td::StringBuilder& sb, td::uint32 att) const {
  cur_round_->dump(desc, sb, att);
}
void ValidatorSessionRoundState::dump_cur_attempt(ValidatorSessionDescription& desc, td::StringBuilder& sb) const {
  dump(desc, sb, desc.get_attempt_seqno(desc.get_ts()));
}
void ValidatorSessionRoundState::dump(ValidatorSessionDescription& desc, td::StringBuilder& sb, td::uint32 att) const {
  sb << "round_id=" << seqno_ << " total_weight=" << desc.get_total_weight()
     << " cutoff_weight=" << desc.get_cutoff_weight() << " precommitted=" << precommitted_ << "\n";
  sb << "sent blocks:>>>>\n";
  if (sent_blocks_) {
    const auto& v = sent_blocks_->data();
    auto size = sent_blocks_->size();
    for (td::uint32 i = 0; i < size; i++) {
      const auto& el = v[i];
      const auto& b = el->get_block();
      auto priority = b ? desc.get_node_priority(el->get_src_idx(), seqno_) : desc.get_max_priority() + 1;
      const auto& x = el->get_approvers_list();
      ValidatorWeight cnt = 0;
      if (x) {
        auto s = desc.get_total_nodes();
        for (td::uint32 j = 0; j < s; j++) {
          if (x->at(j)) {
            cnt += desc.get_node_weight(j);
          }
        }
      }
      if (b) {
        sb << "  block hash=" << SentBlock::get_block_id(b) << " root_hash=" << b->get_root_hash()
           << " file_hash=" << b->get_file_hash() << " approved=" << cnt << " priority=" << priority << "\n";
      } else {
        sb << "  SKIP block approved=" << cnt << "\n";
      }
    }
  }
  sb << "  first attempt: ";
  for (td::uint32 i = 0; i < desc.get_total_nodes(); i++) {
    sb << first_attempt_->at(i) << " ";
  }
  sb << "\n";
  sb << "<<<<\n";
  auto attempt = get_attempt(attempts_, att);
  if (attempt) {
    attempt->dump(desc, sb);
  }
}
}  // namespace validatorsession
}  // namespace ton