mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	All changes from PR "FunC v0.5.0": https://github.com/ton-blockchain/ton/pull/1026 Instead of developing FunC, we decided to fork it. BTW, the first Tolk release will be v0.6, a metaphor of FunC v0.5 that missed a chance to occur.
		
			
				
	
	
		
			4668 lines
		
	
	
	
		
			201 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4668 lines
		
	
	
	
		
			201 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|     This file is part of TON Blockchain source code.
 | |
| 
 | |
|     TON Blockchain is free software; you can redistribute it and/or
 | |
|     modify it under the terms of the GNU 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 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 General Public License for more details.
 | |
| 
 | |
|     You should have received a copy of the GNU General Public License
 | |
|     along with TON Blockchain.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
|     In addition, as a special exception, the copyright holders give permission
 | |
|     to link the code of portions of this program with the OpenSSL library.
 | |
|     You must obey the GNU General Public License in all respects for all
 | |
|     of the code used other than OpenSSL. If you modify file(s) with this
 | |
|     exception, you may extend this exception to your version of the file(s),
 | |
|     but you are not obligated to do so. If you do not wish to do so, delete this
 | |
|     exception statement from your version. If you delete this exception statement
 | |
|     from all source files in the program, then also delete it here.
 | |
| 
 | |
|     Copyright 2017-2020 Telegram Systems LLP
 | |
| */
 | |
| #include "lite-client.h"
 | |
| 
 | |
| #include "lite-client-common.h"
 | |
| 
 | |
| #include "adnl/adnl-ext-client.h"
 | |
| #include "tl-utils/lite-utils.hpp"
 | |
| #include "auto/tl/ton_api_json.h"
 | |
| #include "auto/tl/lite_api.hpp"
 | |
| #include "td/utils/OptionParser.h"
 | |
| #include "td/utils/Time.h"
 | |
| #include "td/utils/filesystem.h"
 | |
| #include "td/utils/format.h"
 | |
| #include "td/utils/Random.h"
 | |
| #include "td/utils/crypto.h"
 | |
| #include "td/utils/overloaded.h"
 | |
| #include "td/utils/port/signals.h"
 | |
| #include "td/utils/port/stacktrace.h"
 | |
| #include "td/utils/port/StdStreams.h"
 | |
| #include "td/utils/port/FileFd.h"
 | |
| #include "terminal/terminal.h"
 | |
| #include "ton/lite-tl.hpp"
 | |
| #include "block/block-db.h"
 | |
| #include "block/block.h"
 | |
| #include "block/block-parse.h"
 | |
| #include "block/block-auto.h"
 | |
| #include "block/mc-config.h"
 | |
| #include "block/check-proof.h"
 | |
| #include "vm/boc.h"
 | |
| #include "vm/cellops.h"
 | |
| #include "vm/cells/MerkleProof.h"
 | |
| #include "vm/vm.h"
 | |
| #include "vm/cp0.h"
 | |
| #include "vm/memo.h"
 | |
| #include "ton/ton-shard.h"
 | |
| #include "openssl/rand.hpp"
 | |
| #include "crypto/vm/utils.h"
 | |
| #include "crypto/common/util.h"
 | |
| #include "common/checksum.h"
 | |
| 
 | |
| #if TD_DARWIN || TD_LINUX
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #endif
 | |
| #include <iostream>
 | |
| #include <sstream>
 | |
| #include "git.h"
 | |
| 
 | |
| using namespace std::literals::string_literals;
 | |
| using td::Ref;
 | |
| 
 | |
| int verbosity;
 | |
| 
 | |
| std::unique_ptr<ton::adnl::AdnlExtClient::Callback> TestNode::make_callback() {
 | |
|   class Callback : public ton::adnl::AdnlExtClient::Callback {
 | |
|    public:
 | |
|     void on_ready() override {
 | |
|       td::actor::send_closure(id_, &TestNode::conn_ready);
 | |
|     }
 | |
|     void on_stop_ready() override {
 | |
|       td::actor::send_closure(id_, &TestNode::conn_closed);
 | |
|     }
 | |
|     Callback(td::actor::ActorId<TestNode> id) : id_(std::move(id)) {
 | |
|     }
 | |
| 
 | |
|    private:
 | |
|     td::actor::ActorId<TestNode> id_;
 | |
|   };
 | |
|   return std::make_unique<Callback>(actor_id(this));
 | |
| }
 | |
| 
 | |
