From 4dd5eea11fe8edf077eac7f9a90f2de2bd39395b Mon Sep 17 00:00:00 2001 From: ton Date: Mon, 30 Mar 2020 17:20:45 +0400 Subject: [PATCH] added support for config change proposals - added some fift scripts for the config change proposal voting - added validator-engine-console support for the config change proposal voting - additional sanity checks in catchain - unsafe slow catchain resync method --- catchain/catchain-receiver-interface.h | 3 +- catchain/catchain-receiver-source.h | 3 + catchain/catchain-receiver-source.hpp | 13 ++ catchain/catchain-receiver.cpp | 92 ++++++++- catchain/catchain-receiver.hpp | 9 +- catchain/catchain.cpp | 17 +- catchain/catchain.h | 3 +- catchain/catchain.hpp | 3 +- crypto/fift/words.cpp | 28 +++ crypto/smartcont/config-code.fc | 104 ++++++---- crypto/smartcont/config-proposal-vote-req.fif | 41 ++-- .../smartcont/config-proposal-vote-signed.fif | 69 ++++--- crypto/smartcont/create-config-proposal.fif | 12 +- .../create-config-upgrade-proposal.fif | 65 +++++++ .../create-elector-upgrade-proposal.fif | 65 +++++++ crypto/vm/boc.cpp | 24 +++ crypto/vm/boc.h | 16 ++ crypto/vm/tonops.cpp | 40 +--- doc/fiftbase.tex | 2 + test/test-catchain.cpp | 5 +- tl/generate/scheme/ton_api.tl | 2 + tl/generate/scheme/ton_api.tlo | Bin 59624 -> 59932 bytes .../validator-engine-console-query.cpp | 21 ++ .../validator-engine-console-query.h | 23 +++ .../validator-engine-console.cpp | 1 + validator-engine/validator-engine.cpp | 182 ++++++++++++++++++ validator-engine/validator-engine.hpp | 9 + validator-session/validator-session.cpp | 14 +- validator-session/validator-session.h | 3 +- validator-session/validator-session.hpp | 3 +- validator/manager.cpp | 7 +- validator/validator-group.cpp | 2 +- validator/validator-group.hpp | 7 +- validator/validator-options.hpp | 7 + validator/validator.h | 2 + 35 files changed, 753 insertions(+), 144 deletions(-) create mode 100644 crypto/smartcont/create-config-upgrade-proposal.fif create mode 100644 crypto/smartcont/create-elector-upgrade-proposal.fif diff --git a/catchain/catchain-receiver-interface.h b/catchain/catchain-receiver-interface.h index 9580c3fb..0a819f5b 100644 --- a/catchain/catchain-receiver-interface.h +++ b/catchain/catchain-receiver-interface.h @@ -60,7 +60,8 @@ class CatChainReceiverInterface : public td::actor::Actor { td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root); + CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync); virtual ~CatChainReceiverInterface() = default; }; diff --git a/catchain/catchain-receiver-source.h b/catchain/catchain-receiver-source.h index d11d7c74..85086573 100644 --- a/catchain/catchain-receiver-source.h +++ b/catchain/catchain-receiver-source.h @@ -53,6 +53,9 @@ class CatChainReceiverSource { virtual void block_received(CatChainBlockHeight height) = 0; virtual void block_delivered(CatChainBlockHeight height) = 0; + virtual bool has_unreceived() const = 0; + virtual bool has_undelivered() const = 0; + virtual td::Status validate_dep_sync(tl_object_ptr &dep) = 0; virtual void on_new_block(CatChainReceivedBlock *block) = 0; virtual void on_found_fork_proof(td::Slice fork) = 0; diff --git a/catchain/catchain-receiver-source.hpp b/catchain/catchain-receiver-source.hpp index ad6935b9..eda5a344 100644 --- a/catchain/catchain-receiver-source.hpp +++ b/catchain/catchain-receiver-source.hpp @@ -78,6 +78,19 @@ class CatChainReceiverSourceImpl : public CatChainReceiverSource { CatChainBlockHeight received_height() const override { return received_height_; } + bool has_unreceived() const override { + if (blamed()) { + return true; + } + if (!blocks_.size()) { + return false; + } + CHECK(blocks_.rbegin()->second->get_height() >= received_height_); + return blocks_.rbegin()->second->get_height() > received_height_; + } + bool has_undelivered() const override { + return delivered_height_ < received_height_; + } CatChainReceivedBlock *get_block(CatChainBlockHeight height) const override; td::Status validate_dep_sync(tl_object_ptr &dep) override; diff --git a/catchain/catchain-receiver.cpp b/catchain/catchain-receiver.cpp index 7b8a6415..95278005 100644 --- a/catchain/catchain-receiver.cpp +++ b/catchain/catchain-receiver.cpp @@ -83,6 +83,16 @@ void CatChainReceiverImpl::receive_block(adnl::AdnlNodeIdShort src, tl_object_pt return; } + if (block->src_ == static_cast(local_idx_)) { + if (!allow_unsafe_self_blocks_resync_ || started_) { + LOG(FATAL) << this << ": received unknown SELF block from " << src + << " (unsafe=" << allow_unsafe_self_blocks_resync_ << ")"; + } else { + LOG(ERROR) << this << ": received unknown SELF block from " << src << ". UPDATING LOCAL DATABASE. UNSAFE"; + initial_sync_complete_at_ = td::Timestamp::in(300.0); + } + } + auto raw_data = serialize_tl_object(block, true, payload.as_slice()); create_block(std::move(block), td::SharedSlice{payload.as_slice()}); @@ -420,14 +430,16 @@ CatChainReceiverImpl::CatChainReceiverImpl(std::unique_ptr callback, C td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root) + CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync) : callback_(std::move(callback)) , opts_(std::move(opts)) , keyring_(keyring) , adnl_(adnl) , overlay_manager_(overlay_manager) , local_id_(local_id) - , db_root_(db_root) { + , db_root_(db_root) + , allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { std::vector short_ids; local_idx_ = static_cast(ids.size()); for (auto &id : ids) { @@ -589,19 +601,20 @@ void CatChainReceiverImpl::read_db() { next_rotate_ = td::Timestamp::in(60 + td::Random::fast(0, 60)); next_sync_ = td::Timestamp::in(0.001 * td::Random::fast(0, 60)); + initial_sync_complete_at_ = td::Timestamp::in(allow_unsafe_self_blocks_resync_ ? 300.0 : 5.0); alarm_timestamp().relax(next_rotate_); alarm_timestamp().relax(next_sync_); - - callback_->start(); + alarm_timestamp().relax(initial_sync_complete_at_); } td::actor::ActorOwn CatChainReceiverInterface::create( std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, - std::vector ids, PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root) { + std::vector ids, PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync) { auto A = td::actor::create_actor("catchainreceiver", std::move(callback), std::move(opts), keyring, adnl, overlay_manager, std::move(ids), local_id, - unique_hash, db_root); + unique_hash, db_root, allow_unsafe_self_blocks_resync); return std::move(A); } @@ -906,6 +919,59 @@ void CatChainReceiverImpl::choose_neighbours() { neighbours_ = std::move(n); } +bool CatChainReceiverImpl::unsafe_start_up_check_completed() { + auto S = get_source(local_idx_); + CHECK(!S->blamed()); + if (S->has_unreceived() || S->has_undelivered()) { + LOG(INFO) << "catchain: has_unreceived=" << S->has_unreceived() << " has_undelivered=" << S->has_undelivered(); + run_scheduler(); + initial_sync_complete_at_ = td::Timestamp::in(60.0); + return false; + } + auto h = S->delivered_height(); + if (h == 0) { + CHECK(last_sent_block_->get_height() == 0); + CHECK(!unsafe_root_block_writing_); + return true; + } + if (last_sent_block_->get_height() == h) { + CHECK(!unsafe_root_block_writing_); + return true; + } + if (unsafe_root_block_writing_) { + initial_sync_complete_at_ = td::Timestamp::in(5.0); + LOG(INFO) << "catchain: writing=true"; + return false; + } + + unsafe_root_block_writing_ = true; + auto B = S->get_block(h); + CHECK(B != nullptr); + CHECK(B->delivered()); + CHECK(B->in_db()); + + auto id = B->get_hash(); + + td::BufferSlice raw_data{32}; + raw_data.as_slice().copy_from(as_slice(id)); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), block = B](td::Result R) mutable { + R.ensure(); + td::actor::send_closure(SelfId, &CatChainReceiverImpl::written_unsafe_root_block, block); + }); + + db_.set(CatChainBlockHash::zero(), std::move(raw_data), std::move(P), 0); + initial_sync_complete_at_ = td::Timestamp::in(5.0); + LOG(INFO) << "catchain: need update root"; + return false; +} + +void CatChainReceiverImpl::written_unsafe_root_block(CatChainReceivedBlock *block) { + CHECK(last_sent_block_->get_height() < block->get_height()); + last_sent_block_ = block; + unsafe_root_block_writing_ = false; +} + void CatChainReceiverImpl::alarm() { alarm_timestamp() = td::Timestamp::never(); if (next_sync_ && next_sync_.is_in_past()) { @@ -923,8 +989,22 @@ void CatChainReceiverImpl::alarm() { next_rotate_ = td::Timestamp::in(td::Random::fast(60.0, 120.0)); choose_neighbours(); } + if (!started_ && read_db_ && initial_sync_complete_at_ && initial_sync_complete_at_.is_in_past()) { + bool allow = false; + if (allow_unsafe_self_blocks_resync_) { + allow = unsafe_start_up_check_completed(); + } else { + allow = true; + } + if (allow) { + initial_sync_complete_at_ = td::Timestamp::never(); + started_ = true; + callback_->start(); + } + } alarm_timestamp().relax(next_rotate_); alarm_timestamp().relax(next_sync_); + alarm_timestamp().relax(initial_sync_complete_at_); } void CatChainReceiverImpl::send_fec_broadcast(td::BufferSlice data) { diff --git a/catchain/catchain-receiver.hpp b/catchain/catchain-receiver.hpp index a75d4cec..0015230e 100644 --- a/catchain/catchain-receiver.hpp +++ b/catchain/catchain-receiver.hpp @@ -134,6 +134,9 @@ class CatChainReceiverImpl : public CatChainReceiver { void block_written_to_db(CatChainBlockHash hash); + bool unsafe_start_up_check_completed(); + void written_unsafe_root_block(CatChainReceivedBlock *block); + void destroy() override; CatChainReceivedBlock *get_block(CatChainBlockHash hash) const; @@ -141,7 +144,7 @@ class CatChainReceiverImpl : public CatChainReceiver { CatChainReceiverImpl(std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId, std::vector ids, PublicKeyHash local_id, - CatChainBlockHash unique_hash, std::string db_root); + CatChainBlockHash unique_hash, std::string db_root, bool allow_unsafe_self_blocks_resync); private: std::unique_ptr make_callback() { @@ -222,6 +225,10 @@ class CatChainReceiverImpl : public CatChainReceiver { DbType db_; bool intentional_fork_ = false; + td::Timestamp initial_sync_complete_at_{td::Timestamp::never()}; + bool allow_unsafe_self_blocks_resync_{false}; + bool unsafe_root_block_writing_{false}; + bool started_{false}; std::list to_run_; }; diff --git a/catchain/catchain.cpp b/catchain/catchain.cpp index 56f70028..0c801deb 100644 --- a/catchain/catchain.cpp +++ b/catchain/catchain.cpp @@ -222,8 +222,9 @@ void CatChainImpl::on_receiver_started() { CatChainImpl::CatChainImpl(std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, - PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root) - : opts_(std::move(opts)), db_root_(db_root) { + PublicKeyHash local_id, CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync) + : opts_(std::move(opts)), db_root_(db_root), allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { callback_ = std::move(callback); sources_.resize(ids.size()); unique_hash_ = unique_hash; @@ -280,9 +281,9 @@ void CatChainImpl::start_up() { auto cb = std::make_unique(actor_id(this)); - receiver_ = - CatChainReceiverInterface::create(std::move(cb), opts_, args_->keyring, args_->adnl, args_->overlay_manager, - std::move(args_->ids), args_->local_id, args_->unique_hash, db_root_); + receiver_ = CatChainReceiverInterface::create(std::move(cb), opts_, args_->keyring, args_->adnl, + args_->overlay_manager, std::move(args_->ids), args_->local_id, + args_->unique_hash, db_root_, allow_unsafe_self_blocks_resync_); args_ = nullptr; //alarm_timestamp() = td::Timestamp::in(opts_.idle_timeout); } @@ -292,9 +293,11 @@ td::actor::ActorOwn CatChain::create(std::unique_ptr callbac td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root) { + CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync) { return td::actor::create_actor("catchain", std::move(callback), std::move(opts), keyring, adnl, - overlay_manager, std::move(ids), local_id, unique_hash, db_root); + overlay_manager, std::move(ids), local_id, unique_hash, db_root, + allow_unsafe_self_blocks_resync); } CatChainBlock *CatChainImpl::get_block(CatChainBlockHash hash) const { diff --git a/catchain/catchain.h b/catchain/catchain.h index 6b622913..43fc795c 100644 --- a/catchain/catchain.h +++ b/catchain/catchain.h @@ -102,7 +102,8 @@ class CatChain : public td::actor::Actor { td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, - CatChainSessionId unique_hash, std::string db_root); + CatChainSessionId unique_hash, std::string db_root, + bool allow_unsafe_self_blocks_resync); virtual ~CatChain() = default; }; diff --git a/catchain/catchain.hpp b/catchain/catchain.hpp index 176491f5..7da1a389 100644 --- a/catchain/catchain.hpp +++ b/catchain/catchain.hpp @@ -51,6 +51,7 @@ class CatChainImpl : public CatChain { bool receiver_started_ = false; std::string db_root_; + bool allow_unsafe_self_blocks_resync_; void send_process(); void send_preprocess(CatChainBlock *block); @@ -118,7 +119,7 @@ class CatChainImpl : public CatChain { CatChainImpl(std::unique_ptr callback, CatChainOptions opts, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId overlay_manager, std::vector ids, PublicKeyHash local_id, CatChainSessionId unique_hash, - std::string db_root); + std::string db_root, bool allow_unsafe_self_blocks_resync); void alarm() override; void start_up() override; diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 775e821a..e4d4b04f 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -932,6 +932,32 @@ void interpret_concat_builders(vm::Stack& stack) { stack.push_builder(std::move(cb1)); } +void interpret_cell_datasize(vm::Stack& stack, int mode) { + auto bound = (mode & 4 ? stack.pop_int() : td::make_refint(1 << 22)); + Ref cell; + Ref cs; + if (mode & 2) { + cs = stack.pop_cellslice(); + } else { + cell = stack.pop_maybe_cell(); + } + if (!bound->is_valid() || bound->sgn() < 0) { + throw IntError{"finite non-negative integer expected"}; + } + vm::VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1}; + bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell))); + if (ok) { + stack.push_smallint(stat.cells); + stack.push_smallint(stat.bits); + stack.push_smallint(stat.refs); + } else if (!(mode & 1)) { + throw IntError{"scanned too many cells"}; + } + if (mode & 1) { + stack.push_bool(ok); + } +} + void interpret_slice_bitrefs(vm::Stack& stack, int mode) { auto cs = stack.pop_cellslice(); if (mode & 1) { @@ -2755,6 +2781,8 @@ void init_words_common(Dictionary& d) { d.def_stack_word("sbits ", std::bind(interpret_slice_bitrefs, _1, 1)); d.def_stack_word("srefs ", std::bind(interpret_slice_bitrefs, _1, 2)); d.def_stack_word("sbitrefs ", std::bind(interpret_slice_bitrefs, _1, 3)); + d.def_stack_word("totalcsize ", std::bind(interpret_cell_datasize, _1, 0)); + d.def_stack_word("totalssize ", std::bind(interpret_cell_datasize, _1, 2)); // boc manipulation d.def_stack_word("B>boc ", interpret_boc_deserialize); d.def_stack_word("boc>B ", interpret_boc_serialize); diff --git a/crypto/smartcont/config-code.fc b/crypto/smartcont/config-code.fc index 612d8c93..4b38891a 100644 --- a/crypto/smartcont/config-code.fc +++ b/crypto/smartcont/config-code.fc @@ -23,18 +23,18 @@ .end_cell()); } -;; [min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price] +;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) _ parse_vote_config(cell c) inline { var cs = c.begin_parse(); throw_unless(44, cs~load_uint(8) == 0x36); - var res = [cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)]; + var res = (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); cs.end_parse(); return res; } ;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup; -_ get_vote_config(int critical?) inline_ref { - var cs = config_param(11).begin_parse(); +_ get_vote_config_internal(int critical?, cell cparam11) inline_ref { + var cs = cparam11.begin_parse(); throw_unless(44, cs~load_uint(8) == 0x91); if (critical?) { cs~load_ref(); @@ -42,6 +42,10 @@ _ get_vote_config(int critical?) inline_ref { return parse_vote_config(cs.preload_ref()); } +_ get_vote_config(int critical?) inline { + return get_vote_config_internal(critical?, config_param(11)); +} + (int, int) check_validator_set(cell vset) { var cs = vset.begin_parse(); throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only @@ -123,7 +127,7 @@ _ perform_action(cfg_dict, public_key, action, cs) inline_ref { } } -(cell, int, slice) get_current_vset() inline_ref { +(cell, int, cell) get_current_vset() inline_ref { var vset = config_param(34); var cs = begin_parse(vset); ;; validators_ext#12 utime_since:uint32 utime_until:uint32 @@ -131,13 +135,13 @@ _ perform_action(cfg_dict, public_key, action, cs) inline_ref { ;; total_weight:uint64 throw_unless(40, cs~load_uint(8) == 0x12); cs~skip_bits(32 + 32 + 16 + 16); - int total_weight = cs~load_uint(64); - return (vset, total_weight, cs); + var (total_weight, dict) = (cs~load_uint(64), cs~load_dict()); + cs.end_parse(); + return (vset, total_weight, dict); } (slice, int) get_validator_descr(int idx) inline_ref { - var (vset, total_weight, cs) = get_current_vset(); - var dict = begin_cell().store_slice(cs).end_cell(); + var (vset, total_weight, dict) = get_current_vset(); var (value, _) = dict.udict_get?(16, idx); return (value, total_weight); } @@ -227,7 +231,7 @@ _ perform_action(cfg_dict, public_key, action, cs) inline_ref { } slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref { - var [min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _] = get_vote_config(critical?); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); losses -= (weight_remaining >= 0); if (losses > max_losses) { @@ -241,8 +245,8 @@ slice update_proposal_status(slice rest, int weight_remaining, int critical?) in } return begin_cell() .store_uint(rounds_remaining, 8) - .store_uint(losses, 8) .store_uint(wins, 8) + .store_uint(losses, 8) .end_cell().begin_parse(); } @@ -261,7 +265,7 @@ builder begin_pack_proposal_status(int expires, cell proposal, int critical?, ce var (pstatus, found?) = vote_dict.udict_get?(256, phash); ifnot (found?) { ;; config proposal not found - return (vote_dict, null(), false); + return (vote_dict, null(), -1); } var (cur_vset, total_weight, _) = get_current_vset(); int cur_vset_id = cur_vset.cell_hash(); @@ -269,7 +273,7 @@ builder begin_pack_proposal_status(int expires, cell proposal, int critical?, ce if (expires <= now()) { ;; config proposal expired, delete and report not found vote_dict~udict_delete?(256, phash); - return (vote_dict, null(), false); + return (vote_dict, null(), -1); } if (vset_id != cur_vset_id) { ;; config proposal belongs to a previous validator set @@ -281,12 +285,12 @@ builder begin_pack_proposal_status(int expires, cell proposal, int critical?, ce if (rest.null?()) { ;; discard proposal (existed for too many rounds, or too many losses) vote_dict~udict_delete?(256, phash); - return (vote_dict, null(), false); + return (vote_dict, null(), -1); } var (_, found?) = voters.udict_get?(16, idx); if (found?) { ;; already voted for this proposal, ignore vote - return (vote_dict, null(), false); + return (vote_dict, null(), -2); } ;; register vote voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32)); @@ -296,16 +300,16 @@ builder begin_pack_proposal_status(int expires, cell proposal, int critical?, ce ;; not enough votes, or proposal already accepted in this round ;; simply update weight_remaining vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest)); - return (vote_dict, null(), false); + return (vote_dict, null(), 2); } ;; proposal wins in this round - var [min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _] = get_vote_config(critical?); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?); var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8)); wins += 1; if (wins >= min_wins) { ;; proposal is accepted, remove and process vote_dict~udict_delete?(256, phash); - return (vote_dict, proposal, critical?); + return (vote_dict, proposal, 6 - critical?); } ;; update proposal info vote_dict~udict_set_builder(256, phash, @@ -313,7 +317,24 @@ builder begin_pack_proposal_status(int expires, cell proposal, int critical?, ce .store_uint(rounds_remaining, 8) .store_uint(wins, 8) .store_uint(losses, 8)); - return (vote_dict, null(), false); + return (vote_dict, null(), 2); +} + +int proceed_register_vote(phash, idx, weight) impure inline_ref { + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + (vote_dict, var accepted_proposal, var status) = register_vote(vote_dict, phash, idx, weight); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + ifnot (accepted_proposal.null?()) { + var critical? = 6 - status; + (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + if (param_id) { + commit(); + (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + } + } + return status; } (slice, int) scan_proposal(int phash, slice pstatus) inline_ref { @@ -394,7 +415,7 @@ int register_voting_proposal(slice cs, int msg_value) impure inline_ref { } ;; obtain vote proposal configuration var vote_cfg = get_vote_config(critical?); - var [min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price] = vote_cfg; + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = vote_cfg; if (expire_at < min_store_sec) { return -0xc5787069; ;; expired } @@ -486,6 +507,19 @@ int register_voting_proposal(slice cs, int msg_value) impure inline_ref { } return send_answer(s_addr, query_id, ans_tag, mode); } + if (tag == 0x566f7465) { + ;; vote for a configuration proposal + var signature = in_msg~load_bits(512); + var msg_body = in_msg; + var (sign_tag, idx, phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256)); + in_msg.end_parse(); + throw_unless(37, sign_tag == 0x566f7445); + var (vdescr, total_weight) = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_data_signature(msg_body, signature, val_pubkey)); + int res = proceed_register_vote(phash, idx, weight); + return send_confirmation(s_addr, query_id, res + 0xd6745240); + } ;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query) ;; to bounce message back to sender throw_unless(37, (tag == 0) | (tag & (1 << 31))); @@ -501,33 +535,24 @@ int register_voting_proposal(slice cs, int msg_value) impure inline_ref { throw_if(35, valid_until < now()); throw_if(39, slice_depth(cs) > 128); var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + throw_unless(33, msg_seqno == stored_seqno); if (action == 0x566f7465) { + ;; vote for a configuration proposal var (idx, phash) = (cs~load_uint(16), cs~load_uint(256)); cs.end_parse(); var (vdescr, total_weight) = get_validator_descr(idx); var (val_pubkey, weight) = unpack_validator_descr(vdescr); throw_unless(34, check_data_signature(in_msg, signature, val_pubkey)); accept_message(); - stored_seqno += 1; + stored_seqno = (stored_seqno + 1) % (1 << 32); store_data(cfg_dict, stored_seqno, public_key, vote_dict); commit(); - (vote_dict, var accepted_proposal, var critical?) = register_vote(vote_dict, phash, idx, weight); - store_data(cfg_dict, stored_seqno, public_key, vote_dict); - ifnot (accepted_proposal.null?()) { - (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?); - store_data(cfg_dict, stored_seqno, public_key, vote_dict); - if (param_id) { - commit(); - (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val); - store_data(cfg_dict, stored_seqno, public_key, vote_dict); - } - } + proceed_register_vote(phash, idx, weight); return (); } - throw_unless(33, msg_seqno == stored_seqno); throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); accept_message(); - stored_seqno += 1; + stored_seqno = (stored_seqno + 1) % (1 << 32); store_data(cfg_dict, stored_seqno, public_key, vote_dict); commit(); (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs); @@ -603,3 +628,14 @@ _ list_proposals() method_id { } until (~ f); return list; } + +_ proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id { + var cfg_dict = get_data().begin_parse().preload_ref(); + var cparam11 = cfg_dict.idict_get_ref(32, 11); + var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = get_vote_config_internal(critical?, cparam11); + if (seconds < min_store_sec) { + return -1; + } + seconds = min(seconds, max_store_sec); + return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds; +} diff --git a/crypto/smartcont/config-proposal-vote-req.fif b/crypto/smartcont/config-proposal-vote-req.fif index 654a6064..d1eaff9d 100644 --- a/crypto/smartcont/config-proposal-vote-req.fif +++ b/crypto/smartcont/config-proposal-vote-req.fif @@ -3,31 +3,44 @@ "GetOpt.fif" include "validator-to-sign.req" =: savefile +false =: internal? +-1 =: seqno +-1 =: expire-at -{ ."usage: " @' $0 type ." []" cr - ."Creates an unsigned request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index in current validator set (as stored in configuration parameter 34)" cr +{ ."usage: " @' $0 type ." (-i | ) []" cr + ."Creates an unsigned request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index in current validator set (as stored in configuration parameter 34)." cr + ."If -i is selected, prepares unsigned request to be incorporated into an internal message instead." cr ."The result is saved into (" savefile type ." by default) and output in hexadecimal form, to be signed later by the validator public key" cr 1 halt } : usage -$# dup 3 < swap 4 > or ' usage if -4 :$1..n +$# dup 3 < swap 5 > or ' usage if +5 :$1..n +{ $* @ dup null? ' dup ' uncons cond $* ! } : $next -$1 parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if - dup =: expire-at - dup now <= abort"expiration time must be in the future" - 32 ufits not abort"invalid expiration time" -$2 parse-int dup =: val-idx +$1 "-i" $= { $next drop true =: internal? } if + +internal? { + $next parse-int =: seqno + $next parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if + dup =: expire-at + dup now <= abort"expiration time must be in the future" + 32 ufits not abort"invalid expiration time" +} ifnot +$# dup 2 < swap 3 > or ' usage if +$1 parse-int dup =: val-idx 16 ufits not abort"validator index out of range" -$3 parse-int dup =: prop-hash +$2 parse-int dup =: prop-hash 256 ufits not abort"invalid proposal hash" -$4 savefile replace-if-null =: savefile -0 =: seqno +$3 savefile replace-if-null =: savefile -."Creating a request expiring at " expire-at . +."Creating a request " internal? { ."with seqno " seqno . ."expiring at " expire-at . } ifnot ."to vote for configuration proposal 0x" prop-hash 64x. ."on behalf of validator with index " val-idx . cr -B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ val-idx 16 u>B B+ prop-hash 256 u>B B+ +internal? { B{566f7445} } { + B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ +} cond +val-idx 16 u>B B+ prop-hash 256 u>B B+ dup Bx. cr dup B>base64url type cr savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/config-proposal-vote-signed.fif b/crypto/smartcont/config-proposal-vote-signed.fif index 38f1eff4..5aaf1270 100644 --- a/crypto/smartcont/config-proposal-vote-signed.fif +++ b/crypto/smartcont/config-proposal-vote-signed.fif @@ -3,49 +3,70 @@ "GetOpt.fif" include "vote-query.boc" =: savefile +false =: internal? +-1 =: seqno +0 0 2=: config-addr +-1 =: expire-at -{ ."usage: " @' $0 type ." []" cr - ."Creates an external message addressed to the configuration smart contract containing a signed request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index and (Base64) public key in current validator set (as stored in configuration parameter 34)" cr +{ ."usage: " @' $0 type ." (-i | ) []" cr + ."Creates an external message addressed to the configuration smart contract and current sequence number containing a signed request expiring at unixtime to vote for configuration proposal (decimal; prefix with '0x' if needed) on behalf of validator with zero-based index and (Base64) public key in current validator set (as stored in configuration parameter 34)" cr ." must be the base64 representation of Ed25519 signature of the previously generated unsigned request by means of " cr + ."If -i is selected, instead generates the body of an internal message to be sent to the configuration smart contract from any smartcontract residing in the masterchain" cr ."The result is saved into (" savefile type ." by default), to be sent later by the lite-client" cr 1 halt } : usage -$# dup 6 < swap 7 > or ' usage if -7 :$1..n +$# dup 5 < swap 8 > or ' usage if +8 :$1..n -$1 true parse-load-address drop over 1+ abort"configuration smart contract must be in masterchain" - 2=: config-addr -$2 parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if - dup =: expire-at - dup now <= abort"expiration time must be in the future" - 32 ufits not abort"invalid expiration time" -$3 parse-int dup =: val-idx +{ $* @ dup null? ' dup ' uncons cond $* ! } : $next +$1 "-i" $= { $next drop true =: internal? "vote-msg-body.boc" =: savefile } if + +internal? { + $next true parse-load-address drop over 1+ abort"configuration smart contract must be in masterchain" + 2=: config-addr + $next parse-int =: seqno + $next parse-int dup 30 1<< < { now + 1000 + 2000 /c 2000 * } if + dup =: expire-at + dup now <= abort"expiration time must be in the future" + 32 ufits not abort"invalid expiration time" +} ifnot +$# dup 4 < swap 5 > or ' usage if +$1 parse-int dup =: val-idx 16 ufits not abort"validator index out of range" -$4 parse-int dup =: prop-hash +$2 parse-int dup =: prop-hash 256 ufits not abort"invalid proposal hash" -$5 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" +$3 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" 32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number" =: pubkey -$6 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" +$4 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" =: signature -$7 savefile replace-if-null =: savefile -0 =: seqno +$5 savefile replace-if-null =: savefile -."Creating an external message to configuration smart contract " -config-addr 2dup 6 .Addr ." = " .addr cr -."containing a signed request expiring at " expire-at . +internal? { + ."Creating the body of an internal message to be sent to the configuration smart contract" cr } +{ ."Creating an external message to configuration smart contract " + config-addr 2dup 6 .Addr ." = " .addr cr +} cond +."containing a signed request " internal? { ."expiring at " expire-at . } ifnot ."to vote for configuration proposal 0x" prop-hash 64x. -."on behalf of validator with index " val-idx . "and public key" pubkey 64x. cr +."on behalf of validator with index " val-idx . "and public key" pubkey Bx. cr -B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ val-idx 16 u>B B+ prop-hash 256 u>B B+ -dup =: to_sign +internal? { B{566f7445} } { + B{566f7465} seqno 32 u>B B+ expire-at 32 u>B B+ +} cond +val-idx 16 u>B B+ prop-hash 256 u>B B+ dup =: to_sign ."String to sign is " Bx. cr to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid" ."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr - -cr ."External message is " dup B savefile tuck B>file ."Saved to file " type cr diff --git a/crypto/smartcont/create-config-proposal.fif b/crypto/smartcont/create-config-proposal.fif index 427f2f25..f76978b6 100644 --- a/crypto/smartcont/create-config-proposal.fif +++ b/crypto/smartcont/create-config-proposal.fif @@ -41,7 +41,11 @@ boc-filename dup "null" $= { } cond =: param-value -{ 2drop true } : is-valid-config? +{ dup -1000 = { drop B dup Bx. cr + +param-value dup null? { drop } { + totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " + drop dup Blen . ."BoC data bytes)" cr +} cond + savefile tuck B>file ."(Saved to file " type .")" cr diff --git a/crypto/smartcont/create-config-upgrade-proposal.fif b/crypto/smartcont/create-config-upgrade-proposal.fif new file mode 100644 index 00000000..35caa64b --- /dev/null +++ b/crypto/smartcont/create-config-upgrade-proposal.fif @@ -0,0 +1,65 @@ +#!/usr/bin/fift -s +"Asm.fif" include +"TonUtil.fif" include +"GetOpt.fif" include + +// only the next three lines differ in elector/configuration smart contract upgrades +"configuration" =: name +"auto/config-code.fif" =: src-file +-1000 =: param-idx + +86400 30 * =: expire-in +true =: critical +-1 =: old-hash + +{ show-options-help 1 halt } : usage +{ name $+ } : +name + +begin-options + " [-s ] [-x ] [-H ] []" +cr +tab + +"Creates a new configuration proposal for upgrading the " +name +" smart contract code to ('" src-file $+ +"' by default), " + +"and saves it as an internal message body into .boc ('config-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets proposal expiration time in seconds (default " expire-in (.) $+ +")" option-help + "s" "--source" { =: src-file } short-long-option-arg + "Fift assembler source file for the new " +name +" smart contract code ('" + src-file $+ +"' by default)" option-help + "H" "--old-hash" { (hex-number) not abort"256-bit hex number expected as hash" =: old-hash } + short-long-option-arg + "Sets the required cell hash of existing parameter value (0 means no value)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 1 > ' usage if +1 :$1..n + +$1 "config-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +."Assembling new " name type ." smart contract code from " src-file type cr +src-file include +dup abort"not valid smart contract code" + =: param-value + +critical { ."Critical" } { ."Non-critical" } cond +." configuration proposal for configuration parameter " param-idx . +."will expire at " expire-at . ."(in " expire-in . ."seconds)" cr + +now 32 << param-idx + =: query-id +."Query id is " query-id . cr + +// create message body += tuck 1 i, -rot { 256 u, } { drop } cond b> ref, + critical 1 i, b> + +dup ."resulting internal message body: " B dup Bx. cr +param-value totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " +drop dup Blen . ."BoC data bytes)" cr + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/smartcont/create-elector-upgrade-proposal.fif b/crypto/smartcont/create-elector-upgrade-proposal.fif new file mode 100644 index 00000000..7eba0b60 --- /dev/null +++ b/crypto/smartcont/create-elector-upgrade-proposal.fif @@ -0,0 +1,65 @@ +#!/usr/bin/fift -s +"Asm.fif" include +"TonUtil.fif" include +"GetOpt.fif" include + +// only the next three lines differ in elector/configuration smart contract upgrades +"elector" =: name +"auto/elector-code.fif" =: src-file +-1001 =: param-idx + +86400 30 * =: expire-in +true =: critical +-1 =: old-hash + +{ show-options-help 1 halt } : usage +{ name $+ } : +name + +begin-options + " [-s ] [-x ] [-H ] []" +cr +tab + +"Creates a new configuration proposal for upgrading the " +name +" smart contract code to ('" src-file $+ +"' by default), " + +"and saves it as an internal message body into .boc ('config-msg-body.boc' by default)" + disable-digit-options generic-help-setopt + "x" "--expires-in" { parse-int =: expire-in } short-long-option-arg + "Sets proposal expiration time in seconds (default " expire-in (.) $+ +")" option-help + "s" "--source" { =: src-file } short-long-option-arg + "Fift assembler source file for the new " +name +" smart contract code ('" + src-file $+ +"' by default)" option-help + "H" "--old-hash" { (hex-number) not abort"256-bit hex number expected as hash" =: old-hash } + short-long-option-arg + "Sets the required cell hash of existing parameter value (0 means no value)" option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# 1 > ' usage if +1 :$1..n + +$1 "config-msg-body.boc" replace-if-null =: savefile +expire-in now + =: expire-at + +."Assembling new " name type ." smart contract code from " src-file type cr +src-file include +dup abort"not valid smart contract code" + =: param-value + +critical { ."Critical" } { ."Non-critical" } cond +." configuration proposal for configuration parameter " param-idx . +."will expire at " expire-at . ."(in " expire-in . ."seconds)" cr + +now 32 << param-idx + =: query-id +."Query id is " query-id . cr + +// create message body += tuck 1 i, -rot { 256 u, } { drop } cond b> ref, + critical 1 i, b> + +dup ."resulting internal message body: " B dup Bx. cr +param-value totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> " +drop dup Blen . ."BoC data bytes)" cr + +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/crypto/vm/boc.cpp b/crypto/vm/boc.cpp index 6ebab76e..5ab6f5ea 100644 --- a/crypto/vm/boc.cpp +++ b/crypto/vm/boc.cpp @@ -1131,4 +1131,28 @@ void NewCellStorageStat::dfs(Ref cell, bool need_stat, bool need_proof_sta } } +bool VmStorageStat::add_storage(Ref cell) { + if (cell.is_null() || !check_visited(cell)) { + return true; + } + if (cells >= limit) { + return false; + } + ++cells; + bool special; + auto cs = load_cell_slice_special(std::move(cell), special); + return cs.is_valid() && add_storage(std::move(cs)); +} + +bool VmStorageStat::add_storage(const CellSlice& cs) { + bits += cs.size(); + refs += cs.size_refs(); + for (unsigned i = 0; i < cs.size_refs(); i++) { + if (!add_storage(cs.prefetch_ref(i))) { + return false; + } + } + return true; +} + } // namespace vm diff --git a/crypto/vm/boc.h b/crypto/vm/boc.h index 740a6b64..cc277a8c 100644 --- a/crypto/vm/boc.h +++ b/crypto/vm/boc.h @@ -22,6 +22,7 @@ #include "td/utils/Status.h" #include "td/utils/buffer.h" #include "td/utils/HashMap.h" +#include "td/utils/HashSet.h" namespace vm { using td::Ref; @@ -127,6 +128,21 @@ struct CellStorageStat { bool add_used_storage(Ref cell, bool kill_dup = true, unsigned skip_count_root = 0); }; +struct VmStorageStat { + td::uint64 cells{0}, bits{0}, refs{0}, limit; + td::HashSet visited; + VmStorageStat(td::uint64 _limit) : limit(_limit) { + } + bool add_storage(Ref cell); + bool add_storage(const CellSlice& cs); + bool check_visited(const CellHash& cell_hash) { + return visited.insert(cell_hash).second; + } + bool check_visited(const Ref& cell) { + return check_visited(cell->get_hash()); + } +}; + struct CellSerializationInfo { bool special; Cell::LevelMask level_mask; diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 88d8fda9..6a4cceaa 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -24,6 +24,7 @@ #include "vm/excno.hpp" #include "vm/vm.h" #include "vm/dict.h" +#include "vm/boc.h" #include "Ed25519.h" #include "openssl/digest.hpp" @@ -399,45 +400,6 @@ void register_ton_crypto_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))); } -struct VmStorageStat { - td::uint64 cells{0}, bits{0}, refs{0}, limit; - td::HashSet visited; - VmStorageStat(td::uint64 _limit) : limit(_limit) { - } - bool add_storage(Ref cell); - bool add_storage(const CellSlice& cs); - bool check_visited(const CellHash& cell_hash) { - return visited.insert(cell_hash).second; - } - bool check_visited(const Ref& cell) { - return check_visited(cell->get_hash()); - } -}; - -bool VmStorageStat::add_storage(Ref cell) { - if (cell.is_null() || !check_visited(cell)) { - return true; - } - if (cells >= limit) { - return false; - } - ++cells; - bool special; - auto cs = load_cell_slice_special(std::move(cell), special); - return cs.is_valid() && add_storage(std::move(cs)); -} - -bool VmStorageStat::add_storage(const CellSlice& cs) { - bits += cs.size(); - refs += cs.size_refs(); - for (unsigned i = 0; i < cs.size_refs(); i++) { - if (!add_storage(cs.prefetch_ref(i))) { - return false; - } - } - return true; -} - int exec_compute_data_size(VmState* st, int mode) { VM_LOG(st) << (mode & 2 ? 'S' : 'C') << "DATASIZE" << (mode & 1 ? "Q" : ""); Stack& stack = st->get_stack(); diff --git a/doc/fiftbase.tex b/doc/fiftbase.tex index e20c2490..c6131b2e 100644 --- a/doc/fiftbase.tex +++ b/doc/fiftbase.tex @@ -2156,6 +2156,8 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt ten} ( -- $10$), pushes {\em Integer\/} constant 10. \item {\tt third} ($t$ -- $x$), returns the third component of a {\em Tuple}, cf.~\ptref{p:tuples}. Equivalent to {\tt 2 []}. \item {\tt times} ($e$ $n$ -- ), executes execution token ({\em WordDef\/}) $e$ exactly $n$ times, if $n\geq0$, cf.~\ptref{p:simple.loops}. If $n$ is negative, throws an exception. +\item {\tt totalcsize} ($c$ -- $x$ $y$ $z$), recursively computes the total number of unique cells $x$, data bits $y$ and cell references $z$ in the tree of cells rooted in {\em Cell\/}~$c$. +\item {\tt totalssize} ($s$ -- $x$ $y$ $z$), recursively computes the total number of unique cells $x$, data bits $y$ and cell references $z$ in the tree of cells rooted in {\em Slice\/}~$s$. \item {\tt triple} ($x$ $y$ $z$ -- $t$), creates new triple $t=(x,y,z)$, cf.~\ptref{p:tuples}. Equivalent to {\tt 3 tuple}. \item {\tt true} ( -- $-1$), pushes $-1$ into the stack, cf.~\ptref{p:bool}. Equivalent to {\tt -1}. \item {\tt tuck} ($x$ $y$ -- $y$ $x$ $y$), equivalent to {\tt swap over}, cf.~\ptref{p:stack.ops}. diff --git a/test/test-catchain.cpp b/test/test-catchain.cpp index 1131a64a..2281cc48 100644 --- a/test/test-catchain.cpp +++ b/test/test-catchain.cpp @@ -129,8 +129,9 @@ class CatChainInst : public td::actor::Actor { for (auto &n : nodes_) { nodes.push_back(ton::catchain::CatChainNode{n.adnl_id, n.id_full}); } - catchain_ = ton::catchain::CatChain::create(make_callback(), opts, keyring_, adnl_, overlay_manager_, - std::move(nodes), nodes_[idx_].id, unique_hash_, std::string("")); + catchain_ = + ton::catchain::CatChain::create(make_callback(), opts, keyring_, adnl_, overlay_manager_, std::move(nodes), + nodes_[idx_].id, unique_hash_, std::string(""), false); } CatChainInst(td::actor::ActorId keyring, td::actor::ActorId adnl, diff --git a/tl/generate/scheme/ton_api.tl b/tl/generate/scheme/ton_api.tl index 329c134b..3886e962 100644 --- a/tl/generate/scheme/ton_api.tl +++ b/tl/generate/scheme/ton_api.tl @@ -598,6 +598,7 @@ engine.validator.success = engine.validator.Success; engine.validator.jsonConfig data:string = engine.validator.JsonConfig; engine.validator.electionBid election_date:int perm_key:int256 adnl_addr:int256 to_send_payload:bytes = engine.validator.ElectionBid; +engine.validator.proposalVote perm_key:int256 to_send:bytes = engine.validator.ProposalVote; engine.validator.dhtServerStatus id:int256 status:int = engine.validator.DhtServerStatus; engine.validator.dhtServersStatus servers:(vector engine.validator.dhtServerStatus) = engine.validator.DhtServersStatus; @@ -638,6 +639,7 @@ engine.validator.getConfig = engine.validator.JsonConfig; engine.validator.setVerbosity verbosity:int = engine.validator.Success; engine.validator.createElectionBid election_date:int election_addr:string wallet:string = engine.validator.ElectionBid; +engine.validator.createProposalVote vote:bytes = engine.validator.ProposalVote; engine.validator.checkDhtServers id:int256 = engine.validator.DhtServersStatus; diff --git a/tl/generate/scheme/ton_api.tlo b/tl/generate/scheme/ton_api.tlo index 57e7ef67d85a74de0e55bfef506357f4fc9d5e6a..77ebc34f32dddb73a593c9ce0229680e48f41339 100644 GIT binary patch delta 212 zcmaEHk$KJ)X5L4$^{p77V9!S0dQru()); + TRY_RESULT_ASSIGN(fname_, tokenizer_.get_token()); + TRY_STATUS(tokenizer_.check_endl()); + return td::Status::OK(); +} + +td::Status CreateProposalVoteQuery::send() { + auto b = ton::create_serialize_tl_object(td::BufferSlice(data_)); + td::actor::send_closure(console_, &ValidatorEngineConsole::envelope_send_query, std::move(b), create_promise()); + return td::Status::OK(); +} + +td::Status CreateProposalVoteQuery::receive(td::BufferSlice data) { + TRY_RESULT_PREFIX(f, ton::fetch_tl_object(data.as_slice(), true), + "received incorrect answer: "); + td::TerminalIO::out() << "success: permkey=" << f->perm_key_.to_hex() << "\n"; + TRY_STATUS(td::write_file(fname_, f->to_send_.as_slice())); + return td::Status::OK(); +} + td::Status CheckDhtServersQuery::run() { TRY_RESULT_ASSIGN(id_, tokenizer_.get_token()); return td::Status::OK(); diff --git a/validator-engine-console/validator-engine-console-query.h b/validator-engine-console/validator-engine-console-query.h index aacff4d5..bd9f88f0 100644 --- a/validator-engine-console/validator-engine-console-query.h +++ b/validator-engine-console/validator-engine-console-query.h @@ -835,6 +835,29 @@ class CreateElectionBidQuery : public Query { std::string fname_; }; +class CreateProposalVoteQuery : public Query { + public: + CreateProposalVoteQuery(td::actor::ActorId console, Tokenizer tokenizer) + : Query(console, std::move(tokenizer)) { + } + td::Status run() override; + td::Status send() override; + td::Status receive(td::BufferSlice data) override; + static std::string get_name() { + return "createproposalvote"; + } + static std::string get_help() { + return "createproposalvote \tcreate proposal vote"; + } + std::string name() const override { + return get_name(); + } + + private: + std::string data_; + std::string fname_; +}; + class CheckDhtServersQuery : public Query { public: CheckDhtServersQuery(td::actor::ActorId console, Tokenizer tokenizer) diff --git a/validator-engine-console/validator-engine-console.cpp b/validator-engine-console/validator-engine-console.cpp index 6887c375..4cd0427f 100644 --- a/validator-engine-console/validator-engine-console.cpp +++ b/validator-engine-console/validator-engine-console.cpp @@ -130,6 +130,7 @@ void ValidatorEngineConsole::run() { add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); + add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); } diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp index 4ac1e45a..e5186ea9 100644 --- a/validator-engine/validator-engine.cpp +++ b/validator-engine/validator-engine.cpp @@ -841,6 +841,131 @@ class ValidatorElectionBidCreator : public td::actor::Actor { td::BufferSlice result_; }; +class ValidatorProposalVoteCreator : public td::actor::Actor { + public: + ValidatorProposalVoteCreator(td::BufferSlice proposal, std::string dir, td::actor::ActorId engine, + td::actor::ActorId keyring, td::Promise promise) + : proposal_(std::move(proposal)) + , dir_(std::move(dir)) + , engine_(engine) + , keyring_(keyring) + , promise_(std::move(promise)) { + } + + void start_up() override { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result> R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &ValidatorProposalVoteCreator::abort_query, + R.move_as_error_prefix("failed to find self permanent key: ")); + } else { + auto v = R.move_as_ok(); + td::actor::send_closure(SelfId, &ValidatorProposalVoteCreator::got_id, v.first, v.second); + } + }); + td::actor::send_closure(engine_, &ValidatorEngine::get_current_validator_perm_key, std::move(P)); + } + + void got_id(ton::PublicKey pubkey, size_t idx) { + pubkey_ = std::move(pubkey); + idx_ = idx; + auto codeR = td::read_file_str(dir_ + "/config-proposal-vote-req.fif"); + if (codeR.is_error()) { + abort_query(codeR.move_as_error_prefix("fif not found (validator-elect-req.fif)")); + return; + } + auto data = proposal_.as_slice().str(); + auto R = fift::mem_run_fift(codeR.move_as_ok(), {"config-proposal-vote-req.fif", "-i", td::to_string(idx_), data}, + dir_ + "/"); + if (R.is_error()) { + abort_query(R.move_as_error_prefix("fift fail (cofig-proposal-vote-req.fif)")); + return; + } + auto res = R.move_as_ok(); + auto to_signR = res.source_lookup.read_file("validator-to-sign.req"); + if (to_signR.is_error()) { + abort_query(td::Status::Error(PSTRING() << "strange error: no to sign file. Output: " << res.output)); + return; + } + auto to_sign = td::BufferSlice{to_signR.move_as_ok().data}; + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &ValidatorProposalVoteCreator::abort_query, + R.move_as_error_prefix("sign fail: ")); + } else { + td::actor::send_closure(SelfId, &ValidatorProposalVoteCreator::signed_vote, R.move_as_ok()); + } + }); + + td::actor::send_closure(keyring_, &ton::keyring::Keyring::sign_message, pubkey_.compute_short_id(), + std::move(to_sign), std::move(P)); + } + + void signed_vote(td::BufferSlice signature) { + signature_ = std::move(signature); + + auto codeR = td::read_file_str(dir_ + "/config-proposal-vote-signed.fif"); + if (codeR.is_error()) { + abort_query(codeR.move_as_error_prefix("fif not found (config-proposal-vote-signed.fif)")); + return; + } + + auto key = td::base64_encode(pubkey_.export_as_slice().as_slice()); + auto sig = td::base64_encode(signature_.as_slice()); + + auto data = proposal_.as_slice().str(); + auto R = fift::mem_run_fift( + codeR.move_as_ok(), {"config-proposal-vote-signed.fif", "-i", td::to_string(idx_), data, key, sig}, dir_ + "/"); + if (R.is_error()) { + abort_query(R.move_as_error_prefix("fift fail (config-proposal-vote-signed.fif)")); + return; + } + + auto res = R.move_as_ok(); + auto dataR = res.source_lookup.read_file("vote-msg-body.boc"); + if (dataR.is_error()) { + abort_query(td::Status::Error("strage error: no result boc")); + return; + } + + result_ = td::BufferSlice(dataR.move_as_ok().data); + finish_query(); + } + + void abort_query(td::Status error) { + promise_.set_value(ValidatorEngine::create_control_query_error(std::move(error))); + stop(); + } + void finish_query() { + promise_.set_value(ton::create_serialize_tl_object( + pubkey_.compute_short_id().bits256_value(), std::move(result_))); + stop(); + } + + private: + td::BufferSlice proposal_; + std::string dir_; + + ton::PublicKey pubkey_; + size_t idx_; + + td::BufferSlice signature_; + td::BufferSlice result_; + td::actor::ActorId engine_; + td::actor::ActorId keyring_; + + td::Promise promise_; + + td::uint32 ttl_; + AdnlCategory cat_ = 2; + double frac = 2.7; + + ton::PublicKeyHash perm_key_; + ton::PublicKey perm_key_full_; + ton::adnl::AdnlNodeIdShort adnl_addr_; + ton::adnl::AdnlNodeIdFull adnl_key_full_; +}; + class CheckDhtServerStatusQuery : public td::actor::Actor { public: void start_up() override { @@ -1064,6 +1189,9 @@ td::Status ValidatorEngine::load_global_config() { if (db_depth_ <= 32) { validator_options_.write().set_filedb_depth(db_depth_); } + for (auto seq : unsafe_catchains_) { + validator_options_.write().add_unsafe_resync_catchain(seq); + } std::vector h; for (auto &x : conf.validator_->hardforks_) { @@ -2822,6 +2950,32 @@ void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_checkDhtS .release(); } +void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_createProposalVote &query, td::BufferSlice data, + ton::PublicKeyHash src, td::uint32 perm, td::Promise promise) { + if (!(perm & ValidatorEnginePermissions::vep_modify)) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "not authorized"))); + return; + } + if (keyring_.empty()) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "keyring not started"))); + return; + } + + if (!started_) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "not started"))); + return; + } + + if (fift_dir_.empty()) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "no fift dir"))); + return; + } + + td::actor::create_actor("votecreate", std::move(query.vote_), fift_dir_, actor_id(this), + keyring_.get(), std::move(promise)) + .release(); +} + void ValidatorEngine::process_control_query(td::uint16 port, ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, td::Promise promise) { @@ -2885,6 +3039,29 @@ void ValidatorEngine::run() { load_config(std::move(P)); } +void ValidatorEngine::get_current_validator_perm_key(td::Promise> promise) { + if (state_.is_null()) { + promise.set_error(td::Status::Error(ton::ErrorCode::notready, "not started")); + return; + } + + auto val_set = state_->get_total_validator_set(0); + CHECK(val_set.not_null()); + auto vec = val_set->export_vector(); + for (size_t idx = 0; idx < vec.size(); idx++) { + auto &el = vec[idx]; + ton::PublicKey pub{ton::pubkeys::Ed25519{el.key.as_bits256()}}; + auto pubkey_hash = pub.compute_short_id(); + + auto it = config_.validators.find(pubkey_hash); + if (it != config_.validators.end()) { + promise.set_value(std::make_pair(pub, idx)); + return; + } + } + promise.set_error(td::Status::Error(ton::ErrorCode::notready, "not a validator")); +} + std::atomic need_stats_flag{false}; void need_stats(int sig) { need_stats_flag.store(true); @@ -3029,6 +3206,11 @@ int main(int argc, char *argv[]) { acts.push_back([&x, v]() { td::actor::send_closure(x, &ValidatorEngine::set_sync_ttl, v); }); return td::Status::OK(); }); + p.add_option('U', "unsafe-catchain-restore", "use SLOW and DANGEROUS catchain recover method", [&](td::Slice id) { + TRY_RESULT(seq, td::to_integer_safe(id)); + acts.push_back([&x, seq]() { td::actor::send_closure(x, &ValidatorEngine::add_unsafe_catchain, seq); }); + return td::Status::OK(); + }); td::uint32 threads = 7; p.add_option('t', "threads", PSTRING() << "number of threads (default=" << threads << ")", [&](td::Slice fname) { td::int32 v; diff --git a/validator-engine/validator-engine.hpp b/validator-engine/validator-engine.hpp index 27e45317..eac7b499 100644 --- a/validator-engine/validator-engine.hpp +++ b/validator-engine/validator-engine.hpp @@ -200,11 +200,16 @@ class ValidatorEngine : public td::actor::Actor { bool started_keyring_ = false; bool started_ = false; + std::set unsafe_catchains_; + public: static constexpr td::uint8 max_cat() { return 250; } + void add_unsafe_catchain(ton::CatchainSeqno seq) { + unsafe_catchains_.insert(seq); + } void set_local_config(std::string str); void set_global_config(std::string str); void set_fift_dir(std::string str) { @@ -286,6 +291,8 @@ class ValidatorEngine : public td::actor::Actor { void alarm() override; void run(); + void get_current_validator_perm_key(td::Promise> promise); + void try_add_adnl_node(ton::PublicKeyHash pub, AdnlCategory cat, td::Promise promise); void try_add_dht_node(ton::PublicKeyHash pub, td::Promise promise); void try_add_validator_permanent_key(ton::PublicKeyHash key_hash, td::uint32 election_date, td::uint32 ttl, @@ -376,6 +383,8 @@ class ValidatorEngine : public td::actor::Actor { ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); void run_control_query(ton::ton_api::engine_validator_checkDhtServers &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); + void run_control_query(ton::ton_api::engine_validator_createProposalVote &query, td::BufferSlice data, + ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); template void run_control_query(T &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise) { diff --git a/validator-session/validator-session.cpp b/validator-session/validator-session.cpp index 5d5f7079..9b8f9832 100644 --- a/validator-session/validator-session.cpp +++ b/validator-session/validator-session.cpp @@ -814,14 +814,16 @@ ValidatorSessionImpl::ValidatorSessionImpl(catchain::CatChainSessionId session_i std::unique_ptr callback, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, - td::actor::ActorId overlays, std::string db_root) + td::actor::ActorId overlays, std::string db_root, + bool allow_unsafe_self_blocks_resync) : unique_hash_(session_id) , callback_(std::move(callback)) , db_root_(db_root) , keyring_(keyring) , adnl_(adnl) , rldp_(rldp) - , overlay_manager_(overlays) { + , overlay_manager_(overlays) + , allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { description_ = ValidatorSessionDescription::create(std::move(opts), nodes, local_id); } @@ -834,7 +836,8 @@ void ValidatorSessionImpl::start() { catchain_ = catchain::CatChain::create( make_catchain_callback(), catchain::CatChainOptions{description().opts().catchain_idle_timeout, description().opts().catchain_max_deps}, - keyring_, adnl_, overlay_manager_, std::move(w), local_id(), unique_hash_, db_root_); + keyring_, adnl_, overlay_manager_, std::move(w), local_id(), unique_hash_, db_root_, + allow_unsafe_self_blocks_resync_); check_all(); } @@ -864,10 +867,11 @@ td::actor::ActorOwn ValidatorSession::create( catchain::CatChainSessionId session_id, ValidatorSessionOptions opts, PublicKeyHash local_id, std::vector nodes, std::unique_ptr callback, td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId overlays, std::string db_root) { + td::actor::ActorId rldp, td::actor::ActorId overlays, std::string db_root, + bool allow_unsafe_self_blocks_resync) { return td::actor::create_actor("session", session_id, std::move(opts), local_id, std::move(nodes), std::move(callback), keyring, adnl, rldp, - overlays, db_root); + overlays, db_root, allow_unsafe_self_blocks_resync); } td::Bits256 ValidatorSessionOptions::get_hash() const { diff --git a/validator-session/validator-session.h b/validator-session/validator-session.h index 31de2e2d..4eecbbb1 100644 --- a/validator-session/validator-session.h +++ b/validator-session/validator-session.h @@ -95,7 +95,8 @@ class ValidatorSession : public td::actor::Actor { catchain::CatChainSessionId session_id, ValidatorSessionOptions opts, PublicKeyHash local_id, std::vector nodes, std::unique_ptr callback, td::actor::ActorId keyring, td::actor::ActorId adnl, - td::actor::ActorId rldp, td::actor::ActorId overlays, std::string db_root); + td::actor::ActorId rldp, td::actor::ActorId overlays, std::string db_root, + bool allow_unsafe_self_blocks_resync); virtual ~ValidatorSession() = default; }; diff --git a/validator-session/validator-session.hpp b/validator-session/validator-session.hpp index b1f8042b..911a08aa 100644 --- a/validator-session/validator-session.hpp +++ b/validator-session/validator-session.hpp @@ -150,13 +150,14 @@ class ValidatorSessionImpl : public ValidatorSession { bool started_ = false; bool catchain_started_ = false; + bool allow_unsafe_self_blocks_resync_; public: ValidatorSessionImpl(catchain::CatChainSessionId session_id, ValidatorSessionOptions opts, PublicKeyHash local_id, std::vector nodes, std::unique_ptr callback, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, td::actor::ActorId overlays, - std::string db_root); + std::string db_root, bool allow_unsafe_self_blocks_resync); void start_up() override; void alarm() override; diff --git a/validator/manager.cpp b/validator/manager.cpp index f4829b97..cb0c897e 100644 --- a/validator/manager.cpp +++ b/validator/manager.cpp @@ -1868,9 +1868,10 @@ td::actor::ActorOwn ValidatorManagerImpl::create_validator_group } else { auto validator_id = get_validator(shard, validator_set); CHECK(!validator_id.is_zero()); - auto G = td::actor::create_actor("validatorgroup", shard, validator_id, session_id, validator_set, - opts, keyring_, adnl_, rldp_, overlays_, db_root_, actor_id(this), - init_session); + auto G = td::actor::create_actor( + "validatorgroup", shard, validator_id, session_id, validator_set, opts, keyring_, adnl_, rldp_, overlays_, + db_root_, actor_id(this), init_session, + opts_->check_unsafe_resync_allowed(validator_set->get_catchain_seqno())); return G; } } diff --git a/validator/validator-group.cpp b/validator/validator-group.cpp index ca5fa065..1fc201fd 100644 --- a/validator/validator-group.cpp +++ b/validator/validator-group.cpp @@ -242,7 +242,7 @@ void ValidatorGroup::create_session() { session_ = validatorsession::ValidatorSession::create(session_id_, config_, local_id_, std::move(vec), make_validator_session_callback(), keyring_, adnl_, rldp_, - overlays_, db_root_); + overlays_, db_root_, allow_unsafe_self_blocks_resync_); if (prev_block_ids_.size() > 0) { td::actor::send_closure(session_, &validatorsession::ValidatorSession::start); } diff --git a/validator/validator-group.hpp b/validator/validator-group.hpp index c2f30801..e50af9ae 100644 --- a/validator/validator-group.hpp +++ b/validator/validator-group.hpp @@ -59,7 +59,8 @@ class ValidatorGroup : public td::actor::Actor { td::Ref validator_set, validatorsession::ValidatorSessionOptions config, td::actor::ActorId keyring, td::actor::ActorId adnl, td::actor::ActorId rldp, td::actor::ActorId overlays, - std::string db_root, td::actor::ActorId validator_manager, bool create_session) + std::string db_root, td::actor::ActorId validator_manager, bool create_session, + bool allow_unsafe_self_blocks_resync) : shard_(shard) , local_id_(std::move(local_id)) , session_id_(session_id) @@ -71,7 +72,8 @@ class ValidatorGroup : public td::actor::Actor { , overlays_(overlays) , db_root_(std::move(db_root)) , manager_(validator_manager) - , init_(create_session) { + , init_(create_session) + , allow_unsafe_self_blocks_resync_(allow_unsafe_self_blocks_resync) { } private: @@ -98,6 +100,7 @@ class ValidatorGroup : public td::actor::Actor { td::actor::ActorOwn session_; bool init_ = false; + bool allow_unsafe_self_blocks_resync_; td::uint32 last_known_round_id_ = 0; }; diff --git a/validator/validator-options.hpp b/validator/validator-options.hpp index 315027e9..9bdc61c0 100644 --- a/validator/validator-options.hpp +++ b/validator/validator-options.hpp @@ -91,6 +91,9 @@ struct ValidatorManagerOptionsImpl : public ValidatorManagerOptions { td::uint32 get_filedb_depth() const override { return db_depth_; } + bool check_unsafe_resync_allowed(CatchainSeqno seqno) const override { + return unsafe_catchains_.count(seqno) > 0; + } void set_zero_block_id(BlockIdExt block_id) override { zero_block_id_ = block_id; @@ -129,6 +132,9 @@ struct ValidatorManagerOptionsImpl : public ValidatorManagerOptions { CHECK(value <= 32); db_depth_ = value; } + void add_unsafe_resync_catchain(CatchainSeqno seqno) override { + unsafe_catchains_.insert(seqno); + } ValidatorManagerOptionsImpl *make_copy() const override { return new ValidatorManagerOptionsImpl(*this); @@ -164,6 +170,7 @@ struct ValidatorManagerOptionsImpl : public ValidatorManagerOptions { bool initial_sync_disabled_; std::vector hardforks_; td::uint32 db_depth_ = 2; + std::set unsafe_catchains_; }; } // namespace validator diff --git a/validator/validator.h b/validator/validator.h index f9f37611..347efb29 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -68,6 +68,7 @@ struct ValidatorManagerOptions : public td::CntObject { virtual td::uint32 key_block_utime_step() const { return 86400; } + virtual bool check_unsafe_resync_allowed(CatchainSeqno seqno) const = 0; virtual void set_zero_block_id(BlockIdExt block_id) = 0; virtual void set_init_block_id(BlockIdExt block_id) = 0; @@ -81,6 +82,7 @@ struct ValidatorManagerOptions : public td::CntObject { virtual void set_initial_sync_disabled(bool value) = 0; virtual void set_hardforks(std::vector hardforks) = 0; virtual void set_filedb_depth(td::uint32 value) = 0; + virtual void add_unsafe_resync_catchain(CatchainSeqno seqno) = 0; static td::Ref create( BlockIdExt zero_block_id, BlockIdExt init_block_id,