/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . Copyright 2017-2020 Telegram Systems LLP */ #include "liteserver.hpp" #include "td/utils/Slice.h" #include "td/utils/common.h" #include "td/utils/crypto.h" #include "td/utils/overloaded.h" #include "auto/tl/lite_api.h" #include "auto/tl/lite_api.hpp" #include "adnl/utils.hpp" #include "ton/lite-tl.hpp" #include "tl-utils/lite-utils.hpp" #include "td/utils/Random.h" #include "vm/boc.h" #include "tl/tlblib.hpp" #include "block/block.h" #include "block/block-parse.h" #include "block/block-auto.h" #include "vm/dict.h" #include "vm/cells/MerkleProof.h" #include "vm/continuation.h" #include "shard.hpp" #include "validator-set.hpp" #include "signature-set.hpp" #include "fabric.h" #include namespace ton { namespace validator { using td::Ref; using namespace std::literals::string_literals; td::int32 get_tl_tag(td::Slice slice) { return slice.size() >= 4 ? td::as(slice.data()) : -1; } void LiteQuery::run_query(td::BufferSlice data, td::actor::ActorId manager, td::Promise promise) { td::actor::create_actor("litequery", std::move(data), std::move(manager), std::move(promise)).release(); } LiteQuery::LiteQuery(td::BufferSlice data, td::actor::ActorId manager, td::Promise promise) : query_(std::move(data)), manager_(std::move(manager)), promise_(std::move(promise)) { timeout_ = td::Timestamp::in(default_timeout_msec * 0.001); } void LiteQuery::abort_query(td::Status reason) { LOG(INFO) << "aborted liteserver query: " << reason.to_string(); if (promise_) { promise_.set_error(std::move(reason)); } stop(); } void LiteQuery::abort_query_ext(td::Status reason, std::string comment) { LOG(INFO) << "aborted liteserver query: " << comment << " : " << reason.to_string(); if (promise_) { promise_.set_error(reason.move_as_error_prefix(comment + " : ")); } stop(); } bool LiteQuery::fatal_error(td::Status error) { abort_query(std::move(error)); return false; } bool LiteQuery::fatal_error(std::string err_msg, int err_code) { return fatal_error(td::Status::Error(err_code, std::move(err_msg))); } bool LiteQuery::fatal_error(int err_code, std::string err_msg) { return fatal_error(td::Status::Error(err_code, err_msg)); } void LiteQuery::alarm() { fatal_error(-503, "timeout"); } bool LiteQuery::finish_query(td::BufferSlice result) { if (promise_) { promise_.set_result(std::move(result)); stop(); return true; } else { stop(); return false; } } void LiteQuery::start_up() { alarm_timestamp() = timeout_; auto F = fetch_tl_object(std::move(query_), true); if (F.is_error()) { abort_query(F.move_as_error()); return; } lite_api::downcast_call( *F.move_as_ok().get(), td::overloaded( [&](lite_api::liteServer_getTime& q) { this->perform_getTime(); }, [&](lite_api::liteServer_getVersion& q) { this->perform_getVersion(); }, [&](lite_api::liteServer_getMasterchainInfo& q) { this->perform_getMasterchainInfo(-1); }, [&](lite_api::liteServer_getMasterchainInfoExt& q) { this->perform_getMasterchainInfo(q.mode_ & 0x7fffffff); }, [&](lite_api::liteServer_getBlock& q) { this->perform_getBlock(ton::create_block_id(q.id_)); }, [&](lite_api::liteServer_getBlockHeader& q) { this->perform_getBlockHeader(ton::create_block_id(q.id_), q.mode_); }, [&](lite_api::liteServer_getState& q) { this->perform_getState(ton::create_block_id(q.id_)); }, [&](lite_api::liteServer_getAccountState& q) { this->perform_getAccountState(ton::create_block_id(q.id_), static_cast(q.account_->workchain_), q.account_->id_, 0); }, [&](lite_api::liteServer_getOneTransaction& q) { this->perform_getOneTransaction(ton::create_block_id(q.id_), static_cast(q.account_->workchain_), q.account_->id_, static_cast(q.lt_)); }, [&](lite_api::liteServer_getTransactions& q) { this->perform_getTransactions(static_cast(q.account_->workchain_), q.account_->id_, static_cast(q.lt_), q.hash_, static_cast(q.count_)); }, [&](lite_api::liteServer_sendMessage& q) { this->perform_sendMessage(std::move(q.body_)); }, [&](lite_api::liteServer_getShardInfo& q) { this->perform_getShardInfo(ton::create_block_id(q.id_), ShardIdFull{q.workchain_, static_cast(q.shard_)}, q.exact_); }, [&](lite_api::liteServer_getAllShardsInfo& q) { this->perform_getAllShardsInfo(ton::create_block_id(q.id_)); }, [&](lite_api::liteServer_lookupBlock& q) { this->perform_lookupBlock(ton::create_block_id_simple(q.id_), q.mode_, q.lt_, q.utime_); }, [&](lite_api::liteServer_listBlockTransactions& q) { this->perform_listBlockTransactions(ton::create_block_id(q.id_), q.mode_, q.count_, (q.mode_ & 128) ? q.after_->account_ : td::Bits256::zero(), static_cast((q.mode_ & 128) ? (q.after_->lt_) : 0)); }, [&](lite_api::liteServer_getConfigParams& q) { this->perform_getConfigParams(ton::create_block_id(q.id_), (q.mode_ & 0xfff) | 0x1000, q.param_list_); }, [&](lite_api::liteServer_getConfigAll& q) { this->perform_getConfigParams(ton::create_block_id(q.id_), (q.mode_ & 0xfff) | 0x2000); }, [&](lite_api::liteServer_getBlockProof& q) { this->perform_getBlockProof(ton::create_block_id(q.known_block_), q.mode_ & 1 ? ton::create_block_id(q.target_block_) : ton::BlockIdExt{}, q.mode_); }, [&](lite_api::liteServer_getValidatorStats& q) { this->perform_getValidatorStats(ton::create_block_id(q.id_), q.mode_, q.limit_, q.mode_ & 1 ? q.start_after_ : td::Bits256::zero(), q.mode_ & 4 ? q.modified_after_ : 0); }, [&](lite_api::liteServer_runSmcMethod& q) { this->perform_runSmcMethod(ton::create_block_id(q.id_), static_cast(q.account_->workchain_), q.account_->id_, q.mode_, q.method_id_, std::move(q.params_)); }, [&](auto& obj) { this->abort_query(td::Status::Error(ErrorCode::protoviolation, "unknown query")); })); } void LiteQuery::perform_getTime() { LOG(INFO) << "started a getTime() liteserver query"; td::int32 now = static_cast(std::time(nullptr)); auto b = ton::create_serialize_tl_object(now); finish_query(std::move(b)); } void LiteQuery::perform_getVersion() { LOG(INFO) << "started a getVersion() liteserver query"; td::int32 now = static_cast(std::time(nullptr)); auto b = ton::create_serialize_tl_object(0, ls_version, ls_capabilities, now); finish_query(std::move(b)); } void LiteQuery::perform_getMasterchainInfo(int mode) { LOG(INFO) << "started a getMasterchainInfo(" << mode << ") liteserver query"; if (mode > 0) { fatal_error("unsupported getMasterchainInfo mode"); return; } td::actor::send_closure_later( manager_, &ton::validator::ValidatorManager::get_top_masterchain_state_block, [ Self = actor_id(this), mode ](td::Result, BlockIdExt>> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { auto pair = res.move_as_ok(); td::actor::send_closure_later(Self, &LiteQuery::continue_getMasterchainInfo, std::move(pair.first), pair.second, mode); } }); } void LiteQuery::continue_getMasterchainInfo(Ref mc_state, BlockIdExt blkid, int mode) { LOG(INFO) << "obtained data for getMasterchainInfo() : last block = " << blkid.to_str(); auto mc_state_q = Ref(std::move(mc_state)); if (mc_state_q.is_null()) { fatal_error("cannot obtain a valid masterchain state"); return; } auto zerostate_id = mc_state_q->get_zerostate_id(); auto zs_tl = create_tl_object(zerostate_id.workchain, zerostate_id.root_hash, zerostate_id.file_hash); td::int32 now = static_cast(std::time(nullptr)); auto b = (mode == -1) ? ton::create_serialize_tl_object( ton::create_tl_lite_block_id(blkid), mc_state_q->root_hash(), std::move(zs_tl)) : ton::create_serialize_tl_object( mode, ls_version, ls_capabilities, ton::create_tl_lite_block_id(blkid), mc_state_q->get_unix_time(), now, mc_state_q->root_hash(), std::move(zs_tl)); finish_query(std::move(b)); } void LiteQuery::perform_getBlock(BlockIdExt blkid) { LOG(INFO) << "started a getBlock(" << blkid.to_str() << ") liteserver query"; if (!blkid.is_valid_full()) { fatal_error("invalid BlockIdExt"); return; } td::actor::send_closure_later(manager_, &ValidatorManager::get_block_data_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getBlock, blkid, res.move_as_ok()); } }); } void LiteQuery::continue_getBlock(BlockIdExt blkid, Ref block) { LOG(INFO) << "obtained data for getBlock(" << blkid.to_str() << ")"; CHECK(block.not_null()); auto b = ton::create_serialize_tl_object(ton::create_tl_lite_block_id(blkid), block->data()); finish_query(std::move(b)); } void LiteQuery::perform_getBlockHeader(BlockIdExt blkid, int mode) { LOG(INFO) << "started a getBlockHeader(" << blkid.to_str() << ", " << mode << ") liteserver query"; if (!blkid.is_valid_full()) { fatal_error("invalid BlockIdExt"); return; } td::actor::send_closure_later(manager_, &ValidatorManager::get_block_data_from_db_short, blkid, [ Self = actor_id(this), blkid, mode ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getBlockHeader, blkid, mode, res.move_as_ok()); } }); } static bool visit(Ref 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 cell) { if (cell.is_null()) { return true; } vm::CellSlice cs{vm::NoVm{}, std::move(cell)}; return visit(cs); } static bool visit(Ref cs_ref) { return cs_ref.is_null() || visit(*cs_ref); } void LiteQuery::continue_getBlockHeader(BlockIdExt blkid, int mode, Ref block) { LOG(INFO) << "obtained data for getBlockHeader(" << blkid.to_str() << ", " << mode << ")"; CHECK(block.not_null()); CHECK(block->block_id() == blkid); auto block_root = block->root_cell(); if (block_root.is_null()) { fatal_error("block has no valid root cell"); return; } // create block header proof RootHash rhash{block_root->get_hash().bits()}; CHECK(rhash == blkid.root_hash); vm::MerkleProofBuilder mpb{block_root}; block::gen::Block::Record blk; block::gen::BlockInfo::Record info; if (!(tlb::unpack_cell(mpb.root(), blk) && tlb::unpack_cell(blk.info, info))) { fatal_error("cannot unpack block header"); return; } if (mode & 1) { // with state_update vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update}; if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update && upd_cs.size_ext() == 0x20228)) { fatal_error("invalid Merkle update in block"); return; } } visit(info.master_ref); visit(info.prev_ref); visit(info.prev_vert_ref); if (mode & 2) { // with value flow visit(blk.value_flow); } if (mode & 16) { // with extra block::gen::BlockExtra::Record extra; if (!tlb::unpack_cell(blk.extra, extra)) { fatal_error("cannot unpack BlockExtra in block"); return; } if (blkid.is_masterchain()) { auto mc_extra_root = extra.custom->prefetch_ref(); block::gen::McBlockExtra::Record mc_extra; if (!(mc_extra_root.not_null() && tlb::unpack_cell(std::move(mc_extra_root), mc_extra))) { fatal_error("cannot unpack McBlockExtra in block"); return; } if (mode & 32) { // with ShardHashes visit(mc_extra.shard_hashes); } if (mode & 64) { // with PrevBlkSignatures visit(mc_extra.r1.prev_blk_signatures); } } } auto proof_data = mpb.extract_proof_boc(); if (proof_data.is_error()) { fatal_error(proof_data.move_as_error()); return; } // send answer auto b = ton::create_serialize_tl_object(ton::create_tl_lite_block_id(blkid), mode, proof_data.move_as_ok()); finish_query(std::move(b)); } void LiteQuery::perform_getState(BlockIdExt blkid) { LOG(INFO) << "started a getState(" << blkid.to_str() << ") liteserver query"; if (!blkid.is_valid_full()) { fatal_error("invalid BlockIdExt"); return; } if (blkid.is_masterchain() && blkid.id.seqno > 1000) { fatal_error("cannot request total state: possibly too large"); return; } if (blkid.id.seqno) { td::actor::send_closure_later(manager_, &ValidatorManager::get_shard_state_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getState, blkid, res.move_as_ok()); } }); } else { td::actor::send_closure_later(manager_, &ValidatorManager::get_zero_state, blkid, [ Self = actor_id(this), blkid ](td::Result res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getZeroState, blkid, res.move_as_ok()); } }); } } void LiteQuery::continue_getState(BlockIdExt blkid, Ref state) { LOG(INFO) << "obtained data for getState(" << blkid.to_str() << ")"; CHECK(state.not_null()); auto res = state->serialize(); if (res.is_error()) { abort_query(res.move_as_error()); return; } auto data = res.move_as_ok(); FileHash file_hash; td::sha256(data, file_hash.as_slice()); auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(blkid), state->root_hash(), file_hash, std::move(data)); finish_query(std::move(b)); } void LiteQuery::continue_getZeroState(BlockIdExt blkid, td::BufferSlice state) { LOG(INFO) << "obtained data for getZeroState(" << blkid.to_str() << ")"; CHECK(!state.empty()); auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(blkid), blkid.root_hash, blkid.file_hash, std::move(state)); finish_query(std::move(b)); } void LiteQuery::perform_sendMessage(td::BufferSlice data) { LOG(INFO) << "started a sendMessage(<" << data.size() << " bytes>) liteserver query"; auto res = ton::validator::create_ext_message(std::move(data)); if (res.is_error()) { abort_query(res.move_as_error()); return; } LOG(INFO) << "sending an external message to validator manager"; td::actor::send_closure_later(manager_, &ValidatorManager::send_external_message, res.move_as_ok()); auto b = ton::create_serialize_tl_object(1); finish_query(std::move(b)); } bool LiteQuery::request_mc_block_data(BlockIdExt blkid) { if (!blkid.is_masterchain() || !blkid.is_valid_full()) { return fatal_error("reference block must belong to the masterchain"); } if (!cont_set_) { return fatal_error("continuation not set"); } base_blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_block_data_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load block "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_mc_block_data, blkid, res.move_as_ok()); } }); return true; } bool LiteQuery::request_mc_proof(BlockIdExt blkid, int mode) { if (!blkid.is_masterchain() || !blkid.is_valid_full()) { return fatal_error("reference block must belong to the masterchain"); } if (!cont_set_) { return fatal_error("continuation not set"); } if (mode) { base_blk_id_alt_ = blkid; } else { base_blk_id_ = blkid; } ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_block_proof_from_db_short, blkid, [ Self = actor_id(this), blkid, mode ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load proof for "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_mc_block_proof, blkid, mode, res.move_as_ok()); } }); return true; } bool LiteQuery::request_mc_block_state(BlockIdExt blkid) { if (!blkid.is_masterchain() || !blkid.is_valid_full()) { return fatal_error("reference block must belong to the masterchain"); } if (!cont_set_) { return fatal_error("continuation not set"); } base_blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_shard_state_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load state for "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_mc_block_state, blkid, res.move_as_ok()); } }); return true; } bool LiteQuery::request_mc_block_data_state(BlockIdExt blkid) { return request_mc_block_data(blkid) && request_mc_block_state(blkid); } bool LiteQuery::request_block_data_state(BlockIdExt blkid) { LOG(INFO) << "requesting state for block (" << blkid.to_str() << ")"; return request_block_data(blkid) && request_block_state(blkid); } bool LiteQuery::request_block_state(BlockIdExt blkid) { if (!blkid.is_valid_full()) { return fatal_error("invalid block id requested"); } if (!cont_set_) { return fatal_error("continuation not set"); } blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_shard_state_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load state for "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_block_state, blkid, res.move_as_ok()); } }); return true; } bool LiteQuery::request_block_data(BlockIdExt blkid) { if (!blkid.is_valid_full()) { return fatal_error("invalid block id requested"); } if (!cont_set_) { return fatal_error("continuation not set"); } blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_block_data_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load block "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_block_data, blkid, res.move_as_ok()); } }); return true; } bool LiteQuery::request_proof_link(BlockIdExt blkid) { if (!blkid.is_valid_full()) { return fatal_error("invalid block id requested"); } if (!cont_set_) { return fatal_error("continuation not set"); } blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_block_proof_link_from_db_short, blkid, [ Self = actor_id(this), blkid ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load proof link for "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_block_proof_link, blkid, res.move_as_ok()); } }); return true; } bool LiteQuery::request_zero_state(BlockIdExt blkid) { if (!blkid.is_valid_full()) { return fatal_error("invalid block id requested"); } if (blkid.seqno()) { return fatal_error("invalid zerostate requested"); } if (!cont_set_) { return fatal_error("continuation not set"); } blk_id_ = blkid; ++pending_; td::actor::send_closure_later( manager_, &ValidatorManager::get_zero_state, blkid, [ Self = actor_id(this), blkid ](td::Result res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error_prefix("cannot load zerostate of "s + blkid.to_str() + " : ")); } else { td::actor::send_closure_later(Self, &LiteQuery::got_zero_state, blkid, res.move_as_ok()); } }); return true; } void LiteQuery::perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode) { LOG(INFO) << "started a getAccountState(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << ", " << mode << ") liteserver query"; if (blkid.id.workchain != masterchainId && blkid.id.workchain != workchain) { fatal_error("reference block for a getAccountState() must belong to the masterchain"); return; } //if (workchain != masterchainId && workchain != basechainId) { // fatal_error("cannot get account states from specified workchain"); // return; //} if (!blkid.is_valid()) { fatal_error("reference block id for a getAccountState() is invalid"); return; } if (workchain == blkid.id.workchain && !ton::shard_contains(blkid.shard_full(), extract_addr_prefix(workchain, addr))) { fatal_error("requested account id is not contained in the shard of the reference block"); return; } acc_workchain_ = workchain; acc_addr_ = addr; mode_ = mode; if (blkid.id.workchain != masterchainId) { base_blk_id_ = blkid; set_continuation([&]() -> void { finish_getAccountState({}); }); request_block_data_state(blkid); } else if (blkid.id.seqno != ~0U) { set_continuation([&]() -> void { continue_getAccountState(); }); request_mc_block_data_state(blkid); } else { LOG(INFO) << "sending a get_top_masterchain_state_block query to manager"; td::actor::send_closure_later( manager_, &ton::validator::ValidatorManager::get_top_masterchain_state_block, [Self = actor_id(this)](td::Result, BlockIdExt>> res)->void { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { auto pair = res.move_as_ok(); td::actor::send_closure_later(Self, &LiteQuery::continue_getAccountState_0, std::move(pair.first), pair.second); } }); } } void LiteQuery::continue_getAccountState_0(Ref mc_state, BlockIdExt blkid) { LOG(INFO) << "obtained last masterchain block = " << blkid.to_str(); base_blk_id_ = blkid; CHECK(mc_state.not_null()); mc_state_ = Ref(std::move(mc_state)); CHECK(mc_state_.not_null()); set_continuation([&]() -> void { continue_getAccountState(); }); request_mc_block_data(blkid); } void LiteQuery::perform_runSmcMethod(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode, td::int64 method_id, td::BufferSlice params) { LOG(INFO) << "started a runSmcMethod(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << ", " << method_id << ", " << mode << ") liteserver query with " << params.size() << " parameter bytes"; if (params.size() >= 65536) { fatal_error("more than 64k parameter bytes passed"); return; } if (mode & ~0x1f) { fatal_error("unsupported mode in runSmcMethod"); return; } stack_.clear(); try { if (params.size()) { auto res = vm::std_boc_deserialize(std::move(params)); if (res.is_error()) { fatal_error("cannot deserialize parameter list boc: "s + res.move_as_error().to_string()); return; } auto cs = vm::load_cell_slice(res.move_as_ok()); if (!(vm::Stack::deserialize_to(cs, stack_, 0) && cs.empty_ext())) { fatal_error("parameter list boc cannot be deserialized as a VmStack"); return; } } else { stack_ = td::make_ref(); } stack_.write().push_smallint(method_id); } catch (vm::VmError& vme) { fatal_error("error deserializing parameter list: "s + vme.get_msg()); return; } catch (vm::VmVirtError& vme) { fatal_error("virtualization error while deserializing parameter list: "s + vme.get_msg()); return; } perform_getAccountState(blkid, workchain, addr, mode | 0x10000); } void LiteQuery::perform_getOneTransaction(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, LogicalTime lt) { LOG(INFO) << "started a getOneTransaction(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << "," << lt << ") liteserver query"; if (!blkid.is_valid_full()) { fatal_error("block id in getOneTransaction() is invalid"); return; } if (!ton::shard_contains(blkid.shard_full(), extract_addr_prefix(workchain, addr))) { fatal_error("requested account id is not contained in the shard of the specified block"); return; } acc_workchain_ = workchain; acc_addr_ = addr; trans_lt_ = lt; set_continuation([&]() -> void { continue_getOneTransaction(); }); request_block_data(blkid); } void LiteQuery::got_block_state(BlockIdExt blkid, Ref state) { LOG(INFO) << "obtained data for getState(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(state.not_null()); state_ = Ref(std::move(state)); CHECK(state_.not_null()); CHECK(blkid == blk_id_); dec_pending(); } void LiteQuery::got_mc_block_state(BlockIdExt blkid, Ref state) { LOG(INFO) << "obtained data for getState(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(state.not_null()); mc_state_ = Ref(std::move(state)); CHECK(mc_state_.not_null()); CHECK(blkid == base_blk_id_); dec_pending(); } void LiteQuery::got_block_data(BlockIdExt blkid, Ref data) { LOG(INFO) << "obtained data for getBlock(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(data.not_null()); block_ = std::move(data); CHECK(block_.not_null()); CHECK(blkid == blk_id_); dec_pending(); } void LiteQuery::got_mc_block_data(BlockIdExt blkid, Ref data) { LOG(INFO) << "obtained data for getBlock(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(data.not_null()); mc_block_ = std::move(data); CHECK(mc_block_.not_null()); CHECK(blkid == base_blk_id_); dec_pending(); } void LiteQuery::got_mc_block_proof(BlockIdExt blkid, int mode, Ref proof) { LOG(INFO) << "obtained data for getBlockProof(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(proof.not_null()); if (mode) { mc_proof_alt_ = Ref(std::move(proof)); CHECK(mc_proof_alt_.not_null()); CHECK(blkid == base_blk_id_alt_); } else { mc_proof_ = Ref(std::move(proof)); CHECK(mc_proof_.not_null()); CHECK(blkid == base_blk_id_); } dec_pending(); } void LiteQuery::got_block_proof_link(BlockIdExt blkid, Ref proof_link) { LOG(INFO) << "obtained data for getBlockProofLink(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(proof_link.not_null()); proof_link_ = Ref(std::move(proof_link)); CHECK(proof_link_.not_null()); CHECK(blkid == blk_id_); dec_pending(); } void LiteQuery::got_zero_state(BlockIdExt blkid, td::BufferSlice zerostate) { LOG(INFO) << "obtained data for getZeroState(" << blkid.to_str() << ") needed by a liteserver query"; CHECK(!zerostate.empty()); buffer_ = std::move(zerostate); CHECK(blkid == blk_id_); dec_pending(); } void LiteQuery::check_pending() { CHECK(pending_ >= 0); if (!pending_) { if (!cont_set_) { fatal_error("no continuation set for completion of data loading process"); } else { cont_set_ = false; std::move(continuation_)(); } } } bool LiteQuery::set_continuation(std::function&& cont) { if (cont_set_) { return fatal_error("continuation already set"); } else { continuation_ = std::move(cont); return cont_set_ = true; } } bool LiteQuery::make_mc_state_root_proof(Ref& proof) { return make_state_root_proof(proof, mc_state_, mc_block_, base_blk_id_); } bool LiteQuery::make_state_root_proof(Ref& proof) { return make_state_root_proof(proof, state_, block_, blk_id_); } bool LiteQuery::make_state_root_proof(Ref& proof, Ref state, Ref block, const BlockIdExt& blkid) { CHECK(block.not_null() && state.not_null()); return make_state_root_proof(proof, state->root_cell(), block->root_cell(), blkid); } bool LiteQuery::make_state_root_proof(Ref& proof, Ref state_root, Ref block_root, const BlockIdExt& blkid) { CHECK(block_root.not_null() && state_root.not_null()); RootHash rhash{block_root->get_hash().bits()}; CHECK(rhash == blkid.root_hash); vm::MerkleProofBuilder pb{std::move(block_root)}; block::gen::Block::Record blk; block::gen::BlockInfo::Record info; if (!(tlb::unpack_cell(pb.root(), blk) && tlb::unpack_cell(blk.info, info))) { return fatal_error("cannot unpack block header"); } vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update}; if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update && upd_cs.size_ext() == 0x20228)) { return fatal_error("invalid Merkle update in block"); } auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0).bits(); auto state_hash = state_root->get_hash().bits(); if (upd_hash.compare(state_hash, 256)) { return fatal_error("cannot construct Merkle proof for given masterchain state because of hash mismatch"); } if (!pb.extract_proof_to(proof)) { return fatal_error("unknown error creating Merkle proof"); } return true; } bool LiteQuery::make_shard_info_proof(Ref& proof, vm::CellSlice& cs, ShardIdFull shard, ShardIdFull& true_shard, Ref& leaf, bool& found, bool exact) { vm::MerkleProofBuilder pb{mc_state_->root_cell()}; block::gen::ShardStateUnsplit::Record sstate; if (!(tlb::unpack_cell(pb.root(), sstate))) { return fatal_error("cannot unpack state header"); } auto shards_dict = block::ShardConfig::extract_shard_hashes_dict(pb.root()); if (!shards_dict) { return fatal_error("cannot extract ShardHashes from last mc state"); } found = block::ShardConfig::get_shard_hash_raw_from(*shards_dict, cs, shard, true_shard, exact, &leaf); if (!pb.extract_proof_to(proof)) { return fatal_error("unknown error creating Merkle proof"); } return true; } bool LiteQuery::make_shard_info_proof(Ref& proof, Ref& info, ShardIdFull shard, bool exact) { Ref leaf; vm::CellSlice cs; ShardIdFull true_shard; bool found; if (!make_shard_info_proof(proof, cs, shard, true_shard, leaf, found, exact)) { return false; } if (found) { info = block::McShardHash::unpack(cs, true_shard); if (info.is_null()) { return fatal_error("cannot unpack a leaf entry from ShardHashes"); } } else { info.clear(); } return true; } bool LiteQuery::make_shard_info_proof(Ref& proof, Ref& info, AccountIdPrefixFull prefix) { return make_shard_info_proof(proof, info, prefix.as_leaf_shard(), false); } bool LiteQuery::make_shard_info_proof(Ref& proof, BlockIdExt& blkid, AccountIdPrefixFull prefix) { Ref info; if (!make_shard_info_proof(proof, info, prefix)) { return false; } if (info.not_null()) { blkid = info->top_block_id(); } else { blkid.invalidate(); } return true; } bool LiteQuery::make_ancestor_block_proof(Ref& proof, Ref state_root, const BlockIdExt& old_blkid) { vm::MerkleProofBuilder mpb{std::move(state_root)}; auto rconfig = block::ConfigInfo::extract_config(mpb.root(), block::ConfigInfo::needPrevBlocks); if (rconfig.is_error()) { return fatal_error( "cannot extract previous block configuration from masterchain state while constructing Merkle proof for "s + old_blkid.to_str()); } if (!rconfig.move_as_ok()->check_old_mc_block_id(old_blkid, true)) { return fatal_error("cannot check that "s + old_blkid.to_str() + " is indeed a previous masterchain block while constructing Merkle proof"); } if (!mpb.extract_proof_to(proof)) { return fatal_error("error while constructing Merkle proof for old masterchain block "s + old_blkid.to_str()); } return true; } void LiteQuery::continue_getAccountState() { LOG(INFO) << "continue getAccountState() query"; if (acc_workchain_ == masterchainId) { blk_id_ = base_blk_id_; block_ = mc_block_; state_ = mc_state_; finish_getAccountState({}); return; } Ref proof3, proof4; ton::BlockIdExt blkid; if (!(make_mc_state_root_proof(proof3) && make_shard_info_proof(proof4, blkid, extract_addr_prefix(acc_workchain_, acc_addr_)))) { return; } auto proof = vm::std_boc_serialize_multi({std::move(proof3), std::move(proof4)}); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; } if (!blkid.is_valid()) { // no shard with requested address found LOG(INFO) << "getAccountState(" << acc_workchain_ << ":" << acc_addr_.to_hex() << ") query completed (unknown workchain/shard)"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blkid), proof.move_as_ok(), td::BufferSlice{}, td::BufferSlice{}); finish_query(std::move(b)); } else { shard_proof_ = proof.move_as_ok(); set_continuation([this]() -> void { finish_getAccountState(std::move(shard_proof_)); }); request_block_data_state(blkid); } } void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { LOG(INFO) << "completing getAccountState() query"; Ref proof1; if (!make_state_root_proof(proof1)) { return; } vm::MerkleProofBuilder pb{state_->root_cell()}; block::gen::ShardStateUnsplit::Record sstate; if (!tlb::unpack_cell(pb.root(), sstate)) { fatal_error("cannot unpack state header"); return; } vm::AugmentedDictionary accounts_dict{vm::load_cell_slice_ref(sstate.accounts), 256, block::tlb::aug_ShardAccounts}; auto acc_csr = accounts_dict.lookup(acc_addr_); Ref acc_root; if (acc_csr.not_null()) { acc_root = acc_csr->prefetch_ref(); } auto proof = vm::std_boc_serialize_multi({std::move(proof1), pb.extract_proof()}); pb.clear(); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; } if (mode_ & 0x10000) { finish_runSmcMethod(std::move(shard_proof), proof.move_as_ok(), std::move(acc_root), sstate.gen_utime, sstate.gen_lt); return; } td::BufferSlice data; if (acc_root.not_null()) { auto res = vm::std_boc_serialize(std::move(acc_root)); if (res.is_error()) { fatal_error(res.move_as_error()); return; } data = res.move_as_ok(); } LOG(INFO) << "getAccountState(" << acc_workchain_ << ":" << acc_addr_.to_hex() << ") query completed"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), proof.move_as_ok(), std::move(data)); finish_query(std::move(b)); } // same as in lite-client/lite-client-common.cpp static td::Ref prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref my_addr, const block::CurrencyCollection& balance) { td::BitArray<256> rand_seed; td::RefInt256 rand_seed_int{true}; td::Random::secure_bytes(rand_seed.as_slice()); if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) { return {}; } auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea td::make_refint(0), // actions:Integer td::make_refint(0), // msgs_sent:Integer td::make_refint(now), // unixtime:Integer td::make_refint(lt), // block_lt:Integer td::make_refint(lt), // trans_lt:Integer std::move(rand_seed_int), // rand_seed:Integer balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] my_addr, // myself:MsgAddressInt vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo; LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); return vm::make_tuple_ref(std::move(tuple)); } void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice state_proof, Ref acc_root, UnixTime gen_utime, LogicalTime gen_lt) { LOG(INFO) << "completing runSmcMethod() query"; int mode = mode_ & 0xffff; if (acc_root.is_null()) { // no such account LOG(INFO) << "runSmcMethod(" << acc_workchain_ << ":" << acc_addr_.to_hex() << ") query completed: account state is empty"; auto b = ton::create_serialize_tl_object( mode, ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), std::move(state_proof), td::BufferSlice(), td::BufferSlice(), td::BufferSlice(), -0x100, td::BufferSlice()); finish_query(std::move(b)); return; } vm::MerkleProofBuilder pb{std::move(acc_root)}; block::gen::Account::Record_account acc; block::gen::AccountStorage::Record store; block::CurrencyCollection balance; block::gen::StateInit::Record state_init; if (!(tlb::unpack_cell(pb.root(), acc) && tlb::csr_unpack(std::move(acc.storage), store) && balance.validate_unpack(store.balance) && store.state->prefetch_ulong(1) == 1 && store.state.write().advance(1) && tlb::csr_unpack(std::move(store.state), state_init))) { LOG(INFO) << "error unpacking account state, or account is frozen or uninitialized"; auto b = ton::create_serialize_tl_object( mode, ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), std::move(state_proof), mode & 2 ? pb.extract_proof_boc().move_as_ok() : td::BufferSlice(), td::BufferSlice(), td::BufferSlice(), -0x100, td::BufferSlice()); finish_query(std::move(b)); return; } auto code = state_init.code->prefetch_ref(); auto data = state_init.data->prefetch_ref(); long long gas_limit = client_method_gas_limit; LOG(DEBUG) << "creating VM with gas limit " << gas_limit; // **** INIT VM **** vm::GasLimits gas{gas_limit}; vm::VmState vm{std::move(code), std::move(stack_), gas, 1, std::move(data), vm::VmLog::Null()}; auto c7 = prepare_vm_c7(gen_utime, gen_lt, td::make_ref(acc.addr->clone()), balance); vm.set_c7(c7); // tuple with SmartContractInfo // vm.incr_stack_trace(1); // enable stack dump after each step LOG(INFO) << "starting VM to run GET-method of smart contract " << acc_workchain_ << ":" << acc_addr_.to_hex(); // **** RUN VM **** int exit_code = ~vm.run(); LOG(DEBUG) << "VM terminated with exit code " << exit_code; stack_ = vm.get_stack_ref(); LOG(INFO) << "runSmcMethod(" << acc_workchain_ << ":" << acc_addr_.to_hex() << ") query completed: exit code is " << exit_code; Ref cell; td::BufferSlice c7_info, result; if (mode & 8) { // serialize c7 vm::CellBuilder cb; if (!(vm::StackEntry{std::move(c7)}.serialize(cb) && cb.finalize_to(cell))) { fatal_error("cannot serialize c7"); return; } auto res = vm::std_boc_serialize(std::move(cell)); if (res.is_error()) { fatal_error("cannot serialize c7 : "s + res.move_as_error().to_string()); return; } c7_info = res.move_as_ok(); } // pre-serialize stack always (to visit all data cells referred from the result) vm::CellBuilder cb; if (!(stack_->serialize(cb) && cb.finalize_to(cell))) { fatal_error("cannot serialize resulting stack"); return; } if (mode & 4) { // serialize stack if required auto res = vm::std_boc_serialize(std::move(cell)); if (res.is_error()) { fatal_error("cannot serialize resulting stack : "s + res.move_as_error().to_string()); return; } result = res.move_as_ok(); } auto b = ton::create_serialize_tl_object( mode, ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(blk_id_), std::move(shard_proof), std::move(state_proof), mode & 2 ? pb.extract_proof_boc().move_as_ok() : td::BufferSlice(), std::move(c7_info), td::BufferSlice(), exit_code, std::move(result)); finish_query(std::move(b)); } void LiteQuery::continue_getOneTransaction() { LOG(INFO) << "completing getOneTransaction() query"; CHECK(block_.not_null()); vm::MerkleProofBuilder pb{block_->root_cell()}; auto trans_res = block::get_block_transaction(pb.root(), acc_workchain_, acc_addr_, trans_lt_); if (trans_res.is_error()) { fatal_error(trans_res.move_as_error()); return; } auto trans_root = trans_res.move_as_ok(); auto proof_boc = pb.extract_proof_boc(); if (proof_boc.is_error()) { fatal_error(proof_boc.move_as_error()); return; } td::BufferSlice data; if (trans_root.not_null()) { auto res = vm::std_boc_serialize(std::move(trans_root)); if (res.is_error()) { fatal_error(res.move_as_error()); return; } data = res.move_as_ok(); } LOG(INFO) << "getOneTransaction(" << acc_workchain_ << ":" << acc_addr_.to_hex() << "," << trans_lt_ << ") query completed"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(blk_id_), proof_boc.move_as_ok(), std::move(data)); finish_query(std::move(b)); } void LiteQuery::perform_getTransactions(WorkchainId workchain, StdSmcAddress addr, LogicalTime lt, Bits256 hash, unsigned count) { LOG(INFO) << "started a getTransactions(" << workchain << ", " << addr.to_hex() << ", " << lt << ", " << hash.to_hex() << ", " << count << ") liteserver query"; count = std::min(count, (unsigned)max_transaction_count); /* if (count > max_transaction_count) { fatal_error("cannot fetch more than 10 preceding transactions at one time"); return; } */ if (workchain == ton::workchainInvalid) { fatal_error("invalid workchain specified"); return; } acc_workchain_ = workchain; acc_addr_ = addr; trans_lt_ = lt; trans_hash_ = hash; continue_getTransactions(count, false); } void LiteQuery::continue_getTransactions(unsigned remaining, bool exact) { LOG(INFO) << "continue getTransactions() : " << remaining << " remaining"; bool redo = true; while (remaining && redo && trans_lt_ && block_.not_null()) { redo = false; if (!ton::shard_contains(block_->block_id().shard_full(), ton::extract_addr_prefix(acc_workchain_, acc_addr_))) { fatal_error("obtained a block that cannot contain specified account"); return; } auto res = block::get_block_transaction_try(block_->root_cell(), acc_workchain_, acc_addr_, trans_lt_); if (res.is_error()) { fatal_error(res.move_as_error()); return; } auto root = res.move_as_ok(); if (root.not_null()) { // transaction found if (trans_hash_ != root->get_hash().bits()) { if (!roots_.empty()) { LOG(ERROR) << "transaction hash mismatch: prev_trans_lt/hash invalid for " << acc_workchain_ << ":" << acc_addr_.to_hex() << " lt=" << trans_lt_ << " hash=" << trans_hash_.to_hex() << " found hash=" << root->get_hash().bits().to_hex(256); } fatal_error("transaction hash mismatch"); return; } block::gen::Transaction::Record trans; if (!tlb::unpack_cell(root, trans)) { fatal_error("cannot unpack transaction"); return; } if (trans.prev_trans_lt >= trans_lt_) { fatal_error("previous transaction time is not less than the current one"); return; } roots_.push_back(std::move(root)); aux_objs_.push_back(block_); blk_ids_.push_back(block_->block_id()); LOG(DEBUG) << "going to previous transaction with lt=" << trans.prev_trans_lt << " from current lt=" << trans_lt_; trans_lt_ = trans.prev_trans_lt; trans_hash_ = trans.prev_trans_hash; redo = (trans_lt_ > 0); exact = false; --remaining; continue; } else if (exact) { LOG(DEBUG) << "could not find transaction " << trans_lt_ << " of " << acc_workchain_ << ':' << acc_addr_.to_hex() << " in block " << block_->block_id().to_str(); if (roots_.empty()) { fatal_error("cannot locate transaction in block with specified logical time"); return; } finish_getTransactions(); return; } } if (!remaining || !trans_lt_) { finish_getTransactions(); return; } ++pending_; LOG(DEBUG) << "sending get_block_by_lt_from_db() query to manager for " << acc_workchain_ << ":" << acc_addr_.to_hex() << " " << trans_lt_; td::actor::send_closure_later( manager_, &ValidatorManager::get_block_by_lt_from_db, ton::extract_addr_prefix(acc_workchain_, acc_addr_), trans_lt_, [ Self = actor_id(this), remaining, manager = manager_ ](td::Result res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_getTransactions, res.move_as_error(), ton::BlockIdExt{}); } else { auto handle = res.move_as_ok(); LOG(DEBUG) << "requesting data for block " << handle->id().to_str(); td::actor::send_closure_later(manager, &ValidatorManager::get_block_data_from_db, handle, [ Self, blkid = handle->id(), remaining ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_getTransactions, res.move_as_error(), blkid); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getTransactions_2, blkid, res.move_as_ok(), remaining); } }); } }); } void LiteQuery::continue_getTransactions_2(BlockIdExt blkid, Ref block, unsigned remaining) { LOG(INFO) << "getTransactions() : loaded block " << blkid.to_str(); --pending_; CHECK(!pending_); CHECK(block.not_null()); block_ = block; blk_id_ = blkid; continue_getTransactions(remaining, true); } void LiteQuery::abort_getTransactions(td::Status error, ton::BlockIdExt blkid) { LOG(INFO) << "getTransactions() : got error " << error.message() << " from manager"; if (roots_.empty()) { if (blkid.is_valid()) { fatal_error(PSTRING() << "cannot load block " << blkid.to_str() << " with specified transaction: " << error.message()); } else { fatal_error(PSTRING() << "cannot compute block with specified transaction: " << error.message()); } } else { pending_ = 0; finish_getTransactions(); } } void LiteQuery::finish_getTransactions() { LOG(INFO) << "completing getTransactions() liteserver query"; auto res = vm::std_boc_serialize_multi(std::move(roots_)); if (res.is_error()) { fatal_error(res.move_as_error()); return; } std::vector> a; for (const auto& id : blk_ids_) { a.push_back(ton::create_tl_lite_block_id(id)); } auto b = ton::create_serialize_tl_object(std::move(a), res.move_as_ok()); finish_query(std::move(b)); } void LiteQuery::perform_getShardInfo(BlockIdExt blkid, ShardIdFull shard, bool exact) { LOG(INFO) << "started a getShardInfo(" << blkid.to_str() << ", " << shard.to_str() << ", " << exact << ") liteserver query"; if (!shard.is_valid()) { fatal_error("requested shard is invalid"); return; } set_continuation([this, shard, exact]() -> void { continue_getShardInfo(shard, exact); }); request_mc_block_data_state(blkid); } void LiteQuery::perform_getConfigParams(BlockIdExt blkid, int mode, std::vector param_list) { LOG(INFO) << "started a getConfigParams(" << blkid.to_str() << ", " << mode << ", ) liteserver query"; set_continuation([ this, mode, param_list = std::move(param_list) ]() mutable { continue_getConfigParams(mode, std::move(param_list)); }); request_mc_block_data_state(blkid); } void LiteQuery::continue_getConfigParams(int mode, std::vector param_list) { LOG(INFO) << "completing getConfigParams(" << base_blk_id_.to_str() << ", " << mode << ", ) liteserver query"; Ref proof1; if (!make_mc_state_root_proof(proof1)) { return; } vm::MerkleProofBuilder mpb{mc_state_->root_cell()}; auto res = block::Config::extract_from_state(mpb.root(), mode); if (res.is_error()) { fatal_error(res.move_as_error()); return; } auto cfg = res.move_as_ok(); if (!cfg) { fatal_error("cannot extract configuration from last mc state"); return; } try { if (mode & 0x2000) { visit(cfg->get_root_cell()); } else if (mode & 0x1000) { for (int i : param_list) { visit(cfg->get_config_param(i)); } } } catch (vm::VmError& err) { fatal_error("error while traversing required configuration parameters: "s + err.get_msg()); return; } auto res1 = vm::std_boc_serialize(std::move(proof1)); if (res1.is_error()) { fatal_error("cannot serialize Merkle proof : "s + res1.move_as_error().to_string()); return; } auto res2 = mpb.extract_proof_boc(); if (res2.is_error()) { fatal_error("cannot serialize Merkle proof : "s + res2.move_as_error().to_string()); return; } LOG(INFO) << "getConfigParams() query completed"; auto b = ton::create_serialize_tl_object( mode & 0xfff, ton::create_tl_lite_block_id(base_blk_id_), res1.move_as_ok(), res2.move_as_ok()); finish_query(std::move(b)); } void LiteQuery::perform_getAllShardsInfo(BlockIdExt blkid) { LOG(INFO) << "started a getAllShardsInfo(" << blkid.to_str() << ") liteserver query"; set_continuation([&]() -> void { continue_getAllShardsInfo(); }); request_mc_block_data_state(blkid); } void LiteQuery::continue_getShardInfo(ShardIdFull shard, bool exact) { LOG(INFO) << "completing getShardInfo(" << shard.to_str() << ") query"; Ref proof1, proof2, leaf; if (!make_mc_state_root_proof(proof1)) { return; } vm::CellSlice cs; ShardIdFull true_shard; bool found; if (!make_shard_info_proof(proof2, cs, shard, true_shard, leaf, found, exact)) { return; } auto proof = vm::std_boc_serialize_multi({std::move(proof1), std::move(proof2)}); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; } BlockIdExt true_id; td::BufferSlice data; if (found) { auto shard_info = block::McShardHash::unpack(cs, true_shard); if (shard_info.is_null()) { fatal_error("cannot unpack a leaf entry from ShardHashes"); return; } true_id = shard_info->top_block_id(); auto res = vm::std_boc_serialize(std::move(leaf)); if (res.is_error()) { fatal_error(res.move_as_error()); return; } data = res.move_as_ok(); } else { true_id.invalidate_clear(); } LOG(INFO) << "getShardInfo() query completed"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(base_blk_id_), ton::create_tl_lite_block_id(true_id), proof.move_as_ok(), std::move(data)); finish_query(std::move(b)); } void LiteQuery::continue_getAllShardsInfo() { LOG(INFO) << "completing getAllShardsInfo() query"; Ref proof1, proof2; if (!make_mc_state_root_proof(proof1)) { return; } vm::MerkleProofBuilder mpb{mc_state_->root_cell()}; auto shards_dict = block::ShardConfig::extract_shard_hashes_dict(mpb.root()); if (!shards_dict) { fatal_error("cannot extract ShardHashes from last mc state"); return; } if (!mpb.extract_proof_to(proof2)) { fatal_error("cannot construct Merkle proof for all shards dictionary"); return; } shards_dict = block::ShardConfig::extract_shard_hashes_dict(mc_state_->root_cell()); vm::CellBuilder cb; Ref cell; if (!(std::move(shards_dict)->append_dict_to_bool(cb) && cb.finalize_to(cell))) { fatal_error("cannot store ShardHashes from last mc state into a new cell"); return; } auto proof = vm::std_boc_serialize_multi({std::move(proof1), std::move(proof2)}); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; } auto data = vm::std_boc_serialize(std::move(cell)); if (data.is_error()) { fatal_error(data.move_as_error()); return; } LOG(INFO) << "getAllShardInfo() query completed"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(base_blk_id_), proof.move_as_ok(), data.move_as_ok()); finish_query(std::move(b)); } void LiteQuery::perform_lookupBlock(BlockId blkid, int mode, LogicalTime lt, UnixTime utime) { if (!((1 << (mode & 7)) & 0x16)) { fatal_error("exactly one of mode.0, mode.1 and mode.2 bits must be set"); return; } if (!(mode & 2)) { lt = 0; } if (!(mode & 4)) { utime = 0; } LOG(INFO) << "performing a lookupBlock(" << blkid.to_str() << ", " << mode << ", " << lt << ", " << utime << ") query"; auto P = td::PromiseCreator::lambda( [ Self = actor_id(this), manager = manager_, mode = (mode >> 4) ](td::Result res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { auto handle = res.move_as_ok(); LOG(DEBUG) << "requesting data for block " << handle->id().to_str(); td::actor::send_closure_later(manager, &ValidatorManager::get_block_data_from_db, handle, [ Self, blkid = handle->id(), mode ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getBlockHeader, blkid, mode, res.move_as_ok()); } }); } }); ton::AccountIdPrefixFull pfx{blkid.workchain, blkid.shard}; if (mode & 2) { td::actor::send_closure_later(manager_, &ValidatorManager::get_block_by_lt_from_db, pfx, lt, std::move(P)); } else if (mode & 4) { td::actor::send_closure_later(manager_, &ValidatorManager::get_block_by_unix_time_from_db, pfx, utime, std::move(P)); } else { td::actor::send_closure_later(manager_, &ValidatorManager::get_block_by_seqno_from_db, pfx, blkid.seqno, std::move(P)); } } void LiteQuery::perform_listBlockTransactions(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt) { LOG(INFO) << "started a listBlockTransactions(" << blkid.to_str() << ", " << mode << ", " << count << ", " << account.to_hex() << ", " << lt << ") liteserver query"; base_blk_id_ = blkid; acc_addr_ = account; trans_lt_ = lt; set_continuation([this, mode, count]() -> void { finish_listBlockTransactions(mode, count); }); request_block_data(blkid); } void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { LOG(INFO) << "completing a listBlockTransactions(" << base_blk_id_.to_str() << ", " << mode << ", " << req_count << ", " << acc_addr_.to_hex() << ", " << trans_lt_ << ") liteserver query"; constexpr int max_answer_transactions = 256; CHECK(block_.not_null()); auto block_root = block_->root_cell(); CHECK(block_root.not_null()); RootHash rhash{block_root->get_hash().bits()}; CHECK(rhash == base_blk_id_.root_hash); vm::MerkleProofBuilder pb; auto virt_root = block_root; if (mode & 32) { // proof requested virt_root = pb.init(std::move(virt_root)); } if ((mode & 192) == 64) { // reverse order, no starting point acc_addr_.set_ones(); trans_lt_ = ~0ULL; } std::vector> result; bool eof = false; ton::LogicalTime reverse = (mode & 64) ? ~0ULL : 0; try { block::gen::Block::Record blk; block::gen::BlockExtra::Record extra; if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { fatal_error("cannot find account transaction data in block "s + base_blk_id_.to_str()); return; } vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, block::tlb::aug_ShardAccountBlocks}; int count = 0; bool allow_same = true; td::Bits256 cur_addr = acc_addr_; while (!eof && count < req_count && count < max_answer_transactions) { Ref value; try { value = acc_dict.extract_value( acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); } catch (vm::VmError err) { fatal_error("error while traversing account block dictionary: "s + err.get_msg()); return; } if (value.is_null()) { eof = true; break; } allow_same = false; if (cur_addr != acc_addr_) { trans_lt_ = reverse; } block::gen::AccountBlock::Record acc_blk; if (!(tlb::csr_unpack(std::move(value), acc_blk) && acc_blk.account_addr == cur_addr)) { fatal_error("invalid AccountBlock for account "s + cur_addr.to_hex()); return; } vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, block::tlb::aug_AccountTransactions}; td::BitArray<64> cur_trans{(long long)trans_lt_}; while (count < req_count && count < max_answer_transactions) { Ref tvalue; try { tvalue = trans_dict.extract_value_ref( trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); } catch (vm::VmError err) { fatal_error("error while traversing transaction dictionary of an AccountBlock: "s + err.get_msg()); return; } if (tvalue.is_null()) { trans_lt_ = reverse; break; } result.push_back(create_tl_object(mode, cur_addr, cur_trans.to_long(), tvalue->get_hash().bits())); ++count; } } } catch (vm::VmError err) { fatal_error("error while parsing AccountBlocks of block "s + base_blk_id_.to_str() + " : " + err.get_msg()); return; } td::BufferSlice proof_data; if (mode & 32) { // create proof auto proof_boc = pb.extract_proof_boc(); if (proof_boc.is_error()) { fatal_error(proof_boc.move_as_error()); return; } proof_data = proof_boc.move_as_ok(); } LOG(INFO) << "listBlockTransactions() query completed"; auto b = ton::create_serialize_tl_object( ton::create_tl_lite_block_id(base_blk_id_), req_count, !eof, std::move(result), std::move(proof_data)); finish_query(std::move(b)); } void LiteQuery::perform_getBlockProof(ton::BlockIdExt from, ton::BlockIdExt to, int mode) { if (!(mode & 1)) { to.invalidate_clear(); } LOG(INFO) << "performing a getBlockProof(" << mode << ", " << from.to_str() << ", " << to.to_str() << ") query"; if (!from.is_masterchain_ext()) { fatal_error("source block "s + from.to_str() + " is not a valid masterchain block id"); return; } if ((mode & 1) && !to.is_masterchain_ext()) { fatal_error("destination block "s + to.to_str() + " is not a valid masterchain block id"); return; } if (mode & 1) { if (mode & 0x1000) { BlockIdExt bblk = (from.seqno() > to.seqno()) ? from : to; td::actor::send_closure_later(manager_, &ValidatorManager::get_shard_state_from_db_short, bblk, [ Self = actor_id(this), from, to, bblk, mode ](td::Result> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::continue_getBlockProof, from, to, mode, bblk, Ref(res.move_as_ok())); } }); } else { td::actor::send_closure_later( manager_, &ton::validator::ValidatorManager::get_top_masterchain_state_block, [ Self = actor_id(this), from, to, mode ](td::Result, BlockIdExt>> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { auto pair = res.move_as_ok(); td::actor::send_closure_later(Self, &LiteQuery::continue_getBlockProof, from, to, mode, pair.second, Ref(std::move(pair.first))); } }); } } else if (mode & 2) { td::actor::send_closure_later( manager_, &ton::validator::ValidatorManager::get_top_masterchain_state_block, [ Self = actor_id(this), from, mode ](td::Result, BlockIdExt>> res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { auto pair = res.move_as_ok(); td::actor::send_closure_later(Self, &LiteQuery::continue_getBlockProof, from, pair.second, mode, pair.second, Ref(std::move(pair.first))); } }); } else { td::actor::send_closure_later(manager_, &ton::validator::ValidatorManager::get_shard_client_state, false, [ Self = actor_id(this), from, mode ](td::Result res) { if (res.is_error()) { td::actor::send_closure(Self, &LiteQuery::abort_query, res.move_as_error()); } else { td::actor::send_closure_later(Self, &LiteQuery::perform_getBlockProof, from, res.move_as_ok(), mode | 0x1001); } }); } } void LiteQuery::continue_getBlockProof(ton::BlockIdExt from, ton::BlockIdExt to, int mode, BlockIdExt baseblk, Ref state) { base_blk_id_ = baseblk; if (!base_blk_id_.is_masterchain_ext()) { fatal_error("reference masterchain block "s + base_blk_id_.to_str() + " for constructing a proof chain is invalid"); return; } if (!(mode & 1)) { if (!to.is_masterchain_ext()) { fatal_error("last masterchain block id "s + to.to_str() + " is invalid"); return; } } if (state.is_null()) { fatal_error("obtained no valid masterchain state for block "s + base_blk_id_.to_str()); return; } if (from.seqno() > base_blk_id_.seqno()) { fatal_error("client knows block "s + from.to_str() + " newer than the reference masterchain block " + base_blk_id_.to_str()); return; } if (to.seqno() > base_blk_id_.seqno()) { fatal_error("client knows block "s + to.to_str() + " newer than the reference masterchain block " + base_blk_id_.to_str()); return; } mc_state0_ = Ref(state); if (base_blk_id_ != state->get_block_id()) { fatal_error("the state for "s + base_blk_id_.to_str() + " is in fact a state for different block " + state->get_block_id().to_str()); return; } LOG(INFO) << "continuing getBlockProof(" << mode << ", " << from.to_str() << ", " << to.to_str() << ") query with a state for " << base_blk_id_.to_str(); if (!state->check_old_mc_block_id(from)) { fatal_error("proof source masterchain block "s + from.to_str() + " is unknown from the perspective of reference block " + base_blk_id_.to_str()); return; } if (!state->check_old_mc_block_id(to)) { fatal_error("proof destination masterchain block "s + to.to_str() + " is unknown from the perspective of reference block " + base_blk_id_.to_str()); return; } chain_ = std::make_unique(from, to, mode); blk_id_ = from; construct_proof_chain(from); } bool LiteQuery::construct_proof_chain(ton::BlockIdExt id) { CHECK(chain_); if (chain_->link_count() >= 16 || id == chain_->to) { if (!(chain_->last_link_incomplete() && chain_->last_link().to.seqno())) { return finish_proof_chain(std::move(id)); } else { set_continuation([this, id]() { finish_proof_chain(id); }); return request_proof_link(id); } } if (chain_->to.seqno() == id.seqno()) { return fatal_error("cannot have two different masterchain blocks "s + chain_->to.to_str() + " and " + id.to_str() + " of the same height"); } if (chain_->to.seqno() < id.seqno()) { return construct_proof_link_back(id, chain_->to); } auto prev_key_blk = mc_state0_->prev_key_block_id(id.seqno()); if (!prev_key_blk.is_masterchain_ext()) { return fatal_error("cannot compute previous key block for "s + id.to_str()); } if (prev_key_blk.seqno() > id.seqno() || (prev_key_blk.seqno() == id.seqno() && prev_key_blk != id)) { return fatal_error("block "s + prev_key_blk.to_str() + " cannot be the previous key block for " + id.to_str()); } if (prev_key_blk.seqno() != id.seqno()) { return construct_proof_link_back(id, prev_key_blk); } auto next_key_blk = mc_state0_->next_key_block_id(id.seqno() + 1); if (next_key_blk.is_valid()) { if (!(next_key_blk.is_masterchain_ext() && next_key_blk.seqno() > id.seqno())) { return fatal_error("block "s + next_key_blk.to_str() + " cannot be the next key block after " + id.to_str()); } return construct_proof_link_forward(id, next_key_blk); } else { return construct_proof_link_forward(id, chain_->to); } } // adjust dest_proof and is_key of the last link of existing proof bool LiteQuery::adjust_last_proof_link(ton::BlockIdExt cur, Ref block_root) { CHECK(chain_); if (!(chain_->last_link_incomplete() && chain_->last_link().to.seqno())) { return true; } auto& link = chain_->last_link(); CHECK(link.dest_proof.is_null()); CHECK(link.to == cur); if (cur.root_hash != block_root->get_hash().bits()) { return fatal_error("root hash mismatch in block root of "s + cur.to_str()); } vm::MerkleProofBuilder mpb{std::move(block_root)}; block::gen::Block::Record blk; block::gen::BlockInfo::Record info; if (!(tlb::unpack_cell(mpb.root(), blk) && tlb::unpack_cell(blk.info, info))) { return fatal_error("cannot unpack header of block "s + cur.to_str()); } link.is_key = info.key_block; return mpb.extract_proof_to(link.dest_proof); } bool LiteQuery::construct_proof_link_forward(ton::BlockIdExt cur, ton::BlockIdExt next) { LOG(INFO) << "constructing a forward proof link from " << cur.to_str() << " to " << next.to_str(); if (!(cur.is_masterchain_ext() && next.is_masterchain_ext() && mc_state0_->check_old_mc_block_id(cur) && mc_state0_->check_old_mc_block_id(next))) { return fatal_error("cannot construct forward proof link from "s + cur.to_str() + " to " + next.to_str() + " because these are not known masterchain block ids"); } if (cur.seqno() >= next.seqno()) { return fatal_error("cannot construct forward proof link from "s + cur.to_str() + " to " + next.to_str()); } set_continuation([this, cur, next]() { construct_proof_link_forward_cont(cur, next); }); return (cur.seqno() ? request_proof_link(cur) : request_zero_state(cur)) && request_mc_proof(next); } bool LiteQuery::construct_proof_link_forward_cont(ton::BlockIdExt cur, ton::BlockIdExt next) { LOG(INFO) << "continue constructing a forward proof link from " << cur.to_str() << " to " << next.to_str(); CHECK(cur.seqno() ? proof_link_.not_null() && proof_link_->block_id() == cur : !buffer_.empty()); CHECK(mc_proof_.not_null() && mc_proof_->block_id() == next); try { Ref cur_root, next_root; // virtualize roots ton::validator::ProofQ::VirtualizedProof virt1; if (cur.seqno()) { auto vres1 = proof_link_->get_virtual_root(); if (vres1.is_error()) { return fatal_error(vres1.move_as_error()); } virt1 = vres1.move_as_ok(); cur_root = virt1.root; } else { // for zero state, lazily deserialize buffer_ instead vm::StaticBagOfCellsDbLazy::Options options; options.check_crc32c = true; auto res = vm::StaticBagOfCellsDbLazy::create(vm::BufferSliceBlobView::create(std::move(buffer_)), options); if (res.is_error()) { return fatal_error(res.move_as_error()); } virt1.boc = res.move_as_ok(); auto t_root = virt1.boc->get_root_cell(0); if (t_root.is_error()) { return fatal_error(t_root.move_as_error()); } cur_root = t_root.move_as_ok(); } auto vres2 = mc_proof_->get_virtual_root(); if (vres2.is_error()) { return fatal_error(vres2.move_as_error()); } next_root = vres2.ok().root; if (cur.root_hash != cur_root->get_hash().bits()) { return fatal_error("incorrect root hash in ProofLink for block "s + cur.to_str()); } if (next.root_hash != next_root->get_hash().bits()) { return fatal_error("incorrect root hash in ProofLink for block "s + cur.to_str()); } // adjust dest_proof and is_key of the last link of existing proof if (!adjust_last_proof_link(cur, cur_root)) { return false; } // extract configuration from current block vm::MerkleProofBuilder cur_mpb{cur_root}, next_mpb{next_root}; if (cur.seqno()) { auto err = block::check_block_header(cur_mpb.root(), cur); if (err.is_error()) { return fatal_error("incorrect header in ProofLink for block "s + cur.to_str()); } } auto cfg_res = cur.seqno() ? block::Config::extract_from_key_block(cur_mpb.root(), block::ConfigInfo::needValidatorSet) : block::Config::extract_from_state(cur_mpb.root(), block::ConfigInfo::needValidatorSet); if (cfg_res.is_error()) { return fatal_error(cfg_res.move_as_error()); } auto config = cfg_res.move_as_ok(); // unpack header of next block auto err = block::check_block_header(next_mpb.root(), next); if (err.is_error()) { return fatal_error("incorrect header in ProofLink for block "s + next.to_str()); } block::gen::Block::Record blk; block::gen::BlockInfo::Record info; if (!(tlb::unpack_cell(next_mpb.root(), blk) && tlb::unpack_cell(blk.info, info))) { return fatal_error("cannot unpack header of block "s + cur.to_str()); } // compute validator set ShardIdFull shard{masterchainId}; auto nodes = config->compute_validator_set(shard, info.gen_utime, info.gen_catchain_seqno); if (nodes.empty()) { return fatal_error(PSTRING() << "cannot compute validator set for block " << next.to_str() << " with utime " << info.gen_utime << " and cc_seqno " << info.gen_catchain_seqno << " starting from previous key block " << cur.to_str()); } auto vset = Ref{true, info.gen_catchain_seqno, shard, std::move(nodes)}; if (vset.is_null()) { return fatal_error(PSTRING() << "cannot create validator set for block " << next.to_str() << " with utime " << info.gen_utime << " and cc_seqno " << info.gen_catchain_seqno << " starting from previous key block " << cur.to_str()); } auto vset_hash = vset->get_validator_set_hash(); if (vset_hash != info.gen_validator_list_hash_short) { return fatal_error(PSTRING() << "computed validator set for block " << next.to_str() << " with utime " << info.gen_utime << " and cc_seqno " << info.gen_catchain_seqno << " starting from previous key block " << cur.to_str() << " has hash " << vset_hash << " different from " << info.gen_validator_list_hash_short << " stated in block header"); } // extract signatures auto sig_outer_root = vres2.ok().sig_root; block::gen::BlockSignatures::Record sign_rec; block::gen::BlockSignaturesPure::Record sign_pure; if (!(sig_outer_root.not_null() && tlb::unpack_cell(sig_outer_root, sign_rec) && tlb::csr_unpack(sign_rec.pure_signatures, sign_pure))) { return fatal_error("cannot extract signature set from proof for block "s + next.to_str()); } auto sigs = BlockSignatureSetQ::fetch(sign_pure.signatures->prefetch_ref()); if (sigs.is_null()) { return fatal_error("cannot deserialize signature set from proof for block "s + next.to_str()); } // check signatures (sanity check; comment later for better performance) /* auto S = vset->check_signatures(next.root_hash, next.file_hash, sigs); if (S.is_error()) { return fatal_error("error checking signatures from proof for block "s + next.to_str() + " : " + S.move_as_error().to_string()); } */ // serialize signatures auto& link = chain_->new_link(cur, next, info.key_block); link.cc_seqno = info.gen_catchain_seqno; link.validator_set_hash = info.gen_validator_list_hash_short; link.signatures = std::move(sigs.write().signatures()); // serialize proofs if (!(cur_mpb.extract_proof_to(link.proof) && next_mpb.extract_proof_to(link.dest_proof))) { return fatal_error("error constructing Merkle proof for forward proof link from "s + cur.to_str() + " to " + next.to_str()); } // continue constructing from `next` return construct_proof_chain(next); } catch (vm::VmVirtError&) { return fatal_error("virtualization error during construction of forward proof link from "s + cur.to_str() + " to " + next.to_str()); } return fatal_error("construction of forward proof links not implemented yet"); } bool LiteQuery::construct_proof_link_back(ton::BlockIdExt cur, ton::BlockIdExt next) { LOG(INFO) << "constructing a backward proof link from " << cur.to_str() << " to " << next.to_str(); CHECK(chain_); if (!(cur.is_masterchain_ext() && next.is_masterchain_ext() && mc_state0_->check_old_mc_block_id(cur) && mc_state0_->check_old_mc_block_id(next))) { return fatal_error("cannot construct backward proof link from "s + cur.to_str() + " to " + next.to_str() + " because these are not known masterchain block ids"); } if (cur.seqno() <= next.seqno()) { return fatal_error("cannot construct backward proof link from "s + cur.to_str() + " to " + next.to_str()); } set_continuation([this, cur, next]() { construct_proof_link_back_cont(cur, next); }); return request_proof_link(cur) && request_mc_block_state(cur); } bool LiteQuery::construct_proof_link_back_cont(ton::BlockIdExt cur, ton::BlockIdExt next) { LOG(INFO) << "continue constructing a backward proof link from " << cur.to_str() << " to " << next.to_str(); CHECK(mc_state_.not_null() && proof_link_.not_null() && mc_state_->get_block_id() == cur && proof_link_->block_id() == cur); try { // virtualize proof link auto vres1 = proof_link_->get_virtual_root(); if (vres1.is_error()) { return fatal_error(vres1.move_as_error()); } auto vroot = vres1.ok().root; // adjust dest_proof and is_key of the last link of existing proof if (!adjust_last_proof_link(cur, vroot)) { return false; } // construct proof that `mc_state_` is the state of `cur` Ref state_proof, proof; if (!make_state_root_proof(proof, mc_state_->root_cell(), vroot, cur)) { return fatal_error("cannot construct proof for state of masterchain block "s + cur.to_str()); } // construct proof that `next` is listed in OldMcBlocksInfo of `mc_state_` if (!make_ancestor_block_proof(state_proof, mc_state_->root_cell(), next)) { return fatal_error("cannot prove that "s + next.to_str() + " is in the previous block set of the masterchain state of " + cur.to_str()); } // create a BlockProofLink for cur -> next (without dest_proof) auto& link = chain_->new_link(cur, next, !next.seqno()); link.proof = std::move(proof); link.state_proof = std::move(state_proof); // continue constructing proof chain from `next` return construct_proof_chain(next); } catch (vm::VmVirtError&) { return fatal_error("virtualization error during construction of backward proof link from "s + cur.to_str() + " to " + next.to_str()); } } bool LiteQuery::finish_proof_chain(ton::BlockIdExt id) { CHECK(chain_); LOG(INFO) << "finish constructing block proof chain from " << chain_->from.to_str() << " to " << chain_->to.to_str() << " (constructed " << chain_->link_count() << " up to " << id.to_str() << ")"; try { if (chain_->last_link_incomplete() && chain_->last_link().to.seqno()) { CHECK(proof_link_.not_null() && proof_link_->block_id() == id); auto vres1 = proof_link_->get_virtual_root(); if (vres1.is_error()) { return fatal_error(vres1.move_as_error()); } if (!adjust_last_proof_link(id, vres1.ok().root)) { return false; } } chain_->complete = (id == chain_->to); chain_->to = id; // serialize answer std::vector> a; for (auto& link : chain_->links) { td::BufferSlice dest_proof_boc; if (link.to.seqno()) { auto res = vm::std_boc_serialize(link.dest_proof); if (res.is_error()) { return fatal_error("error while serializing destination block Merkle proof in block proof link from "s + link.from.to_str() + " to " + link.to.to_str() + " : " + res.move_as_error().to_string()); } dest_proof_boc = res.move_as_ok(); } auto src_proof_boc = vm::std_boc_serialize(link.proof); if (src_proof_boc.is_error()) { return fatal_error("error while serializing source block Merkle proof in block proof link from "s + link.from.to_str() + " to " + link.to.to_str() + " : " + src_proof_boc.move_as_error().to_string()); } if (link.is_fwd) { // serialize forward link std::vector> b; for (auto& sig : link.signatures) { b.push_back(create_tl_object(sig.node, std::move(sig.signature))); } a.push_back(create_tl_object( link.is_key, ton::create_tl_lite_block_id(link.from), ton::create_tl_lite_block_id(link.to), std::move(dest_proof_boc), src_proof_boc.move_as_ok(), create_tl_object(link.validator_set_hash, link.cc_seqno, std::move(b)))); } else { // serialize backward link auto state_proof_boc = vm::std_boc_serialize(link.state_proof); if (state_proof_boc.is_error()) { return fatal_error("error while serializing source state Merkle proof in block proof link from "s + link.from.to_str() + " to " + link.to.to_str() + " : " + state_proof_boc.move_as_error().to_string()); } a.push_back(create_tl_object( link.is_key, ton::create_tl_lite_block_id(link.from), ton::create_tl_lite_block_id(link.to), std::move(dest_proof_boc), src_proof_boc.move_as_ok(), state_proof_boc.move_as_ok())); } } LOG(INFO) << "getBlockProof() query completed"; auto c = ton::create_serialize_tl_object( chain_->complete, ton::create_tl_lite_block_id(chain_->from), ton::create_tl_lite_block_id(chain_->to), std::move(a)); return finish_query(std::move(c)); } catch (vm::VmError& err) { return fatal_error("vm error while constructing block proof chain : "s + err.get_msg()); } catch (vm::VmVirtError& err) { return fatal_error("virtualization error while constructing block proof chain : "s + err.get_msg()); } } void LiteQuery::perform_getValidatorStats(BlockIdExt blkid, int mode, int count, Bits256 start_after, UnixTime min_utime) { LOG(INFO) << "started a getValidatorStats(" << blkid.to_str() << ", " << mode << ", " << count << ", " << start_after.to_hex() << ", " << min_utime << ") liteserver query"; if (count <= 0) { fatal_error("requested entry count limit must be positive"); return; } if ((mode & ~7) != 0) { fatal_error("unknown flags set in mode"); return; } set_continuation([this, mode, count, min_utime, start_after]() { continue_getValidatorStats(mode, count, start_after, min_utime); }); request_mc_block_data_state(blkid); } void LiteQuery::continue_getValidatorStats(int mode, int limit, Bits256 start_after, UnixTime min_utime) { LOG(INFO) << "completing getValidatorStats(" << base_blk_id_.to_str() << ", " << mode << ", " << limit << ", " << start_after.to_hex() << ", " << min_utime << ") liteserver query"; Ref proof1; if (!make_mc_state_root_proof(proof1)) { return; } vm::MerkleProofBuilder mpb{mc_state_->root_cell()}; int count; bool complete = false, allow_eq = (mode & 3) != 1; limit = std::min(limit, 1000); try { auto dict = block::get_block_create_stats_dict(mpb.root()); if (!dict) { fatal_error("cannot extract block create stats from mc state"); return; } for (count = 0; count < limit; count++) { auto v = dict->lookup_nearest_key(start_after, true, allow_eq); if (v.is_null()) { complete = true; break; } if (!block::gen::t_CreatorStats.validate_csr(std::move(v))) { fatal_error("invalid CreatorStats record with key "s + start_after.to_hex()); return; } allow_eq = false; } } catch (vm::VmError& err) { fatal_error("error while traversing required block create stats records: "s + err.get_msg()); return; } auto res1 = vm::std_boc_serialize(std::move(proof1)); if (res1.is_error()) { fatal_error("cannot serialize Merkle proof : "s + res1.move_as_error().to_string()); return; } auto res2 = mpb.extract_proof_boc(); if (res2.is_error()) { fatal_error("cannot serialize Merkle proof : "s + res2.move_as_error().to_string()); return; } LOG(INFO) << "getValidatorStats() query completed"; auto b = ton::create_serialize_tl_object( mode & 0xff, ton::create_tl_lite_block_id(base_blk_id_), count, complete, res1.move_as_ok(), res2.move_as_ok()); finish_query(std::move(b)); } } // namespace validator } // namespace ton