| void TestNode::run() {
 | |
|   class Cb : public td::TerminalIO::Callback {
 | |
|    public:
 | |
|     void line_cb(td::BufferSlice line) override {
 | |
|       td::actor::send_closure(id_, &TestNode::parse_line, std::move(line));
 | |
|     }
 | |
|     Cb(td::actor::ActorId<TestNode> id) : id_(id) {
 | |
|     }
 | |
| 
 | |
|    private:
 | |
|     td::actor::ActorId<TestNode> id_;
 | |
|   };
 | |
|   io_ = td::TerminalIO::create("> ", readline_enabled_, ex_mode_, std::make_unique<Cb>(actor_id(this)));
 | |
|   td::actor::send_closure(io_, &td::TerminalIO::set_log_interface);
 | |
| 
 | |
|   if (remote_public_key_.empty()) {
 | |
|     auto G = td::read_file(global_config_).move_as_ok();
 | |
|     auto gc_j = td::json_decode(G.as_slice()).move_as_ok();
 | |
|     ton::ton_api::liteclient_config_global gc;
 | |
|     ton::ton_api::from_json(gc, gc_j.get_object()).ensure();
 | |
|     CHECK(gc.liteservers_.size() > 0);
 | |
|     auto idx = liteserver_idx_ >= 0 ? liteserver_idx_
 | |
|                                     : td::Random::fast(0, static_cast<td::uint32>(gc.liteservers_.size() - 1));
 | |
|     CHECK(idx >= 0 && static_cast<td::uint32>(idx) <= gc.liteservers_.size());
 | |
|     auto& cli = gc.liteservers_[idx];
 | |
|     remote_addr_.init_host_port(td::IPAddress::ipv4_to_str(cli->ip_), cli->port_).ensure();
 | |
|     remote_public_key_ = ton::PublicKey{cli->id_};
 | |
|     td::TerminalIO::out() << "using liteserver " << idx << " with addr " << remote_addr_ << "\n";
 | |
|     if (gc.validator_ && gc.validator_->zero_state_) {
 | |
|       zstate_id_.workchain = gc.validator_->zero_state_->workchain_;
 | |
|       if (zstate_id_.workchain != ton::workchainInvalid) {
 | |
|         zstate_id_.root_hash = gc.validator_->zero_state_->root_hash_;
 | |
|         zstate_id_.file_hash = gc.validator_->zero_state_->file_hash_;
 | |
|         td::TerminalIO::out() << "zerostate set to " << zstate_id_.to_str() << "\n";
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   client_ =
 | |
|       ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull{remote_public_key_}, remote_addr_, make_callback());
 | |
| }
 | |
| 
 | |
| void TestNode::got_result(td::Result<td::BufferSlice> R, td::Promise<td::BufferSlice> promise) {
 | |
|   if (R.is_error()) {
 | |
|     auto err = R.move_as_error();
 | |
|     LOG(ERROR) << "failed query: " << err;
 | |
|     promise.set_error(std::move(err));
 | |
|     td::actor::send_closure_later(actor_id(this), &TestNode::after_got_result, false);
 | |
|     return;
 | |
|   }
 | |
|   auto data = R.move_as_ok();
 | |
|   auto F = ton::fetch_tl_object<ton::lite_api::liteServer_error>(data.clone(), true);
 | |
|   if (F.is_ok()) {
 | |
|     auto f = F.move_as_ok();
 | |
|     auto err = td::Status::Error(f->code_, f->message_);
 | |
|     LOG(ERROR) << "liteserver error: " << err;
 | |
|     promise.set_error(std::move(err));
 | |
|     td::actor::send_closure_later(actor_id(this), &TestNode::after_got_result, false);
 | |
|     return;
 | |
|   }
 | |
|   promise.set_result(std::move(data));
 | |
|   td::actor::send_closure_later(actor_id(this), &TestNode::after_got_result, true);
 | |
| }
 | |
| 
 | |
| void TestNode::after_got_result(bool ok) {
 | |
|   running_queries_--;
 | |
|   if (ex_mode_ && !ok) {
 | |
|     LOG(ERROR) << "fatal error executing command-line queries, skipping the rest";
 | |
|     std::cout.flush();
 | |
|     std::cerr.flush();
 | |
|     std::_Exit(1);
 | |
|   }
 | |
|   if (!running_queries_ && ex_queries_.size() > 0) {
 | |
|     auto data = std::move(ex_queries_[0]);
 | |
|     ex_queries_.erase(ex_queries_.begin());
 | |
|     parse_line(std::move(data));
 | |
|   }
 | |
|   if (ex_mode_ && !running_queries_ && ex_queries_.size() == 0) {
 | |
|     std::cout.flush();
 | |
|     std::cerr.flush();
 | |
|     std::_Exit(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::envelope_send_query(td::BufferSlice query, td::Promise<td::BufferSlice> promise) {
 | |
|   running_queries_++;
 | |
|   if (!ready_ || client_.empty()) {
 | |
|     got_result(td::Status::Error("failed to send query to server: not ready"), std::move(promise));
 | |
|     return false;
 | |
|   }
 | |
|   auto P = td::PromiseCreator::lambda(
 | |
|       [SelfId = actor_id(this), promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|         td::actor::send_closure(SelfId, &TestNode::got_result, std::move(R), std::move(promise));
 | |
|       });
 | |
|   td::BufferSlice b =
 | |
|       ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_query>(std::move(query)), true);
 | |
|   td::actor::send_closure(client_, &ton::adnl::AdnlExtClient::send_query, "query", std::move(b),
 | |
|                           td::Timestamp::in(10.0), std::move(P));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| td::Promise<td::Unit> TestNode::trivial_promise() {
 | |
|   return td::PromiseCreator::lambda([Self = actor_id(this)](td::Result<td::Unit> res) {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "error: " << res.move_as_error();
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::register_blkid(const ton::BlockIdExt& blkid) {
 | |
|   for (const auto& id : known_blk_ids_) {
 | |
|     if (id == blkid) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   known_blk_ids_.push_back(blkid);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::show_new_blkids(bool all) {
 | |
|   if (all) {
 | |
|     shown_blk_ids_ = 0;
 | |
|   }
 | |
|   int cnt = 0;
 | |
|   while (shown_blk_ids_ < known_blk_ids_.size()) {
 | |
|     td::TerminalIO::out() << "BLK#" << shown_blk_ids_ + 1 << " = " << known_blk_ids_[shown_blk_ids_].to_str()
 | |
|                           << std::endl;
 | |
|     ++shown_blk_ids_;
 | |
|     ++cnt;
 | |
|   }
 | |
|   return cnt;
 | |
| }
 | |
| 
 | |
| bool TestNode::complete_blkid(ton::BlockId partial_blkid, ton::BlockIdExt& complete_blkid) const {
 | |
|   auto n = known_blk_ids_.size();
 | |
|   while (n) {
 | |
|     --n;
 | |
|     if (known_blk_ids_[n].id == partial_blkid) {
 | |
|       complete_blkid = known_blk_ids_[n];
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   if (partial_blkid.is_masterchain() && partial_blkid.seqno == ~0U) {
 | |
|     complete_blkid.id = ton::BlockId{ton::masterchainId, ton::shardIdAll, ~0U};
 | |
|     complete_blkid.root_hash.set_zero();
 | |
|     complete_blkid.file_hash.set_zero();
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| const tlb::TypenameLookup& TestNode::get_tlb_dict() {
 | |
|   static tlb::TypenameLookup tlb_dict = []() {
 | |
|     tlb::TypenameLookup tlb_dict0;
 | |
|     tlb_dict0.register_types(block::gen::register_simple_types);
 | |
|     return tlb_dict0;
 | |
|   }();
 | |
|   return tlb_dict;
 | |
| }
 | |
| 
 | |
| bool TestNode::list_cached_cells() const {
 | |
|   for (const auto& kv : cell_cache_) {
 | |
|     td::TerminalIO::out() << kv.first.to_hex() << std::endl;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::dump_cached_cell(td::Slice hash_pfx, td::Slice type_name) {
 | |
|   if (hash_pfx.size() > 64) {
 | |
|     return false;
 | |
|   }
 | |
|   td::Bits256 hv_min;
 | |
|   int len = (int)hv_min.from_hex(hash_pfx, true);
 | |
|   if (len < 0 || len > 256) {
 | |
|     return set_error("cannot parse hex cell hash prefix");
 | |
|   }
 | |
|   (hv_min.bits() + len).fill(false, 256 - len);
 | |
|   const tlb::TLB* tptr = nullptr;
 | |
|   block::gen::ConfigParam tpconf(0);
 | |
|   if (type_name.size()) {
 | |
|     td::int32 idx;
 | |
|     if (type_name.substr(0, 11) == "ConfigParam" && convert_int32(type_name.substr(11), idx) && idx >= 0) {
 | |
|       tpconf = block::gen::ConfigParam(idx);
 | |
|       tptr = &tpconf;
 | |
|     } else {
 | |
|       tptr = get_tlb_dict().lookup(type_name);
 | |
|     }
 | |
|     if (!tptr) {
 | |
|       return set_error("unknown TL-B type");
 | |
|     }
 | |
|     td::TerminalIO::out() << "dumping cells as values of TLB type " << tptr->get_type_name() << std::endl;
 | |
|   }
 | |
|   auto it = std::lower_bound(cell_cache_.begin(), cell_cache_.end(), hv_min,
 | |
|                              [](const auto& x, const auto& y) { return x.first < y; });
 | |
|   int cnt = 0;
 | |
|   for (; it != cell_cache_.end() && it->first.bits().equals(hv_min.bits(), len); ++it) {
 | |
|     std::ostringstream os;
 | |
|     os << "C{" << it->first.to_hex() << "} =" << std::endl;
 | |
|     vm::load_cell_slice(it->second).print_rec(print_limit_, os, 2);
 | |
|     if (tptr) {
 | |
|       tptr->print_ref(print_limit_, os, it->second, 2);
 | |
|       os << std::endl;
 | |
|     }
 | |
|     td::TerminalIO::out() << os.str();
 | |
|     ++cnt;
 | |
|   }
 | |
|   if (!cnt) {
 | |
|     LOG(ERROR) << "no known cells with specified hash prefix";
 | |
|     return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::get_server_time() {
 | |
|   auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getTime>(), true);
 | |
|   return envelope_send_query(std::move(b), [&, Self = actor_id(this)](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot get server time";
 | |
|       return;
 | |
|     } else {
 | |
|       auto F = ton::fetch_tl_object<ton::lite_api::liteServer_currentTime>(res.move_as_ok(), true);
 | |
|       if (F.is_error()) {
 | |
|         LOG(ERROR) << "cannot parse answer to liteServer.getTime";
 | |
|       } else {
 | |
|         server_time_ = F.move_as_ok()->now_;
 | |
|         server_time_got_at_ = now();
 | |
|         LOG(INFO) << "server time is " << server_time_ << " (delta " << server_time_ - server_time_got_at_ << ")";
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::get_server_version(int mode) {
 | |
|   auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getVersion>(), true);
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), mode](td::Result<td::BufferSlice> res) {
 | |
|     td::actor::send_closure_later(Self, &TestNode::got_server_version, std::move(res), mode);
 | |
|   });
 | |
| };
 | |
| 
 | |
| void TestNode::got_server_version(td::Result<td::BufferSlice> res, int mode) {
 | |
|   server_ok_ = false;
 | |
|   if (res.is_error()) {
 | |
|     LOG(ERROR) << "cannot get server version and time (server too old?)";
 | |
|   } else {
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_version>(res.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getVersion";
 | |
|     } else {
 | |
|       auto a = F.move_as_ok();
 | |
|       set_server_version(a->version_, a->capabilities_);
 | |
|       set_server_time(a->now_);
 | |
|     }
 | |
|   }
 | |
|   if (!server_ok_) {
 | |
|     LOG(ERROR) << "server version is too old (at least " << (min_ls_version >> 8) << "." << (min_ls_version & 0xff)
 | |
|                << " with capabilities " << min_ls_capabilities << " required), some queries are unavailable";
 | |
|   }
 | |
|   if (mode & 0x100) {
 | |
|     get_server_mc_block_id();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::set_server_version(td::int32 version, td::int64 capabilities) {
 | |
|   if (server_version_ != version || server_capabilities_ != capabilities) {
 | |
|     server_version_ = version;
 | |
|     server_capabilities_ = capabilities;
 | |
|     LOG(WARNING) << "server version is " << (server_version_ >> 8) << "." << (server_version_ & 0xff)
 | |
|                  << ", capabilities " << server_capabilities_;
 | |
|   }
 | |
|   server_ok_ = (server_version_ >= min_ls_version) && !(~server_capabilities_ & min_ls_capabilities);
 | |
| }
 | |
| 
 | |
| void TestNode::set_server_time(int server_utime) {
 | |
|   server_time_ = server_utime;
 | |
|   server_time_got_at_ = now();
 | |
|   LOG(INFO) << "server time is " << server_time_ << " (delta " << server_time_ - server_time_got_at_ << ")";
 | |
| }
 | |
| 
 | |
| bool TestNode::get_server_mc_block_id() {
 | |
|   int mode = (server_capabilities_ & 2) ? 0 : -1;
 | |
|   if (mode < 0) {
 | |
|     auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getMasterchainInfo>(), true);
 | |
|     return envelope_send_query(std::move(b), [Self = actor_id(this)](td::Result<td::BufferSlice> res) -> void {
 | |
|       if (res.is_error()) {
 | |
|         LOG(ERROR) << "cannot get masterchain info from server";
 | |
|         return;
 | |
|       } else {
 | |
|         auto F = ton::fetch_tl_object<ton::lite_api::liteServer_masterchainInfo>(res.move_as_ok(), true);
 | |
|         if (F.is_error()) {
 | |
|           LOG(ERROR) << "cannot parse answer to liteServer.getMasterchainInfo";
 | |
|         } else {
 | |
|           auto f = F.move_as_ok();
 | |
|           auto blk_id = create_block_id(f->last_);
 | |
|           auto zstate_id = create_zero_state_id(f->init_);
 | |
|           LOG(INFO) << "last masterchain block is " << blk_id.to_str();
 | |
|           td::actor::send_closure_later(Self, &TestNode::got_server_mc_block_id, blk_id, zstate_id, 0);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   } else {
 | |
|     auto b =
 | |
|         ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getMasterchainInfoExt>(mode), true);
 | |
|     return envelope_send_query(std::move(b), [Self = actor_id(this), mode](td::Result<td::BufferSlice> res) -> void {
 | |
|       if (res.is_error()) {
 | |
|         LOG(ERROR) << "cannot get extended masterchain info from server";
 | |
|         return;
 | |
|       } else {
 | |
|         auto F = ton::fetch_tl_object<ton::lite_api::liteServer_masterchainInfoExt>(res.move_as_ok(), true);
 | |
|         if (F.is_error()) {
 | |
|           LOG(ERROR) << "cannot parse answer to liteServer.getMasterchainInfoExt";
 | |
|         } else {
 | |
|           auto f = F.move_as_ok();
 | |
|           auto blk_id = create_block_id(f->last_);
 | |
|           auto zstate_id = create_zero_state_id(f->init_);
 | |
|           LOG(INFO) << "last masterchain block is " << blk_id.to_str();
 | |
|           td::actor::send_closure_later(Self, &TestNode::got_server_mc_block_id_ext, blk_id, zstate_id, mode,
 | |
|                                         f->version_, f->capabilities_, f->last_utime_, f->now_);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::got_server_mc_block_id(ton::BlockIdExt blkid, ton::ZeroStateIdExt zstateid, int created) {
 | |
|   if (!zstate_id_.is_valid()) {
 | |
|     zstate_id_ = zstateid;
 | |
|     LOG(INFO) << "zerostate id set to " << zstate_id_.to_str();
 | |
|   } else if (zstate_id_ != zstateid) {
 | |
|     LOG(FATAL) << "fatal: masterchain zero state id suddenly changed: expected " << zstate_id_.to_str() << ", found "
 | |
|                << zstateid.to_str();
 | |
|     _exit(3);
 | |
|     return;
 | |
|   }
 | |
|   register_blkid(blkid);
 | |
|   register_blkid(ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, 0, zstateid.root_hash, zstateid.file_hash});
 | |
|   if (!mc_last_id_.is_valid()) {
 | |
|     mc_last_id_ = blkid;
 | |
|     request_block(blkid);
 | |
|     // request_state(blkid);
 | |
|   } else if (mc_last_id_.id.seqno < blkid.id.seqno) {
 | |
|     mc_last_id_ = blkid;
 | |
|   }
 | |
|   td::TerminalIO::out() << "latest masterchain block known to server is " << blkid.to_str();
 | |
|   if (created > 0) {
 | |
|     td::TerminalIO::out() << " created at " << created << " (" << now() - created << " seconds ago)\n";
 | |
|   } else {
 | |
|     td::TerminalIO::out() << "\n";
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| void TestNode::got_server_mc_block_id_ext(ton::BlockIdExt blkid, ton::ZeroStateIdExt zstateid, int mode, int version,
 | |
|                                           long long capabilities, int last_utime, int server_now) {
 | |
|   set_server_version(version, capabilities);
 | |
|   set_server_time(server_now);
 | |
|   if (last_utime > server_now) {
 | |
|     LOG(WARNING) << "server claims to have a masterchain block " << blkid.to_str() << " created at " << last_utime
 | |
|                  << " (" << last_utime - server_now << " seconds in the future)";
 | |
|   } else if (last_utime < server_now - 60) {
 | |
|     LOG(WARNING) << "server appears to be out of sync: its newest masterchain block is " << blkid.to_str()
 | |
|                  << " created at " << last_utime << " (" << server_now - last_utime
 | |
|                  << " seconds ago according to the server's clock)";
 | |
|   } else if (last_utime < server_time_got_at_ - 60) {
 | |
|     LOG(WARNING) << "either the server is out of sync, or the local clock is set incorrectly: the newest masterchain "
 | |
|                     "block known to server is "
 | |
|                  << blkid.to_str() << " created at " << last_utime << " (" << server_now - server_time_got_at_
 | |
|                  << " seconds ago according to the local clock)";
 | |
|   }
 | |
|   got_server_mc_block_id(blkid, zstateid, last_utime);
 | |
| }
 | |
| 
 | |
| bool TestNode::request_block(ton::BlockIdExt blkid) {
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getBlock>(ton::create_tl_lite_block_id(blkid)), true);
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), blkid](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot obtain block " << blkid.to_str() << " from server";
 | |
|       return;
 | |
|     } else {
 | |
|       auto F = ton::fetch_tl_object<ton::lite_api::liteServer_blockData>(res.move_as_ok(), true);
 | |
|       if (F.is_error()) {
 | |
|         LOG(ERROR) << "cannot parse answer to liteServer.getBlock";
 | |
|       } else {
 | |
|         auto f = F.move_as_ok();
 | |
|         auto blk_id = ton::create_block_id(f->id_);
 | |
|         LOG(INFO) << "obtained block " << blk_id.to_str() << " from server";
 | |
|         if (blk_id != blkid) {
 | |
|           LOG(ERROR) << "block id mismatch: expected data for block " << blkid.to_str() << ", obtained for "
 | |
|                      << blk_id.to_str();
 | |
|         }
 | |
|         td::actor::send_closure_later(Self, &TestNode::got_mc_block, blk_id, std::move(f->data_));
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::request_state(ton::BlockIdExt blkid) {
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getState>(ton::create_tl_lite_block_id(blkid)), true);
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), blkid](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot obtain state " << blkid.to_str() << " from server";
 | |
|       return;
 | |
|     } else {
 | |
|       auto F = ton::fetch_tl_object<ton::lite_api::liteServer_blockState>(res.move_as_ok(), true);
 | |
|       if (F.is_error()) {
 | |
|         LOG(ERROR) << "cannot parse answer to liteServer.getState";
 | |
|       } else {
 | |
|         auto f = F.move_as_ok();
 | |
|         auto blk_id = ton::create_block_id(f->id_);
 | |
|         LOG(INFO) << "obtained state " << blk_id.to_str() << " from server";
 | |
|         if (blk_id != blkid) {
 | |
|           LOG(ERROR) << "block id mismatch: expected state for block " << blkid.to_str() << ", obtained for "
 | |
|                      << blk_id.to_str();
 | |
|         }
 | |
|         td::actor::send_closure_later(Self, &TestNode::got_mc_state, blk_id, f->root_hash_, f->file_hash_,
 | |
|                                       std::move(f->data_));
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_mc_block(ton::BlockIdExt blkid, td::BufferSlice data) {
 | |
|   LOG(INFO) << "obtained " << data.size() << " data bytes for block " << blkid.to_str();
 | |
|   ton::FileHash fhash;
 | |
|   td::sha256(data.as_slice(), fhash.as_slice());
 | |
|   if (fhash != blkid.file_hash) {
 | |
|     LOG(ERROR) << "file hash mismatch for block " << blkid.to_str() << ": expected " << blkid.file_hash.to_hex()
 | |
|                << ", computed " << fhash.to_hex();
 | |
|     return;
 | |
|   }
 | |
|   register_blkid(blkid);
 | |
|   last_block_id_ = blkid;
 | |
|   last_block_data_ = data.clone();
 | |
|   if (!db_root_.empty()) {
 | |
|     auto res = save_db_file(fhash, std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "error saving block file: " << res.to_string();
 | |
|     }
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| void TestNode::got_mc_state(ton::BlockIdExt blkid, ton::RootHash root_hash, ton::FileHash file_hash,
 | |
|                             td::BufferSlice data) {
 | |
|   LOG(INFO) << "obtained " << data.size() << " state bytes for block " << blkid.to_str();
 | |
|   ton::FileHash fhash;
 | |
|   td::sha256(data.as_slice(), fhash.as_slice());
 | |
|   if (fhash != file_hash) {
 | |
|     LOG(ERROR) << "file hash mismatch for state " << blkid.to_str() << ": expected " << file_hash.to_hex()
 | |
|                << ", computed " << fhash.to_hex();
 | |
|     return;
 | |
|   }
 | |
|   register_blkid(blkid);
 | |
|   last_state_id_ = blkid;
 | |
|   last_state_data_ = data.clone();
 | |
|   if (!db_root_.empty()) {
 | |
|     auto res = save_db_file(fhash, std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "error saving state file: " << res.to_string();
 | |
|     }
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| td::Status TestNode::save_db_file(ton::FileHash file_hash, td::BufferSlice data) {
 | |
|   std::string fname = block::compute_db_filename(db_root_ + '/', file_hash);
 | |
|   for (int i = 0; i < 10; i++) {
 | |
|     std::string tmp_fname = block::compute_db_tmp_filename(db_root_ + '/', file_hash, i);
 | |
|     auto res = block::save_binary_file(tmp_fname, data);
 | |
|     if (res.is_ok()) {
 | |
|       if (std::rename(tmp_fname.c_str(), fname.c_str()) < 0) {
 | |
|         int err = errno;
 | |
|         LOG(ERROR) << "cannot rename " << tmp_fname << " to " << fname << " : " << std::strerror(err);
 | |
|         return td::Status::Error(std::string{"cannot rename file: "} + std::strerror(err));
 | |
|       } else {
 | |
|         LOG(INFO) << data.size() << " bytes saved into file " << fname;
 | |
|         return td::Status::OK();
 | |
|       }
 | |
|     } else if (i == 9) {
 | |
|       return res;
 | |
|     }
 | |
|   }
 | |
|   return td::Status::Error("cannot save data file");
 | |
| }
 | |
| 
 | |
| void TestNode::run_init_queries() {
 | |
|   get_server_version(0x100);
 | |
| }
 | |
| 
 | |
| td::Slice TestNode::get_word(char delim) {
 | |
|   if (delim == ' ' || !delim) {
 | |
|     skipspc();
 | |
|   }
 | |
|   const char* ptr = parse_ptr_;
 | |
|   while (ptr < parse_end_ && *ptr != delim && (*ptr != '\t' || delim != ' ')) {
 | |
|     ptr++;
 | |
|   }
 | |
|   std::swap(ptr, parse_ptr_);
 | |
|   return td::Slice{ptr, parse_ptr_};
 | |
| }
 | |
| 
 | |
| td::Slice TestNode::get_word_ext(const char* delims, const char* specials) {
 | |
|   if (delims[0] == ' ') {
 | |
|     skipspc();
 | |
|   }
 | |
|   const char* ptr = parse_ptr_;
 | |
|   while (ptr < parse_end_ && !strchr(delims, *ptr)) {
 | |
|     if (specials && strchr(specials, *ptr)) {
 | |
|       if (ptr == parse_ptr_) {
 | |
|         ptr++;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     ptr++;
 | |
|   }
 | |
|   std::swap(ptr, parse_ptr_);
 | |
|   return td::Slice{ptr, parse_ptr_};
 | |
| }
 | |
| 
 | |
| bool TestNode::get_word_to(std::string& str, char delim) {
 | |
|   str = get_word(delim).str();
 | |
|   return !str.empty();
 | |
| }
 | |
| 
 | |
| bool TestNode::get_word_to(td::Slice& str, char delim) {
 | |
|   str = get_word(delim);
 | |
|   return !str.empty();
 | |
| }
 | |
| 
 | |
| int TestNode::skipspc() {
 | |
|   int i = 0;
 | |
|   while (parse_ptr_ < parse_end_ && (*parse_ptr_ == ' ' || *parse_ptr_ == '\t')) {
 | |
|     i++;
 | |
|     parse_ptr_++;
 | |
|   }
 | |
|   return i;
 | |
| }
 | |
| 
 | |
| std::string TestNode::get_line_tail(bool remove_spaces) const {
 | |
|   const char *ptr = parse_ptr_, *end = parse_end_;
 | |
|   if (remove_spaces) {
 | |
|     while (ptr < end && (*ptr == ' ' || *ptr == '\t')) {
 | |
|       ptr++;
 | |
|     }
 | |
|     while (ptr < end && (end[-1] == ' ' || end[-1] == '\t')) {
 | |
|       --end;
 | |
|     }
 | |
|   }
 | |
|   return std::string{ptr, end};
 | |
| }
 | |
| 
 | |
| bool TestNode::eoln() const {
 | |
|   return parse_ptr_ == parse_end_;
 | |
| }
 | |
| 
 | |
| bool TestNode::seekeoln() {
 | |
|   skipspc();
 | |
|   return eoln();
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_account_addr(ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool allow_none) {
 | |
|   auto word = get_word();
 | |
|   if (allow_none && (word == "none" || word == "root")) {
 | |
|     wc = ton::workchainInvalid;
 | |
|     return true;
 | |
|   }
 | |
|   return block::parse_std_account_addr(word, wc, addr) || set_error("cannot parse account address");
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_account_addr_ext(ton::WorkchainId& wc, ton::StdSmcAddress& addr, int& addr_ext, bool allow_none) {
 | |
|   addr_ext = 0;
 | |
|   auto word = get_word();
 | |
|   if (allow_none && (word == "none" || word == "root")) {
 | |
|     wc = ton::workchainInvalid;
 | |
|     return true;
 | |
|   }
 | |
|   if (word == "config" || word == "elector" || word == "dnsroot") {
 | |
|     wc = ton::masterchainId;
 | |
|     addr.set_zero();
 | |
|     addr_ext = 1 + (word == "elector") + (word == "dnsroot") * 2;
 | |
|     if (addr_ext == 1 && config_addr_queried_) {
 | |
|       addr = config_addr_;
 | |
|       addr_ext = 0;
 | |
|     } else if (addr_ext == 2 && elect_addr_queried_) {
 | |
|       addr = elect_addr_;
 | |
|       addr_ext = 0;
 | |
|     } else if (addr_ext == 3 && dns_root_queried_) {
 | |
|       addr = dns_root_;
 | |
|       addr_ext = 0;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
|   return block::parse_std_account_addr(word, wc, addr) || set_error("cannot parse account address");
 | |
| }
 | |
| 
 | |
| bool TestNode::convert_uint64(td::Slice word, td::uint64& val) {
 | |
|   val = ~0ULL;
 | |
|   if (word.empty()) {
 | |
|     return false;
 | |
|   }
 | |
|   const char* ptr = word.data();
 | |
|   char* end = nullptr;
 | |
|   val = std::strtoull(ptr, &end, 10);
 | |
|   if (end == ptr + word.size()) {
 | |
|     return true;
 | |
|   } else {
 | |
|     val = ~0ULL;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::convert_int64(td::Slice word, td::int64& val) {
 | |
|   val = (~0ULL << 63);
 | |
|   if (word.empty()) {
 | |
|     return false;
 | |
|   }
 | |
|   const char* ptr = word.data();
 | |
|   char* end = nullptr;
 | |
|   val = std::strtoll(ptr, &end, 10);
 | |
|   if (end == ptr + word.size()) {
 | |
|     return true;
 | |
|   } else {
 | |
|     val = (~0ULL << 63);
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::convert_uint32(td::Slice word, td::uint32& val) {
 | |
|   td::uint64 tmp;
 | |
|   if (convert_uint64(word, tmp) && (td::uint32)tmp == tmp) {
 | |
|     val = (td::uint32)tmp;
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::convert_int32(td::Slice word, td::int32& val) {
 | |
|   td::int64 tmp;
 | |
|   if (convert_int64(word, tmp) && (td::int32)tmp == tmp) {
 | |
|     val = (td::int32)tmp;
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_lt(ton::LogicalTime& lt) {
 | |
|   return convert_uint64(get_word(), lt) || set_error("cannot parse logical time");
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_uint32(td::uint32& val) {
 | |
|   return convert_uint32(get_word(), val) || set_error("cannot parse 32-bit unsigned integer");
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_int32(td::int32& val) {
 | |
|   return convert_int32(get_word(), val) || set_error("cannot parse 32-bit integer");
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_int16(int& val) {
 | |
|   return (convert_int32(get_word(), val) && val == (td::int16)val) || set_error("cannot parse 16-bit integer");
 | |
| }
 | |
| 
 | |
| bool TestNode::set_error(td::Status error) {
 | |
|   if (error.is_ok()) {
 | |
|     return true;
 | |
|   }
 | |
|   LOG(ERROR) << "error: " << error.to_string();
 | |
|   if (error_.is_ok()) {
 | |
|     error_ = std::move(error);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| int TestNode::parse_hex_digit(int c) {
 | |
|   if (c >= '0' && c <= '9') {
 | |
|     return c - '0';
 | |
|   }
 | |
|   c |= 0x20;
 | |
|   if (c >= 'a' && c <= 'z') {
 | |
|     return c - 'a' + 10;
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_hash(td::Slice str, ton::Bits256& hash) {
 | |
|   return str.size() == 64 && parse_hash(str.data(), hash);
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_hash(const char* str, ton::Bits256& hash) {
 | |
|   unsigned char* data = hash.data();
 | |
|   for (int i = 0; i < 32; i++) {
 | |
|     int a = parse_hex_digit(str[2 * i]);
 | |
|     if (a < 0) {
 | |
|       return false;
 | |
|     }
 | |
|     int b = parse_hex_digit(str[2 * i + 1]);
 | |
|     if (b < 0) {
 | |
|       return false;
 | |
|     }
 | |
|     data[i] = (unsigned char)((a << 4) + b);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_block_id_ext(std::string blkid_str, ton::BlockIdExt& blkid, bool allow_incomplete) const {
 | |
|   if (blkid_str.empty()) {
 | |
|     return false;
 | |
|   }
 | |
|   auto fc = blkid_str[0];
 | |
|   if (fc == 'B' || fc == '#') {
 | |
|     unsigned n = 0;
 | |
|     if (sscanf(blkid_str.c_str(), fc == 'B' ? "BLK#%u" : "#%u", &n) != 1 || !n || n > known_blk_ids_.size()) {
 | |
|       return false;
 | |
|     }
 | |
|     blkid = known_blk_ids_.at(n - 1);
 | |
|     return true;
 | |
|   }
 | |
|   if (blkid_str[0] != '(') {
 | |
|     return false;
 | |
|   }
 | |
|   auto pos = blkid_str.find(')');
 | |
|   if (pos == std::string::npos || pos >= 38) {
 | |
|     return false;
 | |
|   }
 | |
|   char buffer[40];
 | |
|   std::memcpy(buffer, blkid_str.c_str(), pos + 1);
 | |
|   buffer[pos + 1] = 0;
 | |
|   unsigned long long shard;
 | |
|   if (sscanf(buffer, "(%d,%016llx,%u)", &blkid.id.workchain, &shard, &blkid.id.seqno) != 3) {
 | |
|     return false;
 | |
|   }
 | |
|   blkid.id.shard = shard;
 | |
|   if (!blkid.id.is_valid_full()) {
 | |
|     return false;
 | |
|   }
 | |
|   pos++;
 | |
|   if (pos == blkid_str.size()) {
 | |
|     blkid.root_hash.set_zero();
 | |
|     blkid.file_hash.set_zero();
 | |
|     return complete_blkid(blkid.id, blkid) || allow_incomplete;
 | |
|   }
 | |
|   return pos + 2 * 65 == blkid_str.size() && blkid_str[pos] == ':' && blkid_str[pos + 65] == ':' &&
 | |
|          parse_hash(blkid_str.c_str() + pos + 1, blkid.root_hash) &&
 | |
|          parse_hash(blkid_str.c_str() + pos + 66, blkid.file_hash) && blkid.is_valid_full();
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_block_id_ext(ton::BlockIdExt& blk, bool allow_incomplete) {
 | |
|   return parse_block_id_ext(get_word().str(), blk, allow_incomplete) || set_error("cannot parse BlockIdExt");
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_hash(ton::Bits256& hash) {
 | |
|   auto word = get_word();
 | |
|   return parse_hash(word, hash) || set_error("cannot parse hash");
 | |
| }
 | |
| 
 | |
| bool TestNode::convert_shard_id(td::Slice str, ton::ShardIdFull& shard) {
 | |
|   shard.workchain = ton::workchainInvalid;
 | |
|   shard.shard = 0;
 | |
|   auto pos = str.find(':');
 | |
|   if (pos == std::string::npos || pos > 10) {
 | |
|     return false;
 | |
|   }
 | |
|   if (!convert_int32(str.substr(0, pos), shard.workchain)) {
 | |
|     return false;
 | |
|   }
 | |
|   int t = 64;
 | |
|   while (++pos < str.size()) {
 | |
|     int z = parse_hex_digit(str[pos]);
 | |
|     if (z < 0) {
 | |
|       if (t == 64) {
 | |
|         shard.shard = ton::shardIdAll;
 | |
|       }
 | |
|       return pos == str.size() - 1 && str[pos] == '_';
 | |
|     }
 | |
|     t -= 4;
 | |
|     if (t >= 0) {
 | |
|       shard.shard |= ((td::uint64)z << t);
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_shard_id(ton::ShardIdFull& shard) {
 | |
|   return convert_shard_id(get_word(), shard) || set_error("cannot parse full shard identifier or prefix");
 | |
| }
 | |
| 
 | |
| bool TestNode::set_error(std::string err_msg) {
 | |
|   return set_error(td::Status::Error(-1, err_msg));
 | |
| }
 | |
| 
 | |
| void TestNode::parse_line(td::BufferSlice data) {
 | |
|   line_ = data.as_slice().str();
 | |
|   parse_ptr_ = line_.c_str();
 | |
|   parse_end_ = parse_ptr_ + line_.size();
 | |
|   error_ = td::Status::OK();
 | |
|   if (seekeoln()) {
 | |
|     return;
 | |
|   }
 | |
|   if (!do_parse_line() || error_.is_error()) {
 | |
|     show_context();
 | |
|     LOG(ERROR) << (error_.is_ok() ? "Syntax error" : error_.to_string());
 | |
|     error_ = td::Status::OK();
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| void TestNode::show_context() const {
 | |
|   const char* ptr = line_.c_str();
 | |
|   CHECK(parse_ptr_ >= ptr && parse_ptr_ <= parse_end_);
 | |
|   auto out = td::TerminalIO::out();
 | |
|   for (; ptr < parse_ptr_; ptr++) {
 | |
|     out << (char)(*ptr == '\t' ? *ptr : ' ');
 | |
|   }
 | |
|   out << "^" << '\n';
 | |
| }
 | |
| 
 | |
| bool TestNode::show_help(std::string command) {
 | |
|   td::TerminalIO::out()
 | |
|       << "list of available commands:\n"
 | |
|          "time\tGet server time\n"
 | |
|          "remote-version\tShows server time, version and capabilities\n"
 | |
|          "last\tGet last block and state info from server\n"
 | |
|          "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n"
 | |
|          "status\tShow connection and local database status\n"
 | |
|          "getaccount <addr> [<block-id-ext>]\tLoads the most recent state of specified account; <addr> is in "
 | |
|          "[<workchain>:]<hex-or-base64-addr> format\n"
 | |
|          "saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state "
 | |
|          "(StateInit) or just the code or data of specified account; <addr> is in "
 | |
|          "[<workchain>:]<hex-or-base64-addr> format\n"
 | |
|          "runmethod[full] <addr> [<block-id-ext>] <name> <params>...\tRuns GET method <name> of account "
 | |
|          "<addr> "
 | |
|          "with specified parameters\n"
 | |
|          "dnsresolve [<block-id-ext>] <domain> [<category>]\tResolves a domain starting from root dns smart contract\n"
 | |
|          "dnsresolvestep <addr> [<block-id-ext>] <domain> [<category>]\tResolves a subdomain using dns smart contract "
 | |
|          "<addr>\n"
 | |
|          "allshards [<block-id-ext>]\tShows shard configuration from the most recent masterchain "
 | |
|          "state or from masterchain state corresponding to <block-id-ext>\n"
 | |
|          "getconfig [<param>...]\tShows specified or all configuration parameters from the latest masterchain state\n"
 | |
|          "getconfigfrom <block-id-ext> [<param>...]\tShows specified or all configuration parameters from the "
 | |
|          "masterchain state of <block-id-ext>\n"
 | |
|          "getkeyconfig <block-id-ext> [<param>...]\tShows specified or all configuration parameters from the "
 | |
|          "previous key block with respect to <block-id-ext>\n"
 | |
|          "saveconfig <filename> [<block-id-ext>]\tSaves all configuration parameters into specified file\n"
 | |
|          "gethead <block-id-ext>\tShows block header for <block-id-ext>\n"
 | |
|          "getblock <block-id-ext>\tDownloads block\n"
 | |
|          "dumpblock <block-id-ext>\tDownloads and dumps specified block\n"
 | |
|          "getstate <block-id-ext>\tDownloads state corresponding to specified block\n"
 | |
|          "dumpstate <block-id-ext>\tDownloads and dumps state corresponding to specified block\n"
 | |
|          "dumptrans <block-id-ext> <account-id> <trans-lt>\tDumps one transaction of specified account\n"
 | |
|          "lasttrans[dump] <account-id> <trans-lt> <trans-hash> [<count>]\tShows or dumps specified transaction and "
 | |
|          "several preceding "
 | |
|          "ones\n"
 | |
|          "listblocktrans[rev][meta] <block-id-ext> <count> [<start-account-id> <start-trans-lt>]\tLists block "
 | |
|          "transactions, starting immediately after or before the specified one\n"
 | |
|          "blkproofchain[step] <from-block-id-ext> [<to-block-id-ext>]\tDownloads and checks proof of validity of the "
 | |
|          "second "
 | |
|          "indicated block (or the last known masterchain block) starting from given block\n"
 | |
|          "byseqno <workchain> <shard-prefix> <seqno>\tLooks up a block by workchain, shard and seqno, and shows its "
 | |
|          "header\n"
 | |
|          "bylt <workchain> <shard-prefix> <lt>\tLooks up a block by workchain, shard and logical time, and shows its "
 | |
|          "header\n"
 | |
|          "byutime <workchain> <shard-prefix> <utime>\tLooks up a block by workchain, shard and creation time, and "
 | |
|          "shows its header\n"
 | |
|          "creatorstats <block-id-ext> [<count> [<start-pubkey>]]\tLists block creator statistics by validator public "
 | |
|          "key\n"
 | |
|          "recentcreatorstats <block-id-ext> <start-utime> [<count> [<start-pubkey>]]\tLists block creator statistics "
 | |
|          "updated after <start-utime> by validator public "
 | |
|          "key\n"
 | |
|          "checkload[all|severe][-v2] <start-utime> <end-utime> [<savefile-prefix>]\tChecks whether all validators "
 | |
|          "worked properly during specified time "
 | |
|          "interval, and optionally saves proofs into <savefile-prefix>-<n>.boc\n"
 | |
|          "loadproofcheck <filename>\tChecks a validator misbehavior proof previously created by checkload\n"
 | |
|          "pastvalsets\tLists known past validator set ids and their hashes\n"
 | |
|          "savecomplaints <election-id> <filename-pfx>\tSaves all complaints registered for specified validator set id "
 | |
|          "into files <filename-pfx><complaint-hash>.boc\n"
 | |
|          "complaintprice <expires-in> <complaint-boc>\tComputes the price (in nanograms) for creating a complaint\n"
 | |
|          "msgqueuesizes\tShows current sizes of outbound message queues in all shards\n"
 | |
|          "dispatchqueueinfo <block-id>\tShows list of account dispatch queue of a block\n"
 | |
|          "dispatchqueuemessages <block-id> <addr> [<after-lt>]\tShows deferred messages from account <addr>, lt > "
 | |
|          "<after_lt>\n"
 | |
|          "dispatchqueuemessagesall <block-id> [<after-addr> [<after-lt>]]\tShows messages from dispatch queue of a "
 | |
|          "block, starting after <after_addr>, <after-lt>\n"
 | |
|          "known\tShows the list of all known block ids\n"
 | |
|          "knowncells\tShows the list of hashes of all known (cached) cells\n"
 | |
|          "dumpcell <hex-hash-pfx>\nDumps a cached cell by a prefix of its hash\n"
 | |
|          "dumpcellas <tlb-type> <hex-hash-pfx>\nFinds a cached cell by a prefix of its hash and prints it as a value "
 | |
|          "of <tlb-type>\n"
 | |
|          "privkey <filename>\tLoads a private key from file\n"
 | |
|          "help [<command>]\tThis help\n"
 | |
|          "quit\tExit\n";
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::do_parse_line() {
 | |
|   ton::WorkchainId workchain = ton::masterchainId;  // change to basechain later
 | |
|   int addr_ext = 0;
 | |
|   ton::StdSmcAddress addr = ton::StdSmcAddress::zero();
 | |
|   ton::BlockIdExt blkid{};
 | |
|   ton::LogicalTime lt = 0;
 | |
|   ton::Bits256 hash{};
 | |
|   ton::ShardIdFull shard{};
 | |
|   ton::BlockSeqno seqno{};
 | |
|   ton::UnixTime utime{};
 | |
|   unsigned count{};
 | |
|   std::string word = get_word().str();
 | |
|   skipspc();
 | |
|   if (word == "time") {
 | |
|     return eoln() && get_server_time();
 | |
|   } else if (word == "remote-version") {
 | |
|     return eoln() && get_server_version();
 | |
|   } else if (word == "last") {
 | |
|     return eoln() && get_server_mc_block_id();
 | |
|   } else if (word == "sendfile") {
 | |
|     return !eoln() && set_error(send_ext_msg_from_filename(get_line_tail()));
 | |
|   } else if (word == "getaccount" || word == "getaccountprunned") {
 | |
|     bool prunned = word == "getaccountprunned";
 | |
|     return parse_account_addr_ext(workchain, addr, addr_ext) &&
 | |
|            (seekeoln() ? get_account_state(workchain, addr, mc_last_id_, addr_ext, "", -1, prunned)
 | |
|                        : parse_block_id_ext(blkid) && seekeoln() &&
 | |
|                              get_account_state(workchain, addr, blkid, addr_ext, "", -1, prunned));
 | |
|   } else if (word == "saveaccount" || word == "saveaccountcode" || word == "saveaccountdata") {
 | |
|     std::string filename;
 | |
|     int mode = ((word.c_str()[11] >> 1) & 3);
 | |
|     return get_word_to(filename) && parse_account_addr_ext(workchain, addr, addr_ext) &&
 | |
|            (seekeoln() ? get_account_state(workchain, addr, mc_last_id_, addr_ext, filename, mode)
 | |
|                        : parse_block_id_ext(blkid) && seekeoln() &&
 | |
|                              get_account_state(workchain, addr, blkid, addr_ext, filename, mode));
 | |
|   } else if (word == "runmethod" || word == "runmethodx" || word == "runmethodfull") {
 | |
|     std::string method;
 | |
|     return parse_account_addr_ext(workchain, addr, addr_ext) && get_word_to(method) &&
 | |
|            (parse_block_id_ext(method, blkid) ? get_word_to(method) : (blkid = mc_last_id_).is_valid()) &&
 | |
|            parse_run_method(workchain, addr, blkid, addr_ext, method, word.size() <= 10);
 | |
|   } else if (word == "dnsresolve" || word == "dnsresolvestep") {
 | |
|     workchain = ton::workchainInvalid;
 | |
|     bool step = (word.size() > 10);
 | |
|     std::string domain;
 | |
|     std::string cat_str;
 | |
|     return (!step || parse_account_addr(workchain, addr)) && get_word_to(domain) &&
 | |
|            (parse_block_id_ext(domain, blkid) ? get_word_to(domain) : (blkid = mc_last_id_).is_valid()) &&
 | |
|            (seekeoln() || get_word_to(cat_str)) && seekeoln() &&
 | |
|            dns_resolve_start(workchain, addr, blkid, domain,
 | |
|                              cat_str.empty() ? td::Bits256::zero() : td::sha256_bits256(td::as_slice(cat_str)),
 | |
|                              step ? 3 : 0);
 | |
|   } else if (word == "allshards" || word == "allshardssave") {
 | |
|     std::string filename;
 | |
|     return (word.size() <= 9 || get_word_to(filename)) &&
 | |
|            (seekeoln() ? get_all_shards(filename)
 | |
|                        : (parse_block_id_ext(blkid) && seekeoln() && get_all_shards(filename, false, blkid)));
 | |
|   } else if (word == "saveconfig") {
 | |
|     blkid = mc_last_id_;
 | |
|     std::string filename;
 | |
|     return get_word_to(filename) && (seekeoln() || parse_block_id_ext(blkid)) && seekeoln() &&
 | |
|            parse_get_config_params(blkid, -1, filename);
 | |
|   } else if (word == "getconfig" || word == "getconfigfrom") {
 | |
|     blkid = mc_last_id_;
 | |
|     return (word == "getconfig" || parse_block_id_ext(blkid)) && parse_get_config_params(blkid, 0);
 | |
|   } else if (word == "getkeyconfig") {
 | |
|     return parse_block_id_ext(blkid) && parse_get_config_params(blkid, 0x8000);
 | |
|   } else if (word == "getblock") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_block(blkid, false);
 | |
|   } else if (word == "dumpblock") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_block(blkid, true);
 | |
|   } else if (word == "getstate") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_state(blkid, false);
 | |
|   } else if (word == "dumpstate") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_state(blkid, true);
 | |
|   } else if (word == "gethead") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_show_block_header(blkid, 0xffff);
 | |
|   } else if (word == "dumptrans") {
 | |
|     return parse_block_id_ext(blkid) && parse_account_addr(workchain, addr) && parse_lt(lt) && seekeoln() &&
 | |
|            get_one_transaction(blkid, workchain, addr, lt, true);
 | |
|   } else if (word == "lasttrans" || word == "lasttransdump") {
 | |
|     count = 10;
 | |
|     return parse_account_addr(workchain, addr) && parse_lt(lt) && parse_hash(hash) &&
 | |
|            (seekeoln() || parse_uint32(count)) && seekeoln() &&
 | |
|            get_last_transactions(workchain, addr, lt, hash, count, word == "lasttransdump");
 | |
|   } else if (word == "listblocktrans" || word == "listblocktransrev") {
 | |
|     lt = 0;
 | |
|     int mode = (word == "listblocktrans" ? 7 : 0x47);
 | |
|     return parse_block_id_ext(blkid) && parse_uint32(count) &&
 | |
|            (seekeoln() || (parse_hash(hash) && parse_lt(lt) && (mode |= 128) && seekeoln())) &&
 | |
|            get_block_transactions(blkid, mode, count, hash, lt);
 | |
|   } else if (word == "listblocktransmeta" || word == "listblocktransrevmeta") {
 | |
|     lt = 0;
 | |
|     int mode = (word == "listblocktransmeta" ? 7 : 0x47);
 | |
|     mode |= 256;
 | |
|     return parse_block_id_ext(blkid) && parse_uint32(count) &&
 | |
|            (seekeoln() || (parse_hash(hash) && parse_lt(lt) && (mode |= 128) && seekeoln())) &&
 | |
|            get_block_transactions(blkid, mode, count, hash, lt);
 | |
|   } else if (word == "blkproofchain" || word == "blkproofchainstep") {
 | |
|     ton::BlockIdExt blkid2{};
 | |
|     return parse_block_id_ext(blkid) && (seekeoln() || parse_block_id_ext(blkid2)) && seekeoln() &&
 | |
|            get_block_proof(blkid, blkid2, blkid2.is_valid() + (word == "blkproofchain") * 0x1000);
 | |
|   } else if (word == "byseqno") {
 | |
|     return parse_shard_id(shard) && parse_uint32(seqno) && seekeoln() && lookup_show_block(shard, 1, seqno);
 | |
|   } else if (word == "byutime") {
 | |
|     return parse_shard_id(shard) && parse_uint32(utime) && seekeoln() && lookup_show_block(shard, 4, utime);
 | |
|   } else if (word == "bylt") {
 | |
|     return parse_shard_id(shard) && parse_lt(lt) && seekeoln() && lookup_show_block(shard, 2, lt);
 | |
|   } else if (word == "creatorstats" || word == "recentcreatorstats") {
 | |
|     count = 1000;
 | |
|     int mode = (word == "recentcreatorstats" ? 4 : 0);
 | |
|     return parse_block_id_ext(blkid) && (!mode || parse_uint32(utime)) &&
 | |
|            (seekeoln() ? (mode |= 0x100) : parse_uint32(count)) && (seekeoln() || (parse_hash(hash) && (mode |= 1))) &&
 | |
|            seekeoln() && get_creator_stats(blkid, mode, count, hash, utime);
 | |
|   } else if (word == "checkload" || word == "checkloadall" || word == "checkloadsevere" || word == "checkload-v2" ||
 | |
|              word == "checkloadall-v2" || word == "checkloadsevere-v2") {
 | |
|     int time1, time2, mode = 0;
 | |
|     if (word == "checkloadsevere" || word == "checkloadsevere-v2") {
 | |
|       mode |= 1;
 | |
|     }
 | |
|     if (td::ends_with(word, "-v2")) {
 | |
|       mode |= 4;
 | |
|     }
 | |
|     std::string file_pfx;
 | |
|     return parse_int32(time1) && parse_int32(time2) && (seekeoln() || ((mode |= 2) && get_word_to(file_pfx))) &&
 | |
|            seekeoln() && check_validator_load(time1, time2, mode, file_pfx);
 | |
|   } else if (word == "loadproofcheck") {
 | |
|     std::string filename;
 | |
|     return get_word_to(filename) && seekeoln() && set_error(check_validator_load_proof(filename));
 | |
|   } else if (word == "pastvalsets") {
 | |
|     return eoln() && get_past_validator_sets();
 | |
|   } else if (word == "savecomplaints") {
 | |
|     td::uint32 elect_id;
 | |
|     std::string file_pfx;
 | |
|     return parse_uint32(elect_id) && get_word_to(file_pfx) && seekeoln() && get_complaints(elect_id, file_pfx);
 | |
|   } else if (word == "complaintprice") {
 | |
|     td::uint32 expire_in;
 | |
|     std::string filename;
 | |
|     return parse_uint32(expire_in) && get_word_to(filename) && seekeoln() &&
 | |
|            set_error(get_complaint_price(expire_in, filename));
 | |
|   } else if (word == "msgqueuesizes") {
 | |
|     return get_msg_queue_sizes();
 | |
|   } else if (word == "dispatchqueueinfo") {
 | |
|     return parse_block_id_ext(blkid) && seekeoln() && get_dispatch_queue_info(blkid);
 | |
|   } else if (word == "dispatchqueuemessages" || word == "dispatchqueuemessagesall") {
 | |
|     bool one_account = word == "dispatchqueuemessages";
 | |
|     if (!parse_block_id_ext(blkid)) {
 | |
|       return false;
 | |
|     }
 | |
|     workchain = blkid.id.workchain;
 | |
|     return ((!one_account && seekeoln()) || parse_account_addr(workchain, addr)) && (seekeoln() || parse_lt(lt)) &&
 | |
|            seekeoln() && get_dispatch_queue_messages(blkid, workchain, addr, lt, one_account);
 | |
|   } else if (word == "known") {
 | |
|     return eoln() && show_new_blkids(true);
 | |
|   } else if (word == "knowncells") {
 | |
|     return eoln() && list_cached_cells();
 | |
|   } else if (word == "dumpcell" || word == "dumpcellas") {
 | |
|     td::Slice chash;
 | |
|     td::Slice tname;
 | |
|     return (word == "dumpcell" || get_word_to(tname)) && get_word_to(chash) && seekeoln() &&
 | |
|            dump_cached_cell(chash, tname);
 | |
|   } else if (word == "quit" && eoln()) {
 | |
|     LOG(INFO) << "Exiting";
 | |
|     stop();
 | |
|     // std::exit(0);
 | |
|     return true;
 | |
|   } else if (word == "help") {
 | |
|     return show_help(get_line_tail());
 | |
|   } else {
 | |
|     td::TerminalIO::out() << "unknown command: " << word << " ; type `help` to get help" << '\n';
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| td::Result<std::pair<Ref<vm::Cell>, std::shared_ptr<vm::StaticBagOfCellsDb>>> lazy_boc_deserialize(
 | |
|     td::BufferSlice data) {
 | |
|   vm::StaticBagOfCellsDbLazy::Options options;
 | |
|   options.check_crc32c = true;
 | |
|   TRY_RESULT(boc, vm::StaticBagOfCellsDbLazy::create(td::BufferSliceBlobView::create(std::move(data)), options));
 | |
|   TRY_RESULT(rc, boc->get_root_count());
 | |
|   if (rc != 1) {
 | |
|     return td::Status::Error(-668, "bag-of-cells is not standard (exactly one root cell expected)");
 | |
|   }
 | |
|   TRY_RESULT(root, boc->get_root_cell(0));
 | |
|   return std::make_pair(std::move(root), std::move(boc));
 | |
| }
 | |
| 
 | |
| td::Status TestNode::send_ext_msg_from_filename(std::string filename) {
 | |
|   auto F = td::read_file(filename);
 | |
|   if (F.is_error()) {
 | |
|     auto err = F.move_as_error();
 | |
|     LOG(ERROR) << "failed to read file `" << filename << "`: " << err.to_string();
 | |
|     return err;
 | |
|   }
 | |
|   if (ready_ && !client_.empty()) {
 | |
|     LOG(ERROR) << "sending query from file " << filename;
 | |
|     auto P = td::PromiseCreator::lambda([](td::Result<td::BufferSlice> R) {
 | |
|       if (R.is_error()) {
 | |
|         return;
 | |
|       }
 | |
|       auto F = ton::fetch_tl_object<ton::lite_api::liteServer_sendMsgStatus>(R.move_as_ok(), true);
 | |
|       if (F.is_error()) {
 | |
|         LOG(ERROR) << "cannot parse answer to liteServer.sendMessage";
 | |
|       } else {
 | |
|         int status = F.move_as_ok()->status_;
 | |
|         LOG(INFO) << "external message status is " << status;
 | |
|       }
 | |
|     });
 | |
|     auto b =
 | |
|         ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_sendMessage>(F.move_as_ok()), true);
 | |
|     return envelope_send_query(std::move(b), std::move(P)) ? td::Status::OK()
 | |
|                                                            : td::Status::Error("cannot send query to server");
 | |
|   } else {
 | |
|     return td::Status::Error("server connection not ready");
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_account_state(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt ref_blkid,
 | |
|                                  int addr_ext, std::string filename, int mode, bool prunned) {
 | |
|   if (!ref_blkid.is_valid()) {
 | |
|     return set_error("must obtain last block information before making other queries");
 | |
|   }
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   if (addr_ext) {
 | |
|     return get_special_smc_addr(
 | |
|         addr_ext, [this, ref_blkid, filename, mode, prunned](td::Result<ton::StdSmcAddress> res) {
 | |
|           if (res.is_error()) {
 | |
|             LOG(ERROR) << "cannot resolve special smart contract address: " << res.move_as_error();
 | |
|           } else {
 | |
|             get_account_state(ton::masterchainId, res.move_as_ok(), ref_blkid, 0, filename, mode, prunned);
 | |
|           }
 | |
|         });
 | |
|   }
 | |
|   auto a = ton::create_tl_object<ton::lite_api::liteServer_accountId>(workchain, addr);
 | |
|   td::BufferSlice b;
 | |
|   if (prunned) {
 | |
|     b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getAccountStatePrunned>(
 | |
|                                      ton::create_tl_lite_block_id(ref_blkid), std::move(a)),
 | |
|                                  true);
 | |
|   } else {
 | |
|     b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getAccountState>(
 | |
|                                      ton::create_tl_lite_block_id(ref_blkid), std::move(a)),
 | |
|                                  true);
 | |
|   }
 | |
|   LOG(INFO) << "requesting " << (prunned ? "prunned " : "") << "account state for " << workchain << ":" << addr.to_hex()
 | |
|             << " with respect to " << ref_blkid.to_str() << " with savefile `" << filename << "` and mode " << mode;
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), workchain, addr, ref_blkid, filename, mode,
 | |
|                                             prunned](td::Result<td::BufferSlice> R) {
 | |
|     if (R.is_error()) {
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_accountState>(R.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getAccountState";
 | |
|     } else {
 | |
|       auto f = F.move_as_ok();
 | |
|       td::actor::send_closure_later(Self, &TestNode::got_account_state, ref_blkid, ton::create_block_id(f->id_),
 | |
|                                     ton::create_block_id(f->shardblk_), std::move(f->shard_proof_),
 | |
|                                     std::move(f->proof_), std::move(f->state_), workchain, addr, filename, mode,
 | |
|                                     prunned);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| td::int64 TestNode::compute_method_id(std::string method) {
 | |
|   td::int64 method_id;
 | |
|   if (!convert_int64(method, method_id)) {
 | |
|     method_id = (td::crc16(td::Slice{method}) & 0xffff) | 0x10000;
 | |
|   }
 | |
|   return method_id;
 | |
| }
 | |
| 
 | |
| bool TestNode::cache_cell(Ref<vm::Cell> cell) {
 | |
|   if (cell.is_null()) {
 | |
|     return false;
 | |
|   }
 | |
|   td::Bits256 hash = cell->get_hash().bits();
 | |
|   LOG(INFO) << "caching cell " << hash.to_hex();
 | |
|   auto res = cell_cache_.emplace(hash, std::move(cell));
 | |
|   return res.second;
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_run_method(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt ref_blkid,
 | |
|                                 int addr_ext, std::string method_name, bool ext_mode) {
 | |
|   auto R = vm::parse_stack_entries(td::Slice(parse_ptr_, parse_end_));
 | |
|   if (R.is_error()) {
 | |
|     return set_error(R.move_as_error().to_string());
 | |
|   }
 | |
|   parse_ptr_ = parse_end_;
 | |
|   if (addr_ext) {
 | |
|     return get_special_smc_addr(addr_ext, [this, ref_blkid, method_name, ext_mode,
 | |
|                                            args = R.move_as_ok()](td::Result<ton::StdSmcAddress> res) mutable {
 | |
|       if (res.is_error()) {
 | |
|         LOG(ERROR) << "cannot resolve special smart contract address: " << res.move_as_error();
 | |
|       } else {
 | |
|         after_parse_run_method(ton::masterchainId, res.move_as_ok(), ref_blkid, method_name, std::move(args), ext_mode);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
|   return after_parse_run_method(workchain, addr, ref_blkid, method_name, R.move_as_ok(), ext_mode);
 | |
| }
 | |
| 
 | |
| bool TestNode::after_parse_run_method(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt ref_blkid,
 | |
|                                       std::string method_name, std::vector<vm::StackEntry> params, bool ext_mode) {
 | |
|   auto P = td::PromiseCreator::lambda([this](td::Result<std::vector<vm::StackEntry>> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << R.move_as_error();
 | |
|     } else {
 | |
|       for (const auto& v : R.move_as_ok()) {
 | |
|         v.for_each_scalar([this](const vm::StackEntry& val) {
 | |
|           if (val.is_cell()) {
 | |
|             cache_cell(val.as_cell());
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|   });
 | |
|   return start_run_method(workchain, addr, ref_blkid, method_name, std::move(params), ext_mode ? 0x17 : 0,
 | |
|                           std::move(P));
 | |
| }
 | |
| 
 | |
| bool TestNode::start_run_method(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt ref_blkid,
 | |
|                                 std::string method_name, std::vector<vm::StackEntry> params, int mode,
 | |
|                                 td::Promise<std::vector<vm::StackEntry>> promise) {
 | |
|   if (!ref_blkid.is_valid()) {
 | |
|     return set_error("must obtain last block information before making other queries");
 | |
|   }
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   auto a = ton::create_tl_object<ton::lite_api::liteServer_accountId>(workchain, addr);
 | |
|   if (!mode) {
 | |
|     auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getAccountState>(
 | |
|                                           ton::create_tl_lite_block_id(ref_blkid), std::move(a)),
 | |
|                                       true);
 | |
|     LOG(INFO) << "requesting account state for " << workchain << ":" << addr.to_hex() << " with respect to "
 | |
|               << ref_blkid.to_str() << " to run method " << method_name << " with " << params.size() << " parameters";
 | |
|     return envelope_send_query(
 | |
|         std::move(b), [Self = actor_id(this), workchain, addr, ref_blkid, method_name, params = std::move(params),
 | |
|                        promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|           if (R.is_error()) {
 | |
|             promise.set_error(R.move_as_error());
 | |
|             return;
 | |
|           }
 | |
|           auto F = ton::fetch_tl_object<ton::lite_api::liteServer_accountState>(R.move_as_ok(), true);
 | |
|           if (F.is_error()) {
 | |
|             LOG(ERROR) << "cannot parse answer to liteServer.getAccountState";
 | |
|             promise.set_error(td::Status::Error("cannot parse answer to liteServer.getAccountState"));
 | |
|           } else {
 | |
|             auto f = F.move_as_ok();
 | |
|             td::actor::send_closure_later(Self, &TestNode::run_smc_method, 0, ref_blkid, ton::create_block_id(f->id_),
 | |
|                                           ton::create_block_id(f->shardblk_), std::move(f->shard_proof_),
 | |
|                                           std::move(f->proof_), std::move(f->state_), workchain, addr, method_name,
 | |
|                                           std::move(params), td::BufferSlice(), td::BufferSlice(), td::BufferSlice(),
 | |
|                                           -0x10000, std::move(promise));
 | |
|           }
 | |
|         });
 | |
|   } else {
 | |
|     td::int64 method_id = compute_method_id(method_name);
 | |
|     // set serialization limits
 | |
|     vm::FakeVmStateLimits fstate(1000);  // limit recursive (de)serialization calls
 | |
|     vm::VmStateInterface::Guard guard(&fstate);
 | |
|     // serialize parameters
 | |
|     vm::CellBuilder cb;
 | |
|     Ref<vm::Cell> cell;
 | |
|     if (!(vm::Stack{params}.serialize(cb) && cb.finalize_to(cell))) {
 | |
|       return set_error("cannot serialize stack with get-method parameters");
 | |
|     }
 | |
|     auto stk = vm::std_boc_serialize(std::move(cell));
 | |
|     if (stk.is_error()) {
 | |
|       return set_error("cannot serialize stack with get-method parameters : "s + stk.move_as_error().to_string());
 | |
|     }
 | |
|     auto b = ton::serialize_tl_object(
 | |
|         ton::create_tl_object<ton::lite_api::liteServer_runSmcMethod>(mode, ton::create_tl_lite_block_id(ref_blkid),
 | |
|                                                                       std::move(a), method_id, stk.move_as_ok()),
 | |
|         true);
 | |
|     LOG(INFO) << "requesting remote get-method execution for " << workchain << ":" << addr.to_hex()
 | |
|               << " with respect to " << ref_blkid.to_str() << " to run method " << method_name << " with "
 | |
|               << params.size() << " parameters";
 | |
|     return envelope_send_query(std::move(b), [Self = actor_id(this), workchain, addr, ref_blkid, method_name, mode,
 | |
|                                               params = std::move(params),
 | |
|                                               promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|       if (R.is_error()) {
 | |
|         promise.set_error(R.move_as_error());
 | |
|         return;
 | |
|       }
 | |
|       auto F = ton::fetch_tl_object<ton::lite_api::liteServer_runMethodResult>(R.move_as_ok(), true);
 | |
|       if (F.is_error()) {
 | |
|         LOG(ERROR) << "cannot parse answer to liteServer.runSmcMethod";
 | |
|         promise.set_error(td::Status::Error("cannot parse answer to liteServer.runSmcMethod"));
 | |
|       } else {
 | |
|         auto f = F.move_as_ok();
 | |
|         td::actor::send_closure_later(Self, &TestNode::run_smc_method, mode, ref_blkid, ton::create_block_id(f->id_),
 | |
|                                       ton::create_block_id(f->shardblk_), std::move(f->shard_proof_),
 | |
|                                       std::move(f->proof_), std::move(f->state_proof_), workchain, addr, method_name,
 | |
|                                       std::move(params), std::move(f->init_c7_), std::move(f->lib_extras_),
 | |
|                                       std::move(f->result_), f->exit_code_, std::move(promise));
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_config_addr(td::Promise<ton::StdSmcAddress> promise) {
 | |
|   if (config_addr_queried_) {
 | |
|     promise.set_result(config_addr_);
 | |
|     return true;
 | |
|   }
 | |
|   auto P = td::PromiseCreator::lambda([this, promise = std::move(promise)](
 | |
|                                           td::Result<std::unique_ptr<block::Config>> R) mutable {
 | |
|     TRY_RESULT_PROMISE_PREFIX(promise, config, std::move(R), "cannot obtain configurator address from configuration:");
 | |
|     if (config_addr_queried_) {
 | |
|       promise.set_result(config_addr_);
 | |
|     } else {
 | |
|       promise.set_error(td::Status::Error("cannot obtain configurator address from configuration parameter #0"));
 | |
|     }
 | |
|   });
 | |
|   return get_config_params(mc_last_id_, std::move(P), 0x3000, "", {0});
 | |
| }
 | |
| 
 | |
| bool TestNode::get_elector_addr(td::Promise<ton::StdSmcAddress> promise) {
 | |
|   if (elect_addr_queried_) {
 | |
|     promise.set_result(elect_addr_);
 | |
|     return true;
 | |
|   }
 | |
|   auto P = td::PromiseCreator::lambda(
 | |
|       [this, promise = std::move(promise)](td::Result<std::unique_ptr<block::Config>> R) mutable {
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, config, std::move(R), "cannot obtain elector address from configuration:");
 | |
|         if (elect_addr_queried_) {
 | |
|           promise.set_result(elect_addr_);
 | |
|         } else {
 | |
|           promise.set_error(td::Status::Error("cannot obtain elector address from configuration parameter #1"));
 | |
|         }
 | |
|       });
 | |
|   return get_config_params(mc_last_id_, std::move(P), 0x3000, "", {1});
 | |
| }
 | |
| 
 | |
| bool TestNode::get_dns_root(td::Promise<ton::StdSmcAddress> promise) {
 | |
|   if (dns_root_queried_) {
 | |
|     promise.set_result(dns_root_);
 | |
|     return true;
 | |
|   }
 | |
|   auto P = td::PromiseCreator::lambda(
 | |
|       [this, promise = std::move(promise)](td::Result<std::unique_ptr<block::Config>> R) mutable {
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, config, std::move(R), "cannot obtain dns root address from configuration:");
 | |
|         if (dns_root_queried_) {
 | |
|           promise.set_result(dns_root_);
 | |
|         } else {
 | |
|           promise.set_error(td::Status::Error("cannot obtain dns root address from configuration parameter #4"));
 | |
|         }
 | |
|       });
 | |
|   return get_config_params(mc_last_id_, std::move(P), 0x3000, "", {4});
 | |
| }
 | |
| 
 | |
| bool TestNode::get_special_smc_addr(int addr_ext, td::Promise<ton::StdSmcAddress> promise) {
 | |
|   switch (addr_ext) {
 | |
|     case 1:
 | |
|       return get_config_addr(std::move(promise));
 | |
|     case 2:
 | |
|       return get_elector_addr(std::move(promise));
 | |
|     case 3:
 | |
|       return get_dns_root(std::move(promise));
 | |
|     default:
 | |
|       promise.set_error(td::Status::Error(PSLICE() << "unknown special smart contract address class " << addr_ext));
 | |
|       return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_past_validator_sets() {
 | |
|   return get_elector_addr([this](td::Result<ton::StdSmcAddress> res) {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << res.move_as_error();
 | |
|     } else {
 | |
|       send_past_vset_query(res.move_as_ok());
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::send_past_vset_query(ton::StdSmcAddress elector_addr) {
 | |
|   std::vector<vm::StackEntry> params;
 | |
|   auto P = td::PromiseCreator::lambda([this](td::Result<std::vector<vm::StackEntry>> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << R.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto S = R.move_as_ok();
 | |
|     if (S.size() < 1 || !S.back().is_list()) {
 | |
|       LOG(ERROR) << "past_elections_list did not return a value of type tuple";
 | |
|       return;
 | |
|     }
 | |
|     register_past_vset_info(std::move(S.back()));
 | |
|   });
 | |
|   return start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "past_elections_list", std::move(params), 0x17,
 | |
|                           std::move(P));
 | |
| }
 | |
| 
 | |
| void TestNode::register_past_vset_info(vm::StackEntry list) {
 | |
|   try {
 | |
|     while (!list.empty()) {
 | |
|       auto tup = std::move(list).as_tuple_range(2, 2);
 | |
|       if (tup.is_null()) {
 | |
|         LOG(ERROR) << "invalid list in the result of past_elections_list";
 | |
|         return;
 | |
|       }
 | |
|       list = tup->at(1);
 | |
|       auto t2 = tup->at(0).as_tuple_range(255, 3);
 | |
|       if (t2.is_null()) {
 | |
|         LOG(ERROR) << "invalid list entry in the result of past_elections_list";
 | |
|         return;
 | |
|       }
 | |
|       auto x = t2->at(0).as_int(), y = t2->at(2).as_int();
 | |
|       if (x.is_null() || y.is_null() || !x->unsigned_fits_bits(32) || !y->unsigned_fits_bits(256)) {
 | |
|         LOG(ERROR) << "invalid components in a list entry in the result of past_elections_list";
 | |
|         return;
 | |
|       }
 | |
|       td::TerminalIO::out() << "PAST_VSET\t" << td::dec_string(x) << "\t" << td::hex_string(y, true, 64) << std::endl;
 | |
|     }
 | |
|   } catch (vm::VmError& err) {
 | |
|     LOG(ERROR) << "vm error while scanning result: " << err.get_msg();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_complaints(unsigned elect_id, std::string file_pfx) {
 | |
|   return get_elector_addr([this, elect_id, file_pfx](td::Result<ton::StdSmcAddress> res) {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << res.move_as_error();
 | |
|     } else {
 | |
|       send_get_complaints_query(elect_id, res.move_as_ok(), file_pfx);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::send_get_complaints_query(unsigned elect_id, ton::StdSmcAddress elector_addr, std::string file_pfx) {
 | |
|   std::vector<vm::StackEntry> params;
 | |
|   params.emplace_back(td::make_refint(elect_id));
 | |
|   auto P = td::PromiseCreator::lambda([this, elect_id, file_pfx](td::Result<std::vector<vm::StackEntry>> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << R.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto S = R.move_as_ok();
 | |
|     if (S.size() < 1 || !(S.back().empty() || S.back().is_cell())) {
 | |
|       LOG(ERROR) << "get_past_complaints did not return a value of type cell";
 | |
|       return;
 | |
|     }
 | |
|     try {
 | |
|       save_complaints(elect_id, std::move(S.back()).as_cell(), file_pfx);
 | |
|     } catch (vm::VmError& err) {
 | |
|       LOG(ERROR) << "vm error: " << err.get_msg();
 | |
|     } catch (vm::VmVirtError& err) {
 | |
|       LOG(ERROR) << "vm virtualization error: " << err.get_msg();
 | |
|     }
 | |
|   });
 | |
|   start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "get_past_complaints", std::move(params), 0x17,
 | |
|                    std::move(P));
 | |
| }
 | |
| 
 | |
| void TestNode::save_complaints(unsigned elect_id, Ref<vm::Cell> complaints, std::string file_pfx) {
 | |
|   vm::Dictionary dict{std::move(complaints), 256};
 | |
|   for (auto entry : dict) {
 | |
|     block::gen::ValidatorComplaintStatus::Record rec;
 | |
|     block::gen::ValidatorComplaint::Record rec2;
 | |
|     if (!(tlb::csr_unpack(std::move(entry.second), rec) && tlb::unpack_cell(rec.complaint, rec2))) {
 | |
|       LOG(ERROR) << "cannot unpack complaint with key " << entry.first.to_hex(256);
 | |
|       break;
 | |
|     }
 | |
|     if (rec.complaint->get_hash().as_bitslice() != entry.first) {
 | |
|       LOG(ERROR) << "invalid complaint key " << entry.first.to_hex(256) << ": actual complaint hash is "
 | |
|                  << rec.complaint->get_hash().to_hex();
 | |
|       break;
 | |
|     }
 | |
|     std::string filename = file_pfx + entry.first.to_hex(256) + ".boc";
 | |
|     auto boc = vm::std_boc_serialize(rec.complaint, 2);
 | |
|     if (boc.is_error()) {
 | |
|       LOG(ERROR) << "cannot serialize complaint";
 | |
|       break;
 | |
|     }
 | |
|     auto len = boc.ok().size();
 | |
|     auto res1 = td::write_file(filename, boc.move_as_ok());
 | |
|     if (res1.is_error()) {
 | |
|       LOG(ERROR) << "cannot save serialized complaint to file `" << filename << "` : " << res1.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     LOG(DEBUG) << "saved " << len << " bytes into file `" << filename << "`";
 | |
|     td::TerminalIO::out() << "SAVE_COMPLAINT\t" << elect_id << '\t' << entry.first.to_hex(256) << '\t'
 | |
|                           << rec2.validator_pubkey.to_hex() << '\t' << rec2.created_at << '\t' << filename << std::endl;
 | |
|   }
 | |
| }
 | |
| 
 | |
| td::Status TestNode::get_complaint_price(unsigned expires_in, std::string filename) {
 | |
|   LOG(DEBUG) << "reading complaint file " << filename;
 | |
|   TRY_RESULT_PREFIX(data, td::read_file(filename), "cannot read complaint file:");
 | |
|   TRY_RESULT_PREFIX(complaint, vm::std_boc_deserialize(data),
 | |
|                     PSLICE() << "cannot deserialize bag-of-cells read from complaint file `" << filename << "`:");
 | |
|   if (complaint.is_null()) {
 | |
|     return td::Status::Error("complaint is null");
 | |
|   }
 | |
|   block::gen::ValidatorComplaint::Record rec;
 | |
|   if (!tlb::unpack_cell(complaint, rec)) {
 | |
|     return td::Status::Error("cannot deserialize complaint");
 | |
|   }
 | |
|   td::Bits256 chash = complaint->get_hash().bits();
 | |
|   vm::VmStorageStat stat{1 << 22};
 | |
|   if (!stat.add_storage(std::move(complaint))) {
 | |
|     return td::Status::Error("cannot compute storage size for this complaint");
 | |
|   }
 | |
|   return get_complaint_price(expires_in, (unsigned)stat.bits, (unsigned)stat.refs, chash, filename);
 | |
| }
 | |
| 
 | |
| td::Status TestNode::get_complaint_price(unsigned expires_in, unsigned bits, unsigned refs, td::Bits256 chash,
 | |
|                                          std::string filename) {
 | |
|   LOG(INFO) << "complaint `" << filename << "`: " << bits << " bits, " << refs << " references";
 | |
|   return get_elector_addr([this, filename, expires_in, bits, refs, chash](td::Result<ton::StdSmcAddress> res) {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << res.move_as_error();
 | |
|     } else {
 | |
|       send_compute_complaint_price_query(res.move_as_ok(), expires_in, bits, refs, chash, filename);
 | |
|     }
 | |
|   })
 | |
|              ? td::Status::OK()
 | |
|              : td::Status::Error("cannot obtain elector address");
 | |
| }
 | |
| 
 | |
| void TestNode::send_compute_complaint_price_query(ton::StdSmcAddress elector_addr, unsigned expires_in, unsigned bits,
 | |
|                                                   unsigned refs, td::Bits256 chash, std::string filename) {
 | |
|   std::vector<vm::StackEntry> params;
 | |
|   params.emplace_back(td::make_refint(bits));
 | |
|   params.emplace_back(td::make_refint(refs));
 | |
|   params.emplace_back(td::make_refint(expires_in));
 | |
|   auto P = td::PromiseCreator::lambda(
 | |
|       [this, expires_in, bits, refs, chash, filename](td::Result<std::vector<vm::StackEntry>> R) {
 | |
|         if (R.is_error()) {
 | |
|           LOG(ERROR) << R.move_as_error();
 | |
|           return;
 | |
|         }
 | |
|         auto S = R.move_as_ok();
 | |
|         if (S.size() < 1 || !S.back().is_int()) {
 | |
|           LOG(ERROR) << "complaint_storage_price did not return a value of type cell";
 | |
|           return;
 | |
|         }
 | |
|         try {
 | |
|           auto price = std::move(S.back()).as_int();
 | |
|           td::TerminalIO::out() << "COMPLAINT_PRICE\t" << chash.to_hex() << '\t' << td::dec_string(price) << '\t'
 | |
|                                 << bits << '\t' << refs << '\t' << expires_in << '\t' << filename << std::endl;
 | |
|         } catch (vm::VmError& err) {
 | |
|           LOG(ERROR) << "vm error: " << err.get_msg();
 | |
|         } catch (vm::VmVirtError& err) {
 | |
|           LOG(ERROR) << "vm virtualization error: " << err.get_msg();
 | |
|         }
 | |
|       });
 | |
|   start_run_method(ton::masterchainId, elector_addr, mc_last_id_, "complaint_storage_price", std::move(params), 0x17,
 | |
|                    std::move(P));
 | |
| }
 | |
| 
 | |
| bool TestNode::get_msg_queue_sizes() {
 | |
|   auto q = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getOutMsgQueueSizes>(0, 0, 0), true);
 | |
|   return envelope_send_query(std::move(q), [Self = actor_id(this)](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "liteServer.getOutMsgQueueSizes error: " << res.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_outMsgQueueSizes>(res.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getOutMsgQueueSizes";
 | |
|       return;
 | |
|     }
 | |
|     td::actor::send_closure_later(Self, &TestNode::got_msg_queue_sizes, F.move_as_ok());
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_msg_queue_sizes(ton::tl_object_ptr<ton::lite_api::liteServer_outMsgQueueSizes> f) {
 | |
|   td::TerminalIO::out() << "Outbound message queue sizes:" << std::endl;
 | |
|   for (auto &x : f->shards_) {
 | |
|     td::TerminalIO::out() << ton::create_block_id(x->id_).id.to_str() << "    " << x->size_ << std::endl;
 | |
|   }
 | |
|   td::TerminalIO::out() << "External message queue size limit: " << f->ext_msg_queue_size_limit_ << std::endl;
 | |
| }
 | |
| 
 | |
| bool TestNode::get_dispatch_queue_info(ton::BlockIdExt block_id) {
 | |
|   td::TerminalIO::out() << "Dispatch queue in block: " << block_id.id.to_str() << std::endl;
 | |
|   return get_dispatch_queue_info_cont(block_id, true, td::Bits256::zero());
 | |
| }
 | |
| 
 | |
| bool TestNode::get_dispatch_queue_info_cont(ton::BlockIdExt block_id, bool first, td::Bits256 after_addr) {
 | |
|   auto q = ton::create_serialize_tl_object<ton::lite_api::liteServer_getDispatchQueueInfo>(
 | |
|       first ? 0 : 2, ton::create_tl_lite_block_id(block_id), after_addr, 32, false);
 | |
|   return envelope_send_query(std::move(q), [=, Self = actor_id(this)](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "liteServer.getDispatchQueueInfo error: " << res.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_dispatchQueueInfo>(res.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getDispatchQueueInfo";
 | |
|       return;
 | |
|     }
 | |
|     td::actor::send_closure_later(Self, &TestNode::got_dispatch_queue_info, block_id, F.move_as_ok());
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_dispatch_queue_info(ton::BlockIdExt block_id,
 | |
|                                        ton::tl_object_ptr<ton::lite_api::liteServer_dispatchQueueInfo> info) {
 | |
|   for (auto& acc : info->account_dispatch_queues_) {
 | |
|     td::TerminalIO::out() << block_id.id.workchain << ":" << acc->addr_.to_hex() << " : size=" << acc->size_
 | |
|                           << " lt=" << acc->min_lt_ << ".." << acc->max_lt_ << std::endl;
 | |
|   }
 | |
|   if (info->complete_) {
 | |
|     td::TerminalIO::out() << "Done" << std::endl;
 | |
|     return;
 | |
|   }
 | |
|   get_dispatch_queue_info_cont(block_id, false, info->account_dispatch_queues_.back()->addr_);
 | |
| }
 | |
| 
 | |
| bool TestNode::get_dispatch_queue_messages(ton::BlockIdExt block_id, ton::WorkchainId wc, ton::StdSmcAddress addr,
 | |
|                                            ton::LogicalTime lt, bool one_account) {
 | |
|   if (wc != block_id.id.workchain) {
 | |
|     return set_error("workchain mismatch");
 | |
|   }
 | |
|   auto q = ton::create_serialize_tl_object<ton::lite_api::liteServer_getDispatchQueueMessages>(
 | |
|       one_account ? 2 : 0, ton::create_tl_lite_block_id(block_id), addr, lt, 64, false, one_account, false);
 | |
|   return envelope_send_query(std::move(q), [=, Self = actor_id(this)](td::Result<td::BufferSlice> res) -> void {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "liteServer.getDispatchQueueMessages error: " << res.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_dispatchQueueMessages>(res.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getDispatchQueueMessages";
 | |
|       return;
 | |
|     }
 | |
|     td::actor::send_closure_later(Self, &TestNode::got_dispatch_queue_messages, F.move_as_ok());
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_dispatch_queue_messages(ton::tl_object_ptr<ton::lite_api::liteServer_dispatchQueueMessages> msgs) {
 | |
|   td::TerminalIO::out() << "Dispatch queue messages (" << msgs->messages_.size() << "):\n";
 | |
|   int count = 0;
 | |
|   for (auto& m : msgs->messages_) {
 | |
|     auto& meta = m->metadata_;
 | |
|     td::TerminalIO::out() << "Msg #" << ++count << ": " << msgs->id_->workchain_ << ":" << m->addr_.to_hex() << " "
 | |
|                           << m->lt_ << " : "
 | |
|                           << (meta->initiator_->workchain_ == ton::workchainInvalid
 | |
|                                   ? "[ no metadata ]"
 | |
|                                   : block::MsgMetadata{(td::uint32)meta->depth_, meta->initiator_->workchain_,
 | |
|                                                        meta->initiator_->id_, (ton::LogicalTime)meta->initiator_lt_}
 | |
|                                         .to_str())
 | |
|                           << "\n";
 | |
|   }
 | |
|   if (!msgs->complete_) {
 | |
|     td::TerminalIO::out() << "(incomplete list)\n";
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::dns_resolve_start(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt blkid,
 | |
|                                  std::string domain, td::Bits256 cat, int mode) {
 | |
|   if (domain.size() >= 2 && domain[0] == '"' && domain.back() == '"') {
 | |
|     domain.erase(0, 1);
 | |
|     domain.pop_back();
 | |
|   }
 | |
|   std::vector<std::string> components;
 | |
|   if (domain != ".") {
 | |
|     std::size_t i, p = 0;
 | |
|     for (i = 0; i < domain.size(); i++) {
 | |
|       if (!domain[i] || (unsigned char)domain[i] >= 0xfe || (unsigned char)domain[i] <= ' ') {
 | |
|         return set_error("invalid characters in a domain name");
 | |
|       }
 | |
|       if (domain[i] == '.') {
 | |
|         if (i == p) {
 | |
|           return set_error("domain name cannot have an empty component");
 | |
|         }
 | |
|         components.emplace_back(domain, p, i - p);
 | |
|         p = i + 1;
 | |
|       }
 | |
|     }
 | |
|     if (i > p) {
 | |
|       components.emplace_back(domain, p, i - p);
 | |
|     }
 | |
|   }
 | |
|   std::string qdomain;
 | |
|   if (mode & 2) {
 | |
|     qdomain += '\0';
 | |
|   }
 | |
|   while (!components.empty()) {
 | |
|     qdomain += components.back();
 | |
|     qdomain += '\0';
 | |
|     components.pop_back();
 | |
|   }
 | |
|   if (qdomain.size() > 127) {
 | |
|     return set_error("domain name too long");
 | |
|   }
 | |
| 
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
| 
 | |
|   if (workchain == ton::workchainInvalid) {
 | |
|     if (dns_root_queried_) {
 | |
|       workchain = ton::masterchainId;
 | |
|       addr = dns_root_;
 | |
|     } else {
 | |
|       auto P =
 | |
|           td::PromiseCreator::lambda([this, blkid, domain, cat, mode](td::Result<std::unique_ptr<block::Config>> R) {
 | |
|             if (R.is_error()) {
 | |
|               LOG(ERROR) << "cannot obtain root dns address from configuration: " << R.move_as_error();
 | |
|             } else if (dns_root_queried_) {
 | |
|               dns_resolve_start(ton::masterchainId, dns_root_, blkid, domain, cat, mode);
 | |
|             } else {
 | |
|               LOG(ERROR) << "cannot obtain root dns address from configuration parameter #4";
 | |
|             }
 | |
|           });
 | |
|       return get_config_params(mc_last_id_, std::move(P), 0x3000, "", {4});
 | |
|     }
 | |
|   }
 | |
|   return dns_resolve_send(workchain, addr, blkid, domain, qdomain, cat, mode);
 | |
| }
 | |
| 
 | |
| bool TestNode::dns_resolve_send(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt blkid,
 | |
|                                 std::string domain, std::string qdomain, td::Bits256 cat, int mode) {
 | |
|   LOG(INFO) << "dns_resolve for '" << domain << "' category=" << cat << " mode=" << mode
 | |
|             << " starting from smart contract " << workchain << ":" << addr.to_hex() << " with respect to block "
 | |
|             << blkid.to_str();
 | |
|   vm::CellBuilder cb;
 | |
|   Ref<vm::Cell> cell;
 | |
|   if (!(cb.store_bytes_bool(td::Slice(qdomain)) && cb.finalize_to(cell))) {
 | |
|     return set_error("cannot store domain name into slice");
 | |
|   }
 | |
|   std::vector<vm::StackEntry> params;
 | |
|   params.emplace_back(vm::load_cell_slice_ref(cell));
 | |
|   params.emplace_back(td::bits_to_refint(cat.cbits(), 256, false));
 | |
|   auto P = td::PromiseCreator::lambda([this, workchain, addr, blkid, domain, qdomain, cat,
 | |
|                                        mode](td::Result<std::vector<vm::StackEntry>> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << R.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto S = R.move_as_ok();
 | |
|     if (S.size() < 2 || !S[S.size() - 2].is_int() || !(S.back().is_cell() || S.back().is_null())) {
 | |
|       LOG(ERROR) << "dnsresolve did not return a value of type (int,cell)";
 | |
|       return;
 | |
|     }
 | |
|     auto cell = S.back().as_cell();
 | |
|     S.pop_back();
 | |
|     auto x = S.back().as_int();
 | |
|     S.clear();
 | |
|     if (!x->signed_fits_bits(32)) {
 | |
|       LOG(ERROR) << "invalid integer result of dnsresolve (" << x << ")";
 | |
|       return;
 | |
|     }
 | |
|     return dns_resolve_finish(workchain, addr, blkid, domain, qdomain, cat, mode, (int)x->to_long(), std::move(cell));
 | |
|   });
 | |
|   return start_run_method(workchain, addr, blkid, "dnsresolve", std::move(params), 0x17, std::move(P));
 | |
| }
 | |
| 
 | |
| bool TestNode::show_dns_record(std::ostream& os, td::Bits256 cat, Ref<vm::CellSlice> value, bool raw_dump) {
 | |
|   if (raw_dump) {
 | |
|     bool ok = show_dns_record(os, cat, value, false);
 | |
|     if (!ok) {
 | |
|       os << "cannot parse dns record; raw value: ";
 | |
|       value->print_rec(print_limit_, os);
 | |
|     }
 | |
|     return ok;
 | |
|   }
 | |
|   if (value.is_null()) {
 | |
|     os << "(null)";
 | |
|     return true;
 | |
|   }
 | |
|   // block::gen::t_DNSRecord.print_ref(print_limit_, os, value);
 | |
|   if (!block::gen::t_DNSRecord.validate_csr(value)) {
 | |
|     return false;
 | |
|   }
 | |
|   block::gen::t_DNSRecord.print(os, value, 0, print_limit_);
 | |
|   auto cs = *value;
 | |
|   auto tag = block::gen::t_DNSRecord.get_tag(cs);
 | |
|   ton::WorkchainId wc;
 | |
|   ton::StdSmcAddress addr;
 | |
|   switch (tag) {
 | |
|     case block::gen::DNSRecord::dns_adnl_address: {
 | |
|       block::gen::DNSRecord::Record_dns_adnl_address rec;
 | |
|       if (tlb::unpack_exact(cs, rec)) {
 | |
|         os << "\n\tadnl address " << rec.adnl_addr.to_hex() << " = " << td::adnl_id_encode(rec.adnl_addr, true);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case block::gen::DNSRecord::dns_smc_address: {
 | |
|       block::gen::DNSRecord::Record_dns_smc_address rec;
 | |
|       if (tlb::unpack_exact(cs, rec) && block::tlb::t_MsgAddressInt.extract_std_address(rec.smc_addr, wc, addr)) {
 | |
|         os << "\tsmart contract " << wc << ":" << addr.to_hex() << " = "
 | |
|            << block::StdAddress{wc, addr}.rserialize(true);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case block::gen::DNSRecord::dns_storage_address: {
 | |
|       block::gen::DNSRecord::Record_dns_storage_address rec;
 | |
|       if (tlb::unpack_exact(cs, rec)) {
 | |
|         os << "\tstorage address " << rec.bag_id.to_hex();
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case block::gen::DNSRecord::dns_next_resolver: {
 | |
|       block::gen::DNSRecord::Record_dns_next_resolver rec;
 | |
|       if (tlb::unpack_exact(cs, rec) && block::tlb::t_MsgAddressInt.extract_std_address(rec.resolver, wc, addr)) {
 | |
|         os << "\tnext resolver " << wc << ":" << addr.to_hex() << " = " << block::StdAddress{wc, addr}.rserialize(true);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void TestNode::dns_resolve_finish(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt blkid,
 | |
|                                   std::string domain, std::string qdomain, td::Bits256 cat, int mode, int used_bits,
 | |
|                                   Ref<vm::Cell> value) {
 | |
|   if (used_bits <= 0) {
 | |
|     td::TerminalIO::out() << "domain '" << domain << "' not found" << std::endl;
 | |
|     return;
 | |
|   }
 | |
|   if ((used_bits & 7) || (unsigned)used_bits > 8 * std::min<std::size_t>(qdomain.size(), 126)) {
 | |
|     LOG(ERROR) << "too many bits used (" << used_bits << " out of " << qdomain.size() * 8 << ")";
 | |
|     return;
 | |
|   }
 | |
|   size_t pos = used_bits >> 3;
 | |
|   bool end = pos == qdomain.size();
 | |
|   if (!end && qdomain[pos - 1] && qdomain[pos]) {
 | |
|     LOG(ERROR) << "domain split not at a component boundary";
 | |
|     return;
 | |
|   }
 | |
|   if (!end) {
 | |
|     LOG(INFO) << "partial information obtained";
 | |
|     if (value.is_null()) {
 | |
|       td::TerminalIO::out() << "domain '" << domain << "' not found: no next resolver" << std::endl;
 | |
|       return;
 | |
|     }
 | |
|     Ref<vm::CellSlice> nx_address;
 | |
|     ton::WorkchainId nx_wc;
 | |
|     ton::StdSmcAddress nx_addr;
 | |
|     if (!(block::gen::t_DNSRecord.cell_unpack_dns_next_resolver(value, nx_address) &&
 | |
|           block::tlb::t_MsgAddressInt.extract_std_address(std::move(nx_address), nx_wc, nx_addr))) {
 | |
|       LOG(ERROR) << "cannot parse next resolver info for " << domain.substr(qdomain.size() - pos - 1);
 | |
|       std::ostringstream out;
 | |
|       vm::load_cell_slice(value).print_rec(print_limit_, out);
 | |
|       td::TerminalIO::err() << out.str() << std::endl;
 | |
|       return;
 | |
|     }
 | |
|     LOG(INFO) << "next resolver is " << nx_wc << ":" << nx_addr.to_hex();
 | |
|     if ((mode & 1)) {
 | |
|       return;  // no recursive resolving
 | |
|     }
 | |
|     if (!(dns_resolve_send(nx_wc, nx_addr, blkid, domain, qdomain.substr(pos), cat, mode))) {
 | |
|       LOG(ERROR) << "cannot send next dns query";
 | |
|       return;
 | |
|     }
 | |
|     LOG(INFO) << "recursive dns query to '" << domain.substr(qdomain.size() - pos - 1) << "' sent";
 | |
|     return;
 | |
|   }
 | |
|   auto out = td::TerminalIO::out();
 | |
|   if (cat.is_zero()) {
 | |
|     out << "Result for domain '" << domain << "' (all categories)" << std::endl;
 | |
|   } else {
 | |
|     out << "Result for domain '" << domain << "' category " << cat << std::endl;
 | |
|   }
 | |
|   try {
 | |
|     if (value.not_null()) {
 | |
|       std::ostringstream os0;
 | |
|       vm::load_cell_slice(value).print_rec(print_limit_, os0);
 | |
|       out << "raw data: " << os0.str() << std::endl;
 | |
|     }
 | |
|     if (cat.is_zero()) {
 | |
|       vm::Dictionary dict{value, 256};
 | |
|       if (!dict.check_for_each([this, &out](Ref<vm::CellSlice> cs, td::ConstBitPtr key, int n) {
 | |
|             CHECK(n == 256);
 | |
|             td::Bits256 x{key};
 | |
|             if (cs.is_null() || cs->size_ext() != 0x10000) {
 | |
|               out << "category " << x << " : value is not a reference" << std::endl;
 | |
|               return true;
 | |
|             }
 | |
|             cs = vm::load_cell_slice_ref(cs->prefetch_ref());
 | |
|             std::ostringstream os;
 | |
|             (void)show_dns_record(os, x, cs, true);
 | |
|             out << "category " << x << " : " << os.str() << std::endl;
 | |
|             return true;
 | |
|           })) {
 | |
|         out << "invalid dns record dictionary" << std::endl;
 | |
|       }
 | |
|     } else {
 | |
|       std::ostringstream os;
 | |
|       (void)show_dns_record(os, cat, value.is_null() ? Ref<vm::CellSlice>() : vm::load_cell_slice_ref(value), true);
 | |
|       out << "category " << cat << " : " << os.str() << std::endl;
 | |
|     }
 | |
|   } catch (vm::VmError& err) {
 | |
|     LOG(ERROR) << "vm error while traversing dns resolve result: " << err.get_msg();
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     LOG(ERROR) << "vm virtualization error while traversing dns resolve result: " << err.get_msg();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_one_transaction(ton::BlockIdExt blkid, ton::WorkchainId workchain, ton::StdSmcAddress addr,
 | |
|                                    ton::LogicalTime lt, bool dump) {
 | |
|   if (!blkid.is_valid_full()) {
 | |
|     return set_error("invalid block id");
 | |
|   }
 | |
|   if (!ton::shard_contains(blkid.shard_full(), ton::extract_addr_prefix(workchain, addr))) {
 | |
|     return set_error("the shard of this block cannot contain this account");
 | |
|   }
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   auto a = ton::create_tl_object<ton::lite_api::liteServer_accountId>(workchain, addr);
 | |
|   auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getOneTransaction>(
 | |
|                                         ton::create_tl_lite_block_id(blkid), std::move(a), lt),
 | |
|                                     true);
 | |
|   LOG(INFO) << "requesting transaction " << lt << " of " << workchain << ":" << addr.to_hex() << " from block "
 | |
|             << blkid.to_str();
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [Self = actor_id(this), workchain, addr, lt, blkid, dump](td::Result<td::BufferSlice> R) -> void {
 | |
|         if (R.is_error()) {
 | |
|           return;
 | |
|         }
 | |
|         auto F = ton::fetch_tl_object<ton::lite_api::liteServer_transactionInfo>(R.move_as_ok(), true);
 | |
|         if (F.is_error()) {
 | |
|           LOG(ERROR) << "cannot parse answer to liteServer.getOneTransaction";
 | |
|         } else {
 | |
|           auto f = F.move_as_ok();
 | |
|           td::actor::send_closure_later(Self, &TestNode::got_one_transaction, blkid, ton::create_block_id(f->id_),
 | |
|                                         std::move(f->proof_), std::move(f->transaction_), workchain, addr, lt, dump);
 | |
|         }
 | |
|       });
 | |
| }
 | |
| 
 | |
| bool TestNode::get_last_transactions(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::LogicalTime lt,
 | |
|                                      ton::Bits256 hash, unsigned count, bool dump) {
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   auto a = ton::create_tl_object<ton::lite_api::liteServer_accountId>(workchain, addr);
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getTransactions>(count, std::move(a), lt, hash), true);
 | |
|   LOG(INFO) << "requesting " << count << " last transactions from " << lt << ":" << hash.to_hex() << " of " << workchain
 | |
|             << ":" << addr.to_hex();
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [Self = actor_id(this), workchain, addr, lt, hash, count, dump](td::Result<td::BufferSlice> R) {
 | |
|         if (R.is_error()) {
 | |
|           return;
 | |
|         }
 | |
|         auto F = ton::fetch_tl_object<ton::lite_api::liteServer_transactionList>(R.move_as_ok(), true);
 | |
|         if (F.is_error()) {
 | |
|           LOG(ERROR) << "cannot parse answer to liteServer.getTransactions";
 | |
|         } else {
 | |
|           auto f = F.move_as_ok();
 | |
|           std::vector<ton::BlockIdExt> blkids;
 | |
|           for (auto& id : f->ids_) {
 | |
|             blkids.push_back(ton::create_block_id(std::move(id)));
 | |
|           }
 | |
|           td::actor::send_closure_later(Self, &TestNode::got_last_transactions, std::move(blkids),
 | |
|                                         std::move(f->transactions_), workchain, addr, lt, hash, count, dump);
 | |
|         }
 | |
|       });
 | |
| }
 | |
| 
 | |
| void TestNode::got_account_state(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton::BlockIdExt shard_blk,
 | |
|                                  td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state,
 | |
|                                  ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string filename, int mode,
 | |
|                                  bool prunned) {
 | |
|   LOG(INFO) << "got " << (prunned ? "prunned " : "") << "account state for " << workchain << ":" << addr.to_hex()
 | |
|             << " with respect to blocks " << blk.to_str()
 | |
|             << (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str());
 | |
|   block::AccountState account_state;
 | |
|   account_state.blk = blk;
 | |
|   account_state.shard_blk = shard_blk;
 | |
|   account_state.shard_proof = std::move(shard_proof);
 | |
|   account_state.proof = std::move(proof);
 | |
|   account_state.state = std::move(state);
 | |
|   account_state.is_virtualized = prunned;
 | |
|   auto r_info = account_state.validate(ref_blk, block::StdAddress(workchain, addr));
 | |
|   if (r_info.is_error()) {
 | |
|     LOG(ERROR) << r_info.error().message();
 | |
|     return;
 | |
|   }
 | |
|   auto out = td::TerminalIO::out();
 | |
|   auto info = r_info.move_as_ok();
 | |
|   if (mode < 0) {
 | |
|     if (info.root.not_null()) {
 | |
|       out << "account state is ";
 | |
|       std::ostringstream outp;
 | |
|       block::gen::t_Account.print_ref(print_limit_, outp, info.root);
 | |
|       vm::load_cell_slice(info.root).print_rec(print_limit_, outp);
 | |
|       out << outp.str();
 | |
|       out << "last transaction lt = " << info.last_trans_lt << " hash = " << info.last_trans_hash.to_hex() << std::endl;
 | |
|       block::gen::Account::Record_account acc;
 | |
|       block::gen::AccountStorage::Record store;
 | |
|       block::CurrencyCollection balance;
 | |
|       if (tlb::unpack_cell(info.root, acc) && tlb::csr_unpack(acc.storage, store) && balance.unpack(store.balance)) {
 | |
|         out << "account balance is " << balance.to_str() << std::endl;
 | |
|       }
 | |
|     } else {
 | |
|       out << "account state is empty" << std::endl;
 | |
|     }
 | |
|   } else if (info.root.not_null()) {
 | |
|     block::gen::Account::Record_account acc;
 | |
|     block::gen::AccountStorage::Record store;
 | |
|     block::CurrencyCollection balance;
 | |
|     if (!(tlb::unpack_cell(info.root, acc) && tlb::csr_unpack(acc.storage, store) && balance.unpack(store.balance))) {
 | |
|       LOG(ERROR) << "error unpacking account state";
 | |
|       return;
 | |
|     }
 | |
|     out << "account balance is " << balance.to_str() << std::endl;
 | |
|     int tag = block::gen::t_AccountState.get_tag(*store.state);
 | |
|     switch (tag) {
 | |
|       case block::gen::AccountState::account_uninit:
 | |
|         out << "account not initialized (no StateInit to save into file)" << std::endl;
 | |
|         return;
 | |
|       case block::gen::AccountState::account_frozen:
 | |
|         out << "account frozen (no StateInit to save into file)" << std::endl;
 | |
|         return;
 | |
|     }
 | |
|     CHECK(store.state.write().fetch_ulong(1) == 1);  // account_init$1 _:StateInit = AccountState;
 | |
|     block::gen::StateInit::Record state;
 | |
|     CHECK(tlb::csr_unpack(store.state, state));
 | |
|     Ref<vm::Cell> cell;
 | |
|     const char* name = "<unknown-information>";
 | |
|     if (mode == 0) {
 | |
|       // save all state
 | |
|       vm::CellBuilder cb;
 | |
|       CHECK(cb.append_cellslice_bool(store.state) && cb.finalize_to(cell));
 | |
|       name = "StateInit";
 | |
|     } else if (mode == 1) {
 | |
|       // save code
 | |
|       cell = state.code->prefetch_ref();
 | |
|       name = "code";
 | |
|     } else if (mode == 2) {
 | |
|       // save data
 | |
|       cell = state.data->prefetch_ref();
 | |
|       name = "data";
 | |
|     }
 | |
|     if (cell.is_null()) {
 | |
|       out << "no " << name << " to save to file" << std::endl;
 | |
|       return;
 | |
|     }
 | |
|     auto res = vm::std_boc_serialize(std::move(cell), 2);
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot serialize extracted information from account state : " << res.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto len = res.ok().size();
 | |
|     auto res1 = td::write_file(filename, res.move_as_ok());
 | |
|     if (res1.is_error()) {
 | |
|       LOG(ERROR) << "cannot write " << name << " of account " << workchain << ":" << addr.to_hex() << " to file `"
 | |
|                  << filename << "` : " << res1.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     out << "written " << name << " of account " << workchain << ":" << addr.to_hex() << " to file `" << filename
 | |
|         << "` (" << len << " bytes)" << std::endl;
 | |
|   } else {
 | |
|     out << "account state is empty (nothing saved to file `" << filename << "`)" << std::endl;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::run_smc_method(int mode, ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton::BlockIdExt shard_blk,
 | |
|                               td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state,
 | |
|                               ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string method,
 | |
|                               std::vector<vm::StackEntry> params, td::BufferSlice remote_c7,
 | |
|                               td::BufferSlice remote_libs, td::BufferSlice remote_result, int remote_exit_code,
 | |
|                               td::Promise<std::vector<vm::StackEntry>> promise) {
 | |
|   LOG(INFO) << "got (partial) account state (" << state.size() << " bytes) with mode=" << mode << " for " << workchain
 | |
|             << ":" << addr.to_hex() << " with respect to blocks " << blk.to_str()
 | |
|             << (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str());
 | |
|   auto out = td::TerminalIO::out();
 | |
|   try {
 | |
|     block::AccountState account_state;
 | |
|     account_state.blk = blk;
 | |
|     account_state.shard_blk = shard_blk;
 | |
|     account_state.shard_proof = std::move(shard_proof);
 | |
|     account_state.proof = std::move(proof);
 | |
|     LOG(DEBUG) << "serialized state is " << state.size() << " bytes";
 | |
|     LOG(DEBUG) << "serialized remote c7 is " << remote_c7.size() << " bytes";
 | |
|     account_state.state = std::move(state);
 | |
|     account_state.is_virtualized = (mode > 0);
 | |
|     auto r_info = account_state.validate(ref_blk, block::StdAddress(workchain, addr));
 | |
|     if (r_info.is_error()) {
 | |
|       LOG(ERROR) << r_info.error().message();
 | |
|       promise.set_error(r_info.move_as_error());
 | |
|       return;
 | |
|     }
 | |
|     auto out = td::TerminalIO::out();
 | |
|     auto info = r_info.move_as_ok();
 | |
|     if (info.root.is_null()) {
 | |
|       LOG(ERROR) << "account state of " << workchain << ":" << addr.to_hex() << " is empty (cannot run method `"
 | |
|                  << method << "`)";
 | |
|       promise.set_error(td::Status::Error(PSLICE() << "account state of " << workchain << ":" << addr.to_hex()
 | |
|                                                    << " is empty (cannot run method `" << method << "`)"));
 | |
|       return;
 | |
|     }
 | |
|     if (false) {
 | |
|       // DEBUG (dump state)
 | |
|       std::ostringstream os;
 | |
|       vm::CellSlice{vm::NoVm(), info.true_root}.print_rec(print_limit_, os);
 | |
|       out << "dump of account state (proof): " << os.str() << std::endl;
 | |
|     }
 | |
|     // set deserialization limits
 | |
|     vm::FakeVmStateLimits fstate(1000);  // limit recursive (de)serialization calls
 | |
|     vm::VmStateInterface::Guard guard(&fstate);
 | |
|     if (false && remote_c7.size()) {
 | |
|       // DEBUG (dump remote_c7)
 | |
|       auto r_c7 = vm::std_boc_deserialize(remote_c7).move_as_ok();
 | |
|       std::ostringstream os;
 | |
|       vm::StackEntry val;
 | |
|       bool ok = val.deserialize(r_c7);
 | |
|       val.dump(os);
 | |
|       // os << std::endl;
 | |
|       // block::gen::t_VmStackValue.print_ref(print_limit_, os, r_c7);
 | |
|       // os << std::endl;
 | |
|       // vm::CellSlice{vm::NoVmOrd(), r_c7}.print_rec(print_limit_, os);
 | |
|       out << "remote_c7 (deserialized=" << ok << "): " << os.str() << std::endl;
 | |
|     }
 | |
|     block::gen::Account::Record_account acc;
 | |
|     block::gen::AccountStorage::Record store;
 | |
|     block::CurrencyCollection balance;
 | |
|     if (!(tlb::unpack_cell(info.root, acc) && tlb::csr_unpack(acc.storage, store) &&
 | |
|           balance.validate_unpack(store.balance))) {
 | |
|       LOG(ERROR) << "error unpacking account state";
 | |
|       promise.set_error(td::Status::Error("error unpacking account state"));
 | |
|       return;
 | |
|     }
 | |
|     int tag = block::gen::t_AccountState.get_tag(*store.state);
 | |
|     switch (tag) {
 | |
|       case block::gen::AccountState::account_uninit:
 | |
|         LOG(ERROR) << "account " << workchain << ":" << addr.to_hex()
 | |
|                    << " not initialized yet (cannot run any methods)";
 | |
|         promise.set_error(td::Status::Error(PSLICE() << "account " << workchain << ":" << addr.to_hex()
 | |
|                                                      << " not initialized yet (cannot run any methods)"));
 | |
|         return;
 | |
|       case block::gen::AccountState::account_frozen:
 | |
|         LOG(ERROR) << "account " << workchain << ":" << addr.to_hex() << " frozen (cannot run any methods)";
 | |
|         promise.set_error(td::Status::Error(PSLICE() << "account " << workchain << ":" << addr.to_hex()
 | |
|                                                      << " frozen (cannot run any methods)"));
 | |
|         return;
 | |
|     }
 | |
|     CHECK(store.state.write().fetch_ulong(1) == 1);  // account_init$1 _:StateInit = AccountState;
 | |
|     block::gen::StateInit::Record state_init;
 | |
|     CHECK(tlb::csr_unpack(store.state, state_init));
 | |
|     auto code = state_init.code->prefetch_ref();
 | |
|     auto data = state_init.data->prefetch_ref();
 | |
|     auto stack = td::make_ref<vm::Stack>(std::move(params));
 | |
|     td::int64 method_id = compute_method_id(method);
 | |
|     stack.write().push_smallint(method_id);
 | |
|     {
 | |
|       std::ostringstream os;
 | |
|       os << "arguments: ";
 | |
|       stack->dump(os, 3);
 | |
|       out << os.str();
 | |
|     }
 | |
|     long long gas_limit = /* vm::GasLimits::infty */ 10000000;
 | |
|     // OstreamLogger ostream_logger(ctx.error_stream);
 | |
|     // auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr);
 | |
|     vm::GasLimits gas{gas_limit};
 | |
|     LOG(DEBUG) << "creating VM";
 | |
|     vm::VmState vm{code, std::move(stack), gas, 1, data, vm::VmLog()};
 | |
|     vm.set_c7(liteclient::prepare_vm_c7(info.gen_utime, info.gen_lt, td::make_ref<vm::CellSlice>(acc.addr->clone()),
 | |
|                                         balance));  // tuple with SmartContractInfo
 | |
|     // vm.incr_stack_trace(1);    // enable stack dump after each step
 | |
|     LOG(INFO) << "starting VM to run method `" << method << "` (" << method_id << ") of smart contract " << workchain
 | |
|               << ":" << addr.to_hex();
 | |
|     int exit_code;
 | |
|     try {
 | |
|       exit_code = ~vm.run();
 | |
|     } catch (vm::VmVirtError& err) {
 | |
|       LOG(ERROR) << "virtualization error while running VM to locally compute runSmcMethod result: " << err.get_msg();
 | |
|       promise.set_error(
 | |
|           td::Status::Error(PSLICE() << "virtualization error while running VM to locally compute runSmcMethod result: "
 | |
|                                      << err.get_msg()));
 | |
|       exit_code = -1001;
 | |
|     } catch (vm::VmError& err) {
 | |
|       LOG(ERROR) << "error while running VM to locally compute runSmcMethod result: " << err.get_msg();
 | |
|       promise.set_error(td::Status::Error(PSLICE() << "error while running VM to locally compute runSmcMethod result: "
 | |
|                                                    << err.get_msg()));
 | |
|       exit_code = -1000;
 | |
|     }
 | |
|     LOG(DEBUG) << "VM terminated with exit code " << exit_code;
 | |
|     if (mode > 0) {
 | |
|       LOG(DEBUG) << "remote VM exit code is " << remote_exit_code;
 | |
|       if (remote_exit_code == ~(int)vm::Excno::out_of_gas) {
 | |
|         LOG(WARNING) << "remote server ran out of gas while performing this request; consider using runmethodfull";
 | |
|       }
 | |
|     }
 | |
|     if (exit_code != 0) {
 | |
|       out << "result: error " << exit_code << std::endl;
 | |
|     } else {
 | |
|       stack = vm.get_stack_ref();
 | |
|       std::ostringstream os;
 | |
|       os << "result: ";
 | |
|       stack->dump(os, 3);
 | |
|       out << os.str();
 | |
|     }
 | |
|     if (!(mode & 4)) {
 | |
|       if (exit_code != 0) {
 | |
|         LOG(ERROR) << "VM terminated with error code " << exit_code;
 | |
|         promise.set_error(td::Status::Error(PSLICE() << "VM terminated with non-zero exit code " << exit_code));
 | |
|       } else {
 | |
|         promise.set_result(stack->extract_contents());
 | |
|       }
 | |
|     } else {
 | |
|       if (remote_exit_code != 0) {
 | |
|         out << "remote result: error " << remote_exit_code << std::endl;
 | |
|         LOG(ERROR) << "VM terminated with error code " << exit_code;
 | |
|         promise.set_error(td::Status::Error(PSLICE() << "VM terminated with non-zero exit code " << exit_code));
 | |
|       } else if (remote_result.empty()) {
 | |
|         out << "remote result: <none>" << std::endl;
 | |
|         promise.set_value({});
 | |
|       } else {
 | |
|         auto res = vm::std_boc_deserialize(std::move(remote_result));
 | |
|         if (res.is_error()) {
 | |
|           auto err = res.move_as_error();
 | |
|           LOG(ERROR) << "cannot deserialize remote VM result boc: " << err;
 | |
|           promise.set_error(
 | |
|               td::Status::Error(PSLICE() << "cannot deserialize remote VM result boc: " << std::move(err)));
 | |
|           return;
 | |
|         }
 | |
|         auto cs = vm::load_cell_slice(res.move_as_ok());
 | |
|         Ref<vm::Stack> remote_stack;
 | |
|         if (!(vm::Stack::deserialize_to(cs, remote_stack, 0) && cs.empty_ext())) {
 | |
|           LOG(ERROR) << "remote VM result boc cannot be deserialized as a VmStack";
 | |
|           promise.set_error(td::Status::Error("remote VM result boc cannot be deserialized as a VmStack"));
 | |
|           return;
 | |
|         }
 | |
|         std::ostringstream os;
 | |
|         os << "remote result (not to be trusted): ";
 | |
|         remote_stack->dump(os, 3);
 | |
|         out << os.str();
 | |
|         promise.set_value(remote_stack->extract_contents());
 | |
|       }
 | |
|     }
 | |
|     out.flush();
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     out << "virtualization error while parsing runSmcMethod result: " << err.get_msg();
 | |
|     promise.set_error(
 | |
|         td::Status::Error(PSLICE() << "virtualization error while parsing runSmcMethod result: " << err.get_msg()));
 | |
|   } catch (vm::VmError& err) {
 | |
|     out << "error while parsing runSmcMethod result: " << err.get_msg();
 | |
|     promise.set_error(td::Status::Error(PSLICE() << "error while parsing runSmcMethod result: " << err.get_msg()));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::got_one_transaction(ton::BlockIdExt req_blkid, ton::BlockIdExt blkid, td::BufferSlice proof,
 | |
|                                    td::BufferSlice transaction, ton::WorkchainId workchain, ton::StdSmcAddress addr,
 | |
|                                    ton::LogicalTime trans_lt, bool dump) {
 | |
|   LOG(INFO) << "got transaction " << trans_lt << " for " << workchain << ":" << addr.to_hex()
 | |
|             << " with respect to block " << blkid.to_str();
 | |
|   if (blkid != req_blkid) {
 | |
|     LOG(ERROR) << "obtained TransactionInfo for a different block " << blkid.to_str() << " instead of requested "
 | |
|                << req_blkid.to_str();
 | |
|     return;
 | |
|   }
 | |
|   if (!ton::shard_contains(blkid.shard_full(), ton::extract_addr_prefix(workchain, addr))) {
 | |
|     LOG(ERROR) << "received data from block " << blkid.to_str() << " that cannot contain requested account "
 | |
|                << workchain << ":" << addr.to_hex();
 | |
|     return;
 | |
|   }
 | |
|   Ref<vm::Cell> root;
 | |
|   if (!transaction.empty()) {
 | |
|     auto R = vm::std_boc_deserialize(std::move(transaction));
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << "cannot deserialize transaction";
 | |
|       return;
 | |
|     }
 | |
|     root = R.move_as_ok();
 | |
|     CHECK(root.not_null());
 | |
|   }
 | |
|   auto P = vm::std_boc_deserialize(std::move(proof));
 | |
|   if (P.is_error()) {
 | |
|     LOG(ERROR) << "cannot deserialize block transaction proof";
 | |
|     return;
 | |
|   }
 | |
|   auto proof_root = P.move_as_ok();
 | |
|   try {
 | |
|     auto block_root = vm::MerkleProof::virtualize(std::move(proof_root), 1);
 | |
|     if (block_root.is_null()) {
 | |
|       LOG(ERROR) << "transaction block proof is invalid";
 | |
|       return;
 | |
|     }
 | |
|     auto res1 = block::check_block_header_proof(block_root, blkid);
 | |
|     if (res1.is_error()) {
 | |
|       LOG(ERROR) << "error in transaction block header proof : " << res1.move_as_error().to_string();
 | |
|       return;
 | |
|     }
 | |
|     auto trans_root_res = block::get_block_transaction_try(std::move(block_root), workchain, addr, trans_lt);
 | |
|     if (trans_root_res.is_error()) {
 | |
|       LOG(ERROR) << trans_root_res.move_as_error().message();
 | |
|       return;
 | |
|     }
 | |
|     auto trans_root = trans_root_res.move_as_ok();
 | |
|     if (trans_root.is_null() && root.not_null()) {
 | |
|       LOG(ERROR) << "error checking transaction proof: proof claims there is no such transaction, but we have got "
 | |
|                     "transaction data with hash "
 | |
|                  << root->get_hash().bits().to_hex(256);
 | |
|       return;
 | |
|     }
 | |
|     if (trans_root.not_null() && root.is_null()) {
 | |
|       LOG(ERROR) << "error checking transaction proof: proof claims there is such a transaction with hash "
 | |
|                  << trans_root->get_hash().bits().to_hex(256)
 | |
|                  << ", but we have got no "
 | |
|                     "transaction data";
 | |
|       return;
 | |
|     }
 | |
|     if (trans_root.not_null() && trans_root->get_hash().bits().compare(root->get_hash().bits(), 256)) {
 | |
|       LOG(ERROR) << "transaction hash mismatch: Merkle proof expects " << trans_root->get_hash().bits().to_hex(256)
 | |
|                  << " but received data has " << root->get_hash().bits().to_hex(256);
 | |
|       return;
 | |
|     }
 | |
|   } catch (vm::VmError err) {
 | |
|     LOG(ERROR) << "error while traversing block transaction proof : " << err.get_msg();
 | |
|     return;
 | |
|   } catch (vm::VmVirtError err) {
 | |
|     LOG(ERROR) << "virtualization error while traversing block transaction proof : " << err.get_msg();
 | |
|     return;
 | |
|   }
 | |
|   auto out = td::TerminalIO::out();
 | |
|   if (root.is_null()) {
 | |
|     out << "transaction not found" << std::endl;
 | |
|   } else {
 | |
|     out << "transaction is ";
 | |
|     std::ostringstream outp;
 | |
|     block::gen::t_Transaction.print_ref(print_limit_, outp, root, 0);
 | |
|     vm::load_cell_slice(root).print_rec(print_limit_, outp);
 | |
|     out << outp.str();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool unpack_addr(std::ostream& os, Ref<vm::CellSlice> csr) {
 | |
|   ton::WorkchainId wc;
 | |
|   ton::StdSmcAddress addr;
 | |
|   if (!block::tlb::t_MsgAddressInt.extract_std_address(std::move(csr), wc, addr)) {
 | |
|     os << "<cannot unpack address>";
 | |
|     return false;
 | |
|   }
 | |
|   os << wc << ":" << addr.to_hex();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool unpack_message(std::ostream& os, Ref<vm::Cell> msg, int mode) {
 | |
|   if (msg.is_null()) {
 | |
|     os << "<message not found>";
 | |
|     return true;
 | |
|   }
 | |
|   vm::CellSlice cs{vm::NoVmOrd(), msg};
 | |
|   switch (block::gen::t_CommonMsgInfo.get_tag(cs)) {
 | |
|     case block::gen::CommonMsgInfo::ext_in_msg_info: {
 | |
|       block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
 | |
|       if (!tlb::unpack(cs, info)) {
 | |
|         LOG(DEBUG) << "cannot unpack inbound external message";
 | |
|         return false;
 | |
|       }
 | |
|       os << "EXT-IN-MSG";
 | |
|       if (!(mode & 2)) {
 | |
|         os << " TO: ";
 | |
|         if (!unpack_addr(os, std::move(info.dest))) {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
|     case block::gen::CommonMsgInfo::ext_out_msg_info: {
 | |
|       block::gen::CommonMsgInfo::Record_ext_out_msg_info info;
 | |
|       if (!tlb::unpack(cs, info)) {
 | |
|         LOG(DEBUG) << "cannot unpack outbound external message";
 | |
|         return false;
 | |
|       }
 | |
|       os << "EXT-OUT-MSG";
 | |
|       if (!(mode & 1)) {
 | |
|         os << " FROM: ";
 | |
|         if (!unpack_addr(os, std::move(info.src))) {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       os << " LT:" << info.created_lt << " UTIME:" << info.created_at;
 | |
|       return true;
 | |
|     }
 | |
|     case block::gen::CommonMsgInfo::int_msg_info: {
 | |
|       block::gen::CommonMsgInfo::Record_int_msg_info info;
 | |
|       if (!tlb::unpack(cs, info)) {
 | |
|         LOG(DEBUG) << "cannot unpack internal message";
 | |
|         return false;
 | |
|       }
 | |
|       os << "INT-MSG";
 | |
|       if (!(mode & 1)) {
 | |
|         os << " FROM: ";
 | |
|         if (!unpack_addr(os, std::move(info.src))) {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       if (!(mode & 2)) {
 | |
|         os << " TO: ";
 | |
|         if (!unpack_addr(os, std::move(info.dest))) {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|       os << " LT:" << info.created_lt << " UTIME:" << info.created_at;
 | |
|       td::RefInt256 value;
 | |
|       Ref<vm::Cell> extra;
 | |
|       if (!block::unpack_CurrencyCollection(info.value, value, extra)) {
 | |
|         LOG(ERROR) << "cannot unpack message value";
 | |
|         return false;
 | |
|       }
 | |
|       os << " VALUE:" << value;
 | |
|       if (extra.not_null()) {
 | |
|         os << "+extra";
 | |
|       }
 | |
|       return true;
 | |
|     }
 | |
|     default:
 | |
|       LOG(ERROR) << "cannot unpack message";
 | |
|       return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| std::string message_info_str(Ref<vm::Cell> msg, int mode) {
 | |
|   std::ostringstream os;
 | |
|   if (!unpack_message(os, msg, mode)) {
 | |
|     return "<cannot unpack message>";
 | |
|   } else {
 | |
|     return os.str();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::got_last_transactions(std::vector<ton::BlockIdExt> blkids, td::BufferSlice transactions_boc,
 | |
|                                      ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::LogicalTime lt,
 | |
|                                      ton::Bits256 hash, unsigned count, bool dump) {
 | |
|   LOG(INFO) << "got up to " << count << " transactions for " << workchain << ":" << addr.to_hex()
 | |
|             << " from last transaction " << lt << ":" << hash.to_hex();
 | |
|   block::TransactionList transaction_list;
 | |
|   transaction_list.blkids = blkids;
 | |
|   transaction_list.lt = lt;
 | |
|   transaction_list.hash = hash;
 | |
|   transaction_list.transactions_boc = std::move(transactions_boc);
 | |
|   auto r_account_state_info = transaction_list.validate();
 | |
|   if (r_account_state_info.is_error()) {
 | |
|     LOG(ERROR) << "got_last_transactions: " << r_account_state_info.error();
 | |
|     return;
 | |
|   }
 | |
|   auto account_state_info = r_account_state_info.move_as_ok();
 | |
|   unsigned c = 0;
 | |
|   auto out = td::TerminalIO::out();
 | |
|   CHECK(!account_state_info.transactions.empty());
 | |
|   for (auto& info : account_state_info.transactions) {
 | |
|     const auto& blkid = info.blkid;
 | |
|     out << "transaction #" << c << " from block " << blkid.to_str() << (dump ? " is " : "\n");
 | |
|     if (dump) {
 | |
|       std::ostringstream outp;
 | |
|       block::gen::t_Transaction.print_ref(print_limit_, outp, info.transaction);
 | |
|       vm::load_cell_slice(info.transaction).print_rec(print_limit_, outp);
 | |
|       out << outp.str();
 | |
|     }
 | |
|     block::gen::Transaction::Record trans;
 | |
|     if (!tlb::unpack_cell(info.transaction, trans)) {
 | |
|       LOG(ERROR) << "cannot unpack transaction #" << c;
 | |
|       return;
 | |
|     }
 | |
|     out << "  time=" << trans.now << " outmsg_cnt=" << trans.outmsg_cnt << std::endl;
 | |
|     auto in_msg = trans.r1.in_msg->prefetch_ref();
 | |
|     if (in_msg.is_null()) {
 | |
|       out << "  (no inbound message)" << std::endl;
 | |
|     } else {
 | |
|       out << "  inbound message: " << message_info_str(in_msg, 2 * 0) << std::endl;
 | |
|       if (dump) {
 | |
|         out << "    " << block::gen::t_Message_Any.as_string_ref(in_msg, 4);  // indentation = 4 spaces
 | |
|       }
 | |
|     }
 | |
|     vm::Dictionary dict{trans.r1.out_msgs, 15};
 | |
|     for (int x = 0; x < trans.outmsg_cnt && x < 100; x++) {
 | |
|       auto out_msg = dict.lookup_ref(td::BitArray<15>{x});
 | |
|       out << "  outbound message #" << x << ": " << message_info_str(out_msg, 1 * 0) << std::endl;
 | |
|       if (dump) {
 | |
|         out << "    " << block::gen::t_Message_Any.as_string_ref(out_msg, 4);
 | |
|       }
 | |
|     }
 | |
|     register_blkid(blkid);  // unsafe?
 | |
|   }
 | |
|   auto& last = account_state_info.transactions.back();
 | |
|   if (last.prev_trans_lt > 0) {
 | |
|     out << "previous transaction has lt " << last.prev_trans_lt << " hash " << last.prev_trans_hash.to_hex()
 | |
|         << std::endl;
 | |
|     if (account_state_info.transactions.size() < count) {
 | |
|       LOG(WARNING) << "obtained less transactions than required";
 | |
|     }
 | |
|   } else {
 | |
|     out << "no preceding transactions (list complete)" << std::endl;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_block_transactions(ton::BlockIdExt blkid, int mode, unsigned count, ton::Bits256 acc_addr,
 | |
|                                       ton::LogicalTime lt) {
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   auto a = ton::create_tl_object<ton::lite_api::liteServer_transactionId3>(acc_addr, lt);
 | |
|   auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_listBlockTransactions>(
 | |
|                                         ton::create_tl_lite_block_id(blkid), mode, count, std::move(a), false, false),
 | |
|                                     true);
 | |
|   LOG(INFO) << "requesting " << count << " transactions from block " << blkid.to_str() << " starting from account "
 | |
|             << acc_addr.to_hex() << " lt " << lt;
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), mode](td::Result<td::BufferSlice> R) {
 | |
|     if (R.is_error()) {
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_blockTransactions>(R.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.listBlockTransactions";
 | |
|     } else {
 | |
|       auto f = F.move_as_ok();
 | |
|       std::vector<TransId> transactions;
 | |
|       std::vector<ton::tl_object_ptr<ton::lite_api::liteServer_transactionMetadata>> metadata;
 | |
|       for (auto& id : f->ids_) {
 | |
|         transactions.emplace_back(id->account_, id->lt_, id->hash_);
 | |
|         metadata.push_back(std::move(id->metadata_));
 | |
|       }
 | |
|       td::actor::send_closure_later(Self, &TestNode::got_block_transactions, ton::create_block_id(f->id_), mode,
 | |
|                                     f->req_count_, f->incomplete_, std::move(transactions), std::move(metadata),
 | |
|                                     std::move(f->proof_));
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_block_transactions(
 | |
|     ton::BlockIdExt blkid, int mode, unsigned req_count, bool incomplete, std::vector<TestNode::TransId> trans,
 | |
|     std::vector<ton::tl_object_ptr<ton::lite_api::liteServer_transactionMetadata>> metadata, td::BufferSlice proof) {
 | |
|   LOG(INFO) << "got up to " << req_count << " transactions from block " << blkid.to_str();
 | |
|   auto out = td::TerminalIO::out();
 | |
|   int count = 0;
 | |
|   for (size_t i = 0; i < trans.size(); ++i) {
 | |
|     auto& t = trans[i];
 | |
|     out << "transaction #" << ++count << ": account " << t.acc_addr.to_hex() << " lt " << t.trans_lt << " hash "
 | |
|         << t.trans_hash.to_hex() << std::endl;
 | |
|     if (mode & 256) {
 | |
|       auto& meta = metadata.at(i);
 | |
|       if (meta == nullptr) {
 | |
|         out << "    metadata: <none>" << std::endl;
 | |
|       } else {
 | |
|         out << "    metadata: "
 | |
|             << block::MsgMetadata{(td::uint32)meta->depth_, meta->initiator_->workchain_, meta->initiator_->id_,
 | |
|                                   (ton::LogicalTime)meta->initiator_lt_}
 | |
|                    .to_str()
 | |
|             << std::endl;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   out << (incomplete ? "(block transaction list incomplete)" : "(end of block transaction list)") << std::endl;
 | |
| }
 | |
| 
 | |
| bool TestNode::get_all_shards(std::string filename, bool use_last, ton::BlockIdExt blkid) {
 | |
|   if (use_last) {
 | |
|     blkid = mc_last_id_;
 | |
|   }
 | |
|   if (!blkid.is_valid_full()) {
 | |
|     return set_error(use_last ? "must obtain last block information before making other queries"
 | |
|                               : "invalid masterchain block id");
 | |
|   }
 | |
|   if (!blkid.is_masterchain()) {
 | |
|     return set_error("only masterchain blocks contain shard configuration");
 | |
|   }
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getAllShardsInfo>(ton::create_tl_lite_block_id(blkid)), true);
 | |
|   LOG(INFO) << "requesting recent shard configuration";
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), filename](td::Result<td::BufferSlice> R) -> void {
 | |
|     if (R.is_error()) {
 | |
|       return;
 | |
|     }
 | |
|     auto F = ton::fetch_tl_object<ton::lite_api::liteServer_allShardsInfo>(R.move_as_ok(), true);
 | |
|     if (F.is_error()) {
 | |
|       LOG(ERROR) << "cannot parse answer to liteServer.getAllShardsInfo";
 | |
|     } else {
 | |
|       auto f = F.move_as_ok();
 | |
|       td::actor::send_closure_later(Self, &TestNode::got_all_shards, ton::create_block_id(f->id_), std::move(f->proof_),
 | |
|                                     std::move(f->data_), filename);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_all_shards(ton::BlockIdExt blk, td::BufferSlice proof, td::BufferSlice data, std::string filename) {
 | |
|   LOG(INFO) << "got shard configuration with respect to block " << blk.to_str();
 | |
|   if (data.empty()) {
 | |
|     td::TerminalIO::out() << "shard configuration is empty" << '\n';
 | |
|   } else {
 | |
|     auto R = vm::std_boc_deserialize(data.clone());
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << "cannot deserialize shard configuration";
 | |
|       return;
 | |
|     }
 | |
|     auto root = R.move_as_ok();
 | |
|     auto out = td::TerminalIO::out();
 | |
|     out << "shard configuration is ";
 | |
|     std::ostringstream outp;
 | |
|     block::gen::t_ShardHashes.print_ref(print_limit_, outp, root);
 | |
|     vm::load_cell_slice(root).print_rec(print_limit_, outp);
 | |
|     out << outp.str();
 | |
|     block::ShardConfig sh_conf;
 | |
|     if (!sh_conf.unpack(vm::load_cell_slice_ref(root))) {
 | |
|       out << "cannot extract shard block list from shard configuration\n";
 | |
|     } else {
 | |
|       auto ids = sh_conf.get_shard_hash_ids(true);
 | |
|       int cnt = 0;
 | |
|       for (auto id : ids) {
 | |
|         auto ref = sh_conf.get_shard_hash(ton::ShardIdFull(id));
 | |
|         if (ref.not_null()) {
 | |
|           register_blkid(ref->top_block_id());
 | |
|           out << "shard #" << ++cnt << " : " << ref->top_block_id().to_str() << " @ " << ref->created_at() << " lt "
 | |
|               << ref->start_lt() << " .. " << ref->end_lt() << std::endl;
 | |
|         } else {
 | |
|           out << "shard #" << ++cnt << " : " << id.to_str() << " (cannot unpack)\n";
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (!filename.empty()) {
 | |
|       auto res1 = td::write_file(filename, data.as_slice());
 | |
|       if (res1.is_error()) {
 | |
|         LOG(ERROR) << "cannot write shard configuration to file `" << filename << "` : " << res1.move_as_error();
 | |
|       } else {
 | |
|         out << "saved shard configuration (ShardHashes) to file `" << filename << "` (" << data.size() << " bytes)"
 | |
|             << std::endl;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| bool TestNode::parse_get_config_params(ton::BlockIdExt blkid, int mode, std::string filename, std::vector<int> params) {
 | |
|   if (mode < 0) {
 | |
|     mode = 0x80000;
 | |
|   }
 | |
|   if (!(mode & 0x81000) && !seekeoln()) {
 | |
|     mode |= 0x1000;
 | |
|     while (!seekeoln()) {
 | |
|       int x;
 | |
|       if (!convert_int32(get_word(), x)) {
 | |
|         return set_error("integer configuration parameter id expected");
 | |
|       }
 | |
|       params.push_back(x);
 | |
|     }
 | |
|   }
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     return set_error("only masterchain blocks contain configuration");
 | |
|   }
 | |
|   if (blkid == mc_last_id_) {
 | |
|     mode |= 0x2000;
 | |
|   }
 | |
|   return get_config_params(blkid, trivial_promise_of<std::unique_ptr<block::Config>>(), mode, filename,
 | |
|                            std::move(params));
 | |
| }
 | |
| 
 | |
| bool TestNode::get_config_params(ton::BlockIdExt blkid, td::Promise<std::unique_ptr<block::Config>> promise, int mode,
 | |
|                                  std::string filename, std::vector<int> params) {
 | |
|   return get_config_params_ext(blkid, promise.wrap([](ConfigInfo&& info) { return std::move(info.config); }),
 | |
|                                mode | 0x10000, filename, params);
 | |
| }
 | |
| 
 | |
| bool TestNode::get_config_params_ext(ton::BlockIdExt blkid, td::Promise<ConfigInfo> promise, int mode,
 | |
|                                      std::string filename, std::vector<int> params) {
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     promise.set_error(td::Status::Error("server connection not ready"));
 | |
|     return false;
 | |
|   }
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     promise.set_error(td::Status::Error("masterchain reference block expected"));
 | |
|     return false;
 | |
|   }
 | |
|   if (blkid == mc_last_id_) {
 | |
|     mode |= 0x2000;
 | |
|   }
 | |
|   auto params_copy = params;
 | |
|   auto b = (mode & 0x1000) ? ton::serialize_tl_object(
 | |
|                                  ton::create_tl_object<ton::lite_api::liteServer_getConfigParams>(
 | |
|                                      mode & 0x8fff, ton::create_tl_lite_block_id(blkid), std::move(params_copy)),
 | |
|                                  true)
 | |
|                            : ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getConfigAll>(
 | |
|                                                           mode & 0x8fff, ton::create_tl_lite_block_id(blkid)),
 | |
|                                                       true);
 | |
|   LOG(INFO) << "requesting " << params.size() << " configuration parameters with respect to masterchain block "
 | |
|             << blkid.to_str();
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), mode, filename, blkid, params = std::move(params),
 | |
|                                             promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|     td::actor::send_closure_later(Self, &TestNode::got_config_params, blkid, mode, filename, std::move(params),
 | |
|                                   std::move(R), std::move(promise));
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_config_params(ton::BlockIdExt req_blkid, int mode, std::string filename, std::vector<int> params,
 | |
|                                  td::Result<td::BufferSlice> R, td::Promise<ConfigInfo> promise) {
 | |
|   TRY_RESULT_PROMISE(promise, res, std::move(R));
 | |
|   TRY_RESULT_PROMISE_PREFIX(promise, f,
 | |
|                             ton::fetch_tl_object<ton::lite_api::liteServer_configInfo>(std::move(res), true),
 | |
|                             "cannot parse answer to liteServer.getConfigParams");
 | |
|   auto blkid = ton::create_block_id(f->id_);
 | |
|   LOG(INFO) << "got configuration parameters";
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     promise.set_error(td::Status::Error("reference block "s + blkid.to_str() +
 | |
|                                         " for the configuration is not a valid masterchain block"));
 | |
|     return;
 | |
|   }
 | |
|   bool from_key = (mode & 0x8000);
 | |
|   if (blkid.seqno() > req_blkid.seqno() || (!from_key && blkid != req_blkid)) {
 | |
|     promise.set_error(td::Status::Error("got configuration parameters with respect to block "s + blkid.to_str() +
 | |
|                                         " instead of " + req_blkid.to_str()));
 | |
|     return;
 | |
|   }
 | |
|   try {
 | |
|     Ref<vm::Cell> state, block, state_proof, config_proof;
 | |
|     if (!(mode & 0x10000) && !from_key) {
 | |
|       TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise, state_proof, vm::std_boc_deserialize(f->state_proof_.as_slice()),
 | |
|                                        "cannot deserialize state proof :");
 | |
|     }
 | |
|     if (!(mode & 0x10000) || from_key) {
 | |
|       TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise, config_proof, vm::std_boc_deserialize(f->config_proof_.as_slice()),
 | |
|                                        "cannot deserialize config proof :");
 | |
|     }
 | |
|     if (!from_key) {
 | |
|       TRY_RESULT_PROMISE_PREFIX_ASSIGN(
 | |
|           promise, state,
 | |
|           block::check_extract_state_proof(blkid, f->state_proof_.as_slice(), f->config_proof_.as_slice()),
 | |
|           PSLICE() << "masterchain state proof for " << blkid.to_str() << " is invalid :");
 | |
|     } else {
 | |
|       block = vm::MerkleProof::virtualize(config_proof, 1);
 | |
|       if (block.is_null()) {
 | |
|         promise.set_error(
 | |
|             td::Status::Error("cannot virtualize configuration proof constructed from key block "s + blkid.to_str()));
 | |
|         return;
 | |
|       }
 | |
|       //TRY_STATUS_PROMISE_PREFIX(promise, block::check_block_header_proof(block, blkid),
 | |
|       //                          PSLICE() << "incorrect header for key block " << blkid.to_str());
 | |
|     }
 | |
|     TRY_RESULT_PROMISE_PREFIX(promise, config,
 | |
|                               from_key ? block::Config::extract_from_key_block(block, mode & 0xfff)
 | |
|                                        : block::Config::extract_from_state(state, mode & 0xfff),
 | |
|                               "cannot unpack configuration:");
 | |
|     ConfigInfo cinfo{std::move(config), std::move(state_proof), std::move(config_proof)};
 | |
|     if (mode & 0x80000) {
 | |
|       TRY_RESULT_PROMISE_PREFIX(promise, boc, vm::std_boc_serialize(cinfo.config->get_root_cell(), 2),
 | |
|                                 "cannot serialize configuration:");
 | |
|       auto size = boc.size();
 | |
|       TRY_STATUS_PROMISE_PREFIX(promise, td::write_file(filename, std::move(boc)),
 | |
|                                 PSLICE() << "cannot save file `" << filename << "` :");
 | |
|       td::TerminalIO::out() << "saved configuration dictionary into file `" << filename << "` (" << size
 | |
|                             << " bytes written)" << std::endl;
 | |
|       promise.set_result(std::move(cinfo));
 | |
|       return;
 | |
|     }
 | |
|     if (mode & 0x4000) {
 | |
|       promise.set_result(std::move(cinfo));
 | |
|       return;
 | |
|     }
 | |
|     auto out = td::TerminalIO::out();
 | |
|     if (mode & 0x1000) {
 | |
|       for (int i : params) {
 | |
|         out << "ConfigParam(" << i << ") = ";
 | |
|         auto value = cinfo.config->get_config_param(i);
 | |
|         if (value.is_null()) {
 | |
|           out << "(null)\n";
 | |
|         } else {
 | |
|           std::ostringstream os;
 | |
|           if (i >= 0) {
 | |
|             block::gen::ConfigParam{i}.print_ref(print_limit_, os, value);
 | |
|             os << std::endl;
 | |
|           }
 | |
|           vm::load_cell_slice(value).print_rec(print_limit_, os);
 | |
|           out << os.str() << std::endl;
 | |
|           if (mode & 0x2000) {
 | |
|             register_config_param(i, value);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       cinfo.config->foreach_config_param([this, &out, mode](int i, Ref<vm::Cell> value) {
 | |
|         out << "ConfigParam(" << i << ") = ";
 | |
|         if (value.is_null()) {
 | |
|           out << "(null)\n";
 | |
|         } else {
 | |
|           std::ostringstream os;
 | |
|           if (i >= 0) {
 | |
|             block::gen::ConfigParam{i}.print_ref(print_limit_, os, value);
 | |
|             os << std::endl;
 | |
|           }
 | |
|           vm::load_cell_slice(value).print_rec(print_limit_, os);
 | |
|           out << os.str() << std::endl;
 | |
|           if (mode & 0x2000) {
 | |
|             register_config_param(i, value);
 | |
|           }
 | |
|         }
 | |
|         return true;
 | |
|       });
 | |
|     }
 | |
|     promise.set_result(std::move(cinfo));
 | |
|   } catch (vm::VmError& err) {
 | |
|     promise.set_error(err.as_status("error while traversing configuration: "));
 | |
|     return;
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     promise.set_error(err.as_status("virtualization error while traversing configuration: "));
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::register_config_param(int idx, Ref<vm::Cell> value) {
 | |
|   switch (idx) {
 | |
|     case 0:
 | |
|       return register_config_param0(std::move(value));
 | |
|     case 1:
 | |
|       return register_config_param1(std::move(value));
 | |
|     case 4:
 | |
|       return register_config_param4(std::move(value));
 | |
|     default:
 | |
|       return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::register_config_param4(Ref<vm::Cell> value) {
 | |
|   if (value.is_null()) {
 | |
|     return false;
 | |
|   }
 | |
|   vm::CellSlice cs{vm::NoVmOrd(), std::move(value)};
 | |
|   ton::StdSmcAddress addr;
 | |
|   if (cs.size_ext() == 256 && cs.fetch_bits_to(addr)) {
 | |
|     dns_root_queried_ = true;
 | |
|     if (dns_root_ != addr) {
 | |
|       dns_root_ = addr;
 | |
|       LOG(INFO) << "dns root set to -1:" << addr.to_hex();
 | |
|     }
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::register_config_param1(Ref<vm::Cell> value) {
 | |
|   if (value.is_null()) {
 | |
|     return false;
 | |
|   }
 | |
|   vm::CellSlice cs{vm::NoVmOrd(), std::move(value)};
 | |
|   ton::StdSmcAddress addr;
 | |
|   if (cs.size_ext() == 256 && cs.fetch_bits_to(addr)) {
 | |
|     elect_addr_queried_ = true;
 | |
|     if (elect_addr_ != addr) {
 | |
|       elect_addr_ = addr;
 | |
|       LOG(INFO) << "elector smart contract address set to -1:" << addr.to_hex();
 | |
|     }
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::register_config_param0(Ref<vm::Cell> value) {
 | |
|   if (value.is_null()) {
 | |
|     return false;
 | |
|   }
 | |
|   vm::CellSlice cs{vm::NoVmOrd(), std::move(value)};
 | |
|   ton::StdSmcAddress addr;
 | |
|   if (cs.size_ext() == 256 && cs.fetch_bits_to(addr)) {
 | |
|     config_addr_queried_ = true;
 | |
|     if (config_addr_ != addr) {
 | |
|       config_addr_ = addr;
 | |
|       LOG(INFO) << "configuration smart contract address set to -1:" << addr.to_hex();
 | |
|     }
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::get_block(ton::BlockIdExt blkid, bool dump) {
 | |
|   LOG(INFO) << "got block download request for " << blkid.to_str();
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getBlock>(ton::create_tl_lite_block_id(blkid)), true);
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [Self = actor_id(this), blkid, dump](td::Result<td::BufferSlice> res) -> void {
 | |
|         if (res.is_error()) {
 | |
|           LOG(ERROR) << "cannot obtain block " << blkid.to_str()
 | |
|                      << " from server : " << res.move_as_error().to_string();
 | |
|           return;
 | |
|         } else {
 | |
|           auto F = ton::fetch_tl_object<ton::lite_api::liteServer_blockData>(res.move_as_ok(), true);
 | |
|           if (F.is_error()) {
 | |
|             LOG(ERROR) << "cannot parse answer to liteServer.getBlock : " << res.move_as_error().to_string();
 | |
|           } else {
 | |
|             auto f = F.move_as_ok();
 | |
|             auto blk_id = ton::create_block_id(f->id_);
 | |
|             LOG(INFO) << "obtained block " << blk_id.to_str() << " from server";
 | |
|             if (blk_id != blkid) {
 | |
|               LOG(ERROR) << "block id mismatch: expected data for block " << blkid.to_str() << ", obtained for "
 | |
|                          << blk_id.to_str();
 | |
|               return;
 | |
|             }
 | |
|             td::actor::send_closure_later(Self, &TestNode::got_block, blk_id, std::move(f->data_), dump);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
| }
 | |
| 
 | |
| bool TestNode::get_state(ton::BlockIdExt blkid, bool dump) {
 | |
|   LOG(INFO) << "got state download request for " << blkid.to_str();
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getState>(ton::create_tl_lite_block_id(blkid)), true);
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [Self = actor_id(this), blkid, dump](td::Result<td::BufferSlice> res) -> void {
 | |
|         if (res.is_error()) {
 | |
|           LOG(ERROR) << "cannot obtain state " << blkid.to_str()
 | |
|                      << " from server : " << res.move_as_error().to_string();
 | |
|           return;
 | |
|         } else {
 | |
|           auto F = ton::fetch_tl_object<ton::lite_api::liteServer_blockState>(res.move_as_ok(), true);
 | |
|           if (F.is_error()) {
 | |
|             LOG(ERROR) << "cannot parse answer to liteServer.getState";
 | |
|           } else {
 | |
|             auto f = F.move_as_ok();
 | |
|             auto blk_id = ton::create_block_id(f->id_);
 | |
|             LOG(INFO) << "obtained state " << blk_id.to_str() << " from server";
 | |
|             if (blk_id != blkid) {
 | |
|               LOG(ERROR) << "block id mismatch: expected state for block " << blkid.to_str() << ", obtained for "
 | |
|                          << blk_id.to_str();
 | |
|               return;
 | |
|             }
 | |
|             td::actor::send_closure_later(Self, &TestNode::got_state, blk_id, f->root_hash_, f->file_hash_,
 | |
|                                           std::move(f->data_), dump);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
| }
 | |
| 
 | |
| void TestNode::got_block(ton::BlockIdExt blkid, td::BufferSlice data, bool dump) {
 | |
|   LOG(INFO) << "obtained " << data.size() << " data bytes for block " << blkid.to_str();
 | |
|   ton::FileHash fhash;
 | |
|   td::sha256(data.as_slice(), fhash.as_slice());
 | |
|   if (fhash != blkid.file_hash) {
 | |
|     LOG(ERROR) << "file hash mismatch for block " << blkid.to_str() << ": expected " << blkid.file_hash.to_hex()
 | |
|                << ", computed " << fhash.to_hex();
 | |
|     return;
 | |
|   }
 | |
|   register_blkid(blkid);
 | |
|   if (!db_root_.empty()) {
 | |
|     auto res = save_db_file(fhash, data.clone());
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "error saving block file: " << res.to_string();
 | |
|     }
 | |
|   }
 | |
|   if (dump) {
 | |
|     auto res = vm::std_boc_deserialize(std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot deserialize block data : " << res.move_as_error().to_string();
 | |
|       return;
 | |
|     }
 | |
|     auto root = res.move_as_ok();
 | |
|     ton::RootHash rhash{root->get_hash().bits()};
 | |
|     if (rhash != blkid.root_hash) {
 | |
|       LOG(ERROR) << "block root hash mismatch: data has " << rhash.to_hex() << " , expected "
 | |
|                  << blkid.root_hash.to_hex();
 | |
|       return;
 | |
|     }
 | |
|     auto out = td::TerminalIO::out();
 | |
|     out << "block contents is ";
 | |
|     std::ostringstream outp;
 | |
|     block::gen::t_Block.print_ref(print_limit_, outp, root);
 | |
|     vm::load_cell_slice(root).print_rec(print_limit_, outp);
 | |
|     out << outp.str();
 | |
|     show_block_header(blkid, std::move(root), 0xffff);
 | |
|   } else {
 | |
|     auto res = lazy_boc_deserialize(std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot lazily deserialize block data : " << res.move_as_error().to_string();
 | |
|       return;
 | |
|     }
 | |
|     auto pair = res.move_as_ok();
 | |
|     auto root = std::move(pair.first);
 | |
|     ton::RootHash rhash{root->get_hash().bits()};
 | |
|     if (rhash != blkid.root_hash) {
 | |
|       LOG(ERROR) << "block root hash mismatch: data has " << rhash.to_hex() << " , expected "
 | |
|                  << blkid.root_hash.to_hex();
 | |
|       return;
 | |
|     }
 | |
|     show_block_header(blkid, std::move(root), 0xffff);
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| void TestNode::got_state(ton::BlockIdExt blkid, ton::RootHash root_hash, ton::FileHash file_hash, td::BufferSlice data,
 | |
|                          bool dump) {
 | |
|   LOG(INFO) << "obtained " << data.size() << " state bytes for block " << blkid.to_str();
 | |
|   ton::FileHash fhash;
 | |
|   td::sha256(data.as_slice(), fhash.as_slice());
 | |
|   if (fhash != file_hash) {
 | |
|     LOG(ERROR) << "file hash mismatch for state " << blkid.to_str() << ": expected " << file_hash.to_hex()
 | |
|                << ", computed " << fhash.to_hex();
 | |
|     return;
 | |
|   }
 | |
|   register_blkid(blkid);
 | |
|   if (!db_root_.empty()) {
 | |
|     auto res = save_db_file(fhash, data.clone());
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "error saving state file: " << res.to_string();
 | |
|     }
 | |
|   }
 | |
|   if (dump) {
 | |
|     auto res = vm::std_boc_deserialize(std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot deserialize block data : " << res.move_as_error().to_string();
 | |
|       return;
 | |
|     }
 | |
|     auto root = res.move_as_ok();
 | |
|     ton::RootHash rhash{root->get_hash().bits()};
 | |
|     if (rhash != root_hash) {
 | |
|       LOG(ERROR) << "block state root hash mismatch: data has " << rhash.to_hex() << " , expected "
 | |
|                  << root_hash.to_hex();
 | |
|       return;
 | |
|     }
 | |
|     auto out = td::TerminalIO::out();
 | |
|     out << "shard state contents is ";
 | |
|     std::ostringstream outp;
 | |
|     block::gen::t_ShardState.print_ref(print_limit_, outp, root);
 | |
|     vm::load_cell_slice(root).print_rec(print_limit_, outp);
 | |
|     out << outp.str();
 | |
|     show_state_header(blkid, std::move(root), 0xffff);
 | |
|   } else {
 | |
|     auto res = lazy_boc_deserialize(std::move(data));
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot lazily deserialize block data : " << res.move_as_error().to_string();
 | |
|       return;
 | |
|     }
 | |
|     auto pair = res.move_as_ok();
 | |
|     auto root = std::move(pair.first);
 | |
|     ton::RootHash rhash{root->get_hash().bits()};
 | |
|     if (rhash != root_hash) {
 | |
|       LOG(ERROR) << "block state root hash mismatch: data has " << rhash.to_hex() << " , expected "
 | |
|                  << root_hash.to_hex();
 | |
|       return;
 | |
|     }
 | |
|     show_state_header(blkid, std::move(root), 0xffff);
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| bool TestNode::get_show_block_header(ton::BlockIdExt blkid, int mode) {
 | |
|   return get_block_header(blkid, mode, [this, blkid](td::Result<BlockHdrInfo> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << "unable to fetch block header: " << R.move_as_error();
 | |
|     } else {
 | |
|       auto res = R.move_as_ok();
 | |
|       show_block_header(res.blk_id, res.virt_blk_root, res.mode);
 | |
|       show_new_blkids();
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::get_block_header(ton::BlockIdExt blkid, int mode, td::Promise<TestNode::BlockHdrInfo> promise) {
 | |
|   LOG(INFO) << "got block header request for " << blkid.to_str() << " with mode " << mode;
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getBlockHeader>(ton::create_tl_lite_block_id(blkid), mode), true);
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [this, blkid, promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable -> void {
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, res, std::move(R),
 | |
|                                   PSLICE() << "cannot obtain block header for " << blkid.to_str() << " from server :");
 | |
|         got_block_header_raw(std::move(res), std::move(promise), blkid);
 | |
|       });
 | |
| }
 | |
| 
 | |
| bool TestNode::lookup_show_block(ton::ShardIdFull shard, int mode, td::uint64 arg) {
 | |
|   return lookup_block(shard, mode, arg, [this](td::Result<BlockHdrInfo> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << "unable to look up block: " << R.move_as_error();
 | |
|     } else {
 | |
|       auto res = R.move_as_ok();
 | |
|       show_block_header(res.blk_id, res.virt_blk_root, res.mode);
 | |
|       show_new_blkids();
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool TestNode::lookup_block(ton::ShardIdFull shard, int mode, td::uint64 arg,
 | |
|                             td::Promise<TestNode::BlockHdrInfo> promise) {
 | |
|   ton::BlockId id{shard, mode & 1 ? (td::uint32)arg : 0};
 | |
|   LOG(INFO) << "got block lookup request for " << id.to_str() << " with mode " << mode << " and argument " << arg;
 | |
|   auto b = ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_lookupBlock>(
 | |
|                                         mode, ton::create_tl_lite_block_id_simple(id), arg, (td::uint32)arg),
 | |
|                                     true);
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [this, id, mode, arg, promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable -> void {
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, res, std::move(R),
 | |
|                                   PSLICE() << "cannot look up block header for " << id.to_str() << " with mode " << mode
 | |
|                                            << " and argument " << arg << " from server :");
 | |
|         got_block_header_raw(std::move(res), std::move(promise));
 | |
|       });
 | |
| }
 | |
| 
 | |
| void TestNode::got_block_header_raw(td::BufferSlice res, td::Promise<TestNode::BlockHdrInfo> promise,
 | |
|                                     ton::BlockIdExt req_blkid) {
 | |
|   TRY_RESULT_PROMISE_PREFIX(promise, f,
 | |
|                             ton::fetch_tl_object<ton::lite_api::liteServer_blockHeader>(std::move(res), true),
 | |
|                             "cannot parse answer to liteServer.lookupBlock :");
 | |
|   auto blk_id = ton::create_block_id(f->id_);
 | |
|   LOG(INFO) << "obtained block header for " << blk_id.to_str() << " from server (" << f->header_proof_.size()
 | |
|             << " data bytes)";
 | |
|   if (req_blkid.is_valid() && blk_id != req_blkid) {
 | |
|     promise.set_error(td::Status::Error(PSLICE() << "block id mismatch: expected data for block " << req_blkid.to_str()
 | |
|                                                  << ", obtained for " << blk_id.to_str()));
 | |
|     return;
 | |
|   }
 | |
|   TRY_RESULT_PROMISE_PREFIX(promise, root, vm::std_boc_deserialize(std::move(f->header_proof_)),
 | |
|                             "cannot deserialize block header data :");
 | |
|   bool ok = false;
 | |
|   td::Status E;
 | |
|   try {
 | |
|     auto virt_root = vm::MerkleProof::virtualize(root, 1);
 | |
|     if (virt_root.is_null()) {
 | |
|       promise.set_error(td::Status::Error(PSLICE() << "block header proof for block " << blk_id.to_str()
 | |
|                                                    << " is not a valid Merkle proof"));
 | |
|       return;
 | |
|     }
 | |
|     ok = true;
 | |
|     promise.set_result(BlockHdrInfo{blk_id, std::move(root), std::move(virt_root), f->mode_});
 | |
|     return;
 | |
|   } catch (vm::VmError& err) {
 | |
|     E = err.as_status(PSLICE() << "error processing header for " << blk_id.to_str() << " :");
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     E = err.as_status(PSLICE() << "error processing header for " << blk_id.to_str() << " :");
 | |
|   }
 | |
|   if (ok) {
 | |
|     LOG(ERROR) << std::move(E);
 | |
|   } else {
 | |
|     promise.set_error(std::move(E));
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::show_block_header(ton::BlockIdExt blkid, Ref<vm::Cell> root, int mode) {
 | |
|   ton::RootHash vhash{root->get_hash().bits()};
 | |
|   if (vhash != blkid.root_hash) {
 | |
|     LOG(ERROR) << " block header for block " << blkid.to_str() << " has incorrect root hash " << vhash.to_hex()
 | |
|                << " instead of " << blkid.root_hash.to_hex();
 | |
|     return false;
 | |
|   }
 | |
|   std::vector<ton::BlockIdExt> prev;
 | |
|   ton::BlockIdExt mc_blkid;
 | |
|   bool after_split;
 | |
|   auto res = block::unpack_block_prev_blk_ext(root, blkid, prev, mc_blkid, after_split);
 | |
|   if (res.is_error()) {
 | |
|     LOG(ERROR) << "cannot unpack header for block " << blkid.to_str() << " : " << res.to_string();
 | |
|     return false;
 | |
|   }
 | |
|   block::gen::Block::Record blk;
 | |
|   block::gen::BlockInfo::Record info;
 | |
|   if (!(tlb::unpack_cell(root, blk) && tlb::unpack_cell(blk.info, info))) {
 | |
|     LOG(ERROR) << "cannot unpack header for block " << blkid.to_str();
 | |
|     return false;
 | |
|   }
 | |
|   auto out = td::TerminalIO::out();
 | |
|   out << "block header of " << blkid.to_str() << " @ " << info.gen_utime << " lt " << info.start_lt << " .. "
 | |
|       << info.end_lt << std::endl;
 | |
|   out << "global_id=" << blk.global_id << " version=" << info.version << " not_master=" << info.not_master
 | |
|       << " after_merge=" << info.after_merge << " after_split=" << info.after_split
 | |
|       << " before_split=" << info.before_split << " want_merge=" << info.want_merge << " want_split=" << info.want_split
 | |
|       << " validator_list_hash_short=" << info.gen_validator_list_hash_short
 | |
|       << " catchain_seqno=" << info.gen_catchain_seqno << " min_ref_mc_seqno=" << info.min_ref_mc_seqno;
 | |
|   if (!info.not_master) {
 | |
|     out << " is_key_block=" << info.key_block << " prev_key_block_seqno=" << info.prev_key_block_seqno;
 | |
|   }
 | |
|   out << std::endl;
 | |
|   register_blkid(blkid);
 | |
|   int cnt = 0;
 | |
|   for (auto id : prev) {
 | |
|     out << "previous block #" << ++cnt << " : " << id.to_str() << std::endl;
 | |
|     register_blkid(id);
 | |
|   }
 | |
|   out << "reference masterchain block : " << mc_blkid.to_str() << std::endl;
 | |
|   register_blkid(mc_blkid);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::show_state_header(ton::BlockIdExt blkid, Ref<vm::Cell> root, int mode) {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void TestNode::got_block_header(ton::BlockIdExt blkid, td::BufferSlice data, int mode) {
 | |
|   LOG(INFO) << "obtained " << data.size() << " data bytes as block header for " << blkid.to_str();
 | |
|   auto res = vm::std_boc_deserialize(data.clone());
 | |
|   if (res.is_error()) {
 | |
|     LOG(ERROR) << "cannot deserialize block header data : " << res.move_as_error().to_string();
 | |
|     return;
 | |
|   }
 | |
|   auto root = res.move_as_ok();
 | |
|   std::ostringstream outp;
 | |
|   vm::CellSlice cs{vm::NoVm(), root};
 | |
|   cs.print_rec(print_limit_, outp);
 | |
|   td::TerminalIO::out() << outp.str();
 | |
|   try {
 | |
|     auto virt_root = vm::MerkleProof::virtualize(root, 1);
 | |
|     if (virt_root.is_null()) {
 | |
|       LOG(ERROR) << " block header proof for block " << blkid.to_str() << " is not a valid Merkle proof";
 | |
|       return;
 | |
|     }
 | |
|     show_block_header(blkid, std::move(virt_root), mode);
 | |
|   } catch (vm::VmError err) {
 | |
|     LOG(ERROR) << "error processing header for " << blkid.to_str() << " : " << err.get_msg();
 | |
|   } catch (vm::VmVirtError err) {
 | |
|     LOG(ERROR) << "error processing header for " << blkid.to_str() << " : " << err.get_msg();
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| bool TestNode::get_block_proof(ton::BlockIdExt from, ton::BlockIdExt to, int mode) {
 | |
|   if (!(mode & 1)) {
 | |
|     to.invalidate_clear();
 | |
|   }
 | |
|   if (!(mode & 0x2000)) {
 | |
|     LOG(INFO) << "got block proof request from " << from.to_str() << " to "
 | |
|               << ((mode & 1) ? to.to_str() : "last masterchain block") << " with mode=" << mode;
 | |
|   } else {
 | |
|     LOG(DEBUG) << "got block proof request from " << from.to_str() << " to "
 | |
|                << ((mode & 1) ? to.to_str() : "last masterchain block") << " with mode=" << mode;
 | |
|   }
 | |
|   if (!from.is_masterchain_ext()) {
 | |
|     LOG(ERROR) << "source block " << from.to_str() << " is not a valid masterchain block id";
 | |
|     return false;
 | |
|   }
 | |
|   if ((mode & 1) && !to.is_masterchain_ext()) {
 | |
|     LOG(ERROR) << "destination block " << to.to_str() << " is not a valid masterchain block id";
 | |
|     return false;
 | |
|   }
 | |
|   auto b =
 | |
|       ton::serialize_tl_object(ton::create_tl_object<ton::lite_api::liteServer_getBlockProof>(
 | |
|                                    mode & 0xfff, ton::create_tl_lite_block_id(from), ton::create_tl_lite_block_id(to)),
 | |
|                                true);
 | |
|   return envelope_send_query(std::move(b), [Self = actor_id(this), from, to, mode](td::Result<td::BufferSlice> res) {
 | |
|     if (res.is_error()) {
 | |
|       LOG(ERROR) << "cannot obtain block proof for " << ((mode & 1) ? to.to_str() : "last masterchain block")
 | |
|                  << " starting from " << from.to_str() << " from server : " << res.move_as_error().to_string();
 | |
|     } else {
 | |
|       td::actor::send_closure_later(Self, &TestNode::got_block_proof, from, to, mode, res.move_as_ok());
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void TestNode::got_block_proof(ton::BlockIdExt from, ton::BlockIdExt to, int mode, td::BufferSlice pchain) {
 | |
|   LOG(INFO) << "got block proof from " << from.to_str() << " to "
 | |
|             << ((mode & 1) ? to.to_str() : "last masterchain block") << " with mode=" << mode << " (" << pchain.size()
 | |
|             << " bytes)";
 | |
|   auto r_f = ton::fetch_tl_object<ton::lite_api::liteServer_partialBlockProof>(std::move(pchain), true);
 | |
|   if (r_f.is_error()) {
 | |
|     LOG(ERROR) << "cannot deserialize liteServer.partialBlockProof: " << r_f.move_as_error();
 | |
|     return;
 | |
|   }
 | |
|   auto f = r_f.move_as_ok();
 | |
|   auto res = liteclient::deserialize_proof_chain(std::move(f));
 | |
|   if (res.is_error()) {
 | |
|     LOG(ERROR) << "cannot deserialize liteServer.partialBlockProof: " << res.move_as_error();
 | |
|     return;
 | |
|   }
 | |
|   auto chain = res.move_as_ok();
 | |
|   if (chain->from != from) {
 | |
|     LOG(ERROR) << "block proof chain starts from block " << chain->from.to_str() << ", not from requested block "
 | |
|                << from.to_str();
 | |
|     return;
 | |
|   }
 | |
|   auto err = chain->validate();
 | |
|   if (err.is_error()) {
 | |
|     LOG(ERROR) << "block proof chain is invalid: " << err;
 | |
|     return;
 | |
|   }
 | |
|   // TODO: if `from` was a trusted key block, then mark `to` as a trusted key block, and update the known value of latest trusted key block if `to` is newer
 | |
|   if (!chain->complete && (mode & 0x1000)) {
 | |
|     LOG(INFO) << "valid " << (chain->complete ? "" : "in") << "complete proof chain: last block is "
 | |
|               << chain->to.to_str() << ", last key block is "
 | |
|               << (chain->has_key_block ? chain->key_blkid.to_str() : "(undefined)");
 | |
|     get_block_proof(chain->to, to, mode | 0x2000);
 | |
|     return;
 | |
|   }
 | |
|   td::TerminalIO::out() << "valid " << (chain->complete ? "" : "in") << "complete proof chain: last block is "
 | |
|                         << chain->to.to_str() << ", last key block is "
 | |
|                         << (chain->has_key_block ? chain->key_blkid.to_str() : "(undefined)") << std::endl;
 | |
|   if (chain->has_key_block) {
 | |
|     register_blkid(chain->key_blkid);
 | |
|   }
 | |
|   register_blkid(chain->to);
 | |
|   auto time = now();
 | |
|   if (!(mode & 1) || (chain->last_utime > time - 3600)) {
 | |
|     td::TerminalIO::out() << "last block in chain was generated at " << chain->last_utime << " ("
 | |
|                           << time - chain->last_utime << " seconds ago)\n";
 | |
|   }
 | |
|   show_new_blkids();
 | |
| }
 | |
| 
 | |
| bool TestNode::get_creator_stats(ton::BlockIdExt blkid, int mode, unsigned req_count, ton::Bits256 start_after,
 | |
|                                  ton::UnixTime min_utime) {
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     return set_error("server connection not ready");
 | |
|   }
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     return set_error("only masterchain blocks contain block creator statistics");
 | |
|   }
 | |
|   if (!(mode & 1)) {
 | |
|     start_after.set_zero();
 | |
|   }
 | |
|   auto osp = std::make_unique<std::ostringstream>();
 | |
|   auto& os = *osp;
 | |
|   return get_creator_stats(
 | |
|       blkid, mode, req_count, start_after, min_utime,
 | |
|       [min_utime, &os](const td::Bits256& key, const block::DiscountedCounter& mc_cnt,
 | |
|                        const block::DiscountedCounter& shard_cnt) -> bool {
 | |
|         os << key.to_hex() << " mc_cnt:" << mc_cnt << " shard_cnt:" << shard_cnt << std::endl;
 | |
|         return true;
 | |
|       },
 | |
|       td::PromiseCreator::lambda([os = std::move(osp)](td::Result<td::Bits256> res) {
 | |
|         if (res.is_error()) {
 | |
|           LOG(ERROR) << "error obtaining creator stats: " << res.move_as_error();
 | |
|         } else {
 | |
|           if (res.ok().is_zero()) {
 | |
|             *os << "(complete)" << std::endl;
 | |
|           } else {
 | |
|             *os << "(incomplete, repeat query from " << res.move_as_ok().to_hex() << " )" << std::endl;
 | |
|           }
 | |
|           td::TerminalIO::out() << os->str();
 | |
|         }
 | |
|       }));
 | |
| }
 | |
| 
 | |
| bool TestNode::get_creator_stats(ton::BlockIdExt blkid, int mode, unsigned req_count, ton::Bits256 start_after,
 | |
|                                  ton::UnixTime min_utime, TestNode::creator_stats_func_t func,
 | |
|                                  td::Promise<td::Bits256> promise) {
 | |
|   return get_creator_stats(blkid, req_count, min_utime, std::move(func),
 | |
|                            std::make_unique<CreatorStatsRes>(mode | 0x10000, start_after),
 | |
|                            promise.wrap([](auto&& p) { return p->last_key; }));
 | |
| }
 | |
| 
 | |
| bool TestNode::get_creator_stats(ton::BlockIdExt blkid, unsigned req_count, ton::UnixTime min_utime,
 | |
|                                  TestNode::creator_stats_func_t func, std::unique_ptr<TestNode::CreatorStatsRes> state,
 | |
|                                  td::Promise<std::unique_ptr<TestNode::CreatorStatsRes>> promise) {
 | |
|   if (!(ready_ && !client_.empty())) {
 | |
|     promise.set_error(td::Status::Error("server connection not ready"));
 | |
|     return false;
 | |
|   }
 | |
|   if (!state) {
 | |
|     promise.set_error(td::Status::Error("null CreatorStatsRes"));
 | |
|     return false;
 | |
|   }
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     promise.set_error(td::Status::Error("only masterchain blocks contain block creator statistics"));
 | |
|     return false;
 | |
|   }
 | |
|   if (!(state->mode & 1)) {
 | |
|     state->last_key.set_zero();
 | |
|   }
 | |
|   auto b = ton::serialize_tl_object(
 | |
|       ton::create_tl_object<ton::lite_api::liteServer_getValidatorStats>(
 | |
|           state->mode & 0xff, ton::create_tl_lite_block_id(blkid), req_count, state->last_key, min_utime),
 | |
|       true);
 | |
|   LOG(INFO) << "requesting up to " << req_count << " block creator stats records with respect to masterchain block "
 | |
|             << blkid.to_str() << " starting from validator public key " << state->last_key.to_hex() << " created after "
 | |
|             << min_utime << " (mode=" << state->mode << ")";
 | |
|   return envelope_send_query(
 | |
|       std::move(b), [this, blkid, req_count, state = std::move(state), min_utime, func = std::move(func),
 | |
|                      promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|         TRY_RESULT_PROMISE(promise, res, std::move(R));
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, f,
 | |
|                                   ton::fetch_tl_object<ton::lite_api::liteServer_validatorStats>(std::move(res), true),
 | |
|                                   "cannot parse answer to liteServer.getValidatorStats");
 | |
|         got_creator_stats(blkid, ton::create_block_id(f->id_), f->mode_, min_utime, std::move(f->state_proof_),
 | |
|                           std::move(f->data_proof_), f->count_, req_count, f->complete_, std::move(func),
 | |
|                           std::move(state), std::move(promise));
 | |
|       });
 | |
| }
 | |
| 
 | |
| void TestNode::got_creator_stats(ton::BlockIdExt req_blkid, ton::BlockIdExt blkid, int mode, ton::UnixTime min_utime,
 | |
|                                  td::BufferSlice state_proof, td::BufferSlice data_proof, int count, int req_count,
 | |
|                                  bool complete, TestNode::creator_stats_func_t func,
 | |
|                                  std::unique_ptr<TestNode::CreatorStatsRes> status,
 | |
|                                  td::Promise<std::unique_ptr<TestNode::CreatorStatsRes>> promise) {
 | |
|   LOG(INFO) << "got answer to getValidatorStats query: " << count << " records out of " << req_count << ", "
 | |
|             << (complete ? "complete" : "incomplete");
 | |
|   if (!blkid.is_masterchain_ext()) {
 | |
|     promise.set_error(td::Status::Error(PSLICE() << "reference block " << blkid.to_str()
 | |
|                                                  << " for block creator statistics is not a valid masterchain block"));
 | |
|     return;
 | |
|   }
 | |
|   if (count > req_count) {
 | |
|     promise.set_error(td::Status::Error(PSLICE()
 | |
|                                         << "obtained " << count << " answers to getValidatorStats query, but only "
 | |
|                                         << req_count << " were requested"));
 | |
|     return;
 | |
|   }
 | |
|   if (blkid != req_blkid) {
 | |
|     promise.set_error(td::Status::Error(PSLICE()
 | |
|                                         << "answer to getValidatorStats refers to masterchain block " << blkid.to_str()
 | |
|                                         << " different from requested " << req_blkid.to_str()));
 | |
|     return;
 | |
|   }
 | |
|   TRY_RESULT_PROMISE_PREFIX(promise, state,
 | |
|                             block::check_extract_state_proof(blkid, state_proof.as_slice(), data_proof.as_slice()),
 | |
|                             PSLICE() << "masterchain state proof for " << blkid.to_str() << " is invalid :");
 | |
|   if (!(mode & 0x10000)) {
 | |
|     if (status->state_proof.is_null()) {
 | |
|       TRY_RESULT_PROMISE_PREFIX(
 | |
|           promise, state_root, vm::std_boc_deserialize(state_proof.as_slice()),
 | |
|           PSLICE() << "cannot deserialize masterchain state proof for " << blkid.to_str() << ": ");
 | |
|       status->state_proof = std::move(state_root);
 | |
|     }
 | |
|     TRY_RESULT_PROMISE_PREFIX(
 | |
|         promise, data_root, vm::std_boc_deserialize(data_proof.as_slice()),
 | |
|         PSLICE() << "cannot deserialize masterchain creators data proof for " << blkid.to_str() << ": ");
 | |
|     if (status->data_proof.is_null()) {
 | |
|       status->data_proof = std::move(data_root);
 | |
|     } else {
 | |
|       TRY_RESULT_PROMISE_PREFIX(promise, data_proof2,
 | |
|                                 vm::MerkleProof::combine_fast_status(status->data_proof, std::move(data_root)),
 | |
|                                 "cannot combine Merkle proofs for creator data");
 | |
|       status->data_proof = std::move(data_proof2);
 | |
|     }
 | |
|   }
 | |
|   bool allow_eq = (mode & 3) != 1;
 | |
|   ton::Bits256 key{status->last_key};
 | |
|   std::ostringstream os;
 | |
|   try {
 | |
|     auto dict = block::get_block_create_stats_dict(std::move(state));
 | |
|     if (!dict) {
 | |
|       promise.set_error(td::Status::Error("cannot extract BlockCreateStats from mc state"));
 | |
|       return;
 | |
|     }
 | |
|     for (int i = 0; i < count + (int)complete; i++) {
 | |
|       auto v = dict->lookup_nearest_key(key, true, allow_eq);
 | |
|       if (v.is_null()) {
 | |
|         if (i != count) {
 | |
|           promise.set_error(td::Status::Error(PSLICE() << "could fetch only " << i << " CreatorStats entries out of "
 | |
|                                                        << count << " declared in answer to getValidatorStats"));
 | |
|           return;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       block::DiscountedCounter mc_cnt, shard_cnt;
 | |
|       if (!block::unpack_CreatorStats(std::move(v), mc_cnt, shard_cnt)) {
 | |
|         promise.set_error(td::Status::Error(PSLICE() << "invalid CreatorStats record with key " << key.to_hex()));
 | |
|         return;
 | |
|       }
 | |
|       func(key, mc_cnt, shard_cnt);
 | |
|       allow_eq = false;
 | |
|     }
 | |
|     if (complete) {
 | |
|       status->last_key.set_zero();
 | |
|       status->complete = true;
 | |
|       promise.set_result(std::move(status));
 | |
|     } else if (!(status->mode & 0x100)) {
 | |
|       status->last_key = key;
 | |
|       promise.set_result(std::move(status));
 | |
|     } else {
 | |
|       // incomplete, send new query to fetch next entries
 | |
|       status->last_key = key;
 | |
|       status->mode |= 1;
 | |
|       get_creator_stats(blkid, req_count, min_utime, std::move(func), std::move(status), std::move(promise));
 | |
|     }
 | |
|   } catch (vm::VmError& err) {
 | |
|     promise.set_error(err.as_status("error while traversing block creator stats:"));
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     promise.set_error(err.as_status("virtualization error while traversing block creator stats:"));
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool TestNode::check_validator_load(int start_time, int end_time, int mode, std::string file_pfx) {
 | |
|   int time = now();
 | |
|   if (start_time <= 0) {
 | |
|     start_time += time;
 | |
|   }
 | |
|   if (end_time <= 0) {
 | |
|     end_time += time;
 | |
|   }
 | |
|   if (start_time >= end_time) {
 | |
|     return set_error("end time must be later than start time");
 | |
|   }
 | |
|   LOG(INFO) << "requesting masterchain blocks corresponding to unixtime " << start_time << " and " << end_time;
 | |
|   auto P = td::split_promise([this, mode, file_pfx](td::Result<std::pair<BlockHdrInfo, BlockHdrInfo>> R) {
 | |
|     if (R.is_error()) {
 | |
|       LOG(ERROR) << "cannot obtain block info: " << R.move_as_error();
 | |
|       return;
 | |
|     }
 | |
|     auto res = R.move_as_ok();
 | |
|     continue_check_validator_load(res.first.blk_id, res.first.proof, res.second.blk_id, res.second.proof, mode,
 | |
|                                   file_pfx);
 | |
|   });
 | |
|   lookup_block(ton::ShardIdFull(ton::masterchainId), 4, start_time, std::move(P.first));
 | |
|   return lookup_block(ton::ShardIdFull(ton::masterchainId), 4, end_time, std::move(P.second));
 | |
| }
 | |
| 
 | |
| void TestNode::continue_check_validator_load(ton::BlockIdExt blkid1, Ref<vm::Cell> root1, ton::BlockIdExt blkid2,
 | |
|                                              Ref<vm::Cell> root2, int mode, std::string file_pfx) {
 | |
|   LOG(INFO) << "continue_check_validator_load for blocks " << blkid1.to_str() << " and " << blkid2.to_str()
 | |
|             << " : requesting configuration parameter #34";
 | |
|   auto P = td::split_promise(
 | |
|       [this, blkid1, root1, blkid2, root2, mode, file_pfx](td::Result<std::pair<ConfigInfo, ConfigInfo>> R) mutable {
 | |
|         if (R.is_error()) {
 | |
|           LOG(ERROR) << "cannot obtain configuration parameter #34 : " << R.move_as_error();
 | |
|           return;
 | |
|         }
 | |
|         auto res = R.move_as_ok();
 | |
|         root1 = vm::MerkleProof::combine_fast(std::move(root1), std::move(res.first.state_proof));
 | |
|         root2 = vm::MerkleProof::combine_fast(std::move(root2), std::move(res.second.state_proof));
 | |
|         if (root1.is_null() || root2.is_null()) {
 | |
|           LOG(ERROR) << "cannot merge block header proof with block state proof";
 | |
|           return;
 | |
|         }
 | |
|         auto info1 = std::make_unique<ValidatorLoadInfo>(blkid1, std::move(root1), std::move(res.first.config_proof),
 | |
|                                                          std::move(res.first.config));
 | |
|         auto info2 = std::make_unique<ValidatorLoadInfo>(blkid2, std::move(root2), std::move(res.second.config_proof),
 | |
|                                                          std::move(res.second.config));
 | |
|         continue_check_validator_load2(std::move(info1), std::move(info2), mode, file_pfx);
 | |
|       });
 | |
|   get_config_params_ext(blkid1, std::move(P.first), 0x4000, "", {28, 34});
 | |
|   get_config_params_ext(blkid2, std::move(P.second), 0x4000, "", {28, 34});
 | |
| }
 | |
| 
 | |
| td::Status TestNode::ValidatorLoadInfo::unpack_vset() {
 | |
|   if (!config) {
 | |
|     return td::Status::Error("no configuration to unpack validator set");
 | |
|   }
 | |
|   auto vset_root_c = config->get_config_param(34);
 | |
|   if (vset_root_c.is_null()) {
 | |
|     vset_hash.set_zero();
 | |
|     return td::Status::Error("no configuration parameter 34 for block "s + blk_id.to_str());
 | |
|   }
 | |
|   if (vset_root.not_null() && vset_root->get_hash() == vset_root_c->get_hash()) {
 | |
|     vset_root_c = vset_root;
 | |
|   } else {
 | |
|     vset_root = vset_root_c;
 | |
|   }
 | |
|   vset_hash = vset_root->get_hash().bits();
 | |
|   TRY_RESULT_PREFIX_ASSIGN(
 | |
|       vset, block::Config::unpack_validator_set(vset_root),
 | |
|       PSLICE() << "cannot unpack validator set from configuration parameter 34 of block " << blk_id.to_str() << " :");
 | |
|   valid_since = vset->utime_since;
 | |
|   vset_map = vset->compute_validator_map();
 | |
|   return td::Status::OK();
 | |
| }
 | |
| 
 | |
| bool TestNode::ValidatorLoadInfo::store_record(const td::Bits256& key, const block::DiscountedCounter& mc_cnt,
 | |
|                                                const block::DiscountedCounter& shard_cnt) {
 | |
|   if (!(mc_cnt.is_valid() && shard_cnt.is_valid())) {
 | |
|     return false;
 | |
|   }
 | |
|   if (mc_cnt.total >= (1ULL << 60) || shard_cnt.total >= (1ULL << 60)) {
 | |
|     return false;
 | |
|   }
 | |
|   if (key.is_zero()) {
 | |
|     created_total.first = (td::int64)mc_cnt.total;
 | |
|     created_total.second = (td::int64)shard_cnt.total;
 | |
|     return true;
 | |
|   }
 | |
|   auto it = vset_map.find(key);
 | |
|   if (it == vset_map.end()) {
 | |
|     return false;
 | |
|   }
 | |
|   created.at(it->second) = std::make_pair(mc_cnt.total, shard_cnt.total);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TestNode::load_creator_stats(std::unique_ptr<TestNode::ValidatorLoadInfo> load_to,
 | |
|                                   td::Promise<std::unique_ptr<TestNode::ValidatorLoadInfo>> promise, bool need_proofs) {
 | |
|   if (!load_to) {
 | |
|     promise.set_error(td::Status::Error("no ValidatorLoadInfo"));
 | |
|     return false;
 | |
|   }
 | |
|   auto& info = *load_to;
 | |
|   info.created_total.first = info.created_total.second = 0;
 | |
|   info.created.resize(0);
 | |
|   info.created.resize(info.vset->total, std::make_pair<td::uint64, td::uint64>(0, 0));
 | |
|   ton::UnixTime min_utime = info.valid_since - 1000;
 | |
|   return get_creator_stats(
 | |
|       info.blk_id, 1000, min_utime,
 | |
|       [min_utime, &info](const td::Bits256& key, const block::DiscountedCounter& mc_cnt,
 | |
|                          const block::DiscountedCounter& shard_cnt) -> bool {
 | |
|         info.store_record(key, mc_cnt, shard_cnt);
 | |
|         return true;
 | |
|       },
 | |
|       std::make_unique<CreatorStatsRes>(need_proofs ? 0x100 : 0x10100),
 | |
|       td::PromiseCreator::lambda([load_to = std::move(load_to), promise = std::move(promise)](
 | |
|                                      td::Result<std::unique_ptr<CreatorStatsRes>> R) mutable {
 | |
|         TRY_RESULT_PROMISE_PREFIX(promise, res, std::move(R), "error obtaining creator stats:");
 | |
|         if (!res->complete) {
 | |
|           promise.set_error(td::Status::Error("incomplete creator stats"));
 | |
|           return;
 | |
|         }
 | |
|         // merge
 | |
|         load_to->state_proof =
 | |
|             vm::MerkleProof::combine_fast(std::move(load_to->state_proof), std::move(res->state_proof));
 | |
|         load_to->data_proof = vm::MerkleProof::combine_fast(std::move(load_to->data_proof), std::move(res->data_proof));
 | |
|         promise.set_result(std::move(load_to));
 | |
|       }));
 | |
| }
 | |
| 
 | |
| void TestNode::continue_check_validator_load2(std::unique_ptr<TestNode::ValidatorLoadInfo> info1,
 | |
|                                               std::unique_ptr<TestNode::ValidatorLoadInfo> info2, int mode,
 | |
|                                               std::string file_pfx) {
 | |
|   LOG(INFO) << "continue_check_validator_load2 for blocks " << info1->blk_id.to_str() << " and "
 | |
|             << info2->blk_id.to_str() << " : requesting block creators data";
 | |
|   td::Status st = info1->unpack_vset();
 | |
|   if (st.is_error()) {
 | |
|     LOG(ERROR) << "cannot unpack validator set from block " << info1->blk_id.to_str() << " :" << st.move_as_error();
 | |
|     return;
 | |
|   }
 | |
|   st = info2->unpack_vset();
 | |
|   if (st.is_error()) {
 | |
|     LOG(ERROR) << "cannot unpack validator set from block " << info2->blk_id.to_str() << " :" << st.move_as_error();
 | |
|     return;
 | |
|   }
 | |
|   if (info1->vset_hash != info2->vset_hash || info1->valid_since != info2->valid_since) {
 | |
|     LOG(ERROR) << "blocks appear to have different validator sets";
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "validator sets valid since " << info1->valid_since;
 | |
|   auto P = td::split_promise(
 | |
|       [this, mode,
 | |
|        file_pfx](td::Result<std::pair<std::unique_ptr<ValidatorLoadInfo>, std::unique_ptr<ValidatorLoadInfo>>> R) {
 | |
|         if (R.is_error()) {
 | |
|           LOG(ERROR) << "cannot load block creation statistics : " << R.move_as_error();
 | |
|           return;
 | |
|         }
 | |
|         auto res = R.move_as_ok();
 | |
|         continue_check_validator_load3(std::move(res.first), std::move(res.second), mode, file_pfx);
 | |
|       });
 | |
|   load_creator_stats(std::move(info1), std::move(P.first), true);
 | |
|   load_creator_stats(std::move(info2), std::move(P.second), true);
 | |
| }
 | |
| 
 | |
| // computes the probability of creating <= x blocks if the expected value is y
 | |
| static double create_prob(int x, double y) {
 | |
|   if (x < 0 || y < 0) {
 | |
|     return .5;
 | |
|   }
 | |
|   if (x >= y) {
 | |
|     return .5;
 | |
|   }
 | |
|   if (x <= 20) {
 | |
|     // Poisson
 | |
|     double t = exp(-y), s = t;
 | |
|     for (int n = 1; n <= x; n++) {
 | |
|       s += t = (t * y) / n;
 | |
|     }
 | |
|     return s;
 | |
|   }
 | |
|   // normal approximation
 | |
|   double z = (x - y) / sqrt(2. * y);
 | |
|   return (1. + erf(z)) / 2;
 | |
| }
 | |
| 
 | |
| static double shard_create_prob(int x, double y, double chunk_size) {
 | |
|   if (x < 0 || y < 0) {
 | |
|     return .5;
 | |
|   }
 | |
|   if (x >= y) {
 | |
|     return .5;
 | |
|   }
 | |
|   double y0 = y / chunk_size;  // expected chunks
 | |
|   if (!x) {
 | |
|     return y0 > 100 ? 0 : exp(-y0);  // Poisson approximation for having participated in zero chunks
 | |
|   }
 | |
|   // at least ten chunks, normal approximation
 | |
|   double z = (x - y) / sqrt(2. * y * chunk_size);
 | |
|   return (1. + erf(z)) / 2;
 | |
| }
 | |
| 
 | |
| void TestNode::continue_check_validator_load3(std::unique_ptr<TestNode::ValidatorLoadInfo> info1,
 | |
|                                               std::unique_ptr<TestNode::ValidatorLoadInfo> info2, int mode,
 | |
|                                               std::string file_pfx) {
 | |
|   LOG(INFO) << "continue_check_validator_load3 for blocks " << info1->blk_id.to_str() << " and "
 | |
|             << info2->blk_id.to_str() << " with mode=" << mode << " and file prefix `" << file_pfx;
 | |
| 
 | |
|   if (mode & 4) {
 | |
|     ton::BlockSeqno start_seqno = info1->blk_id.seqno();
 | |
|     ton::BlockSeqno end_seqno = info2->blk_id.seqno();
 | |
|     block::ValidatorSet validator_set = *info1->vset;
 | |
|     if (info1->config->get_config_param(28)->get_hash() != info2->config->get_config_param(28)->get_hash()) {
 | |
|       LOG(ERROR) << "Catchain validator config (28) changed between the first and the last block";
 | |
|       return;
 | |
|     }
 | |
|     auto catchain_config = std::make_unique<block::CatchainValidatorsConfig>(
 | |
|         block::Config::unpack_catchain_validators_config(info1->config->get_config_param(28)));
 | |
|     load_validator_shard_shares(
 | |
|         start_seqno, end_seqno, std::move(validator_set), std::move(catchain_config),
 | |
|         [=, this, info1 = std::move(info1),
 | |
|          info2 = std::move(info2)](td::Result<std::map<td::Bits256, td::uint64>> R) mutable {
 | |
|           if (R.is_error()) {
 | |
|             LOG(ERROR) << "failed to load validator shard shares: " << R.move_as_error();
 | |
|           } else {
 | |
|             continue_check_validator_load4(std::move(info1), std::move(info2), mode, file_pfx, R.move_as_ok());
 | |
|           }
 | |
|         });
 | |
|   } else {
 | |
|     continue_check_validator_load4(std::move(info1), std::move(info2), mode, std::move(file_pfx), {});
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::continue_check_validator_load4(std::unique_ptr<TestNode::ValidatorLoadInfo> info1,
 | |
|                                               std::unique_ptr<TestNode::ValidatorLoadInfo> info2, int mode,
 | |
|                                               std::string file_pfx,
 | |
|                                               std::map<td::Bits256, td::uint64> exact_shard_shares) {
 | |
|   LOG(INFO) << "continue_check_validator_load4 for blocks " << info1->blk_id.to_str() << " and "
 | |
|             << info2->blk_id.to_str() << " with mode=" << mode << " and file prefix `" << file_pfx;
 | |
|   if (info1->created_total.first <= 0 || info2->created_total.first <= 0) {
 | |
|     LOG(ERROR) << "no total created blocks statistics";
 | |
|     return;
 | |
|   }
 | |
|   td::TerminalIO::out() << "total: (" << info1->created_total.first << "," << info1->created_total.second << ") -> ("
 | |
|                         << info2->created_total.first << "," << info2->created_total.second << ")\n";
 | |
|   auto created_total_mc = info2->created_total.first - info1->created_total.first;
 | |
|   auto created_total_bc = info2->created_total.second - info1->created_total.second;
 | |
|   td::int64 created_mc_sum = 0, created_bc_sum = 0;
 | |
|   if (created_total_mc <= 0 || created_total_bc < 0 || (created_total_mc | created_total_bc) >= (1U << 31)) {
 | |
|     LOG(ERROR) << "impossible situation: zero or no blocks created: " << created_total_mc << " masterchain blocks, "
 | |
|                << created_total_bc << " shardchain blocks";
 | |
|     return;
 | |
|   }
 | |
|   int count = info1->vset->total;
 | |
|   CHECK(info2->vset->total == count);
 | |
|   CHECK((int)info1->created.size() == count);
 | |
|   CHECK((int)info2->created.size() == count);
 | |
|   std::vector<std::pair<int, int>> vals_created;
 | |
|   vals_created.reserve(count);
 | |
|   for (int i = 0; i < count; i++) {
 | |
|     auto created_mc = info2->created[i].first - info1->created[i].first;
 | |
|     auto created_bc = info2->created[i].second - info1->created[i].second;
 | |
|     if (created_mc < 0 || created_bc < 0 || (created_mc | created_bc) >= (1u << 31)) {
 | |
|       LOG(ERROR) << "impossible situation: validator #" << i << " created a negative amount of blocks: " << created_mc
 | |
|                  << " masterchain blocks, " << created_bc << " shardchain blocks";
 | |
|       return;
 | |
|     }
 | |
|     created_mc_sum += created_mc;
 | |
|     created_bc_sum += created_bc;
 | |
|     vals_created.emplace_back((int)created_mc, (int)created_bc);
 | |
|     td::TerminalIO::out() << "val #" << i << ": created (" << created_mc << "," << created_bc << ") ; was ("
 | |
|                           << info1->created[i].first << "," << info1->created[i].second << ")\n";
 | |
|   }
 | |
|   if (created_mc_sum != created_total_mc || created_bc_sum != created_total_bc) {
 | |
|     LOG(ERROR) << "cannot account for all blocks created: total is (" << created_total_mc << "," << created_total_bc
 | |
|                << "), but the sum for all validators is (" << created_mc_sum << "," << created_bc_sum << ")";
 | |
|     return;
 | |
|   }
 | |
|   td::TerminalIO::out() << "total: (" << created_total_mc << "," << created_total_bc << ")\n";
 | |
|   auto ccfg = block::Config::unpack_catchain_validators_config(info2->config->get_config_param(28));
 | |
|   auto ccfg_old = block::Config::unpack_catchain_validators_config(info1->config->get_config_param(28));
 | |
|   if (ccfg.shard_val_num != ccfg_old.shard_val_num || ccfg.shard_val_num <= 0) {
 | |
|     LOG(ERROR) << "shard validator group size changed from " << ccfg_old.shard_val_num << " to " << ccfg.shard_val_num
 | |
|                << ", or is not positive";
 | |
|     return;
 | |
|   }
 | |
|   int shard_vals = ccfg.shard_val_num, master_vals = info2->vset->main;
 | |
|   if (info1->vset->main != master_vals || master_vals <= 0) {
 | |
|     LOG(ERROR) << "masterchain validator group size changed from " << info1->vset->main << " to " << master_vals
 | |
|                << ", or is not positive";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool use_exact_shard_share = mode & 4;
 | |
|   int proofs_cnt = 0, proofs_cnt_ok = 0;
 | |
|   double chunk_size = ccfg.shard_val_lifetime / 3. / shard_vals;
 | |
| 
 | |
|   std::vector<double> mtc_shard_share;
 | |
|   if (use_exact_shard_share) {
 | |
|     LOG(INFO) << "using exact shard shares";
 | |
|     td::uint64 exact_shard_shares_sum = 0;
 | |
|     for (auto& [_, count] : exact_shard_shares) {
 | |
|       exact_shard_shares_sum += count;
 | |
|     }
 | |
|     if ((td::int64)exact_shard_shares_sum != shard_vals * created_bc_sum) {
 | |
|       LOG(ERROR) << "unexpected total shard shares: blocks=" << created_bc_sum << ", shard_vals=" << shard_vals
 | |
|                  << ", expected_sum=" << shard_vals * created_bc_sum << ", found=" << exact_shard_shares_sum;
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     LOG(INFO) << "using MtCarloComputeShare";
 | |
|     block::MtCarloComputeShare mtc(shard_vals, info2->vset->export_scaled_validator_weights());
 | |
|     if (!mtc.is_ok()) {
 | |
|       LOG(ERROR) << "failed to compute shard shares";
 | |
|       return;
 | |
|     }
 | |
|     mtc_shard_share.resize(count);
 | |
|     for (size_t i = 0; i < count; ++i) {
 | |
|       mtc_shard_share[i] = mtc[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   auto validators = info1->vset->export_validator_set();
 | |
|   for (int i = 0; i < count; i++) {
 | |
|     int created_mc = vals_created[i].first, created_bc = vals_created[i].second;
 | |
|     bool is_masterchain_validator = i < master_vals;
 | |
| 
 | |
|     double expected_created_mc = (is_masterchain_validator ? (double)created_mc_sum / master_vals : 0);
 | |
|     double prob_mc = create_prob(created_mc, .9 * expected_created_mc);
 | |
| 
 | |
|     double expected_created_bc, prob_bc;
 | |
|     if (use_exact_shard_share) {
 | |
|       expected_created_bc = (double)exact_shard_shares[validators[i].key.as_bits256()] / shard_vals;
 | |
|       prob_bc = create_prob(created_bc, .9 * expected_created_bc);
 | |
|     } else {
 | |
|       expected_created_bc = mtc_shard_share[i] * (double)created_bc_sum / shard_vals;
 | |
|       prob_bc = shard_create_prob(created_bc, .9 * expected_created_bc, chunk_size);
 | |
|     }
 | |
| 
 | |
|     td::Bits256 pk = info2->vset->list[i].pubkey.as_bits256();
 | |
|     td::TerminalIO::out() << "val #" << i << ": pubkey " << pk.to_hex() << ", blocks created (" << created_mc << ","
 | |
|                           << created_bc << "), expected (" << expected_created_mc << "," << expected_created_bc
 | |
|                           << "), probabilities " << prob_mc << " and " << prob_bc << "\n";
 | |
|     if ((is_masterchain_validator ? prob_mc : prob_bc) < .00001) {
 | |
|       LOG(ERROR) << "validator #" << i << " with pubkey " << pk.to_hex()
 | |
|                  << " : serious misbehavior detected: created less than 90% of the expected amount of blocks with "
 | |
|                     "probability 99.999% : created ("
 | |
|                  << created_mc << "," << created_bc << "), expected (" << expected_created_mc << ","
 | |
|                  << expected_created_bc << ") masterchain/shardchain blocks\n";
 | |
|       if (mode & 2) {
 | |
|         auto st = write_val_create_proof(*info1, *info2, i, true, file_pfx, ++proofs_cnt);
 | |
|         if (st.is_error()) {
 | |
|           LOG(ERROR) << "cannot create proof: " << st.move_as_error();
 | |
|         } else {
 | |
|           proofs_cnt_ok++;
 | |
|         }
 | |
|       }
 | |
|     } else if ((is_masterchain_validator ? prob_mc : prob_bc) < .005) {
 | |
|       LOG(ERROR) << "validator #" << i << " with pubkey " << pk.to_hex()
 | |
|                  << " : moderate misbehavior detected: created less than 90% of the expected amount of blocks with "
 | |
|                     "probability 99.5% : created ("
 | |
|                  << created_mc << "," << created_bc << "), expected (" << expected_created_mc << ","
 | |
|                  << expected_created_bc << ") masterchain/shardchain blocks\n";
 | |
|       if ((mode & 3) == 2) {
 | |
|         auto st = write_val_create_proof(*info1, *info2, i, false, file_pfx, ++proofs_cnt);
 | |
|         if (st.is_error()) {
 | |
|           LOG(ERROR) << "cannot create proof: " << st.move_as_error();
 | |
|         } else {
 | |
|           proofs_cnt_ok++;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (proofs_cnt > 0) {
 | |
|     LOG(INFO) << proofs_cnt_ok << " out of " << proofs_cnt << " proofs written to " << file_pfx << "-*.boc";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void TestNode::load_validator_shard_shares(ton::BlockSeqno start_seqno, ton::BlockSeqno end_seqno,
 | |
|                                            block::ValidatorSet validator_set,
 | |
|                                            std::unique_ptr<block::CatchainValidatorsConfig> catchain_config,
 | |
|                                            td::Promise<std::map<td::Bits256, td::uint64>> promise) {
 | |
|   CHECK(start_seqno <= end_seqno);
 | |
|   LOG(INFO) << "loading shard shares from mc blocks " << start_seqno << ".." << end_seqno << " ("
 | |
|             << end_seqno - start_seqno + 1 << " blocks)";
 | |
|   auto state = std::make_shared<LoadValidatorShardSharesState>();
 | |
|   state->start_seqno = start_seqno;
 | |
|   state->end_seqno = end_seqno;
 | |
|   state->validator_set = std::move(validator_set);
 | |
|   state->catchain_config = std::move(catchain_config);
 | |
|   state->shard_configs.resize(end_seqno - start_seqno + 1);
 | |
|   state->promise = std::move(promise);
 | |
|   load_validator_shard_shares_cont(std::move(state));
 | |
| }
 | |
| 
 | |
| void TestNode::load_validator_shard_shares_cont(std::shared_ptr<LoadValidatorShardSharesState> state) {
 | |
|   if (!state->promise) {
 | |
|     return;
 | |
|   }
 | |
|   if (state->loaded % 100 == 0) {
 | |
|     LOG(INFO) << "loaded " << state->loaded << "/" << state->shard_configs.size() << " mc blocks";
 | |
|   }
 | |
|   while (state->cur_idx < state->shard_configs.size() && state->pending < 8) {
 | |
|     load_block_shard_configuration(state->start_seqno + state->cur_idx,
 | |
|                                    [this, state, idx = state->cur_idx](td::Result<block::ShardConfig> R) mutable {
 | |
|                                      if (R.is_error()) {
 | |
|                                        state->promise.set_error(R.move_as_error());
 | |
|                                        state->promise = {};
 | |
|                                      } else {
 | |
|                                        state->shard_configs[idx] = R.move_as_ok();
 | |
|                                        --state->pending;
 | |
|                                        ++state->loaded;
 | |
|                                        load_validator_shard_shares_cont(std::move(state));
 | |
|                                      }
 | |
|                                    });
 | |
|     ++state->pending;
 | |
|     ++state->cur_idx;
 | |
|   }
 | |
| 
 | |
|   if (state->loaded != state->shard_configs.size()) {
 | |
|     return;
 | |
|   }
 | |
|   LOG(INFO) << "loaded all " << state->shard_configs.size() << " mc blocks, computing shard shares";
 | |
|   std::map<td::Bits256, td::uint64> result;
 | |
|   try {
 | |
|     for (size_t idx = 0; idx + 1 < state->shard_configs.size(); ++idx) {
 | |
|       block::ShardConfig& shards1 = state->shard_configs[idx];
 | |
|       block::ShardConfig& shards2 = state->shard_configs[idx + 1];
 | |
| 
 | |
|       // Compute validator groups, see ValidatorManagerImpl::update_shards
 | |
|       auto process_shard = [&](ton::ShardIdFull shard, ton::BlockSeqno first_seqno) {
 | |
|         auto desc2 = shards2.get_shard_hash(shard);
 | |
|         if (desc2.is_null() || desc2->seqno() < first_seqno) {
 | |
|           return;
 | |
|         }
 | |
|         td::uint32 blocks_count = desc2->seqno() - first_seqno + 1;
 | |
|         ton::CatchainSeqno cc_seqno = shards1.get_shard_cc_seqno(shard);
 | |
|         auto val_set =
 | |
|             block::ConfigInfo::do_compute_validator_set(*state->catchain_config, shard, state->validator_set, cc_seqno);
 | |
|         for (const auto &val : val_set) {
 | |
|           result[val.key.as_bits256()] += blocks_count;
 | |
|         }
 | |
|       };
 | |
| 
 | |
|       for (const ton::BlockId& id : shards1.get_shard_hash_ids()) {
 | |
|         ton::ShardIdFull shard = id.shard_full();
 | |
|         auto desc = shards1.get_shard_hash(shard);
 | |
|         CHECK(desc.not_null());
 | |
|         if (desc->before_split()) {
 | |
|           ton::ShardIdFull l_shard = shard_child(shard, true);
 | |
|           ton::ShardIdFull r_shard = shard_child(shard, false);
 | |
|           process_shard(l_shard, desc->seqno() + 1);
 | |
|           process_shard(r_shard, desc->seqno() + 1);
 | |
|         } else if (desc->before_merge()) {
 | |
|           if (is_right_child(shard)) {
 | |
|             continue;
 | |
|           }
 | |
|           ton::ShardIdFull sibling_shard = shard_sibling(shard);
 | |
|           auto sibling_desc = shards1.get_shard_hash(sibling_shard);
 | |
|           CHECK(sibling_desc.not_null());
 | |
|           ton::ShardIdFull p_shard = shard_parent(shard);
 | |
|           process_shard(p_shard, std::max(desc->seqno(), sibling_desc->seqno()) + 1);
 | |
|         } else {
 | |
|           process_shard(shard, desc->seqno() + 1);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } catch (vm::VmError &e) {
 | |
|     state->promise.set_error(e.as_status("cannot parse shard hashes: "));
 | |
|     return;
 | |
|   }
 | |
|   state->promise.set_value(std::move(result));
 | |
| }
 | |
| 
 | |
| void TestNode::load_block_shard_configuration(ton::BlockSeqno seqno, td::Promise<block::ShardConfig> promise) {
 | |
|   lookup_block(
 | |
|       ton::ShardIdFull{ton::masterchainId}, 1, seqno,
 | |
|       [this, promise = std::move(promise)](td::Result<BlockHdrInfo> R) mutable {
 | |
|         TRY_RESULT_PROMISE(promise, res, std::move(R));
 | |
|         auto b = ton::serialize_tl_object(
 | |
|             ton::create_tl_object<ton::lite_api::liteServer_getAllShardsInfo>(ton::create_tl_lite_block_id(res.blk_id)),
 | |
|             true);
 | |
|         envelope_send_query(std::move(b), [this, promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
 | |
|           TRY_RESULT_PROMISE(promise, data, std::move(R));
 | |
|           TRY_RESULT_PROMISE(promise, f, ton::fetch_tl_object<ton::lite_api::liteServer_allShardsInfo>(data, true));
 | |
|           TRY_RESULT_PROMISE(promise, root, vm::std_boc_deserialize(f->data_));
 | |
|           block::ShardConfig sh_conf;
 | |
|           if (!sh_conf.unpack(load_cell_slice_ref(root))) {
 | |
|             promise.set_error(td::Status::Error("cannot extract shard block list from shard configuration"));
 | |
|           } else {
 | |
|             promise.set_value(std::move(sh_conf));
 | |
|           }
 | |
|         });
 | |
|       });
 | |
| }
 | |
| 
 | |
| bool compute_punishment_default(int interval, bool severe, td::RefInt256& fine, unsigned& fine_part) {
 | |
|   if (interval <= 1000) {
 | |
|     return false;  // no punishments for less than 1000 seconds
 | |
|   }
 | |
| 
 | |
|   fine = td::make_refint(101 * 1000000000LL);  // 101
 | |
|   fine_part = 0;
 | |
| 
 | |
|   return true; // todo: (tolya-yanot) temporary reduction of fine
 | |
| 
 | |
|   if (severe) {
 | |
|     fine = td::make_refint(2500 * 1000000000LL);  // GR$2500
 | |
|     fine_part = (1 << 30);                        // 1/4 of stake
 | |
|   } else {
 | |
|     fine = td::make_refint(1000 * 1000000000LL);  // GR$1000
 | |
|     fine_part = (1 << 28);                        // 1/16 of stake
 | |
|   }
 | |
|   if (interval >= 80000) {
 | |
|     return true;
 | |
|   }
 | |
|   if (interval >= 20000) {
 | |
|     fine >>= 2;
 | |
|     fine_part >>= 2;
 | |
|     return true;
 | |
|   }
 | |
|   fine >>= 4;
 | |
|   fine_part >>= 4;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool compute_punishment(int interval, bool severe, td::RefInt256& fine, unsigned& fine_part, Ref<vm::Cell> punishment_params) {
 | |
|   if(punishment_params.is_null()) {
 | |
|     return compute_punishment_default(interval, severe, fine, fine_part);
 | |
|   }
 | |
|   block::gen::MisbehaviourPunishmentConfig::Record rec;
 | |
|   if (!tlb::unpack_cell(punishment_params, rec)) {
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   if(interval <= rec.unpunishable_interval) {
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   fine = block::tlb::t_Grams.as_integer(rec.default_flat_fine);
 | |
|   fine_part = rec.default_proportional_fine;
 | |
| 
 | |
|   if (severe) {
 | |
|     fine = fine * rec.severity_flat_mult; fine >>= 8;
 | |
|     fine_part = fine_part * rec.severity_proportional_mult; fine_part >>= 8;
 | |
|   }
 | |
| 
 | |
|   if (interval >= rec.long_interval) {
 | |
|     fine = fine * rec.long_flat_mult; fine >>= 8;
 | |
|     fine_part = fine_part * rec.long_proportional_mult; fine_part >>= 8;
 | |
|     return true;
 | |
|   }
 | |
|   if (interval >= rec.medium_interval) {
 | |
|     fine = fine * rec.medium_flat_mult; fine >>= 8;
 | |
|     fine_part = fine_part * rec.medium_proportional_mult; fine_part >>= 8;
 | |
|     return true;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool check_punishment(int interval, bool severe, td::RefInt256 fine, unsigned fine_part, Ref<vm::Cell> punishment_params) {
 | |
|   td::RefInt256 computed_fine;
 | |
|   unsigned computed_fine_part;
 | |
|   return compute_punishment(interval, severe, computed_fine, computed_fine_part, punishment_params) &&
 | |
|          std::llabs((long long)fine_part - (long long)computed_fine_part) <=
 | |
|              (std::max(fine_part, computed_fine_part) >> 3) &&
 | |
|          fine * 7 <= computed_fine * 8 && computed_fine * 7 <= fine * 8;
 | |
| }
 | |
| 
 | |
| td::Status TestNode::write_val_create_proof(TestNode::ValidatorLoadInfo& info1, TestNode::ValidatorLoadInfo& info2,
 | |
|                                             int idx, bool severe, std::string file_pfx, int cnt) {
 | |
|   std::string filename = PSTRING() << file_pfx << '-' << cnt << ".boc";
 | |
|   if (!info1.has_data()) {
 | |
|     return td::Status::Error("first block information is incomplete");
 | |
|   }
 | |
|   if (!info2.has_data()) {
 | |
|     return td::Status::Error("second block information is incomplete");
 | |
|   }
 | |
|   LOG(INFO) << "creating proof file " << filename;
 | |
|   TRY_STATUS(info1.check_header_proof(&info1.block_created_at, &info1.end_lt));
 | |
|   TRY_STATUS(info2.check_header_proof(&info2.block_created_at, &info2.end_lt));
 | |
|   td::Bits256 val_pk1, val_pk2;
 | |
|   TRY_RESULT(prod1, info1.build_producer_info(idx, &val_pk1));
 | |
|   TRY_RESULT(prod2, info2.build_producer_info(idx, &val_pk2));
 | |
|   if (val_pk1 != val_pk2) {
 | |
|     return td::Status::Error("validator public key mismatch");
 | |
|   }
 | |
|   int interval = (int)(info2.block_created_at - info1.block_created_at);
 | |
|   if (interval <= 0) {
 | |
|     return td::Status::Error("non-positive time interval");
 | |
|   }
 | |
| 
 | |
|   auto punishment_params = info2.config->get_config_param(40);
 | |
| 
 | |
|   int severity = (severe ? 2 : 1);
 | |
|   td::RefInt256 fine = td::make_refint(101000000000);
 | |
|   unsigned fine_part = 0; // todo: (tolya-yanot) temporary reduction of fine  // 0xffffffff / 16;  // 1/16
 | |
|   if (!compute_punishment(interval, severe, fine, fine_part, punishment_params)) {
 | |
|     return td::Status::Error("cannot compute adequate punishment");
 | |
|   }
 | |
|   Ref<vm::Cell> cpl_descr, complaint;
 | |
|   vm::CellBuilder cb;
 | |
|   // no_blk_gen_diff prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr
 | |
|   if (!(block::gen::t_ComplaintDescr.cell_pack_no_blk_gen_diff(cpl_descr, prod1, prod2) &&
 | |
|         cb.store_long_bool(0xbc, 8)                                    // validator_complaint#bc
 | |
|         && cb.store_bits_bool(val_pk1)                                 // validator_pubkey:uint256
 | |
|         && cb.store_ref_bool(cpl_descr)                                // description:^ComplaintDescr
 | |
|         && cb.store_long_bool(now(), 32)                               // created_at:uint32
 | |
|         && cb.store_long_bool(severity, 8)                             // severity:uint8
 | |
|         && cb.store_zeroes_bool(256)                                   // reward_addr:uint256
 | |
|         && cb.store_zeroes_bool(4)                                     // paid:Grams
 | |
|         && block::tlb::t_Grams.store_integer_ref(cb, std::move(fine))  // suggested_fine:Grams
 | |
|         && cb.store_long_bool(fine_part, 32)                           // suggested_fine_part:uint32
 | |
|         && cb.finalize_to(complaint))) {
 | |
|     return td::Status::Error("cannot serialize ValidatorComplaint");
 | |
|   }
 | |
|   if (verbosity >= 5) {
 | |
|     std::ostringstream os;
 | |
|     os << "complaint: ";
 | |
|     block::gen::t_ValidatorComplaint.print_ref(print_limit_, os, complaint);
 | |
|     td::TerminalIO::out() << os.str() << std::endl;
 | |
|   }
 | |
|   if (!block::gen::t_ComplaintDescr.validate_ref(cpl_descr)) {
 | |
|     return td::Status::Error("created an invalid ComplaintDescr");
 | |
|   }
 | |
|   if (!block::gen::t_ValidatorComplaint.validate_ref(complaint)) {
 | |
|     return td::Status::Error("created an invalid ValidatorComplaint");
 | |
|   }
 | |
|   TRY_RESULT_PREFIX(boc, vm::std_boc_serialize(complaint, 2), "cannot create boc:");
 | |
|   auto size = boc.size();
 | |
|   TRY_STATUS_PREFIX(td::write_file(filename, std::move(boc)), PSLICE() << "cannot save file `" << filename << "` :");
 | |
|   td::TerminalIO::out() << "saved validator misbehavior proof into file `" << filename << "` (" << size
 | |
|                         << " bytes written)" << std::endl;
 | |
|   td::TerminalIO::out() << "COMPLAINT_SAVED\t" << info1.vset_hash.to_hex() << "\t" << complaint->get_hash().to_hex()
 | |
|                         << "\t" << filename << std::endl;
 | |
|   return td::Status::OK();
 | |
| }
 | |
| 
 | |
| td::Status TestNode::ValidatorLoadInfo::check_header_proof(ton::UnixTime* save_utime, ton::LogicalTime* save_lt) const {
 | |
|   auto state_virt_root = vm::MerkleProof::virtualize(std::move(data_proof), 1);
 | |
|   if (state_virt_root.is_null()) {
 | |
|     return td::Status::Error("account state proof is invalid");
 | |
|   }
 | |
|   td::Bits256 state_hash = state_virt_root->get_hash().bits();
 | |
|   TRY_STATUS(block::check_block_header_proof(vm::MerkleProof::virtualize(state_proof, 1), blk_id, &state_hash, true,
 | |
|                                              save_utime, save_lt));
 | |
|   return td::Status::OK();
 | |
| }
 | |
| 
 | |
| static bool visit(Ref<vm::Cell> cell);
 | |
| 
 | |
| static bool visit(const vm::CellSlice& cs) {
 | |
|   auto cnt = cs.size_refs();
 | |
|   bool res = true;
 | |
|   for (unsigned i = 0; i < cnt; i++) {
 | |
|     res &= visit(cs.prefetch_ref(i));
 | |
|   }
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| static bool visit(Ref<vm::Cell> cell) {
 | |
|   if (cell.is_null()) {
 | |
|     return true;
 | |
|   }
 | |
|   vm::CellSlice cs{vm::NoVm{}, std::move(cell)};
 | |
|   return visit(cs);
 | |
| }
 | |
| 
 | |
| static bool visit(Ref<vm::CellSlice> cs_ref) {
 | |
|   return cs_ref.is_null() || visit(*cs_ref);
 | |
| }
 | |
| 
 | |
| td::Result<Ref<vm::Cell>> TestNode::ValidatorLoadInfo::build_proof(int idx, td::Bits256* save_pubkey) const {
 | |
|   try {
 | |
|     auto state_virt_root = vm::MerkleProof::virtualize(std::move(data_proof), 1);
 | |
|     if (state_virt_root.is_null()) {
 | |
|       return td::Status::Error("account state proof is invalid");
 | |
|     }
 | |
|     vm::MerkleProofBuilder pb{std::move(state_virt_root)};
 | |
|     TRY_RESULT(cfg, block::Config::extract_from_state(pb.root()));
 | |
|     visit(cfg->get_config_param(28));
 | |
|     block::gen::ValidatorSet::Record_validators_ext rec;
 | |
|     if (!tlb::unpack_cell(cfg->get_config_param(34), rec)) {
 | |
|       return td::Status::Error("cannot unpack ValidatorSet");
 | |
|     }
 | |
|     vm::Dictionary vdict{rec.list, 16};
 | |
|     auto entry = vdict.lookup(td::BitArray<16>(idx));
 | |
|     if (entry.is_null()) {
 | |
|       return td::Status::Error("validator entry not found");
 | |
|     }
 | |
|     Ref<vm::CellSlice> pk;
 | |
|     block::gen::ValidatorDescr::Record_validator rec1;
 | |
|     block::gen::ValidatorDescr::Record_validator_addr rec2;
 | |
|     if (tlb::csr_unpack(entry, rec1)) {
 | |
|       pk = std::move(rec1.public_key);
 | |
|     } else if (tlb::csr_unpack(std::move(entry), rec2)) {
 | |
|       pk = std::move(rec2.public_key);
 | |
|     } else {
 | |
|       return td::Status::Error("cannot unpack ValidatorDescr");
 | |
|     }
 | |
|     block::gen::SigPubKey::Record rec3;
 | |
|     if (!tlb::csr_unpack(std::move(pk), rec3)) {
 | |
|       return td::Status::Error("cannot unpack ed25519_pubkey");
 | |
|     }
 | |
|     if (save_pubkey) {
 | |
|       *save_pubkey = rec3.pubkey;
 | |
|     }
 | |
|     visit(std::move(entry));
 | |
|     auto dict = block::get_block_create_stats_dict(pb.root());
 | |
|     if (!dict) {
 | |
|       return td::Status::Error("cannot extract BlockCreateStats from mc state");
 | |
|     }
 | |
|     visit(dict->lookup(rec3.pubkey));
 | |
|     visit(dict->lookup(td::Bits256::zero()));
 | |
|     return pb.extract_proof();
 | |
|   } catch (vm::VmError& err) {
 | |
|     return err.as_status("cannot build proof: ");
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     return err.as_status("cannot build proof: ");
 | |
|   }
 | |
| }
 | |
| 
 | |
| td::Result<Ref<vm::Cell>> TestNode::ValidatorLoadInfo::build_producer_info(int idx, td::Bits256* save_pubkey) const {
 | |
|   TRY_RESULT(proof, build_proof(idx, save_pubkey));
 | |
|   vm::CellBuilder cb;
 | |
|   Ref<vm::Cell> res;
 | |
|   if (!(cb.store_long_bool(0x34, 8)                           // prod_info#34
 | |
|         && cb.store_long_bool(block_created_at, 32)           // utime:uint32
 | |
|         && block::tlb::t_ExtBlkRef.store(cb, blk_id, end_lt)  // mc_blk_ref:ExtBlkRef
 | |
|         && cb.store_ref_bool(state_proof)                     // state_proof:^Cell
 | |
|         && cb.store_ref_bool(proof)                           // prod_proof:^Cell = ProducerInfo
 | |
|         && cb.finalize_to(res))) {
 | |
|     return td::Status::Error("cannot construct ProducerInfo");
 | |
|   }
 | |
|   if (!block::gen::t_ProducerInfo.validate_ref(res)) {
 | |
|     return td::Status::Error("constructed ProducerInfo failed to pass automated validity checks");
 | |
|   }
 | |
|   return std::move(res);
 | |
| }
 | |
| 
 | |
| td::Status TestNode::check_validator_load_proof(std::string filename, std::string vset_filename,
 | |
|                                                 ton::Bits256 vset_hash) {
 | |
|   TRY_RESULT_PREFIX(data, td::read_file(filename), "cannot read proof file:");
 | |
|   TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(std::move(data)),
 | |
|                     PSTRING() << "cannot deserialize boc from file `" << filename << "`:");
 | |
|   Ref<vm::Cell> vset_root;
 | |
|   if (!vset_filename.empty()) {
 | |
|     TRY_RESULT_PREFIX(vdata, td::read_file(vset_filename), "cannot read validator set file:");
 | |
|     TRY_RESULT_PREFIX_ASSIGN(vset_root, vm::std_boc_deserialize(std::move(vdata)),
 | |
|                              PSLICE() << "cannot deserialize validator set boc from file `" << vset_filename << "`:");
 | |
|     if (vset_hash.is_zero()) {
 | |
|       vset_hash = vset_root->get_hash().bits();
 | |
|     } else if (vset_hash != vset_root->get_hash().bits()) {
 | |
|       return td::Status::Error("validator set hash mismatch: indicated "s + vset_hash.to_hex() + ", loaded from file " +
 | |
|                                vset_root->get_hash().to_hex());
 | |
|     }
 | |
|   }
 | |
|   if (verbosity >= 5) {
 | |
|     std::ostringstream os;
 | |
|     os << "complaint: ";
 | |
|     block::gen::t_ValidatorComplaint.print_ref(print_limit_, os, root);
 | |
|     td::TerminalIO::out() << os.str() << std::endl;
 | |
|   }
 | |
|   if (!block::gen::t_ValidatorComplaint.validate_ref(root)) {
 | |
|     return td::Status::Error("proof file does not contain a valid ValidatorComplaint");
 | |
|   }
 | |
|   block::gen::ValidatorComplaint::Record rec;
 | |
|   if (!tlb::unpack_cell(root, rec)) {
 | |
|     return td::Status::Error("cannot unpack ValidatorComplaint");
 | |
|   }
 | |
|   auto cs = vm::load_cell_slice(rec.description);
 | |
|   int tag = block::gen::t_ComplaintDescr.get_tag(cs);
 | |
|   if (tag < 0) {
 | |
|     return td::Status::Error("ComplaintDescr has an unknown tag");
 | |
|   }
 | |
|   if (tag != block::gen::ComplaintDescr::no_blk_gen_diff) {
 | |
|     return td::Status::Error("can check only ComplaintDescr of type no_blk_gen_diff");
 | |
|   }
 | |
|   block::gen::ComplaintDescr::Record_no_blk_gen_diff crec;
 | |
|   if (!tlb::unpack_exact(cs, crec)) {
 | |
|     return td::Status::Error("cannot unpack ComplaintDescr");
 | |
|   }
 | |
|   TRY_RESULT_PREFIX(info1, ValidatorLoadInfo::preinit_from_producer_info(crec.prod_info_old),
 | |
|                     "cannot unpack ProducerInfo in prod_info_old:")
 | |
|   TRY_RESULT_PREFIX(info2, ValidatorLoadInfo::preinit_from_producer_info(crec.prod_info_new),
 | |
|                     "cannot unpack ProducerInfo in prod_info_new:")
 | |
|   if (info1->vset_hash != info2->vset_hash) {
 | |
|     return td::Status::Error("validator hash changed between the two blocks: "s + info1->vset_hash.to_hex() + " and " +
 | |
|                              info2->vset_hash.to_hex());
 | |
|   }
 | |
|   if (vset_hash.is_zero()) {
 | |
|     vset_hash = info1->vset_hash;
 | |
|   } else if (vset_hash != info1->vset_hash) {
 | |
|     return td::Status::Error("validator set hash mismatch: blocks have "s + info1->vset_hash.to_hex() +
 | |
|                              ", actual value is " + vset_hash.to_hex());
 | |
|   }
 | |
|   auto blkid2 = info2->blk_id;
 | |
|   if (vset_root.not_null()) {
 | |
|     info1->vset_root = info2->vset_root = std::move(vset_root);
 | |
|     set_error(continue_check_validator_load_proof(std::move(info1), std::move(info2), std::move(root)));
 | |
|   } else if (!get_config_params(blkid2,
 | |
|                                 [this, info1 = std::move(info1), info2 = std::move(info2),
 | |
|                                  root = std::move(root)](td::Result<std::unique_ptr<block::Config>> res) mutable {
 | |
|                                   if (res.is_error()) {
 | |
|                                     LOG(ERROR)
 | |
|                                         << "cannot fetch configuration parameters from key block corresponding to "
 | |
|                                         << info2->blk_id.to_str() << " : " << res.move_as_error();
 | |
|                                   } else {
 | |
|                                     auto vset_root = res.move_as_ok()->get_config_param(34);
 | |
|                                     if (vset_root.is_null()) {
 | |
|                                       LOG(ERROR) << "no configuration parameter #34 in key block corresponding to "
 | |
|                                                  << info2->blk_id.to_str();
 | |
|                                     } else if (info2->vset_hash != vset_root->get_hash().bits()) {
 | |
|                                       LOG(ERROR) << "validator hash set mismatch for block " << info2->blk_id.to_str();
 | |
|                                     } else {
 | |
|                                       info1->vset_root = info2->vset_root = std::move(vset_root);
 | |
|                                       set_error(continue_check_validator_load_proof(std::move(info1), std::move(info2),
 | |
|                                                                                     std::move(root)));
 | |
|                                     }
 | |
|                                   }
 | |
|                                 },
 | |
|                                 0xd000, "", {28, 34})) {
 | |
|     return td::Status::Error("cannot request configuration parameters from key block corresponding to "s +
 | |
|                              blkid2.to_str());
 | |
|   }
 | |
|   return td::Status::OK();
 | |
| }
 | |
| 
 | |
| static void show_vote(td::Bits256 complaint_hash, bool outcome) {
 | |
|   td::TerminalIO::out() << "COMPLAINT_VOTE_FOR\t" << complaint_hash.to_hex() << "\t" << (outcome ? "YES" : "NO")
 | |
|                         << std::endl;
 | |
| }
 | |
| 
 | |
| td::Status TestNode::continue_check_validator_load_proof(std::unique_ptr<ValidatorLoadInfo> info1,
 | |
|                                                          std::unique_ptr<ValidatorLoadInfo> info2, Ref<vm::Cell> root) {
 | |
|   TRY_STATUS(info1->unpack_vset());
 | |
|   TRY_STATUS(info2->unpack_vset());
 | |
|   int interval = (int)(info2->block_created_at - info1->block_created_at);
 | |
|   if (interval <= 0) {
 | |
|     return td::Status::Error("non-positive time interval");
 | |
|   }
 | |
|   block::gen::ValidatorComplaint::Record rec;
 | |
|   block::gen::ComplaintDescr::Record_no_blk_gen_diff crec;
 | |
|   if (!(tlb::unpack_cell(root, rec) && tlb::unpack_cell(rec.description, crec))) {
 | |
|     return td::Status::Error("cannot unpack ValidatorComplaint second time (?)");
 | |
|   }
 | |
|   td::Bits256 val_pubkey = rec.validator_pubkey;
 | |
|   int val_idx = info1->vset->lookup_public_key(val_pubkey);
 | |
|   if (val_idx < 0) {
 | |
|     return td::Status::Error("validator with public key "s + val_pubkey.to_hex() +
 | |
|                              " is not present in active validator set");
 | |
|   }
 | |
|   TRY_STATUS(info1->load_special_creator_stat(val_pubkey, true));
 | |
|   TRY_STATUS(info2->load_special_creator_stat(val_pubkey, true));
 | |
|   td::TerminalIO::out() << "total: (" << info1->created_total.first << "," << info1->created_total.second << ") -> ("
 | |
|                         << info2->created_total.first << "," << info2->created_total.second << ")\n";
 | |
|   auto x = info2->created_total.first - info1->created_total.first;
 | |
|   auto y = info2->created_total.second - info1->created_total.second;
 | |
|   if (x <= 0 || y < 0 || (x | y) >= (1u << 31)) {
 | |
|     return td::Status::Error(PSLICE() << "impossible situation: zero or no blocks created: " << x
 | |
|                                       << " masterchain blocks, " << y << " shardchain blocks");
 | |
|   }
 | |
|   auto x1 = info2->created_special.first - info1->created_special.first;
 | |
|   auto y1 = info2->created_special.second - info1->created_special.second;
 | |
|   if ((x1 | y1) < 0 || (x1 | y1) >= (1u << 31)) {
 | |
|     return td::Status::Error(PSLICE() << "impossible situation: validator " << val_pubkey.to_hex() << " created " << x1
 | |
|                                       << " masterchain blocks, " << y1 << " shardchain blocks");
 | |
|   }
 | |
|   td::TerminalIO::out() << "total: (" << x << "," << y << ")\n";
 | |
|   try {
 | |
|     auto ccfg = block::Config::unpack_catchain_validators_config(info2->config->get_config_param(28));
 | |
|     auto ccfg_old = block::Config::unpack_catchain_validators_config(info1->config->get_config_param(28));
 | |
|     if (ccfg.shard_val_num != ccfg_old.shard_val_num || ccfg.shard_val_num <= 0) {
 | |
|       return td::Status::Error(PSLICE() << "shard validator group size changed from " << ccfg_old.shard_val_num
 | |
|                                         << " to " << ccfg.shard_val_num << ", or is not positive");
 | |
|     }
 | |
|     int shard_count = ccfg.shard_val_num, main_count = info2->vset->main;
 | |
|     if (info1->vset->main != main_count || main_count <= 0) {
 | |
|       return td::Status::Error(PSLICE() << "masterchain validator group size changed from " << info1->vset->main
 | |
|                                         << " to " << main_count << ", or is not positive");
 | |
|     }
 | |
|     double chunk_size = ccfg.shard_val_lifetime / 3. / shard_count;
 | |
|     block::MtCarloComputeShare shard_share(shard_count, info2->vset->export_scaled_validator_weights());
 | |
| 
 | |
|     double xe = (val_idx < main_count ? (double)x / main_count : 0);
 | |
|     double ye = shard_share[val_idx] * (double)y / shard_count;
 | |
|     td::Bits256 pk = info2->vset->list[val_idx].pubkey.as_bits256();
 | |
|     CHECK(pk == val_pubkey);
 | |
|     double p1 = create_prob((int)x1, .9 * xe), p2 = shard_create_prob((int)y1, .9 * ye, chunk_size);
 | |
|     td::TerminalIO::out() << "val #" << val_idx << ": pubkey " << pk.to_hex() << ", blocks created (" << x1 << "," << y1
 | |
|                           << "), expected (" << xe << "," << ye << "), probabilities " << p1 << " and " << p2 << "\n";
 | |
|     bool severe = (rec.severity >= 2);
 | |
|     if (severe && std::min(p1, p2) < .00001) {
 | |
|       LOG(ERROR) << "validator #" << val_idx << " with pubkey " << pk.to_hex()
 | |
|                  << " : serious misbehavior detected: created less than 90% of the expected amount of blocks with "
 | |
|                     "probability 99.999% : created ("
 | |
|                  << x1 << "," << y1 << "), expected (" << xe << "," << ye << ") masterchain/shardchain blocks\n";
 | |
|     } else if (!severe && std::min(p1, p2) < .001) {
 | |
|       LOG(ERROR) << "validator #" << val_idx << " with pubkey " << pk.to_hex()
 | |
|                  << " : moderate misbehavior detected: created less than 90% of the expected amount of blocks with "
 | |
|                     "probability 99.9% : created ("
 | |
|                  << x1 << "," << y1 << "), expected (" << xe << "," << ye << ") masterchain/shardchain blocks\n";
 | |
|     } else {
 | |
|       LOG(ERROR) << "invalid (unsupported) complaint for validator #" << val_idx << " with pubkey " << pk.to_hex();
 | |
|       show_vote(root->get_hash().bits(), false);
 | |
|       return td::Status::OK();
 | |
|     }
 | |
|     auto suggested_fine = block::tlb::t_Grams.as_integer(rec.suggested_fine);
 | |
|     if (suggested_fine.is_null()) {
 | |
|       return td::Status::Error("cannot parse suggested fine");
 | |
|     }
 | |
|     if (!check_punishment(interval, severe, suggested_fine, rec.suggested_fine_part, info2->config->get_config_param(40))) {
 | |
|       LOG(ERROR) << "proposed punishment (fine " << td::dec_string(suggested_fine)
 | |
|                  << ", fine_part=" << (double)rec.suggested_fine_part / (1LL << 32) << " is too harsh";
 | |
|       show_vote(root->get_hash().bits(), false);
 | |
|       return td::Status::OK();
 | |
|     }
 | |
|     LOG(INFO) << "accepting suggested punishment (affirmative vote)";
 | |
|     show_vote(root->get_hash().bits(), true);
 | |
|     return td::Status::OK();
 | |
|   } catch (vm::VmError& err) {
 | |
|     return err.as_status("vm error while scanning configuration proof:");
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     return err.as_status("virtualization error while scanning configuration proof:");
 | |
|   }
 | |
| }
 | |
| 
 | |
| td::Status TestNode::ValidatorLoadInfo::load_special_creator_stat(const td::Bits256& spec_pubkey, bool load_total) {
 | |
|   if (!vset) {
 | |
|     return td::Status::Error("no validator set loaded");
 | |
|   }
 | |
|   int idx = vset->lookup_public_key(spec_pubkey);
 | |
|   if (idx < 0) {
 | |
|     return td::Status::Error("validator with public key "s + spec_pubkey.to_hex() + " not present in validator set");
 | |
|   }
 | |
|   if (virt_root.is_null()) {
 | |
|     return td::Status::Error("no virtualized block state");
 | |
|   }
 | |
|   try {
 | |
|     auto dict = block::get_block_create_stats_dict(virt_root);
 | |
|     if (!dict) {
 | |
|       return td::Status::Error("cannot extract BlockCreateStats from virtualized mc state");
 | |
|     }
 | |
|     for (int i = 0; i <= (int)load_total; i++) {
 | |
|       td::Bits256 key = (i ? td::Bits256::zero() : spec_pubkey);
 | |
|       auto& p = *(i ? &created_total : &created_special);
 | |
|       auto cell = dict->lookup(key);
 | |
|       if (cell.is_null()) {
 | |
|         p.first = p.second = 0;
 | |
|         if (i) {
 | |
|           return td::Status::Error("no total created block statistics in BlockCreateStats");
 | |
|         }
 | |
|       } else {
 | |
|         block::DiscountedCounter mc_cnt, shard_cnt;
 | |
|         if (!block::unpack_CreatorStats(std::move(cell), mc_cnt, shard_cnt)) {
 | |
|           return td::Status::Error(PSLICE() << "invalid CreatorStats record with key " << key.to_hex());
 | |
|         }
 | |
|         p.first = mc_cnt.total;
 | |
|         p.second = shard_cnt.total;
 | |
|       }
 | |
|     }
 | |
|     special_idx = idx;
 | |
|     return td::Status::OK();
 | |
|   } catch (vm::VmError& err) {
 | |
|     return err.as_status("vm error while extracting block creator data: ");
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     return err.as_status("virtualization error while extracting block creator data: ");
 | |
|   }
 | |
| }
 | |
| 
 | |
| td::Result<std::unique_ptr<TestNode::ValidatorLoadInfo>> TestNode::ValidatorLoadInfo::preinit_from_producer_info(
 | |
|     Ref<vm::Cell> prod_info) {
 | |
|   if (prod_info.is_null()) {
 | |
|     return td::Status::Error("ProducerInfo cell is null");
 | |
|   }
 | |
|   if (!block::gen::t_ProducerInfo.validate_ref(prod_info)) {
 | |
|     return td::Status::Error("invalid ProducerInfo");
 | |
|   }
 | |
|   block::gen::ProducerInfo::Record rec;
 | |
|   ton::BlockIdExt blk_id;
 | |
|   ton::LogicalTime end_lt;
 | |
|   if (!(tlb::unpack_cell(prod_info, rec) &&
 | |
|         block::tlb::t_ExtBlkRef.unpack(std::move(rec.mc_blk_ref), blk_id, &end_lt))) {
 | |
|     return td::Status::Error("cannot unpack ProducerInfo");
 | |
|   }
 | |
|   auto info = std::make_unique<ValidatorLoadInfo>(blk_id, std::move(rec.state_proof), std::move(rec.prod_proof));
 | |
|   CHECK(info);
 | |
|   info->end_lt = end_lt;
 | |
|   info->block_created_at = rec.utime;
 | |
|   TRY_STATUS_PREFIX(info->init_check_proofs(), "error checking block/state proofs:");
 | |
|   return std::move(info);
 | |
| }
 | |
| 
 | |
| td::Status TestNode::ValidatorLoadInfo::init_check_proofs() {
 | |
|   try {
 | |
|     ton::UnixTime utime;
 | |
|     ton::LogicalTime lt;
 | |
|     TRY_STATUS(check_header_proof(&utime, <));
 | |
|     if (utime != block_created_at) {
 | |
|       return td::Status::Error(PSLICE() << "incorrect block creation time: declared " << block_created_at << ", actual "
 | |
|                                         << utime);
 | |
|     }
 | |
|     if (lt != end_lt) {
 | |
|       return td::Status::Error(PSLICE() << "incorrect block logical time: declared " << end_lt << ", actual " << lt);
 | |
|     }
 | |
|     auto vstate = vm::MerkleProof::virtualize(data_proof, 1);
 | |
|     if (vstate.is_null()) {
 | |
|       return td::Status::Error(PSLICE() << "cannot virtualize state of block " << blk_id.to_str());
 | |
|     }
 | |
|     TRY_RESULT_PREFIX_ASSIGN(config, block::Config::extract_from_state(vstate, 0), "cannot unpack configuration:");
 | |
|     auto vset_root = config->get_config_param(34);
 | |
|     if (vset_root.is_null()) {
 | |
|       vset_hash.set_zero();
 | |
|       return td::Status::Error(PSLICE() << "no configuration parameter 34 (validator set) for block "
 | |
|                                         << blk_id.to_str());
 | |
|     }
 | |
|     vset_hash = vset_root->get_hash().bits();
 | |
|     virt_root = vstate;
 | |
|     return td::Status::OK();
 | |
|   } catch (vm::VmError& err) {
 | |
|     return err.as_status("vm error:");
 | |
|   } catch (vm::VmVirtError& err) {
 | |
|     return err.as_status("virtualization error:");
 | |
|   }
 | |
| }
 | |
| 
 | |
| int main(int argc, char* argv[]) {
 | |
|   SET_VERBOSITY_LEVEL(verbosity_INFO);
 | |
|   td::set_default_failure_signal_handler();
 | |
| 
 | |
|   td::actor::ActorOwn<TestNode> x;
 | |
| 
 | |
|   td::OptionParser p;
 | |
|   p.set_description("Test Lite Client for TON Blockchain");
 | |
|   p.add_option('h', "help", "prints_help", [&]() {
 | |
|     char b[10240];
 | |
|     td::StringBuilder sb(td::MutableSlice{b, 10000});
 | |
|     sb << p;
 | |
|     std::cout << sb.as_cslice().c_str();
 | |
|     std::exit(2);
 | |
|   });
 | |
|   p.add_option('C', "global-config", "file to read global config",
 | |
|                [&](td::Slice fname) { td::actor::send_closure(x, &TestNode::set_global_config, fname.str()); });
 | |
|   p.add_option('r', "disable-readline", "",
 | |
|                [&]() { td::actor::send_closure(x, &TestNode::set_readline_enabled, false); });
 | |
|   p.add_option('R', "enable-readline", "",
 | |
|                [&]() { td::actor::send_closure(x, &TestNode::set_readline_enabled, true); });
 | |
|   p.add_option('D', "db", "root for dbs",
 | |
|                [&](td::Slice fname) { td::actor::send_closure(x, &TestNode::set_db_root, fname.str()); });
 | |
|   p.add_checked_option('L', "print-limit", "sets maximum count of recursively printed objects", [&](td::Slice arg) {
 | |
|     auto plimit = td::to_integer<int>(arg);
 | |
|     td::actor::send_closure(x, &TestNode::set_print_limit, plimit);
 | |
|     return plimit >= 0 ? td::Status::OK() : td::Status::Error("printing limit must be non-negative");
 | |
|   });
 | |
|   p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
 | |
|     verbosity = td::to_integer<int>(arg);
 | |
|     SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
 | |
|     return (verbosity >= 0 && verbosity <= 9) ? td::Status::OK() : td::Status::Error("verbosity must be 0..9");
 | |
|   });
 | |
|   p.add_option('V', "version", "shows lite-client build information", [&]() {
 | |
|     std::cout << "lite-client build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n";
 | |
| 
 | |
|     std::exit(0);
 | |
|   });
 | |
|   p.add_option('i', "idx", "set liteserver idx", [&](td::Slice arg) {
 | |
|     auto idx = td::to_integer<int>(arg);
 | |
|     td::actor::send_closure(x, &TestNode::set_liteserver_idx, idx);
 | |
|   });
 | |
|   p.add_checked_option('a', "addr", "connect to ip:port", [&](td::Slice arg) {
 | |
|     td::IPAddress addr;
 | |
|     TRY_STATUS(addr.init_host_port(arg.str()));
 | |
|     td::actor::send_closure(x, &TestNode::set_remote_addr, addr);
 | |
|     return td::Status::OK();
 | |
|   });
 | |
|   p.add_option('c', "cmd", "schedule command",
 | |
|                [&](td::Slice arg) { td::actor::send_closure(x, &TestNode::add_cmd, td::BufferSlice{arg}); });
 | |
|   p.add_option('t', "timeout", "timeout in batch mode", [&](td::Slice arg) {
 | |
|     auto d = td::to_double(arg);
 | |
|     td::actor::send_closure(x, &TestNode::set_fail_timeout, td::Timestamp::in(d));
 | |
|   });
 | |
|   p.add_option('p', "pub", "remote public key",
 | |
|                [&](td::Slice arg) { td::actor::send_closure(x, &TestNode::set_public_key, td::BufferSlice{arg}); });
 | |
|   p.add_option('b', "b64", "remote public key as base64",
 | |
|                [&](td::Slice arg) { td::actor::send_closure(x, &TestNode::decode_public_key, td::BufferSlice{arg}); });
 | |
|   p.add_option('d', "daemonize", "set SIGHUP", [&]() {
 | |
|     td::set_signal_handler(td::SignalType::HangUp, [](int sig) {
 | |
| #if TD_DARWIN || TD_LINUX
 | |
|       close(0);
 | |
|       setsid();
 | |
| #endif
 | |
|     }).ensure();
 | |
|   });
 | |
| #if TD_DARWIN || TD_LINUX
 | |
|   p.add_option('l', "logname", "log to file", [&](td::Slice fname) {
 | |
|     auto FileLog = td::FileFd::open(td::CSlice(fname.str().c_str()),
 | |
|                                     td::FileFd::Flags::Create | td::FileFd::Flags::Append | td::FileFd::Flags::Write)
 | |
|                        .move_as_ok();
 | |
| 
 | |
|     dup2(FileLog.get_native_fd().fd(), 1);
 | |
|     dup2(FileLog.get_native_fd().fd(), 2);
 | |
|   });
 | |
| #endif
 | |
| 
 | |
|   vm::init_vm(true).ensure();  // enable vm debug
 | |
| 
 | |
|   td::actor::Scheduler scheduler({2});
 | |
| 
 | |
|   scheduler.run_in_context([&] { x = td::actor::create_actor<TestNode>("testnode"); });
 | |
| 
 | |
|   scheduler.run_in_context([&] { p.run(argc, argv).ensure(); });
 | |
|   scheduler.run_in_context([&] {
 | |
|     td::actor::send_closure(x, &TestNode::run);
 | |
|     x.release();
 | |
|   });
 | |
|   scheduler.run();
 | |
| 
 | |
|   return 0;
 | |
| }
 |