mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 11:12:16 +00:00
2362 lines
91 KiB
C++
2362 lines
91 KiB
C++
/*
|
|
This file is part of TON Blockchain Library.
|
|
|
|
TON Blockchain Library is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
TON Blockchain Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
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 "mc-config.h"
|
|
#include "block/block.h"
|
|
#include "block/block-parse.h"
|
|
#include "block/block-auto.h"
|
|
#include "common/bitstring.h"
|
|
#include "vm/dict.h"
|
|
#include "td/utils/bits.h"
|
|
#include "td/utils/uint128.h"
|
|
#include "ton/ton-types.h"
|
|
#include "ton/ton-shard.h"
|
|
#include "openssl/digest.hpp"
|
|
#include <stack>
|
|
#include <algorithm>
|
|
|
|
namespace block {
|
|
using namespace std::literals::string_literals;
|
|
using td::Ref;
|
|
|
|
#define DBG(__n) dbg(__n)&&
|
|
#define DSTART int __dcnt = 0;
|
|
#define DEB DBG(++__dcnt)
|
|
|
|
static inline bool dbg(int c) TD_UNUSED;
|
|
static inline bool dbg(int c) {
|
|
std::cerr << '[' << (char)('0' + c / 10) << (char)('0' + c % 10) << ']';
|
|
return true;
|
|
}
|
|
|
|
Config::Config(Ref<vm::Cell> config_root, const td::Bits256& config_addr, int _mode)
|
|
: mode(_mode), config_addr(config_addr), config_root(std::move(config_root)) {
|
|
}
|
|
|
|
td::Result<std::unique_ptr<Config>> Config::unpack_config(Ref<vm::Cell> config_root, const td::Bits256& config_addr,
|
|
int mode) {
|
|
std::unique_ptr<Config> ptr{new Config(std::move(config_root), config_addr, mode)};
|
|
TRY_STATUS(ptr->unpack_wrapped());
|
|
return std::move(ptr);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<Config>> Config::unpack_config(Ref<vm::CellSlice> config_csr, int mode) {
|
|
std::unique_ptr<Config> ptr{new Config(mode)};
|
|
TRY_STATUS(ptr->unpack_wrapped(std::move(config_csr)));
|
|
return std::move(ptr);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<Config>> Config::extract_from_key_block(Ref<vm::Cell> key_block_root, int mode) {
|
|
block::gen::Block::Record blk;
|
|
block::gen::BlockExtra::Record extra;
|
|
block::gen::McBlockExtra::Record mc_extra;
|
|
if (!(tlb::unpack_cell(key_block_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra) &&
|
|
tlb::unpack_cell(extra.custom->prefetch_ref(), mc_extra) && mc_extra.key_block && mc_extra.config.not_null())) {
|
|
return td::Status::Error(-400, "cannot unpack extra header of key block to extract configuration");
|
|
}
|
|
return block::Config::unpack_config(std::move(mc_extra.config), mode);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<Config>> Config::extract_from_state(Ref<vm::Cell> mc_state_root, int mode) {
|
|
gen::ShardStateUnsplit::Record state;
|
|
gen::McStateExtra::Record extra;
|
|
if (!(tlb::unpack_cell(mc_state_root, state) && state.global_id &&
|
|
tlb::unpack_cell(state.custom->prefetch_ref(), extra))) {
|
|
return td::Status::Error("cannot extract configuration from masterchain state extra information");
|
|
}
|
|
return unpack_config(std::move(extra.config), mode);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<ConfigInfo>> ConfigInfo::extract_config(std::shared_ptr<vm::StaticBagOfCellsDb> static_boc,
|
|
int mode) {
|
|
TRY_RESULT(rc, static_boc->get_root_count());
|
|
if (rc != 1) {
|
|
return td::Status::Error(-668, "Masterchain state BoC is invalid");
|
|
}
|
|
TRY_RESULT(root, static_boc->get_root_cell(0));
|
|
return extract_config(std::move(root), mode);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<ConfigInfo>> ConfigInfo::extract_config(Ref<vm::Cell> mc_state_root, int mode) {
|
|
if (mc_state_root.is_null()) {
|
|
return td::Status::Error("configuration state root cell is null");
|
|
}
|
|
auto config = std::unique_ptr<ConfigInfo>{new ConfigInfo(std::move(mc_state_root), mode)};
|
|
TRY_STATUS(config->unpack_wrapped());
|
|
return std::move(config);
|
|
}
|
|
|
|
ConfigInfo::ConfigInfo(Ref<vm::Cell> mc_state_root, int _mode) : Config(_mode), state_root(std::move(mc_state_root)) {
|
|
block_id.root_hash.set_zero();
|
|
block_id.file_hash.set_zero();
|
|
}
|
|
|
|
td::Status ConfigInfo::unpack_wrapped() {
|
|
try {
|
|
return unpack();
|
|
} catch (vm::VmError& err) {
|
|
return td::Status::Error(PSLICE() << "error unpacking block state header and configuration: " << err.get_msg());
|
|
} catch (vm::VmVirtError& err) {
|
|
return td::Status::Error(PSLICE() << "virtualization error while unpacking block state header and configuration: "
|
|
<< err.get_msg());
|
|
}
|
|
}
|
|
|
|
td::Status ConfigInfo::unpack() {
|
|
gen::ShardStateUnsplit::Record root_info;
|
|
if (!tlb::unpack_cell(state_root, root_info) || !root_info.global_id) {
|
|
return td::Status::Error("configuration state root cannot be deserialized");
|
|
}
|
|
global_id_ = root_info.global_id;
|
|
block::ShardId shard_id{root_info.shard_id};
|
|
block_id.id = ton::BlockId{ton::ShardIdFull(shard_id), (unsigned)root_info.seq_no};
|
|
block_id.root_hash.set_zero();
|
|
block_id.file_hash.set_zero();
|
|
vert_seqno = root_info.vert_seq_no;
|
|
utime = root_info.gen_utime;
|
|
lt = root_info.gen_lt;
|
|
min_ref_mc_seqno_ = root_info.min_ref_mc_seqno;
|
|
if (!root_info.custom->size_refs()) {
|
|
return td::Status::Error("state does not have a `custom` field with masterchain configuration");
|
|
}
|
|
if (mode & needLibraries) {
|
|
lib_root_ = root_info.r1.libraries->prefetch_ref();
|
|
libraries_dict_ = std::make_unique<vm::Dictionary>(lib_root_, 256);
|
|
}
|
|
if (mode & needAccountsRoot) {
|
|
accounts_root = vm::load_cell_slice_ref(root_info.accounts);
|
|
LOG(DEBUG) << "requested accounts dictionary";
|
|
accounts_dict = std::make_unique<vm::AugmentedDictionary>(accounts_root, 256, block::tlb::aug_ShardAccounts);
|
|
LOG(DEBUG) << "accounts dictionary created";
|
|
}
|
|
state_extra_root_ = root_info.custom->prefetch_ref();
|
|
if (!is_masterchain()) {
|
|
if (mode & (needShardHashes | needValidatorSet | needSpecialSmc | needPrevBlocks | needWorkchainInfo)) {
|
|
return td::Status::Error("cannot extract masterchain-specific configuration data from a non-masterchain state");
|
|
}
|
|
cleanup();
|
|
return td::Status::OK();
|
|
}
|
|
gen::McStateExtra::Record extra_info;
|
|
if (!tlb::unpack_cell(state_extra_root_, extra_info)) {
|
|
vm::load_cell_slice(state_extra_root_).print_rec(std::cerr);
|
|
block::gen::t_McStateExtra.print_ref(std::cerr, state_extra_root_);
|
|
return td::Status::Error("state extra information is invalid");
|
|
}
|
|
gen::ValidatorInfo::Record validator_info;
|
|
if (!tlb::csr_unpack(extra_info.r1.validator_info, validator_info)) {
|
|
return td::Status::Error("validator_info in state extra information is invalid");
|
|
}
|
|
cc_seqno_ = validator_info.catchain_seqno;
|
|
nx_cc_updated = validator_info.nx_cc_updated;
|
|
if ((mode & needShardHashes) && !ShardConfig::unpack(extra_info.shard_hashes)) {
|
|
return td::Status::Error("cannot unpack Shard configuration");
|
|
}
|
|
is_key_state_ = extra_info.r1.after_key_block;
|
|
if (extra_info.r1.last_key_block->size() > 1) {
|
|
auto& cs = extra_info.r1.last_key_block.write();
|
|
block::gen::ExtBlkRef::Record ext_ref;
|
|
if (!(cs.advance(1) && tlb::unpack_exact(cs, ext_ref))) {
|
|
return td::Status::Error("cannot unpack last_key_block from masterchain state");
|
|
}
|
|
last_key_block_.id = ton::BlockId{ton::masterchainId, ton::shardIdAll, ext_ref.seq_no};
|
|
last_key_block_.root_hash = ext_ref.root_hash;
|
|
last_key_block_.file_hash = ext_ref.file_hash;
|
|
last_key_block_lt_ = ext_ref.end_lt;
|
|
} else {
|
|
last_key_block_.invalidate();
|
|
last_key_block_.id.seqno = 0;
|
|
last_key_block_lt_ = 0;
|
|
}
|
|
// unpack configuration
|
|
TRY_STATUS(Config::unpack_wrapped(std::move(extra_info.config)));
|
|
// unpack previous masterchain block collection
|
|
std::unique_ptr<vm::AugmentedDictionary> prev_blocks_dict =
|
|
std::make_unique<vm::AugmentedDictionary>(extra_info.r1.prev_blocks, 32, block::tlb::aug_OldMcBlocksInfo);
|
|
if (block_id.id.seqno) {
|
|
block::gen::ExtBlkRef::Record extref = {};
|
|
auto ref = prev_blocks_dict->lookup(td::BitArray<32>::zero());
|
|
if (!(ref.not_null() && ref.write().advance(1) && tlb::csr_unpack(ref, extref) && !extref.seq_no)) {
|
|
return td::Status::Error("OldMcBlocks in masterchain state does not contain a valid zero state reference");
|
|
}
|
|
zerostate_id_.root_hash = extref.root_hash;
|
|
zerostate_id_.file_hash = extref.file_hash;
|
|
} else {
|
|
zerostate_id_.root_hash.set_zero();
|
|
zerostate_id_.file_hash.set_zero();
|
|
}
|
|
zerostate_id_.workchain = ton::masterchainId;
|
|
if (mode & needPrevBlocks) {
|
|
prev_blocks_dict_ = std::move(prev_blocks_dict);
|
|
}
|
|
// ...
|
|
cleanup();
|
|
return td::Status::OK();
|
|
}
|
|
|
|
td::Status Config::unpack_wrapped(Ref<vm::CellSlice> config_csr) {
|
|
try {
|
|
return unpack(std::move(config_csr));
|
|
} catch (vm::VmError err) {
|
|
return td::Status::Error(PSLICE() << "error unpacking masterchain configuration: " << err.get_msg());
|
|
}
|
|
}
|
|
|
|
td::Status Config::unpack_wrapped() {
|
|
try {
|
|
return unpack();
|
|
} catch (vm::VmError err) {
|
|
return td::Status::Error(PSLICE() << "error unpacking masterchain configuration: " << err.get_msg());
|
|
}
|
|
}
|
|
|
|
td::Status Config::unpack(Ref<vm::CellSlice> config_cs) {
|
|
gen::ConfigParams::Record config_params;
|
|
if (!tlb::csr_unpack(std::move(config_cs), config_params)) {
|
|
return td::Status::Error("cannot unpack ConfigParams");
|
|
}
|
|
config_addr = config_params.config_addr;
|
|
config_root = std::move(config_params.config);
|
|
return unpack();
|
|
}
|
|
|
|
td::Status Config::unpack() {
|
|
if (config_root.is_null()) {
|
|
return td::Status::Error("configuration root not set");
|
|
}
|
|
config_dict = std::make_unique<vm::Dictionary>(config_root, 32);
|
|
if (mode & needValidatorSet) {
|
|
auto vset_res = unpack_validator_set(get_config_param(35, 34));
|
|
if (vset_res.is_error()) {
|
|
return vset_res.move_as_error();
|
|
}
|
|
cur_validators_ = vset_res.move_as_ok();
|
|
}
|
|
if (mode & needSpecialSmc) {
|
|
LOG(DEBUG) << "needSpecialSmc flag set";
|
|
auto param = get_config_param(31);
|
|
if (param.is_null()) {
|
|
special_smc_dict = std::make_unique<vm::Dictionary>(256);
|
|
} else {
|
|
special_smc_dict = std::make_unique<vm::Dictionary>(vm::load_cell_slice_ref(std::move(param)), 256);
|
|
LOG(DEBUG) << "smc dictionary created";
|
|
}
|
|
}
|
|
if (mode & needWorkchainInfo) {
|
|
TRY_RESULT(pair, unpack_workchain_list_ext(get_config_param(12)));
|
|
workchains_ = std::move(pair.first);
|
|
workchains_dict_ = std::move(pair.second);
|
|
}
|
|
if (mode & needCapabilities) {
|
|
auto cell = get_config_param(8);
|
|
if (cell.is_null()) {
|
|
version_ = 0;
|
|
capabilities_ = 0;
|
|
} else {
|
|
block::gen::GlobalVersion::Record gv;
|
|
if (!tlb::unpack_cell(std::move(cell), gv)) {
|
|
return td::Status::Error(
|
|
"cannot extract global blockchain version and capabilities from GlobalVersion in configuration parameter "
|
|
"#8");
|
|
}
|
|
version_ = gv.version;
|
|
capabilities_ = gv.capabilities;
|
|
}
|
|
}
|
|
// ...
|
|
return td::Status::OK();
|
|
}
|
|
|
|
td::Status Config::visit_validator_params() const {
|
|
{
|
|
// current validator set
|
|
TRY_RESULT(vset, unpack_validator_set(get_config_param(34)));
|
|
}
|
|
for (int i = 32; i < 38; i++) {
|
|
// prev/current/next persistent and temporary validator sets
|
|
auto vs = get_config_param(i);
|
|
if (vs.not_null()) {
|
|
TRY_RESULT(vset, unpack_validator_set(std::move(vs)));
|
|
}
|
|
}
|
|
get_catchain_validators_config();
|
|
return td::Status::OK();
|
|
}
|
|
|
|
ton::ValidatorSessionConfig Config::get_consensus_config() const {
|
|
auto cc = get_config_param(29);
|
|
ton::ValidatorSessionConfig c;
|
|
auto set_v1 = [&](auto& r) {
|
|
c.catchain_opts.idle_timeout = r.consensus_timeout_ms * 0.001;
|
|
c.catchain_opts.max_deps = r.catchain_max_deps;
|
|
c.round_candidates = r.round_candidates;
|
|
c.next_candidate_delay = r.next_candidate_delay_ms * 0.001;
|
|
c.round_attempt_duration = r.attempt_duration;
|
|
c.max_round_attempts = r.fast_attempts;
|
|
c.max_block_size = r.max_block_bytes;
|
|
c.max_collated_data_size = r.max_collated_bytes;
|
|
};
|
|
auto set_v2 = [&](auto& r) {
|
|
set_v1(r);
|
|
c.new_catchain_ids = r.new_catchain_ids;
|
|
};
|
|
auto set_v3 = [&](auto& r) {
|
|
set_v2(r);
|
|
c.proto_version = r.proto_version;
|
|
};
|
|
auto set_v4 = [&](auto& r) {
|
|
set_v3(r);
|
|
td::uint64 max_blocks_coeff = r.catchain_max_blocks_coeff;
|
|
if (max_blocks_coeff == 0) {
|
|
c.catchain_opts.max_block_height_coeff = 0;
|
|
} else {
|
|
auto catchain_config = get_catchain_validators_config();
|
|
td::uint64 catchain_lifetime = std::max(catchain_config.mc_cc_lifetime, catchain_config.shard_cc_lifetime);
|
|
c.catchain_opts.max_block_height_coeff = catchain_lifetime * max_blocks_coeff;
|
|
}
|
|
};
|
|
if (cc.not_null()) {
|
|
block::gen::ConsensusConfig::Record_consensus_config_v4 r4;
|
|
block::gen::ConsensusConfig::Record_consensus_config_v3 r3;
|
|
block::gen::ConsensusConfig::Record_consensus_config_new r2;
|
|
block::gen::ConsensusConfig::Record_consensus_config r1;
|
|
if (tlb::unpack_cell(cc, r4)) {
|
|
set_v4(r4);
|
|
} else if (tlb::unpack_cell(cc, r3)) {
|
|
set_v3(r3);
|
|
} else if (tlb::unpack_cell(cc, r2)) {
|
|
set_v2(r2);
|
|
} else if (tlb::unpack_cell(cc, r1)) {
|
|
set_v1(r1);
|
|
}
|
|
}
|
|
if (c.proto_version >= ton::ValidatorSessionConfig::BLOCK_HASH_COVERS_DATA_FROM_VERSION) {
|
|
c.catchain_opts.block_hash_covers_data = true;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
bool Config::foreach_config_param(std::function<bool(int, Ref<vm::Cell>)> scan_func) const {
|
|
if (!config_dict) {
|
|
return false;
|
|
}
|
|
return config_dict->check_for_each([scan_func](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) {
|
|
return n == 32 && cs_ref.not_null() && cs_ref->size_ext() == 0x10000 &&
|
|
scan_func((int)key.get_int(n), cs_ref->prefetch_ref());
|
|
});
|
|
}
|
|
|
|
std::unique_ptr<vm::Dictionary> ShardConfig::extract_shard_hashes_dict(Ref<vm::Cell> mc_state_root) {
|
|
gen::ShardStateUnsplit::Record root_info;
|
|
gen::McStateExtra::Record extra_info;
|
|
if (mc_state_root.not_null() //
|
|
&& tlb::unpack_cell(mc_state_root, root_info) //
|
|
&& tlb::unpack_cell(root_info.custom->prefetch_ref(), extra_info)) {
|
|
return std::make_unique<vm::Dictionary>(std::move(extra_info.shard_hashes), 32);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
td::Result<std::vector<int>> Config::unpack_param_dict(vm::Dictionary& dict) {
|
|
try {
|
|
std::vector<int> vect;
|
|
if (dict.check_for_each(
|
|
[&vect](Ref<vm::CellSlice> value, td::ConstBitPtr key, int key_len) {
|
|
bool ok = (key_len == 32 && value->empty_ext());
|
|
if (ok) {
|
|
vect.push_back((int)key.get_int(32));
|
|
}
|
|
return ok;
|
|
},
|
|
true)) {
|
|
return std::move(vect);
|
|
} else {
|
|
return td::Status::Error("invalid parameter list dictionary");
|
|
}
|
|
} catch (vm::VmError& vme) {
|
|
return td::Status::Error("error unpacking parameter list dictionary: "s + vme.get_msg());
|
|
}
|
|
}
|
|
|
|
td::Result<std::vector<int>> Config::unpack_param_dict(Ref<vm::Cell> dict_root) {
|
|
vm::Dictionary dict{std::move(dict_root), 32};
|
|
return unpack_param_dict(dict);
|
|
}
|
|
|
|
std::unique_ptr<vm::Dictionary> Config::get_param_dict(int idx) const {
|
|
return std::make_unique<vm::Dictionary>(get_config_param(idx), 32);
|
|
}
|
|
|
|
td::Result<std::vector<int>> Config::unpack_param_list(int idx) const {
|
|
return unpack_param_dict(*get_param_dict(idx));
|
|
}
|
|
|
|
bool Config::all_mandatory_params_defined(int* bad_idx_ptr) const {
|
|
auto res = get_mandatory_param_list();
|
|
if (res.is_error()) {
|
|
if (bad_idx_ptr) {
|
|
*bad_idx_ptr = -1;
|
|
}
|
|
return false;
|
|
}
|
|
for (int x : res.move_as_ok()) {
|
|
if (get_config_param(x).is_null()) {
|
|
if (bad_idx_ptr) {
|
|
*bad_idx_ptr = x;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<vm::AugmentedDictionary> ConfigInfo::create_accounts_dict() const {
|
|
if (mode & needAccountsRoot) {
|
|
return std::make_unique<vm::AugmentedDictionary>(accounts_root, 256, block::tlb::aug_ShardAccounts);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
const vm::AugmentedDictionary& ConfigInfo::get_accounts_dict() const {
|
|
return *accounts_dict;
|
|
}
|
|
|
|
bool ConfigInfo::get_last_key_block(ton::BlockIdExt& blkid, ton::LogicalTime& blklt, bool strict) const {
|
|
if (strict || !is_key_state_) {
|
|
blkid = last_key_block_;
|
|
blklt = last_key_block_lt_;
|
|
} else {
|
|
blkid = block_id;
|
|
blklt = lt;
|
|
}
|
|
return blkid.is_valid();
|
|
}
|
|
|
|
td::Result<std::pair<WorkchainSet, std::unique_ptr<vm::Dictionary>>> Config::unpack_workchain_list_ext(
|
|
Ref<vm::Cell> root) {
|
|
if (root.is_null()) {
|
|
LOG(DEBUG) << "workchain description dictionary is empty (no configuration parameter #12)";
|
|
return std::make_pair(WorkchainSet{}, std::make_unique<vm::Dictionary>(32));
|
|
} else {
|
|
auto wc_dict = std::make_unique<vm::Dictionary>(vm::load_cell_slice_ref(std::move(root)), 32);
|
|
WorkchainSet wc_list;
|
|
LOG(DEBUG) << "workchain description dictionary created";
|
|
if (!(wc_dict->check_for_each([&wc_list](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) -> bool {
|
|
ton::WorkchainId wc = ton::WorkchainId(key.get_int(32));
|
|
Ref<WorkchainInfo> wc_info{true};
|
|
return wc_info.unique_write().unpack(wc, cs_ref.write()) && wc_list.emplace(wc, std::move(wc_info)).second;
|
|
}))) {
|
|
return td::Status::Error("cannot unpack WorkchainDescr from masterchain configuration");
|
|
}
|
|
return std::make_pair(std::move(wc_list), std::move(wc_dict));
|
|
}
|
|
}
|
|
|
|
td::Result<WorkchainSet> Config::unpack_workchain_list(Ref<vm::Cell> root) {
|
|
TRY_RESULT(pair, unpack_workchain_list_ext(std::move(root)));
|
|
return std::move(pair.first);
|
|
}
|
|
|
|
td::Result<std::unique_ptr<ValidatorSet>> Config::unpack_validator_set(Ref<vm::Cell> vset_root) {
|
|
if (vset_root.is_null()) {
|
|
return td::Status::Error("validator set is absent");
|
|
}
|
|
gen::ValidatorSet::Record_validators_ext rec;
|
|
Ref<vm::Cell> dict_root;
|
|
if (!tlb::unpack_cell(vset_root, rec)) {
|
|
gen::ValidatorSet::Record_validators rec0;
|
|
if (!tlb::unpack_cell(std::move(vset_root), rec0)) {
|
|
return td::Status::Error("validator set is invalid");
|
|
}
|
|
rec.utime_since = rec0.utime_since;
|
|
rec.utime_until = rec0.utime_until;
|
|
rec.total = rec0.total;
|
|
rec.main = rec0.main;
|
|
dict_root = vm::Dictionary::construct_root_from(*rec0.list);
|
|
rec.total_weight = 0;
|
|
} else if (rec.total_weight) {
|
|
dict_root = rec.list->prefetch_ref();
|
|
} else {
|
|
return td::Status::Error("validator set cannot have zero total weight");
|
|
}
|
|
vm::Dictionary dict{std::move(dict_root), 16};
|
|
td::BitArray<16> key_buffer;
|
|
auto last = dict.get_minmax_key(key_buffer.bits(), 16, true);
|
|
if (last.is_null() || (int)key_buffer.to_ulong() != rec.total - 1) {
|
|
return td::Status::Error(
|
|
"maximal index in a validator set dictionary must be one less than the total number of validators");
|
|
}
|
|
auto ptr = std::make_unique<ValidatorSet>(rec.utime_since, rec.utime_until, rec.total, rec.main);
|
|
for (int i = 0; i < rec.total; i++) {
|
|
key_buffer.store_ulong(i);
|
|
auto descr_cs = dict.lookup(key_buffer.bits(), 16);
|
|
if (descr_cs.is_null()) {
|
|
return td::Status::Error("indices in a validator set dictionary must be integers 0..total-1");
|
|
}
|
|
gen::ValidatorDescr::Record_validator_addr descr;
|
|
if (!tlb::csr_unpack(descr_cs, descr)) {
|
|
descr.adnl_addr.set_zero();
|
|
if (!(gen::t_ValidatorDescr.unpack_validator(descr_cs.write(), descr.public_key, descr.weight) &&
|
|
descr_cs->empty_ext())) {
|
|
return td::Status::Error(PSLICE() << "validator #" << i
|
|
<< " has an invalid ValidatorDescr record in the validator set dictionary");
|
|
}
|
|
}
|
|
gen::SigPubKey::Record sig_pubkey;
|
|
if (!tlb::csr_unpack(std::move(descr.public_key), sig_pubkey)) {
|
|
return td::Status::Error(PSLICE() << "validator #" << i
|
|
<< " has no public key or its public key is in unsupported format");
|
|
}
|
|
if (!descr.weight) {
|
|
return td::Status::Error(PSLICE() << "validator #" << i << " has zero weight");
|
|
}
|
|
if (descr.weight > ~(ptr->total_weight)) {
|
|
return td::Status::Error("total weight of all validators in validator set exceeds 2^64");
|
|
}
|
|
ptr->list.emplace_back(sig_pubkey.pubkey, descr.weight, ptr->total_weight, descr.adnl_addr);
|
|
ptr->total_weight += descr.weight;
|
|
}
|
|
if (rec.total_weight && rec.total_weight != ptr->total_weight) {
|
|
return td::Status::Error("validator set declares incorrect total weight");
|
|
}
|
|
return std::move(ptr);
|
|
}
|
|
|
|
bool Config::set_block_id_ext(const ton::BlockIdExt& block_id_ext) {
|
|
if (block_id.id == block_id_ext.id) {
|
|
block_id = block_id_ext;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ConfigInfo::set_block_id_ext(const ton::BlockIdExt& block_id_ext) {
|
|
if (!Config::set_block_id_ext(block_id_ext)) {
|
|
return false;
|
|
}
|
|
if (!block_id.seqno()) {
|
|
zerostate_id_.workchain = ton::masterchainId;
|
|
zerostate_id_.root_hash = block_id_ext.root_hash;
|
|
zerostate_id_.file_hash = block_id_ext.file_hash;
|
|
}
|
|
reset_mc_hash();
|
|
return true;
|
|
}
|
|
|
|
void ConfigInfo::cleanup() {
|
|
if (!(mode & needStateRoot)) {
|
|
state_root.clear();
|
|
}
|
|
if (!(mode & needStateExtraRoot)) {
|
|
state_extra_root_.clear();
|
|
}
|
|
}
|
|
|
|
Ref<vm::Cell> Config::get_config_param(int idx) const {
|
|
if (!config_dict) {
|
|
return {};
|
|
}
|
|
return config_dict->lookup_ref(td::BitArray<32>{idx});
|
|
}
|
|
|
|
Ref<vm::Cell> Config::get_config_param(int idx, int idx2) const {
|
|
if (!config_dict) {
|
|
return {};
|
|
}
|
|
auto res = config_dict->lookup_ref(td::BitArray<32>{idx});
|
|
if (res.not_null()) {
|
|
return res;
|
|
} else {
|
|
return config_dict->lookup_ref(td::BitArray<32>{idx2});
|
|
}
|
|
}
|
|
|
|
td::Result<std::unique_ptr<BlockLimits>> Config::get_block_limits(bool is_masterchain) const {
|
|
int param = (is_masterchain ? 22 : 23);
|
|
auto cell = get_config_param(param);
|
|
if (cell.is_null()) {
|
|
return td::Status::Error(PSTRING() << "configuration parameter " << param << " with block limits is absent");
|
|
}
|
|
auto cs = vm::load_cell_slice(std::move(cell));
|
|
auto ptr = std::make_unique<BlockLimits>();
|
|
if (!ptr->deserialize(cs) || cs.size_ext()) {
|
|
return td::Status::Error(PSTRING() << "cannot deserialize BlockLimits obtained from configuration parameter "
|
|
<< param);
|
|
}
|
|
return std::move(ptr);
|
|
}
|
|
|
|
td::Result<std::vector<StoragePrices>> Config::get_storage_prices() const {
|
|
auto cell = get_config_param(18);
|
|
std::vector<StoragePrices> res;
|
|
if (cell.is_null()) {
|
|
return td::Status::Error("configuration parameter 18 with storage prices dictionary is absent");
|
|
}
|
|
vm::Dictionary dict{std::move(cell), 32};
|
|
if (!dict.check_for_each([&res](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) -> bool {
|
|
auto r_prices = do_get_one_storage_prices(*cs_ref);
|
|
if (r_prices.is_error()) {
|
|
return false;
|
|
}
|
|
res.push_back(r_prices.move_as_ok());
|
|
if (res.back().valid_since != key.get_uint(n)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})) {
|
|
return td::Status::Error("invalid storage prices dictionary in configuration parameter 18");
|
|
}
|
|
return std::move(res);
|
|
}
|
|
|
|
td::Result<StoragePrices> Config::do_get_one_storage_prices(vm::CellSlice cs) {
|
|
block::gen::StoragePrices::Record data;
|
|
if (!tlb::unpack(cs, data)) {
|
|
return td::Status::Error("invalid storage prices dictionary in configuration parameter 18");
|
|
}
|
|
return StoragePrices{data.utime_since, data.bit_price_ps, data.cell_price_ps, data.mc_bit_price_ps,
|
|
data.mc_cell_price_ps};
|
|
}
|
|
|
|
td::Result<GasLimitsPrices> Config::do_get_gas_limits_prices(vm::CellSlice cs, int id) {
|
|
GasLimitsPrices res;
|
|
vm::CellSlice cs0 = cs;
|
|
block::gen::GasLimitsPrices::Record_gas_flat_pfx flat;
|
|
if (tlb::unpack(cs, flat)) {
|
|
cs = *flat.other;
|
|
res.flat_gas_limit = flat.flat_gas_limit;
|
|
res.flat_gas_price = flat.flat_gas_price;
|
|
} else {
|
|
cs = cs0;
|
|
}
|
|
auto f = [&](const auto& r, td::uint64 spec_limit) {
|
|
res.gas_limit = r.gas_limit;
|
|
res.special_gas_limit = spec_limit;
|
|
res.gas_credit = r.gas_credit;
|
|
res.gas_price = r.gas_price;
|
|
res.freeze_due_limit = r.freeze_due_limit;
|
|
res.delete_due_limit = r.delete_due_limit;
|
|
};
|
|
block::gen::GasLimitsPrices::Record_gas_prices_ext rec;
|
|
if (tlb::unpack(cs, rec)) {
|
|
f(rec, rec.special_gas_limit);
|
|
} else {
|
|
block::gen::GasLimitsPrices::Record_gas_prices rec0;
|
|
if (tlb::unpack(cs = cs0, rec0)) {
|
|
f(rec0, rec0.gas_limit);
|
|
} else {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << id
|
|
<< " with gas prices is invalid - can't parse");
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
td::Result<ton::StdSmcAddress> Config::get_dns_root_addr() const {
|
|
auto cell = get_config_param(4);
|
|
if (cell.is_null()) {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address is absent");
|
|
}
|
|
auto cs = vm::load_cell_slice(std::move(cell));
|
|
if (cs.size() != 0x100) {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address has wrong size");
|
|
}
|
|
ton::StdSmcAddress res;
|
|
CHECK(cs.fetch_bits_to(res));
|
|
return res;
|
|
}
|
|
|
|
td::Result<GasLimitsPrices> Config::get_gas_limits_prices(bool is_masterchain) const {
|
|
auto id = is_masterchain ? 20 : 21;
|
|
auto cell = get_config_param(id);
|
|
if (cell.is_null()) {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with gas prices is absent");
|
|
}
|
|
return do_get_gas_limits_prices(vm::load_cell_slice(cell), id);
|
|
}
|
|
|
|
td::Result<MsgPrices> Config::get_msg_prices(bool is_masterchain) const {
|
|
auto id = is_masterchain ? 24 : 25;
|
|
auto cell = get_config_param(id);
|
|
if (cell.is_null()) {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with msg prices is absent");
|
|
}
|
|
return do_get_msg_prices(vm::load_cell_slice(cell), id);
|
|
}
|
|
|
|
td::Result<MsgPrices> Config::do_get_msg_prices(vm::CellSlice cs, int id) {
|
|
block::gen::MsgForwardPrices::Record rec;
|
|
if (!tlb::unpack(cs, rec)) {
|
|
return td::Status::Error(PSLICE() << "configuration parameter " << id
|
|
<< " with msg prices is invalid - can't parse");
|
|
}
|
|
return MsgPrices(rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, rec.first_frac, rec.next_frac);
|
|
}
|
|
|
|
CatchainValidatorsConfig Config::unpack_catchain_validators_config(Ref<vm::Cell> cell) {
|
|
if (cell.not_null()) {
|
|
block::gen::CatchainConfig::Record_catchain_config cfg;
|
|
if (tlb::unpack_cell(cell, cfg)) {
|
|
return {cfg.mc_catchain_lifetime, cfg.shard_catchain_lifetime, cfg.shard_validators_lifetime,
|
|
cfg.shard_validators_num};
|
|
}
|
|
block::gen::CatchainConfig::Record_catchain_config_new cfg2;
|
|
if (tlb::unpack_cell(std::move(cell), cfg2)) {
|
|
return {cfg2.mc_catchain_lifetime, cfg2.shard_catchain_lifetime, cfg2.shard_validators_lifetime,
|
|
cfg2.shard_validators_num, cfg2.shuffle_mc_validators};
|
|
}
|
|
}
|
|
return {default_mc_catchain_lifetime, default_shard_catchain_lifetime, default_shard_validators_lifetime,
|
|
default_shard_validators_num};
|
|
}
|
|
|
|
CatchainValidatorsConfig Config::get_catchain_validators_config() const {
|
|
return unpack_catchain_validators_config(get_config_param(28));
|
|
}
|
|
|
|
// compares all fields except fsm*, before_merge_, nx_cc_updated_, next_catchain_seqno_
|
|
bool McShardHash::basic_info_equal(const McShardHash& other, bool compare_fees, bool compare_reg_seqno) const {
|
|
return blk_ == other.blk_ && start_lt_ == other.start_lt_ && end_lt_ == other.end_lt_ &&
|
|
(!compare_reg_seqno || reg_mc_seqno_ == other.reg_mc_seqno_) && gen_utime_ == other.gen_utime_ &&
|
|
min_ref_mc_seqno_ == other.min_ref_mc_seqno_ && before_split_ == other.before_split_ &&
|
|
want_split_ == other.want_split_ && want_merge_ == other.want_merge_ &&
|
|
(!compare_fees || (fees_collected_ == other.fees_collected_ && funds_created_ == other.funds_created_));
|
|
}
|
|
|
|
void McShardHash::set_fsm(FsmState fsm, ton::UnixTime fsm_utime, ton::UnixTime fsm_interval) {
|
|
fsm_ = fsm;
|
|
fsm_utime_ = fsm_utime;
|
|
fsm_interval_ = fsm_interval;
|
|
}
|
|
|
|
Ref<McShardHash> McShardHash::unpack(vm::CellSlice& cs, ton::ShardIdFull id) {
|
|
int tag = gen::t_ShardDescr.get_tag(cs);
|
|
if (tag < 0) {
|
|
return {};
|
|
}
|
|
auto create = [&id](auto& descr, Ref<vm::CellSlice> fees, Ref<vm::CellSlice> funds) {
|
|
CurrencyCollection fees_collected, funds_created;
|
|
if (!(fees_collected.unpack(std::move(fees)) && funds_created.unpack(std::move(funds)))) {
|
|
return Ref<McShardHash>{};
|
|
}
|
|
return td::make_ref<McShardHash>(ton::BlockId{id, (unsigned)descr.seq_no}, descr.start_lt, descr.end_lt,
|
|
descr.gen_utime, descr.root_hash, descr.file_hash, fees_collected, funds_created,
|
|
descr.reg_mc_seqno, descr.min_ref_mc_seqno, descr.next_catchain_seqno,
|
|
descr.next_validator_shard, /* descr.nx_cc_updated */ false, descr.before_split,
|
|
descr.before_merge, descr.want_split, descr.want_merge);
|
|
};
|
|
Ref<McShardHash> res;
|
|
Ref<vm::CellSlice> fsm_cs;
|
|
if (tag == gen::ShardDescr::shard_descr) {
|
|
gen::ShardDescr::Record_shard_descr descr;
|
|
if (tlb::unpack_exact(cs, descr)) {
|
|
fsm_cs = std::move(descr.split_merge_at);
|
|
res = create(descr, std::move(descr.fees_collected), std::move(descr.funds_created));
|
|
}
|
|
} else {
|
|
gen::ShardDescr::Record_shard_descr_new descr;
|
|
if (tlb::unpack_exact(cs, descr)) {
|
|
fsm_cs = std::move(descr.split_merge_at);
|
|
res = create(descr, std::move(descr.r1.fees_collected), std::move(descr.r1.funds_created));
|
|
}
|
|
}
|
|
if (res.is_null()) {
|
|
return res;
|
|
}
|
|
McShardHash& sh = res.unique_write();
|
|
switch (gen::t_FutureSplitMerge.get_tag(*fsm_cs)) {
|
|
case gen::FutureSplitMerge::fsm_none:
|
|
return res;
|
|
case gen::FutureSplitMerge::fsm_split:
|
|
if (gen::t_FutureSplitMerge.unpack_fsm_split(fsm_cs.write(), sh.fsm_utime_, sh.fsm_interval_)) {
|
|
sh.fsm_ = FsmState::fsm_split;
|
|
return res;
|
|
}
|
|
break;
|
|
case gen::FutureSplitMerge::fsm_merge:
|
|
if (gen::t_FutureSplitMerge.unpack_fsm_merge(fsm_cs.write(), sh.fsm_utime_, sh.fsm_interval_)) {
|
|
sh.fsm_ = FsmState::fsm_merge;
|
|
return res;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool McShardHash::pack(vm::CellBuilder& cb) const {
|
|
if (!(is_valid() // (validate)
|
|
&& cb.store_long_bool(10, 4) // shard_descr_new#a
|
|
&& cb.store_long_bool(blk_.id.seqno, 32) // seq_no:uint32
|
|
&& cb.store_long_bool(reg_mc_seqno_, 32) // reg_mc_seqno:uint32
|
|
&& cb.store_long_bool(start_lt_, 64) // start_lt:uint64
|
|
&& cb.store_long_bool(end_lt_, 64) // end_lt:uint64
|
|
&& cb.store_bits_bool(blk_.root_hash) // root_hash:bits256
|
|
&& cb.store_bits_bool(blk_.file_hash) // file_hash:bits256
|
|
&& cb.store_bool_bool(before_split_) // before_split:Bool
|
|
&& cb.store_bool_bool(before_merge_) // before_merge:Bool
|
|
&& cb.store_bool_bool(want_split_) // want_split:Bool
|
|
&& cb.store_bool_bool(want_merge_) // want_merge:Bool
|
|
&& cb.store_bool_bool(false) // nx_cc_updated:Bool
|
|
&& cb.store_long_bool(0, 3) // flags:(## 3) { flags = 0 }
|
|
&& cb.store_long_bool(next_catchain_seqno_, 32) // next_catchain_seqno:uint32
|
|
&& cb.store_long_bool(next_validator_shard_, 64) // next_validator_shard:uint64
|
|
&& cb.store_long_bool(min_ref_mc_seqno_, 32) // min_ref_mc_seqno:uint32
|
|
&& cb.store_long_bool(gen_utime_, 32) // gen_utime:uint32
|
|
)) {
|
|
return false;
|
|
}
|
|
bool ok;
|
|
switch (fsm_) { // split_merge_at:FutureSplitMerge
|
|
case FsmState::fsm_none:
|
|
ok = gen::t_FutureSplitMerge.pack_fsm_none(cb);
|
|
break;
|
|
case FsmState::fsm_split:
|
|
ok = gen::t_FutureSplitMerge.pack_fsm_split(cb, fsm_utime_, fsm_interval_);
|
|
break;
|
|
case FsmState::fsm_merge:
|
|
ok = gen::t_FutureSplitMerge.pack_fsm_merge(cb, fsm_utime_, fsm_interval_);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
vm::CellBuilder cb2;
|
|
return ok // split_merge_at:FutureSplitMerge
|
|
&& fees_collected_.store_or_zero(cb2) // ^[ fees_collected:CurrencyCollection
|
|
&& funds_created_.store_or_zero(cb2) // funds_created:CurrencyCollection ]
|
|
&& cb.store_builder_ref_bool(std::move(cb2)); // = ShardDescr;
|
|
}
|
|
|
|
Ref<McShardHash> McShardHash::from_block(Ref<vm::Cell> block_root, const ton::FileHash& fhash, bool init_fees) {
|
|
if (block_root.is_null()) {
|
|
return {};
|
|
}
|
|
block::gen::Block::Record rec;
|
|
block::gen::BlockInfo::Record info;
|
|
block::ShardId shard;
|
|
if (!(tlb::unpack_cell(block_root, rec) && tlb::unpack_cell(rec.info, info) &&
|
|
shard.deserialize(info.shard.write()))) {
|
|
return {};
|
|
}
|
|
ton::RootHash rhash = block_root->get_hash().bits();
|
|
CurrencyCollection fees_collected, funds_created;
|
|
if (init_fees) {
|
|
block::ValueFlow flow;
|
|
if (!flow.unpack(vm::load_cell_slice_ref(rec.value_flow))) {
|
|
return {};
|
|
}
|
|
fees_collected = flow.fees_collected;
|
|
funds_created = flow.created;
|
|
}
|
|
return Ref<McShardHash>(true, ton::BlockId{ton::ShardIdFull(shard), (unsigned)info.seq_no}, info.start_lt,
|
|
info.end_lt, info.gen_utime, rhash, fhash, fees_collected, funds_created, ~0U,
|
|
info.min_ref_mc_seqno, info.gen_catchain_seqno, shard.shard_pfx, false, info.before_split,
|
|
false, info.want_split, info.want_merge);
|
|
}
|
|
|
|
McShardDescr::McShardDescr(const McShardDescr& other)
|
|
: McShardHash(other)
|
|
, block_root(other.block_root)
|
|
, state_root(other.state_root)
|
|
, processed_upto(other.processed_upto) {
|
|
set_queue_root(other.outmsg_root);
|
|
}
|
|
|
|
McShardDescr& McShardDescr::operator=(const McShardDescr& other) {
|
|
McShardHash::operator=(other);
|
|
block_root = other.block_root;
|
|
outmsg_root = other.outmsg_root;
|
|
processed_upto = other.processed_upto;
|
|
set_queue_root(other.outmsg_root);
|
|
return *this;
|
|
}
|
|
|
|
Ref<McShardDescr> McShardDescr::from_block(Ref<vm::Cell> block_root, Ref<vm::Cell> state_root,
|
|
const ton::FileHash& fhash, bool init_fees) {
|
|
if (block_root.is_null()) {
|
|
return {};
|
|
}
|
|
block::gen::Block::Record rec;
|
|
block::gen::BlockInfo::Record info;
|
|
block::ShardId shard;
|
|
if (!(tlb::unpack_cell(block_root, rec) && tlb::unpack_cell(rec.info, info) &&
|
|
shard.deserialize(info.shard.write()))) {
|
|
return {};
|
|
}
|
|
// TODO: use a suitable vm::MerkleUpdate method here
|
|
vm::CellSlice cs(vm::NoVmSpec(), rec.state_update);
|
|
if (!cs.is_valid() || cs.special_type() != vm::Cell::SpecialType::MerkleUpdate) {
|
|
LOG(ERROR) << "state update in a block is not a Merkle update";
|
|
return {};
|
|
}
|
|
if (cs.size_refs() != 2 || cs.prefetch_ref(1)->get_hash(0) != state_root->get_hash()) {
|
|
LOG(ERROR) << "invalid Merkle update for block state : resulting state hash mismatch";
|
|
return {};
|
|
}
|
|
ton::RootHash rhash = block_root->get_hash().bits();
|
|
CurrencyCollection fees_collected, funds_created;
|
|
if (init_fees) {
|
|
block::ValueFlow flow;
|
|
if (!flow.unpack(vm::load_cell_slice_ref(rec.value_flow))) {
|
|
return {};
|
|
}
|
|
fees_collected = flow.fees_collected;
|
|
funds_created = flow.created;
|
|
}
|
|
auto res = Ref<McShardDescr>(true, ton::BlockId{ton::ShardIdFull(shard), (unsigned)info.seq_no}, info.start_lt,
|
|
info.end_lt, info.gen_utime, rhash, fhash, fees_collected, funds_created, ~0U,
|
|
info.min_ref_mc_seqno, info.gen_catchain_seqno, shard.shard_pfx, false,
|
|
info.before_split, false, info.want_split, info.want_merge);
|
|
auto& descr = res.unique_write();
|
|
descr.block_root = std::move(block_root);
|
|
descr.state_root = std::move(state_root);
|
|
return res;
|
|
}
|
|
|
|
Ref<McShardDescr> McShardDescr::from_state(ton::BlockIdExt blkid, Ref<vm::Cell> state_root) {
|
|
if (state_root.is_null()) {
|
|
return {};
|
|
}
|
|
block::gen::ShardStateUnsplit::Record info;
|
|
block::gen::OutMsgQueueInfo::Record qinfo;
|
|
block::ShardId shard;
|
|
if (!(tlb::unpack_cell(state_root, info) && shard.deserialize(info.shard_id.write()) &&
|
|
tlb::unpack_cell(info.out_msg_queue_info, qinfo))) {
|
|
LOG(DEBUG) << "cannot create McShardDescr from a shardchain state";
|
|
return {};
|
|
}
|
|
if (ton::ShardIdFull(shard) != ton::ShardIdFull(blkid) || info.seq_no != blkid.seqno()) {
|
|
LOG(DEBUG) << "shard id mismatch, cannot construct McShardDescr";
|
|
return {};
|
|
}
|
|
auto res = Ref<McShardDescr>(true, blkid.id, info.gen_lt, info.gen_lt, info.gen_utime, blkid.root_hash,
|
|
blkid.file_hash, CurrencyCollection{}, CurrencyCollection{}, ~0U, info.min_ref_mc_seqno,
|
|
0, shard.shard_pfx, false, info.before_split);
|
|
res.unique_write().state_root = state_root;
|
|
res.unique_write().set_queue_root(qinfo.out_queue->prefetch_ref(0));
|
|
return res;
|
|
}
|
|
|
|
bool McShardDescr::set_queue_root(Ref<vm::Cell> queue_root) {
|
|
outmsg_root = std::move(queue_root);
|
|
out_msg_queue = std::make_unique<vm::AugmentedDictionary>(outmsg_root, 352, block::tlb::aug_OutMsgQueue);
|
|
return true;
|
|
}
|
|
|
|
void McShardDescr::disable() {
|
|
block_root.clear();
|
|
state_root.clear();
|
|
outmsg_root.clear();
|
|
out_msg_queue.reset();
|
|
processed_upto.reset();
|
|
McShardHash::disable();
|
|
}
|
|
|
|
void ConfigInfo::reset_mc_hash() {
|
|
if (block_id.is_masterchain() && !block_id.root_hash.is_zero()) {
|
|
// TODO: use block_start_lt instead of lt if available
|
|
set_mc_hash(Ref<McShardHash>(true, block_id.id, lt, lt, utime, block_id.root_hash, block_id.file_hash));
|
|
} else {
|
|
set_mc_hash(Ref<McShardHash>{});
|
|
}
|
|
}
|
|
|
|
Ref<vm::CellSlice> ShardConfig::get_root_csr() const {
|
|
if (!shard_hashes_dict_) {
|
|
return {};
|
|
}
|
|
return shard_hashes_dict_->get_root();
|
|
}
|
|
|
|
bool ShardConfig::unpack(Ref<vm::Cell> shard_hashes, Ref<McShardHash> mc_shard_hash) {
|
|
shard_hashes_ = std::move(shard_hashes);
|
|
mc_shard_hash_ = std::move(mc_shard_hash);
|
|
return init();
|
|
}
|
|
|
|
bool ShardConfig::unpack(Ref<vm::CellSlice> shard_hashes, Ref<McShardHash> mc_shard_hash) {
|
|
shard_hashes_ = shard_hashes->prefetch_ref();
|
|
mc_shard_hash_ = std::move(mc_shard_hash);
|
|
return init();
|
|
}
|
|
|
|
bool ShardConfig::init() {
|
|
shard_hashes_dict_ = std::make_unique<vm::Dictionary>(shard_hashes_, 32);
|
|
valid_ = true;
|
|
return true;
|
|
}
|
|
|
|
ShardConfig::ShardConfig(const ShardConfig& other)
|
|
: shard_hashes_(other.shard_hashes_), mc_shard_hash_(other.mc_shard_hash_) {
|
|
init();
|
|
}
|
|
|
|
bool ShardConfig::get_shard_hash_raw_from(vm::Dictionary& dict, vm::CellSlice& cs, ton::ShardIdFull id,
|
|
ton::ShardIdFull& true_id, bool exact, Ref<vm::Cell>* leaf) {
|
|
if (id.is_masterchain() || !id.is_valid()) {
|
|
return false;
|
|
}
|
|
auto root = dict.lookup_ref(td::BitArray<32>{id.workchain});
|
|
if (root.is_null()) {
|
|
return false;
|
|
}
|
|
unsigned long long z = id.shard, m = std::numeric_limits<unsigned long long>::max();
|
|
int len = id.pfx_len();
|
|
while (true) {
|
|
cs.load(vm::NoVmOrd(), leaf ? root : std::move(root));
|
|
int t = (int)cs.fetch_ulong(1);
|
|
if (t < 0) {
|
|
return false; // throw DictError ?
|
|
} else if (!t) {
|
|
if (len && exact) {
|
|
return false;
|
|
}
|
|
true_id = ton::ShardIdFull{id.workchain, (id.shard | m) - (m >> 1)};
|
|
if (leaf) {
|
|
*leaf = std::move(root);
|
|
}
|
|
return true;
|
|
}
|
|
if (!len || cs.size_ext() != 0x20000) {
|
|
return false; // throw DictError in the second case?
|
|
}
|
|
root = cs.prefetch_ref((unsigned)(z >> 63));
|
|
z <<= 1;
|
|
--len;
|
|
m >>= 1;
|
|
}
|
|
}
|
|
|
|
bool ShardConfig::get_shard_hash_raw(vm::CellSlice& cs, ton::ShardIdFull id, ton::ShardIdFull& true_id,
|
|
bool exact) const {
|
|
return shard_hashes_dict_ && get_shard_hash_raw_from(*shard_hashes_dict_, cs, id, true_id, exact);
|
|
}
|
|
|
|
Ref<McShardHash> ShardConfig::get_shard_hash(ton::ShardIdFull id, bool exact) const {
|
|
if (id.is_masterchain()) {
|
|
return (!exact || id.shard == ton::shardIdAll) ? get_mc_hash() : Ref<McShardHash>{};
|
|
}
|
|
ton::ShardIdFull true_id;
|
|
vm::CellSlice cs;
|
|
if (get_shard_hash_raw(cs, id, true_id, exact)) {
|
|
// block::gen::t_ShardDescr.print(std::cerr, vm::CellSlice{cs});
|
|
return McShardHash::unpack(cs, true_id);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool McShardHash::extract_cc_seqno(vm::CellSlice& cs, ton::CatchainSeqno* cc) {
|
|
auto get = [&cs, cc](auto& rec) {
|
|
if (tlb::unpack_exact(cs, rec)) {
|
|
*cc = rec.next_catchain_seqno;
|
|
return true;
|
|
} else {
|
|
*cc = std::numeric_limits<ton::CatchainSeqno>::max();
|
|
return false;
|
|
}
|
|
};
|
|
if (block::gen::t_ShardDescr.get_tag(cs) == block::gen::ShardDescr::shard_descr) {
|
|
gen::ShardDescr::Record_shard_descr rec;
|
|
return get(rec);
|
|
} else {
|
|
gen::ShardDescr::Record_shard_descr_new rec;
|
|
return get(rec);
|
|
}
|
|
}
|
|
|
|
ton::CatchainSeqno ShardConfig::get_shard_cc_seqno(ton::ShardIdFull shard) const {
|
|
if (shard.is_masterchain() || !shard.is_valid()) {
|
|
return std::numeric_limits<ton::CatchainSeqno>::max();
|
|
}
|
|
ton::ShardIdFull true_id;
|
|
ton::CatchainSeqno cc_seqno, cc_seqno2;
|
|
vm::CellSlice cs;
|
|
if (!(get_shard_hash_raw(cs, shard - 1, true_id, false) &&
|
|
(ton::shard_is_ancestor(true_id, shard) || ton::shard_is_parent(shard, true_id)) &&
|
|
McShardHash::extract_cc_seqno(cs, &cc_seqno))) {
|
|
return std::numeric_limits<ton::CatchainSeqno>::max();
|
|
}
|
|
if (ton::shard_is_ancestor(true_id, shard)) {
|
|
return cc_seqno;
|
|
}
|
|
if (!(get_shard_hash_raw(cs, shard + 1, true_id, false) && ton::shard_is_parent(shard, true_id) &&
|
|
McShardHash::extract_cc_seqno(cs, &cc_seqno2))) {
|
|
return std::numeric_limits<ton::CatchainSeqno>::max();
|
|
}
|
|
return std::max(cc_seqno, cc_seqno2) + 1;
|
|
}
|
|
|
|
ton::LogicalTime ShardConfig::get_shard_end_lt_ext(ton::AccountIdPrefixFull acc, ton::ShardIdFull& actual_shard) const {
|
|
if (!acc.is_valid()) {
|
|
actual_shard.workchain = ton::workchainInvalid;
|
|
return 0;
|
|
}
|
|
if (acc.is_masterchain()) {
|
|
actual_shard = ton::ShardIdFull(ton::masterchainId);
|
|
CHECK(mc_shard_hash_.not_null());
|
|
return mc_shard_hash_->end_lt_;
|
|
}
|
|
vm::CellSlice cs;
|
|
unsigned long long end_lt;
|
|
return get_shard_hash_raw(cs, acc.as_leaf_shard(), actual_shard, false) // lookup ShardDescr containing acc
|
|
&& cs.advance(4 + 128) // shard_descr#b seq_no:uint32 reg_mc_seqno:uint32 start_lt:uint64
|
|
&& cs.fetch_ulong_bool(64, end_lt) // end_lt:uint64
|
|
? end_lt
|
|
: 0;
|
|
}
|
|
|
|
ton::LogicalTime ShardConfig::get_shard_end_lt(ton::AccountIdPrefixFull acc) const {
|
|
ton::ShardIdFull tmp;
|
|
return get_shard_end_lt_ext(acc, tmp);
|
|
}
|
|
|
|
bool ShardConfig::contains(ton::BlockIdExt blkid) const {
|
|
auto entry = get_shard_hash(blkid.shard_full());
|
|
return entry.not_null() && entry->blk_ == blkid;
|
|
}
|
|
|
|
static int process_workchain_shard_hashes(Ref<vm::Cell>& branch, ton::ShardIdFull shard,
|
|
std::function<int(McShardHash&)>& func) {
|
|
auto cs = vm::load_cell_slice(branch);
|
|
int f = (int)cs.fetch_ulong(1);
|
|
if (f == 1) {
|
|
if ((shard.shard & 1) || cs.size_ext() != 0x20000) {
|
|
return -1;
|
|
}
|
|
auto left = cs.prefetch_ref(0), right = cs.prefetch_ref(1);
|
|
int r = process_workchain_shard_hashes(left, ton::shard_child(shard, true), func);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
r |= process_workchain_shard_hashes(right, ton::shard_child(shard, false), func);
|
|
if (r <= 0) {
|
|
return r;
|
|
}
|
|
vm::CellBuilder cb;
|
|
return cb.store_bool_bool(true) && cb.store_ref_bool(std::move(left)) && cb.store_ref_bool(std::move(right)) &&
|
|
cb.finalize_to(branch)
|
|
? r
|
|
: -1;
|
|
} else if (!f) {
|
|
auto shard_info = McShardHash::unpack(cs, shard);
|
|
if (shard_info.is_null()) {
|
|
return -1;
|
|
}
|
|
int r = func(shard_info.write());
|
|
if (r <= 0) {
|
|
return r;
|
|
}
|
|
vm::CellBuilder cb;
|
|
return cb.store_bool_bool(false) && shard_info->pack(cb) && cb.finalize_to(branch) ? r : -1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool ShardConfig::process_shard_hashes(std::function<int(McShardHash&)> func) {
|
|
if (!shard_hashes_dict_) {
|
|
return false;
|
|
}
|
|
bool ok = true;
|
|
shard_hashes_dict_->map(
|
|
[&ok, &func](vm::CellBuilder& cb, Ref<vm::CellSlice> csr, td::ConstBitPtr key, int n) -> bool {
|
|
Ref<vm::Cell> root;
|
|
ok = ok && (n == 32) && csr->size_ext() == 0x10000 && std::move(csr)->prefetch_ref_to(root) &&
|
|
process_workchain_shard_hashes(root, ton::ShardIdFull{(int)key.get_int(32)}, func) >= 0 &&
|
|
cb.store_ref_bool(std::move(root));
|
|
return true;
|
|
});
|
|
return ok;
|
|
}
|
|
|
|
static int process_workchain_sibling_shard_hashes(Ref<vm::Cell>& branch, Ref<vm::Cell> sibling, ton::ShardIdFull shard,
|
|
std::function<int(McShardHash&, const McShardHash*)>& func) {
|
|
auto cs = vm::load_cell_slice(branch);
|
|
int f = (int)cs.fetch_ulong(1);
|
|
if (f == 1) {
|
|
if ((shard.shard & 1) || cs.size_ext() != 0x20000) {
|
|
return false;
|
|
}
|
|
auto left = cs.prefetch_ref(0), right = cs.prefetch_ref(1);
|
|
auto orig_left = left;
|
|
int r = process_workchain_sibling_shard_hashes(left, right, ton::shard_child(shard, true), func);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
r |= process_workchain_sibling_shard_hashes(right, std::move(orig_left), ton::shard_child(shard, false), func);
|
|
if (r <= 0) {
|
|
return r;
|
|
}
|
|
vm::CellBuilder cb;
|
|
return cb.store_bool_bool(true) && cb.store_ref_bool(std::move(left)) && cb.store_ref_bool(std::move(right)) &&
|
|
cb.finalize_to(branch)
|
|
? r
|
|
: -1;
|
|
} else if (!f) {
|
|
auto shard_info = McShardHash::unpack(cs, shard);
|
|
if (shard_info.is_null()) {
|
|
return -1;
|
|
}
|
|
Ref<McShardHash> sibling_info;
|
|
if (sibling.not_null()) {
|
|
auto cs2 = vm::load_cell_slice(sibling);
|
|
if (!cs2.fetch_ulong(1)) {
|
|
sibling_info = McShardHash::unpack(cs2, ton::shard_sibling(shard));
|
|
if (sibling_info.is_null()) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
int r = func(shard_info.write(), sibling_info.get());
|
|
if (r <= 0) {
|
|
return r;
|
|
}
|
|
vm::CellBuilder cb;
|
|
return cb.store_bool_bool(false) && shard_info->pack(cb) && cb.finalize_to(branch) ? r : -1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool ShardConfig::process_sibling_shard_hashes(std::function<int(McShardHash&, const McShardHash*)> func) {
|
|
if (!shard_hashes_dict_) {
|
|
return false;
|
|
}
|
|
bool ok = true;
|
|
shard_hashes_dict_->map([&ok, &func](vm::CellBuilder& cb, Ref<vm::CellSlice> csr, td::ConstBitPtr key,
|
|
int n) -> bool {
|
|
Ref<vm::Cell> root;
|
|
ok = ok && (n == 32) && csr->size_ext() == 0x10000 && std::move(csr)->prefetch_ref_to(root) &&
|
|
process_workchain_sibling_shard_hashes(root, Ref<vm::Cell>{}, ton::ShardIdFull{(int)key.get_int(32)}, func) >=
|
|
0;
|
|
bool f = cb.store_ref_bool(std::move(root));
|
|
ok &= f;
|
|
return f;
|
|
});
|
|
return ok;
|
|
}
|
|
|
|
std::vector<ton::BlockId> ShardConfig::get_shard_hash_ids(
|
|
const std::function<bool(ton::ShardIdFull, bool)>& filter) const {
|
|
if (!shard_hashes_dict_) {
|
|
return {};
|
|
}
|
|
std::vector<ton::BlockId> res;
|
|
bool mcout = mc_shard_hash_.is_null() || !mc_shard_hash_->seqno(); // include masterchain as a shard if seqno > 0
|
|
bool ok = shard_hashes_dict_->check_for_each(
|
|
[&res, &mcout, mc_shard_hash_ = mc_shard_hash_, &filter](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key,
|
|
int n) -> bool {
|
|
int workchain = (int)key.get_int(n);
|
|
if (workchain >= 0 && !mcout) {
|
|
if (filter(ton::ShardIdFull{ton::masterchainId}, true)) {
|
|
res.emplace_back(mc_shard_hash_->blk_.id);
|
|
}
|
|
mcout = true;
|
|
}
|
|
if (!cs_ref->have_refs()) {
|
|
return false;
|
|
}
|
|
std::stack<std::pair<Ref<vm::Cell>, unsigned long long>> stack;
|
|
stack.emplace(cs_ref->prefetch_ref(), ton::shardIdAll);
|
|
while (!stack.empty()) {
|
|
vm::CellSlice cs{vm::NoVmOrd(), std::move(stack.top().first)};
|
|
unsigned long long shard = stack.top().second;
|
|
stack.pop();
|
|
int t = (int)cs.fetch_ulong(1);
|
|
if (t < 0) {
|
|
return false;
|
|
}
|
|
if (!filter(ton::ShardIdFull{workchain, shard}, !t)) {
|
|
continue;
|
|
}
|
|
if (!t) {
|
|
if (!(cs.advance(4) && cs.have(32))) {
|
|
return false;
|
|
}
|
|
res.emplace_back(workchain, shard, (int)cs.prefetch_ulong(32));
|
|
continue;
|
|
}
|
|
unsigned long long delta = (td::lower_bit64(shard) >> 1);
|
|
if (!delta || cs.size_ext() != 0x20000) {
|
|
return false;
|
|
}
|
|
stack.emplace(cs.prefetch_ref(1), shard + delta);
|
|
stack.emplace(cs.prefetch_ref(0), shard - delta);
|
|
}
|
|
return true;
|
|
},
|
|
true);
|
|
if (!ok) {
|
|
return {};
|
|
}
|
|
if (!mcout && filter(ton::ShardIdFull{ton::masterchainId}, true)) {
|
|
res.emplace_back(mc_shard_hash_->blk_.id);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::vector<ton::BlockId> ShardConfig::get_shard_hash_ids(bool skip_mc) const {
|
|
return get_shard_hash_ids(
|
|
[skip_mc](ton::ShardIdFull shard, bool) -> bool { return !(skip_mc && shard.is_masterchain()); });
|
|
}
|
|
|
|
std::vector<ton::BlockId> ShardConfig::get_intersecting_shard_hash_ids(ton::ShardIdFull myself) const {
|
|
return get_shard_hash_ids(
|
|
[myself](ton::ShardIdFull shard, bool) -> bool { return ton::shard_intersects(myself, shard); });
|
|
}
|
|
|
|
std::vector<ton::BlockId> ShardConfig::get_neighbor_shard_hash_ids(ton::ShardIdFull myself) const {
|
|
return get_shard_hash_ids([myself](ton::ShardIdFull shard, bool) -> bool { return is_neighbor(myself, shard); });
|
|
}
|
|
|
|
std::vector<ton::BlockId> ShardConfig::get_proper_neighbor_shard_hash_ids(ton::ShardIdFull myself) const {
|
|
return get_shard_hash_ids([myself](ton::ShardIdFull shard, bool leaf) -> bool {
|
|
return is_neighbor(myself, shard) && !(leaf && ton::shard_intersects(myself, shard));
|
|
});
|
|
}
|
|
|
|
bool ShardConfig::is_neighbor(ton::ShardIdFull x, ton::ShardIdFull y) {
|
|
if (x.is_masterchain() || y.is_masterchain()) {
|
|
return true;
|
|
}
|
|
unsigned long long xs = x.shard, ys = y.shard;
|
|
unsigned long long xl = td::lower_bit64(xs), yl = td::lower_bit64(ys);
|
|
unsigned long long z = (xs ^ ys) & td::bits_negate64(std::max(xl, yl) << 1);
|
|
if (!z) {
|
|
return true;
|
|
}
|
|
if (x.workchain != y.workchain) {
|
|
return false;
|
|
}
|
|
int c1 = (td::count_leading_zeroes_non_zero64(z) >> 2);
|
|
int c2 = (td::count_trailing_zeroes_non_zero64(z) >> 2);
|
|
return c1 + c2 == 15;
|
|
}
|
|
|
|
bool ShardConfig::has_workchain(ton::WorkchainId workchain) const {
|
|
return shard_hashes_dict_ && shard_hashes_dict_->key_exists(td::BitArray<32>{workchain});
|
|
}
|
|
|
|
std::vector<ton::WorkchainId> ShardConfig::get_workchains() const {
|
|
if (!shard_hashes_dict_) {
|
|
return {};
|
|
}
|
|
std::vector<ton::WorkchainId> res;
|
|
if (!shard_hashes_dict_->check_for_each([&res](Ref<vm::CellSlice> val, td::ConstBitPtr key, int n) {
|
|
CHECK(n == 32);
|
|
ton::WorkchainId w = (int)key.get_int(32);
|
|
res.push_back(w);
|
|
return true;
|
|
})) {
|
|
return {};
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool ShardConfig::new_workchain(ton::WorkchainId workchain, ton::BlockSeqno reg_mc_seqno,
|
|
const ton::RootHash& zerostate_root_hash, const ton::FileHash& zerostate_file_hash) {
|
|
if (!shard_hashes_dict_ || has_workchain(workchain)) {
|
|
return false;
|
|
}
|
|
vm::CellBuilder cb;
|
|
Ref<vm::Cell> cell;
|
|
return cb.store_long_bool(11, 1 + 4) // bt_leaf$0 ; shard_descr#b
|
|
&& cb.store_zeroes_bool(32) // seq_no:uint32
|
|
&& cb.store_long_bool(reg_mc_seqno, 32) // reg_mc_seqno:uint32
|
|
&& cb.store_zeroes_bool(64 * 2) // start_lt:uint64 end_lt:uint64
|
|
&& cb.store_bits_bool(zerostate_root_hash) // root_hash:bits256
|
|
&& cb.store_bits_bool(zerostate_file_hash) // file_hash:bits256
|
|
&& cb.store_long_bool(0, 8) // ... nx_cc_updated:Bool ...
|
|
&& cb.store_long_bool(0, 32) // next_catchain_seqno:uint32
|
|
&& cb.store_long_bool(1ULL << 63, 64) // next_validator_shard:uint64
|
|
&& cb.store_long_bool(~0U, 32) // min_ref_mc_seqno:uint32
|
|
&& cb.store_long_bool(0, 32) // gen_utime:uint32
|
|
&&
|
|
cb.store_zeroes_bool(
|
|
1 + 5 +
|
|
5) // split_merge_at:FutureSplitMerge fees_collected:CurrencyCollection funds_created:CurrencyCollection
|
|
&& cb.finalize_to(cell) && block::gen::t_BinTree_ShardDescr.validate_ref(1024, cell) &&
|
|
shard_hashes_dict_->set_ref(td::BitArray<32>{workchain}, std::move(cell), vm::Dictionary::SetMode::Add);
|
|
}
|
|
|
|
td::Result<bool> ShardConfig::may_update_shard_block_info(Ref<McShardHash> new_info,
|
|
const std::vector<ton::BlockIdExt>& old_blkids,
|
|
ton::LogicalTime lt_limit, Ref<McShardHash>* ancestor) const {
|
|
if (!shard_hashes_dict_) {
|
|
return td::Status::Error(-666, "no shard top block dictionary present");
|
|
}
|
|
if (new_info.is_null()) {
|
|
return td::Status::Error(-666, "suggested new top shard block info is empty");
|
|
}
|
|
if (!new_info->is_valid()) {
|
|
return td::Status::Error(-666, "new top shard block description is invalid");
|
|
}
|
|
auto wc = new_info->shard().workchain;
|
|
if (wc == ton::workchainInvalid || wc == ton::masterchainId) {
|
|
return td::Status::Error(-666, "new top shard block description belongs to an invalid workchain");
|
|
}
|
|
if (!has_workchain(wc)) {
|
|
return td::Status::Error(-666, "new top shard block belongs to an unknown or disabled workchain");
|
|
}
|
|
if (old_blkids.size() != 1 && old_blkids.size() != 2) {
|
|
return td::Status::Error(-666, "must have either one or two start blocks in a top shard block update");
|
|
}
|
|
bool before_split = ton::shard_is_parent(old_blkids[0].shard_full(), new_info->shard());
|
|
bool before_merge = (old_blkids.size() == 2);
|
|
if (before_merge) {
|
|
if (!ton::shard_is_sibling(old_blkids[0].shard_full(), old_blkids[1].shard_full())) {
|
|
return td::Status::Error(-666, "the two start blocks of a top shard block update must be siblings");
|
|
}
|
|
if (!ton::shard_is_parent(new_info->shard(), old_blkids[0].shard_full())) {
|
|
return td::Status::Error(
|
|
-666,
|
|
std::string{"the two start blocks of a top shard block update do not merge into expected final shard "} +
|
|
old_blkids[0].shard_full().to_str());
|
|
}
|
|
} else if (new_info->shard() != old_blkids[0].shard_full() && !before_split) {
|
|
return td::Status::Error(
|
|
-666, "the start block of a top shard block update must either coincide with the final shard or be its parent");
|
|
}
|
|
if (ancestor) {
|
|
ancestor->clear();
|
|
}
|
|
ton::CatchainSeqno old_cc_seqno = 0;
|
|
for (const auto& ob : old_blkids) {
|
|
auto odef = get_shard_hash(ob.shard_full());
|
|
if (odef.is_null() || odef->blk_ != ob) {
|
|
return td::Status::Error(-666,
|
|
std::string{"the start block "} + ob.to_str() +
|
|
" of a top shard block update is not contained in the previous shard configuration");
|
|
}
|
|
old_cc_seqno = std::max(old_cc_seqno, odef->next_catchain_seqno_);
|
|
if (shards_updated_.find(ob.shard_full()) != shards_updated_.end()) {
|
|
return td::Status::Error(
|
|
-666, std::string{"the shard of the start block "} + ob.to_str() +
|
|
" of a top shard block update has been already updated in the current shard configuration");
|
|
}
|
|
if (odef->before_split_ != before_split) {
|
|
return td::Status::Error(
|
|
-666, PSTRING() << "the shard of the start block " << ob.to_str()
|
|
<< " had before_split=" << odef->before_split_
|
|
<< " but the top shard block update is valid only if before_split=" << before_split);
|
|
}
|
|
if (odef->before_merge_ != before_merge) {
|
|
return td::Status::Error(
|
|
-666, PSTRING() << "the shard of the start block " << ob.to_str()
|
|
<< " had before_merge=" << odef->before_merge_
|
|
<< " but the top shard block update is valid only if before_merge=" << before_merge);
|
|
}
|
|
if (new_info->before_split_) {
|
|
if (before_merge || before_split) {
|
|
return td::Status::Error(
|
|
-666, PSTRING() << "cannot register a before-split block " << new_info->top_block_id().to_str()
|
|
<< " at the end of a chain that itself starts with a split/merge event");
|
|
}
|
|
if (odef->fsm_state() != block::McShardHash::FsmState::fsm_split) {
|
|
return td::Status::Error(-666, PSTRING() << "cannot register a before-split block "
|
|
<< new_info->top_block_id().to_str()
|
|
<< " because fsm_split state was not set for this shard beforehand");
|
|
}
|
|
if (new_info->gen_utime_ < odef->fsm_utime_ || new_info->gen_utime_ >= odef->fsm_utime_ + odef->fsm_interval_) {
|
|
return td::Status::Error(-666, PSTRING() << "cannot register a before-split block "
|
|
<< new_info->top_block_id().to_str()
|
|
<< " because fsm_split state was enabled for unixtime "
|
|
<< odef->fsm_utime_ << " .. " << odef->fsm_utime_ + odef->fsm_interval_
|
|
<< " but the block is generated at " << new_info->gen_utime_);
|
|
}
|
|
}
|
|
if (before_merge) {
|
|
if (odef->fsm_state() != block::McShardHash::FsmState::fsm_merge) {
|
|
return td::Status::Error(-666, PSTRING() << "cannot register merged block " << new_info->top_block_id().to_str()
|
|
<< " because fsm_merge state was not set for shard "
|
|
<< odef->top_block_id().shard_full().to_str() << " beforehand");
|
|
}
|
|
if (new_info->gen_utime_ < odef->fsm_utime_ || new_info->gen_utime_ >= odef->fsm_utime_ + odef->fsm_interval_) {
|
|
return td::Status::Error(-666, PSTRING() << "cannot register merged block " << new_info->top_block_id().to_str()
|
|
<< " because fsm_merge state was enabled for shard "
|
|
<< odef->top_block_id().shard_full().to_str() << " for unixtime "
|
|
<< odef->fsm_utime_ << " .. " << odef->fsm_utime_ + odef->fsm_interval_
|
|
<< " but the block is generated at " << new_info->gen_utime_);
|
|
}
|
|
}
|
|
if (ancestor && !before_merge && !before_split) {
|
|
*ancestor = odef;
|
|
}
|
|
}
|
|
if (old_cc_seqno + before_merge != new_info->next_catchain_seqno_) {
|
|
return td::Status::Error(-666, PSTRING()
|
|
<< "the top shard block update is generated with catchain_seqno="
|
|
<< new_info->next_catchain_seqno_ << " but previous shard configuration expects "
|
|
<< old_cc_seqno + before_merge);
|
|
}
|
|
if (new_info->end_lt_ >= lt_limit) {
|
|
return td::Status::Error(-666, PSTRING() << "the top shard block update has end_lt " << new_info->end_lt_
|
|
<< " which is larger than the current limit " << lt_limit);
|
|
}
|
|
return !before_split;
|
|
}
|
|
|
|
td::Result<bool> ShardConfig::update_shard_block_info(Ref<McShardHash> new_info,
|
|
const std::vector<ton::BlockIdExt>& old_blkids) {
|
|
Ref<McShardHash> ancestor;
|
|
auto res = may_update_shard_block_info(new_info, old_blkids, ~0ULL, &ancestor);
|
|
if (res.is_error()) {
|
|
return res;
|
|
}
|
|
if (!res.move_as_ok()) {
|
|
return td::Status::Error(-666, std::string{"cannot apply the after-split update for "} + new_info->blk_.to_str() +
|
|
" without a corresponding sibling update");
|
|
}
|
|
if (ancestor.not_null() && ancestor->fsm_ != McShardHash::FsmState::fsm_none) {
|
|
new_info.write().set_fsm(ancestor->fsm_, ancestor->fsm_utime_, ancestor->fsm_interval_);
|
|
}
|
|
auto blk = new_info->blk_;
|
|
bool ok = do_update_shard_info(std::move(new_info));
|
|
if (!ok) {
|
|
return td::Status::Error(
|
|
-666,
|
|
std::string{
|
|
"unknown serialization error for (BinTree ShardDescr) while updating shard configuration to include "} +
|
|
blk.to_str());
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
td::Result<bool> ShardConfig::update_shard_block_info2(Ref<McShardHash> new_info1, Ref<McShardHash> new_info2,
|
|
const std::vector<ton::BlockIdExt>& old_blkids) {
|
|
auto res1 = may_update_shard_block_info(new_info1, old_blkids);
|
|
if (res1.is_error()) {
|
|
return res1;
|
|
}
|
|
auto res2 = may_update_shard_block_info(new_info2, old_blkids);
|
|
if (res2.is_error()) {
|
|
return res2;
|
|
}
|
|
if (res1.move_as_ok() || res2.move_as_ok()) {
|
|
return td::Status::Error(-666, "the two updates in update_shard_block_info2 must follow a shard split event");
|
|
}
|
|
if (new_info1->blk_.id.shard > new_info2->blk_.id.shard) {
|
|
std::swap(new_info1, new_info2);
|
|
}
|
|
auto blk1 = new_info1->blk_, blk2 = new_info2->blk_;
|
|
bool ok = do_update_shard_info2(std::move(new_info1), std::move(new_info2));
|
|
if (!ok) {
|
|
return td::Status::Error(
|
|
-666,
|
|
std::string{
|
|
"unknown serialization error for (BinTree ShardDescr) while updating shard configuration to include "} +
|
|
blk1.to_str() + " and " + blk2.to_str());
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool ShardConfig::do_update_shard_info(Ref<McShardHash> new_info) {
|
|
vm::CellBuilder cb;
|
|
Ref<vm::Cell> ref;
|
|
return new_info.not_null() && cb.store_bool_bool(false) // bt_leaf$0
|
|
&& new_info->pack(cb) // leaf:ShardDescr
|
|
&& cb.finalize_to(ref) && set_shard_info(new_info->shard(), std::move(ref));
|
|
}
|
|
|
|
bool ShardConfig::do_update_shard_info2(Ref<McShardHash> new_info1, Ref<McShardHash> new_info2) {
|
|
if (new_info1.is_null() || new_info2.is_null() || !ton::shard_is_sibling(new_info1->shard(), new_info2->shard())) {
|
|
return false;
|
|
}
|
|
if (new_info1->blk_.id.shard > new_info2->blk_.id.shard) {
|
|
std::swap(new_info1, new_info2);
|
|
}
|
|
vm::CellBuilder cb, cb1;
|
|
Ref<vm::Cell> ref;
|
|
return cb.store_bool_bool(true) // bt_node$1
|
|
&& cb1.store_bool_bool(false) // ( bt_leaf$0
|
|
&& new_info1->pack(cb1) // leaf:ShardDescr
|
|
&& cb1.finalize_to(ref) // ) -> ref
|
|
&& cb.store_ref_bool(std::move(ref)) // left:^(BinTree ShardDescr)
|
|
&& cb1.store_bool_bool(false) // ( bt_leaf$0
|
|
&& new_info2->pack(cb1) // leaf:ShardDescr
|
|
&& cb1.finalize_to(ref) // ) -> ref
|
|
&& cb.store_ref_bool(std::move(ref)) // right:^(BinTree ShardDescr)
|
|
&& cb.finalize_to(ref) && set_shard_info(ton::shard_parent(new_info1->shard()), std::move(ref));
|
|
}
|
|
|
|
static bool btree_set(Ref<vm::Cell>& root, ton::ShardId shard, Ref<vm::Cell> value) {
|
|
if (!shard) {
|
|
return false;
|
|
}
|
|
if (shard == ton::shardIdAll) {
|
|
root = value;
|
|
return true;
|
|
}
|
|
auto cs = vm::load_cell_slice(std::move(root));
|
|
if (cs.size_ext() != 0x20001 || cs.prefetch_ulong(1) != 1) {
|
|
return false; // branch does not exist
|
|
}
|
|
Ref<vm::Cell> left = cs.prefetch_ref(0), right = cs.prefetch_ref(1);
|
|
if (!(btree_set(shard & (1ULL << 63) ? right : left, shard << 1, std::move(value)))) {
|
|
return false;
|
|
}
|
|
vm::CellBuilder cb;
|
|
return cb.store_bool_bool(true) // bt_node$1
|
|
&& cb.store_ref_bool(std::move(left)) // left:^(BinTree ShardDescr)
|
|
&& cb.store_ref_bool(std::move(right)) // right:^(BinTree ShardDescr)
|
|
&& cb.finalize_to(root); // = BinTree ShardDescr
|
|
}
|
|
|
|
bool ShardConfig::set_shard_info(ton::ShardIdFull shard, Ref<vm::Cell> value) {
|
|
if (!gen::t_BinTree_ShardDescr.validate_ref(1024, value)) {
|
|
LOG(ERROR) << "attempting to store an invalid (BinTree ShardDescr) at shard configuration position "
|
|
<< shard.to_str();
|
|
gen::t_BinTree_ShardDescr.print_ref(std::cerr, value);
|
|
vm::load_cell_slice(value).print_rec(std::cerr);
|
|
return false;
|
|
}
|
|
auto root = shard_hashes_dict_->lookup_ref(td::BitArray<32>{shard.workchain});
|
|
if (root.is_null()) {
|
|
LOG(ERROR) << "attempting to store a new ShardDescr for shard " << shard.to_str() << " in an undefined workchain";
|
|
return false;
|
|
}
|
|
if (!btree_set(root, shard.shard, value)) {
|
|
LOG(ERROR) << "error while storing a new ShardDescr for shard " << shard.to_str() << " into shard configuration";
|
|
return false;
|
|
}
|
|
if (!shard_hashes_dict_->set_ref(td::BitArray<32>{shard.workchain}, std::move(root),
|
|
vm::Dictionary::SetMode::Replace)) {
|
|
return false;
|
|
}
|
|
auto ins = shards_updated_.insert(shard);
|
|
CHECK(ins.second);
|
|
return true;
|
|
}
|
|
|
|
bool Config::is_special_smartcontract(const ton::StdSmcAddress& addr) const {
|
|
CHECK(special_smc_dict);
|
|
return special_smc_dict->lookup(addr).not_null() || addr == config_addr;
|
|
}
|
|
|
|
td::Result<std::vector<ton::StdSmcAddress>> Config::get_special_smartcontracts(bool without_config) const {
|
|
if (!special_smc_dict) {
|
|
return td::Status::Error(-666, "configuration loaded without fundamental smart contract list");
|
|
}
|
|
std::vector<ton::StdSmcAddress> res;
|
|
if (!special_smc_dict->check_for_each([&res, &without_config, conf_addr = config_addr.bits()](
|
|
Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) {
|
|
if (cs_ref->size_ext() || n != 256) {
|
|
return false;
|
|
}
|
|
res.emplace_back(key);
|
|
if (!without_config && key.equals(conf_addr, 256)) {
|
|
without_config = true;
|
|
}
|
|
return true;
|
|
})) {
|
|
return td::Status::Error(-666, "invalid fundamental smart contract set in configuration parameter 31");
|
|
}
|
|
if (!without_config) {
|
|
res.push_back(config_addr);
|
|
}
|
|
return std::move(res);
|
|
}
|
|
|
|
td::Result<std::vector<std::pair<ton::StdSmcAddress, int>>> ConfigInfo::get_special_ticktock_smartcontracts(
|
|
int tick_tock) const {
|
|
if (!special_smc_dict) {
|
|
return td::Status::Error(-666, "configuration loaded without fundamental smart contract list");
|
|
}
|
|
if (!accounts_dict) {
|
|
return td::Status::Error(-666, "state loaded without accounts information");
|
|
}
|
|
std::vector<std::pair<ton::StdSmcAddress, int>> res;
|
|
if (!special_smc_dict->check_for_each(
|
|
[this, &res, tick_tock](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) -> bool {
|
|
if (cs_ref->size_ext() || n != 256) {
|
|
return false;
|
|
}
|
|
int tt = get_smc_tick_tock(key);
|
|
if (tt < -1) {
|
|
return false;
|
|
}
|
|
if (tt >= 0 && (tt & tick_tock) != 0) {
|
|
res.emplace_back(key, tt);
|
|
}
|
|
return true;
|
|
})) {
|
|
return td::Status::Error(-666,
|
|
"invalid fundamental smart contract set in configuration parameter 31, or unable to "
|
|
"recover tick-tock value from one of them");
|
|
}
|
|
return std::move(res);
|
|
}
|
|
|
|
int ConfigInfo::get_smc_tick_tock(td::ConstBitPtr smc_addr) const {
|
|
if (!accounts_dict) {
|
|
return -2;
|
|
}
|
|
auto acc_csr = accounts_dict->lookup(smc_addr, 256);
|
|
Ref<vm::Cell> acc_cell;
|
|
if (acc_csr.is_null() || !acc_csr->prefetch_ref_to(acc_cell)) {
|
|
return -1;
|
|
}
|
|
auto acc_cs = vm::load_cell_slice(std::move(acc_cell));
|
|
if (block::gen::t_Account.get_tag(acc_cs) == block::gen::Account::account_none) {
|
|
return 0;
|
|
}
|
|
block::gen::Account::Record_account acc;
|
|
block::gen::AccountStorage::Record storage;
|
|
int ticktock;
|
|
return (tlb::unpack_exact(acc_cs, acc) && tlb::csr_unpack(acc.storage, storage) &&
|
|
block::tlb::t_AccountState.get_ticktock(storage.state.write(), ticktock))
|
|
? ticktock
|
|
: -2;
|
|
}
|
|
|
|
ton::CatchainSeqno ConfigInfo::get_shard_cc_seqno(ton::ShardIdFull shard) const {
|
|
return shard.is_masterchain() ? cc_seqno_ : ShardConfig::get_shard_cc_seqno(shard);
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> Config::compute_validator_set(ton::ShardIdFull shard, const block::ValidatorSet& vset,
|
|
ton::UnixTime time, ton::CatchainSeqno cc_seqno) const {
|
|
return do_compute_validator_set(get_catchain_validators_config(), shard, vset, cc_seqno);
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> Config::compute_validator_set(ton::ShardIdFull shard, ton::UnixTime time,
|
|
ton::CatchainSeqno cc_seqno) const {
|
|
if (!cur_validators_) {
|
|
LOG(DEBUG) << "failed to compute validator set: cur_validators_ is empty";
|
|
return {};
|
|
} else {
|
|
return compute_validator_set(shard, *cur_validators_, time, cc_seqno);
|
|
}
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> ConfigInfo::compute_validator_set_cc(ton::ShardIdFull shard,
|
|
const block::ValidatorSet& vset,
|
|
ton::UnixTime time,
|
|
ton::CatchainSeqno* cc_seqno_delta) const {
|
|
if (cc_seqno_delta && (*cc_seqno_delta & -2)) {
|
|
return {};
|
|
}
|
|
ton::CatchainSeqno cc_seqno = get_shard_cc_seqno(shard);
|
|
if (cc_seqno == ~0U) {
|
|
return {};
|
|
}
|
|
if (cc_seqno_delta) {
|
|
cc_seqno = *cc_seqno_delta += cc_seqno;
|
|
}
|
|
return do_compute_validator_set(get_catchain_validators_config(), shard, vset, cc_seqno);
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> ConfigInfo::compute_validator_set_cc(ton::ShardIdFull shard, ton::UnixTime time,
|
|
ton::CatchainSeqno* cc_seqno_delta) const {
|
|
auto vset = get_cur_validator_set();
|
|
if (!vset) {
|
|
return {};
|
|
} else {
|
|
return compute_validator_set_cc(shard, *vset, time, cc_seqno_delta);
|
|
}
|
|
}
|
|
|
|
void validator_set_descr::incr_seed() {
|
|
for (int i = 31; i >= 0 && !++(seed[i]); --i) {
|
|
}
|
|
}
|
|
|
|
void validator_set_descr::hash_to(unsigned char hash_buffer[64]) const {
|
|
digest::hash_str<digest::SHA512>(hash_buffer, (const void*)this, sizeof(*this));
|
|
}
|
|
|
|
td::uint64 ValidatorSetPRNG::next_ulong() {
|
|
if (pos < limit) {
|
|
return td::bswap64(hash_longs[pos++]);
|
|
}
|
|
data.hash_to(hash);
|
|
data.incr_seed();
|
|
pos = 1;
|
|
limit = 8;
|
|
return td::bswap64(hash_longs[0]);
|
|
}
|
|
|
|
td::uint64 ValidatorSetPRNG::next_ranged(td::uint64 range) {
|
|
td::uint64 y = next_ulong();
|
|
return td::uint128(range).mult(y).hi();
|
|
}
|
|
|
|
inline bool operator<(td::uint64 pos, const ValidatorDescr& descr) {
|
|
return pos < descr.cum_weight;
|
|
}
|
|
|
|
const ValidatorDescr& ValidatorSet::at_weight(td::uint64 weight_pos) const {
|
|
CHECK(weight_pos < total_weight);
|
|
auto it = std::upper_bound(list.begin(), list.end(), weight_pos);
|
|
CHECK(it != list.begin());
|
|
return *--it;
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> ValidatorSet::export_validator_set() const {
|
|
std::vector<ton::ValidatorDescr> l;
|
|
l.reserve(list.size());
|
|
for (const auto& node : list) {
|
|
l.emplace_back(node.pubkey, node.weight, node.adnl_addr);
|
|
}
|
|
return l;
|
|
}
|
|
|
|
std::map<ton::Bits256, int> ValidatorSet::compute_validator_map() const {
|
|
std::map<ton::Bits256, int> res;
|
|
for (int i = 0; i < (int)list.size(); i++) {
|
|
res.emplace(list[i].pubkey.as_bits256(), i);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::vector<double> ValidatorSet::export_scaled_validator_weights() const {
|
|
std::vector<double> res;
|
|
for (const auto& node : list) {
|
|
res.push_back((double)node.weight / (double)total_weight);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int ValidatorSet::lookup_public_key(td::ConstBitPtr pubkey) const {
|
|
for (int i = 0; i < (int)list.size(); i++) {
|
|
if (list[i].pubkey.as_bits256() == pubkey) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> Config::do_compute_validator_set(const CatchainValidatorsConfig& ccv_conf,
|
|
ton::ShardIdFull shard, const ValidatorSet& vset,
|
|
ton::CatchainSeqno cc_seqno) {
|
|
// LOG(DEBUG) << "in Config::do_compute_validator_set() for " << shard.to_str() << " ; cc_seqno=" << cc_seqno;
|
|
std::vector<ton::ValidatorDescr> nodes;
|
|
bool is_mc = shard.is_masterchain();
|
|
unsigned count = std::min<unsigned>(is_mc ? vset.main : ccv_conf.shard_val_num, vset.total);
|
|
CHECK((unsigned)vset.total == vset.list.size());
|
|
if (!count) {
|
|
return {}; // no validators?
|
|
}
|
|
nodes.reserve(count);
|
|
ValidatorSetPRNG gen{shard, cc_seqno}; // use zero seed (might use a non-trivial seed from ccv_conf in the future)
|
|
if (is_mc) {
|
|
if (ccv_conf.shuffle_mc_val) {
|
|
// shuffle mc validators from the head of the list
|
|
std::vector<unsigned> idx(count);
|
|
CHECK(idx.size() == count);
|
|
for (unsigned i = 0; i < count; i++) {
|
|
unsigned j = (unsigned)gen.next_ranged(i + 1); // number 0 .. i
|
|
CHECK(j <= i);
|
|
idx[i] = idx[j];
|
|
idx[j] = i;
|
|
}
|
|
for (unsigned i = 0; i < count; i++) {
|
|
const auto& v = vset.list[idx[i]];
|
|
nodes.emplace_back(v.pubkey, v.weight, v.adnl_addr);
|
|
}
|
|
} else {
|
|
// simply take needed number of validators from the head of the list
|
|
for (unsigned i = 0; i < count; i++) {
|
|
const auto& v = vset.list[i];
|
|
nodes.emplace_back(v.pubkey, v.weight, v.adnl_addr);
|
|
}
|
|
}
|
|
return nodes;
|
|
}
|
|
// this is the "true" algorithm for generating shardchain validator subgroups
|
|
std::vector<std::pair<td::uint64, td::uint64>> holes;
|
|
holes.reserve(count);
|
|
td::uint64 total_wt = vset.total_weight;
|
|
for (unsigned i = 0; i < count; i++) {
|
|
CHECK(total_wt > 0);
|
|
auto p = gen.next_ranged(total_wt); // generate a pseudo-random number 0 .. total_wt-1
|
|
// auto op = p;
|
|
for (auto& hole : holes) {
|
|
if (p < hole.first) {
|
|
break;
|
|
}
|
|
p += hole.second;
|
|
}
|
|
auto& entry = vset.at_weight(p);
|
|
// LOG(DEBUG) << "vset entry #" << i << ": rem_wt=" << total_wt << ", total_wt=" << vset.total_weight << ", op=" << op << ", p=" << p << "; entry.cum_wt=" << entry.cum_weight << ", entry.wt=" << entry.weight << " " << entry.cum_weight / entry.weight;
|
|
nodes.emplace_back(entry.pubkey, 1, entry.adnl_addr); // NB: shardchain validator lists have all weights = 1
|
|
CHECK(total_wt >= entry.weight);
|
|
total_wt -= entry.weight;
|
|
std::pair<td::uint64, td::uint64> new_hole{entry.cum_weight, entry.weight};
|
|
auto it = std::upper_bound(holes.begin(), holes.end(), new_hole);
|
|
CHECK(it == holes.begin() || *(it - 1) < new_hole);
|
|
holes.insert(it, new_hole);
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
std::vector<ton::ValidatorDescr> Config::compute_total_validator_set(int next) const {
|
|
auto res = unpack_validator_set(get_config_param(next < 0 ? 32 : (next ? 36 : 34)));
|
|
if (res.is_error()) {
|
|
return {};
|
|
}
|
|
return res.move_as_ok()->export_validator_set();
|
|
}
|
|
|
|
td::Result<SizeLimitsConfig> Config::get_size_limits_config() const {
|
|
td::Ref<vm::Cell> param = get_config_param(43);
|
|
if (param.is_null()) {
|
|
return do_get_size_limits_config({});
|
|
}
|
|
return do_get_size_limits_config(vm::load_cell_slice_ref(param));
|
|
}
|
|
|
|
td::Result<SizeLimitsConfig> Config::do_get_size_limits_config(td::Ref<vm::CellSlice> cs) {
|
|
SizeLimitsConfig limits;
|
|
if (cs.is_null()) {
|
|
return limits; // default values
|
|
}
|
|
auto unpack_v1 = [&](auto& rec) {
|
|
limits.max_msg_bits = rec.max_msg_bits;
|
|
limits.max_msg_cells = rec.max_msg_cells;
|
|
limits.max_library_cells = rec.max_library_cells;
|
|
limits.max_vm_data_depth = static_cast<td::uint16>(rec.max_vm_data_depth);
|
|
limits.ext_msg_limits.max_size = rec.max_ext_msg_size;
|
|
limits.ext_msg_limits.max_depth = static_cast<td::uint16>(rec.max_ext_msg_depth);
|
|
};
|
|
|
|
auto unpack_v2 = [&](auto& rec) {
|
|
unpack_v1(rec);
|
|
limits.max_acc_state_bits = rec.max_acc_state_bits;
|
|
limits.max_acc_state_cells = rec.max_acc_state_cells;
|
|
limits.max_acc_public_libraries = rec.max_acc_public_libraries;
|
|
limits.defer_out_queue_size_limit = rec.defer_out_queue_size_limit;
|
|
};
|
|
gen::SizeLimitsConfig::Record_size_limits_config rec_v1;
|
|
gen::SizeLimitsConfig::Record_size_limits_config_v2 rec_v2;
|
|
if (tlb::csr_unpack(cs, rec_v1)) {
|
|
unpack_v1(rec_v1);
|
|
} else if (tlb::csr_unpack(cs, rec_v2)) {
|
|
unpack_v2(rec_v2);
|
|
} else {
|
|
return td::Status::Error("configuration parameter 43 is invalid");
|
|
}
|
|
return limits;
|
|
}
|
|
|
|
std::unique_ptr<vm::Dictionary> Config::get_suspended_addresses(ton::UnixTime now) const {
|
|
td::Ref<vm::Cell> param = get_config_param(44);
|
|
gen::SuspendedAddressList::Record rec;
|
|
if (param.is_null() || !tlb::unpack_cell(param, rec) || rec.suspended_until <= now) {
|
|
return {};
|
|
}
|
|
return std::make_unique<vm::Dictionary>(rec.addresses->prefetch_ref(), 288);
|
|
}
|
|
|
|
BurningConfig Config::get_burning_config() const {
|
|
td::Ref<vm::Cell> param = get_config_param(5);
|
|
gen::BurningConfig::Record rec;
|
|
if (param.is_null() || !tlb::unpack_cell(param, rec)) {
|
|
return {};
|
|
}
|
|
BurningConfig c;
|
|
c.fee_burn_num = rec.fee_burn_num;
|
|
c.fee_burn_denom = rec.fee_burn_denom;
|
|
vm::CellSlice& addr = rec.blackhole_addr.write();
|
|
if (addr.fetch_long(1)) {
|
|
td::Bits256 x;
|
|
addr.fetch_bits_to(x.bits(), 256);
|
|
c.blackhole_addr = x;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
td::Ref<vm::Tuple> Config::get_unpacked_config_tuple(ton::UnixTime now) const {
|
|
auto get_param = [&](td::int32 idx) -> vm::StackEntry {
|
|
auto cell = get_config_param(idx);
|
|
if (cell.is_null()) {
|
|
return {};
|
|
}
|
|
return vm::load_cell_slice_ref(cell);
|
|
};
|
|
auto get_current_storage_prices = [&]() -> vm::StackEntry {
|
|
auto cell = get_config_param(18);
|
|
if (cell.is_null()) {
|
|
return {};
|
|
}
|
|
vm::StackEntry res;
|
|
vm::Dictionary dict{std::move(cell), 32};
|
|
dict.check_for_each([&](Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) -> bool {
|
|
auto utime_since = key.get_uint(n);
|
|
if (now >= utime_since) {
|
|
res = std::move(cs_ref);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
return res;
|
|
};
|
|
std::vector<vm::StackEntry> tuple;
|
|
tuple.push_back(get_current_storage_prices()); // storage_prices
|
|
tuple.push_back(get_param(19)); // global_id
|
|
tuple.push_back(get_param(20)); // config_mc_gas_prices
|
|
tuple.push_back(get_param(21)); // config_gas_prices
|
|
tuple.push_back(get_param(24)); // config_mc_fwd_prices
|
|
tuple.push_back(get_param(25)); // config_fwd_prices
|
|
tuple.push_back(get_param(43)); // size_limits_config
|
|
return td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(tuple));
|
|
}
|
|
|
|
PrecompiledContractsConfig Config::get_precompiled_contracts_config() const {
|
|
PrecompiledContractsConfig c;
|
|
td::Ref<vm::Cell> param = get_config_param(45);
|
|
gen::PrecompiledContractsConfig::Record rec;
|
|
if (param.is_null() || !tlb::unpack_cell(param, rec)) {
|
|
return c;
|
|
}
|
|
c.list = vm::Dictionary{rec.list->prefetch_ref(), 256};
|
|
return c;
|
|
}
|
|
|
|
td::Result<std::pair<ton::UnixTime, ton::UnixTime>> Config::unpack_validator_set_start_stop(Ref<vm::Cell> vset_root) {
|
|
if (vset_root.is_null()) {
|
|
return td::Status::Error("validator set absent");
|
|
}
|
|
gen::ValidatorSet::Record_validators_ext rec;
|
|
if (tlb::unpack_cell(vset_root, rec)) {
|
|
return std::pair<ton::UnixTime, ton::UnixTime>(rec.utime_since, rec.utime_until);
|
|
}
|
|
gen::ValidatorSet::Record_validators rec0;
|
|
if (tlb::unpack_cell(std::move(vset_root), rec0)) {
|
|
return std::pair<ton::UnixTime, ton::UnixTime>(rec0.utime_since, rec0.utime_until);
|
|
}
|
|
return td::Status::Error("validator set is invalid");
|
|
}
|
|
|
|
std::pair<ton::UnixTime, ton::UnixTime> Config::get_validator_set_start_stop(int next) const {
|
|
auto res = unpack_validator_set_start_stop(get_config_param(next < 0 ? 32 : (next ? 36 : 34)));
|
|
if (res.is_error()) {
|
|
return {0, 0};
|
|
} else {
|
|
return res.move_as_ok();
|
|
}
|
|
}
|
|
|
|
bool WorkchainInfo::unpack(ton::WorkchainId wc, vm::CellSlice& cs) {
|
|
workchain = ton::workchainInvalid;
|
|
if (wc == ton::workchainInvalid) {
|
|
return false;
|
|
}
|
|
auto unpack_v1 = [this](auto& info) {
|
|
enabled_since = info.enabled_since;
|
|
monitor_min_split = info.monitor_min_split;
|
|
min_split = info.min_split;
|
|
max_split = info.max_split;
|
|
basic = info.basic;
|
|
active = info.active;
|
|
accept_msgs = info.accept_msgs;
|
|
flags = info.flags;
|
|
zerostate_root_hash = info.zerostate_root_hash;
|
|
zerostate_file_hash = info.zerostate_file_hash;
|
|
version = info.version;
|
|
if (basic) {
|
|
min_addr_len = max_addr_len = addr_len_step = 256;
|
|
} else {
|
|
block::gen::WorkchainFormat::Record_wfmt_ext ext;
|
|
if (!tlb::csr_type_unpack(info.format, block::gen::WorkchainFormat{basic}, ext)) {
|
|
return false;
|
|
}
|
|
min_addr_len = ext.min_addr_len;
|
|
max_addr_len = ext.max_addr_len;
|
|
addr_len_step = ext.addr_len_step;
|
|
}
|
|
return true;
|
|
};
|
|
auto unpack_v2 = [&, this](auto& info) {
|
|
if (!unpack_v1(info)) {
|
|
return false;
|
|
}
|
|
block::gen::WcSplitMergeTimings::Record rec;
|
|
if (!tlb::csr_unpack(info.split_merge_timings, rec)) {
|
|
return false;
|
|
}
|
|
split_merge_delay = rec.split_merge_delay;
|
|
split_merge_interval = rec.split_merge_interval;
|
|
min_split_merge_interval = rec.min_split_merge_interval;
|
|
max_split_merge_delay = rec.max_split_merge_delay;
|
|
return true;
|
|
};
|
|
block::gen::WorkchainDescr::Record_workchain info_v1;
|
|
block::gen::WorkchainDescr::Record_workchain_v2 info_v2;
|
|
vm::CellSlice cs0 = cs;
|
|
if (tlb::unpack(cs, info_v1)) {
|
|
if (!unpack_v1(info_v1)) {
|
|
return false;
|
|
}
|
|
} else if (tlb::unpack(cs = cs0, info_v2)) {
|
|
if (!unpack_v2(info_v2)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
workchain = wc;
|
|
LOG(DEBUG) << "unpacked info for workchain " << wc << ": basic=" << basic << ", active=" << active
|
|
<< ", accept_msgs=" << accept_msgs << ", min_split=" << min_split << ", max_split=" << max_split;
|
|
return true;
|
|
}
|
|
|
|
Ref<WorkchainInfo> Config::get_workchain_info(ton::WorkchainId workchain_id) const {
|
|
if (!workchains_dict_) {
|
|
return {};
|
|
}
|
|
auto it = workchains_.find(workchain_id);
|
|
if (it == workchains_.end()) {
|
|
return {};
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
bool ConfigInfo::get_old_mc_block_id(ton::BlockSeqno seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt) const {
|
|
if (block_id.is_valid() && seqno == block_id.id.seqno) {
|
|
blkid = block_id;
|
|
if (end_lt) {
|
|
*end_lt = lt;
|
|
}
|
|
return true;
|
|
} else {
|
|
return block::get_old_mc_block_id(prev_blocks_dict_.get(), seqno, blkid, end_lt);
|
|
}
|
|
}
|
|
|
|
bool ConfigInfo::check_old_mc_block_id(const ton::BlockIdExt& blkid, bool strict) const {
|
|
return (!strict && blkid.id.seqno == block_id.id.seqno && block_id.is_valid())
|
|
? blkid == block_id
|
|
: block::check_old_mc_block_id(prev_blocks_dict_.get(), blkid);
|
|
}
|
|
|
|
// returns block with min block.seqno and req_lt <= block.end_lt
|
|
bool ConfigInfo::get_mc_block_by_lt(ton::LogicalTime req_lt, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt) const {
|
|
if (req_lt > lt) {
|
|
return false;
|
|
}
|
|
td::BitArray<32> key;
|
|
auto found = prev_blocks_dict_->traverse_extra(
|
|
key.bits(), 32,
|
|
[req_lt](td::ConstBitPtr key_prefix, int key_pfx_len, Ref<vm::CellSlice> extra, Ref<vm::CellSlice> value) {
|
|
unsigned long long found_lt;
|
|
if (!(extra.write().advance(1) && extra.write().fetch_ulong_bool(64, found_lt))) {
|
|
return -1;
|
|
}
|
|
if (found_lt < req_lt) {
|
|
return 0; // all leaves in subtree have end_lt <= found_lt < req_lt, skip
|
|
}
|
|
return 6; // visit left subtree, then right subtree; for leaf: accept and return to the top
|
|
});
|
|
if (found.first.not_null()) {
|
|
CHECK(unpack_old_mc_block_id(std::move(found.first), (unsigned)key.to_ulong(), blkid, end_lt));
|
|
return true;
|
|
}
|
|
if (block_id.is_valid()) {
|
|
blkid = block_id;
|
|
if (end_lt) {
|
|
*end_lt = lt;
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// returns key block with max block.seqno and block.seqno <= req_seqno
|
|
bool ConfigInfo::get_prev_key_block(ton::BlockSeqno req_seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt) const {
|
|
if (block_id.is_valid() && is_key_state_ && block_id.seqno() <= req_seqno) {
|
|
blkid = block_id;
|
|
if (end_lt) {
|
|
*end_lt = lt;
|
|
}
|
|
return true;
|
|
}
|
|
td::BitArray<32> key;
|
|
auto found =
|
|
prev_blocks_dict_->traverse_extra(key.bits(), 32,
|
|
[req_seqno](td::ConstBitPtr key_prefix, int key_pfx_len,
|
|
Ref<vm::CellSlice> extra, Ref<vm::CellSlice> value) -> int {
|
|
if (extra->prefetch_ulong(1) != 1) {
|
|
return 0; // no key blocks in subtree, skip
|
|
}
|
|
unsigned x = (unsigned)key_prefix.get_uint(key_pfx_len);
|
|
unsigned d = 32 - key_pfx_len;
|
|
if (!d) {
|
|
return x <= req_seqno;
|
|
}
|
|
unsigned y = req_seqno >> (d - 1);
|
|
if (y < 2 * x) {
|
|
// (x << d) > req_seqno <=> x > (req_seqno >> d) = (y >> 1) <=> 2 * x > y
|
|
return 0; // all nodes in subtree have block.seqno > req_seqno => skip
|
|
}
|
|
return y == 2 * x ? 1 /* visit only left */ : 5 /* visit right, then left */;
|
|
});
|
|
if (found.first.not_null()) {
|
|
CHECK(unpack_old_mc_block_id(std::move(found.first), (unsigned)key.to_ulong(), blkid, end_lt));
|
|
CHECK(blkid.seqno() <= req_seqno);
|
|
return true;
|
|
} else {
|
|
blkid.invalidate();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// returns key block with min block.seqno and block.seqno >= req_seqno
|
|
bool ConfigInfo::get_next_key_block(ton::BlockSeqno req_seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt) const {
|
|
td::BitArray<32> key;
|
|
auto found = prev_blocks_dict_->traverse_extra(
|
|
key.bits(), 32,
|
|
[req_seqno](td::ConstBitPtr key_prefix, int key_pfx_len, Ref<vm::CellSlice> extra,
|
|
Ref<vm::CellSlice> value) -> int {
|
|
if (extra->prefetch_ulong(1) != 1) {
|
|
return 0; // no key blocks in subtree, skip
|
|
}
|
|
unsigned x = (unsigned)key_prefix.get_uint(key_pfx_len);
|
|
unsigned d = 32 - key_pfx_len;
|
|
if (!d) {
|
|
return x >= req_seqno;
|
|
}
|
|
unsigned y = req_seqno >> (d - 1);
|
|
if (y > 2 * x + 1) {
|
|
// ((x + 1) << d) <= req_seqno <=> (x+1) <= (req_seqno >> d) = (y >> 1) <=> 2*x+2 <= y <=> y > 2*x+1
|
|
return 0; // all nodes in subtree have block.seqno < req_seqno => skip
|
|
}
|
|
return y == 2 * x + 1 ? 2 /* visit only right */ : 6 /* visit left, then right */;
|
|
});
|
|
if (found.first.not_null()) {
|
|
CHECK(unpack_old_mc_block_id(std::move(found.first), (unsigned)key.to_ulong(), blkid, end_lt));
|
|
CHECK(blkid.seqno() >= req_seqno);
|
|
return true;
|
|
}
|
|
if (block_id.is_valid() && is_key_state_ && block_id.seqno() >= req_seqno) {
|
|
blkid = block_id;
|
|
if (end_lt) {
|
|
*end_lt = lt;
|
|
}
|
|
return true;
|
|
} else {
|
|
blkid.invalidate();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Ref<vm::Cell> ConfigInfo::lookup_library(td::ConstBitPtr root_hash) const {
|
|
if (!libraries_dict_) {
|
|
return {};
|
|
}
|
|
auto csr = libraries_dict_->lookup(root_hash, 256);
|
|
if (csr.is_null() || csr->prefetch_ulong(2) != 0 || !csr->have_refs()) { // shared_lib_descr$00 lib:^Cell
|
|
return {};
|
|
}
|
|
auto lib = csr->prefetch_ref();
|
|
if (lib->get_hash().bits().compare(root_hash, 256)) {
|
|
LOG(ERROR) << "public library hash mismatch: expected " << root_hash.to_hex(256) << " , found "
|
|
<< lib->get_hash().bits().to_hex(256);
|
|
return {};
|
|
}
|
|
return lib;
|
|
}
|
|
|
|
td::Result<Ref<vm::Tuple>> ConfigInfo::get_prev_blocks_info() const {
|
|
// [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId;
|
|
// [ last_mc_blocks:[BlockId...]
|
|
// prev_key_block:BlockId
|
|
// last_mc_blocks_100[BlockId...] ] : PrevBlocksInfo
|
|
auto block_id_to_tuple = [](const ton::BlockIdExt& block_id) -> vm::Ref<vm::Tuple> {
|
|
td::RefInt256 shard = td::make_refint(block_id.id.shard);
|
|
if (shard->sgn() < 0) {
|
|
shard &= ((td::make_refint(1) << 64) - 1);
|
|
}
|
|
return vm::make_tuple_ref(td::make_refint(block_id.id.workchain), std::move(shard),
|
|
td::make_refint(block_id.id.seqno), td::bits_to_refint(block_id.root_hash.bits(), 256),
|
|
td::bits_to_refint(block_id.file_hash.bits(), 256));
|
|
};
|
|
std::vector<vm::StackEntry> tuple;
|
|
|
|
std::vector<vm::StackEntry> last_mc_blocks;
|
|
last_mc_blocks.push_back(block_id_to_tuple(block_id));
|
|
for (ton::BlockSeqno seqno = block_id.id.seqno; seqno > 0 && last_mc_blocks.size() < 16;) {
|
|
--seqno;
|
|
ton::BlockIdExt id;
|
|
if (!get_old_mc_block_id(seqno, id)) {
|
|
return td::Status::Error("cannot fetch old mc block");
|
|
}
|
|
last_mc_blocks.push_back(block_id_to_tuple(id));
|
|
}
|
|
tuple.push_back(td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(last_mc_blocks)));
|
|
|
|
ton::BlockIdExt last_key_block;
|
|
ton::LogicalTime last_key_block_lt;
|
|
if (!get_last_key_block(last_key_block, last_key_block_lt)) {
|
|
return td::Status::Error("cannot fetch last key block");
|
|
}
|
|
tuple.push_back(block_id_to_tuple(last_key_block));
|
|
|
|
if (get_global_version() >= 9) {
|
|
std::vector<vm::StackEntry> last_mc_blocks_100;
|
|
for (ton::BlockSeqno seqno = block_id.id.seqno / 100 * 100; last_mc_blocks_100.size() < 16;) {
|
|
ton::BlockIdExt id;
|
|
if (!get_old_mc_block_id(seqno, id)) {
|
|
return td::Status::Error("cannot fetch old mc block");
|
|
}
|
|
last_mc_blocks_100.push_back(block_id_to_tuple(id));
|
|
if (seqno < 100) {
|
|
break;
|
|
}
|
|
seqno -= 100;
|
|
}
|
|
tuple.push_back(td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(last_mc_blocks_100)));
|
|
}
|
|
|
|
return td::make_cnt_ref<std::vector<vm::StackEntry>>(std::move(tuple));
|
|
}
|
|
|
|
td::optional<PrecompiledContractsConfig::Contract> PrecompiledContractsConfig::get_contract(
|
|
td::Bits256 code_hash) const {
|
|
auto list_copy = list;
|
|
auto cs = list_copy.lookup(code_hash);
|
|
if (cs.is_null()) {
|
|
return {};
|
|
}
|
|
gen::PrecompiledSmc::Record rec;
|
|
if (!tlb::csr_unpack(cs, rec)) {
|
|
return {};
|
|
}
|
|
Contract c;
|
|
c.gas_usage = rec.gas_usage;
|
|
return c;
|
|
}
|
|
|
|
} // namespace block
|