1
0
mirror of https://github.com/danog/ton.git synced 2024-12-02 17:38:33 +01:00
ton/crypto/block/block.cpp
ton 13140ddf29 updated block header
1. Updated block header, proofs now contain more data
   Notice, that old proofs may become invalid in the future
2. Fixed message routing
3. Fixed block creator id in block header
4. Support for full proofs in tonlib
5. Support for partial state download
6. Some other bugfixes
2019-09-18 21:46:32 +04:00

2123 lines
79 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/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/utils/bits.h"
#include "block/block.h"
#include "block/block-auto.h"
#include "block/block-parse.h"
#include "ton/ton-shard.h"
#include "common/bigexp.h"
#include "common/util.h"
#include "td/utils/crypto.h"
#include "td/utils/tl_storers.h"
#include "td/utils/misc.h"
namespace block {
using namespace std::literals::string_literals;
td::Result<PublicKey> PublicKey::from_bytes(td::Slice key) {
if (key.size() != 32) {
return td::Status::Error("Ed25519 public key must be exactly 32 bytes long");
}
PublicKey res;
res.key = key.str();
return res;
}
td::Result<PublicKey> PublicKey::parse(td::Slice key) {
if (key.size() != 48) {
return td::Status::Error("Serialized Ed25519 public key must be exactly 48 characters long");
}
td::uint8 buf[36];
if (!buff_base64_decode(td::MutableSlice(buf, 36), key, true)) {
return td::Status::Error("Public key is not serialized in base64 encoding");
}
td::uint16 hash = static_cast<td::uint16>((static_cast<unsigned>(buf[34]) << 8) + buf[35]);
if (hash != td::crc16(td::Slice(buf, 34))) {
return td::Status::Error("Public key has incorrect crc16 hash");
}
if (buf[0] != 0x3e) {
return td::Status::Error("Not a public key");
}
if (buf[1] != 0xe6) {
return td::Status::Error("Not an ed25519 public key");
}
return from_bytes(td::Slice(buf + 2, 32));
}
std::string PublicKey::serialize(bool base64_url) {
CHECK(key.size() == 32);
std::string buf(36, 0);
td::MutableSlice bytes(buf);
bytes[0] = static_cast<char>(0x3e);
bytes[1] = static_cast<char>(0xe6);
bytes.substr(2).copy_from(key);
auto hash = td::crc16(bytes.substr(0, 34));
bytes[34] = static_cast<char>(hash >> 8);
bytes[35] = static_cast<char>(hash & 255);
std::string res(48, 0);
buff_base64_encode(res, bytes, base64_url);
return res;
}
bool pack_std_smc_addr_to(char result[48], bool base64_url, ton::WorkchainId wc, const ton::StdSmcAddress& addr,
bool bounceable, bool testnet) {
if (wc < -128 || wc >= 128) {
return false;
}
unsigned char buffer[36];
buffer[0] = (unsigned char)(0x51 - bounceable * 0x40 + testnet * 0x80);
buffer[1] = (unsigned char)wc;
std::memcpy(buffer + 2, addr.data(), 32);
unsigned crc = td::crc16(td::Slice{buffer, 34});
buffer[34] = (unsigned char)(crc >> 8);
buffer[35] = (unsigned char)(crc & 0xff);
CHECK(buff_base64_encode(td::MutableSlice{result, 48}, td::Slice{buffer, 36}, base64_url) == 48);
return true;
}
std::string pack_std_smc_addr(bool base64_url, ton::WorkchainId wc, const ton::StdSmcAddress& addr, bool bounceable,
bool testnet) {
char result[48];
if (pack_std_smc_addr_to(result, base64_url, wc, addr, bounceable, testnet)) {
return std::string{result, 48};
} else {
return "";
}
}
bool unpack_std_smc_addr(const char packed[48], ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet) {
unsigned char buffer[36];
wc = ton::workchainInvalid;
if (!buff_base64_decode(td::MutableSlice{buffer, 36}, td::Slice{packed, 48}, true)) {
return false;
}
unsigned crc = td::crc16(td::Slice{buffer, 34});
if (buffer[34] != (unsigned char)(crc >> 8) || buffer[35] != (unsigned char)(crc & 0xff)) {
return false;
}
if ((buffer[0] & 0x3f) != 0x11) {
return false;
}
testnet = (buffer[0] & 0x80);
bounceable = !(buffer[0] & 0x40);
wc = (td::int8)buffer[1];
std::memcpy(addr.data(), buffer + 2, 32);
return true;
}
bool unpack_std_smc_addr(td::Slice packed, ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet) {
return packed.size() == 48 && unpack_std_smc_addr(packed.data(), wc, addr, bounceable, testnet);
}
bool unpack_std_smc_addr(std::string packed, ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet) {
return packed.size() == 48 && unpack_std_smc_addr(packed.data(), wc, addr, bounceable, testnet);
}
StdAddress::StdAddress(std::string serialized) {
rdeserialize(std::move(serialized));
}
StdAddress::StdAddress(td::Slice from) {
rdeserialize(std::move(from));
}
std::string StdAddress::rserialize(bool base64_url) const {
char buffer[48];
return rserialize_to(buffer, base64_url) ? std::string{buffer, 48} : "";
}
bool StdAddress::rserialize_to(td::MutableSlice to, bool base64_url) const {
return to.size() == 48 && rserialize_to(to.data(), base64_url);
}
bool StdAddress::rserialize_to(char to[48], bool base64_url) const {
return pack_std_smc_addr_to(to, base64_url, workchain, addr, bounceable, testnet);
}
bool StdAddress::rdeserialize(td::Slice from) {
return from.size() == 48 && unpack_std_smc_addr(from.data(), workchain, addr, bounceable, testnet);
}
bool StdAddress::rdeserialize(std::string from) {
return from.size() == 48 && unpack_std_smc_addr(from.data(), workchain, addr, bounceable, testnet);
}
bool StdAddress::rdeserialize(const char from[48]) {
return unpack_std_smc_addr(from, workchain, addr, bounceable, testnet);
}
bool StdAddress::operator==(const StdAddress& other) const {
return workchain == other.workchain && addr == other.addr && bounceable == other.bounceable &&
testnet == other.testnet;
}
int parse_hex_digit(int c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
c |= 0x20;
if (c >= 'a' && c <= 'z') {
return c - 'a' + 10;
}
return -1;
}
bool StdAddress::parse_addr(td::Slice acc_string) {
if (rdeserialize(acc_string)) {
return true;
}
testnet = false;
bounceable = true;
auto pos = acc_string.find(':');
if (pos != std::string::npos) {
if (pos > 10) {
return invalidate();
}
auto tmp = acc_string.substr(0, pos);
auto r_wc = td::to_integer_safe<ton::WorkchainId>(tmp);
if (r_wc.is_error()) {
return invalidate();
}
workchain = r_wc.move_as_ok();
if (workchain == ton::workchainInvalid) {
return invalidate();
}
++pos;
} else {
pos = 0;
}
// LOG(DEBUG) << "parsing " << acc_string << " address";
if (acc_string.size() != pos + 64) {
return invalidate();
}
for (unsigned i = 0; i < 64; i++) {
int x = parse_hex_digit(acc_string[pos + i]), m = 15;
if (x < 0) {
return invalidate();
}
if (!(i & 1)) {
x <<= 4;
m <<= 4;
}
addr.data()[i >> 1] = (unsigned char)((addr.data()[i >> 1] & ~m) | x);
}
return true;
}
bool parse_std_account_addr(td::Slice acc_string, ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool* bounceable,
bool* testnet_only) {
StdAddress a;
if (!a.parse_addr(acc_string)) {
return false;
}
wc = a.workchain;
addr = a.addr;
if (testnet_only) {
*testnet_only = a.testnet;
}
if (bounceable) {
*bounceable = a.bounceable;
}
return true;
}
td::Result<StdAddress> StdAddress::parse(td::Slice acc_string) {
StdAddress res;
if (res.parse_addr(acc_string)) {
return res;
}
return td::Status::Error("Failed to parse account address");
}
void ShardId::init() {
if (!shard_pfx) {
shard_pfx = (1ULL << 63);
shard_pfx_len = 0;
} else {
shard_pfx_len = 63 - td::count_trailing_zeroes_non_zero64(shard_pfx);
}
}
ShardId::ShardId(ton::WorkchainId wc_id, unsigned long long sh_pfx) : workchain_id(wc_id), shard_pfx(sh_pfx) {
init();
}
ShardId::ShardId(ton::ShardIdFull ton_shard_id) : workchain_id(ton_shard_id.workchain), shard_pfx(ton_shard_id.shard) {
init();
}
ShardId::ShardId(ton::BlockId ton_block_id) : workchain_id(ton_block_id.workchain), shard_pfx(ton_block_id.shard) {
init();
}
ShardId::ShardId(const ton::BlockIdExt& ton_block_id)
: workchain_id(ton_block_id.id.workchain), shard_pfx(ton_block_id.id.shard) {
init();
}
ShardId::ShardId(ton::WorkchainId wc_id, unsigned long long sh_pfx, int sh_pfx_len)
: workchain_id(wc_id), shard_pfx_len(sh_pfx_len) {
if (sh_pfx_len < 0) {
shard_pfx_len = 0;
shard_pfx = (1ULL << 63);
} else if (sh_pfx_len > 63) {
shard_pfx_len = 63;
shard_pfx = sh_pfx | 1;
} else {
unsigned long long pow = 1ULL << (63 - sh_pfx_len);
shard_pfx = (sh_pfx | pow) & (pow - 1);
}
}
std::ostream& operator<<(std::ostream& os, const ShardId& shard_id) {
shard_id.show(os);
return os;
}
void ShardId::show(std::ostream& os) const {
if (workchain_id == ton::workchainInvalid) {
os << '?';
return;
}
os << workchain_id << ':' << shard_pfx_len << ',';
unsigned long long t = shard_pfx;
int cnt = 0;
while ((t & ((1ULL << 63) - 1)) != 0) {
static const char hex_digit[] = "0123456789ABCDEF";
os << (char)hex_digit[t >> 60];
t <<= 4;
++cnt;
}
if (!t || !cnt) {
os << '_';
}
}
std::string ShardId::to_str() const {
std::ostringstream os;
show(os);
return os.str();
}
bool ShardId::serialize(vm::CellBuilder& cb) const {
if (workchain_id == ton::workchainInvalid || cb.remaining_bits() < 104) {
return false;
}
return cb.store_long_bool(0, 2) && cb.store_ulong_rchk_bool(shard_pfx_len, 6) &&
cb.store_long_bool(workchain_id, 32) && cb.store_long_bool(shard_pfx & (shard_pfx - 1));
}
bool ShardId::deserialize(vm::CellSlice& cs) {
if (cs.fetch_ulong(2) == 0 && cs.fetch_uint_to(6, shard_pfx_len) && cs.fetch_int_to(32, workchain_id) &&
workchain_id != ton::workchainInvalid && cs.fetch_uint_to(64, shard_pfx)) {
auto pow2 = (1ULL << (63 - shard_pfx_len));
if (!(shard_pfx & (pow2 - 1))) {
shard_pfx |= pow2;
return true;
}
}
invalidate();
return false;
}
MsgProcessedUptoCollection::MsgProcessedUptoCollection(ton::ShardIdFull _owner, Ref<vm::CellSlice> cs_ref)
: owner(_owner) {
vm::Dictionary dict{std::move(cs_ref), 96};
valid = dict.check_for_each([&](Ref<vm::CellSlice> value, td::ConstBitPtr key, int n) -> bool {
if (value->size_ext() != 64 + 256) {
return false;
}
list.emplace_back();
MsgProcessedUpto& z = list.back();
z.shard = key.get_uint(64);
z.mc_seqno = (unsigned)((key + 64).get_uint(32));
z.last_inmsg_lt = value.write().fetch_ulong(64);
// std::cerr << "ProcessedUpto shard " << std::hex << z.shard << std::dec << std::endl;
return value.write().fetch_bits_to(z.last_inmsg_hash) && z.shard && ton::shard_contains(owner.shard, z.shard);
});
}
std::unique_ptr<MsgProcessedUptoCollection> MsgProcessedUptoCollection::unpack(ton::ShardIdFull _owner,
Ref<vm::CellSlice> cs_ref) {
auto v = std::make_unique<MsgProcessedUptoCollection>(_owner, std::move(cs_ref));
return v && v->valid ? std::move(v) : std::unique_ptr<MsgProcessedUptoCollection>{};
}
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const & {
return ton::shard_is_ancestor(shard, other.shard) && mc_seqno >= other.mc_seqno &&
(last_inmsg_lt > other.last_inmsg_lt ||
(last_inmsg_lt == other.last_inmsg_lt && !(last_inmsg_hash < other.last_inmsg_hash)));
}
bool MsgProcessedUpto::contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
ton::BlockSeqno other_mc_seqno) const & {
return ton::shard_is_ancestor(shard, other_shard) && mc_seqno >= other_mc_seqno &&
(last_inmsg_lt > other_lt || (last_inmsg_lt == other_lt && !(last_inmsg_hash < other_hash)));
}
bool MsgProcessedUptoCollection::insert(ton::BlockSeqno mc_seqno, ton::LogicalTime last_proc_lt,
td::ConstBitPtr last_proc_hash) {
if (!last_proc_lt) {
return false;
}
for (const auto& z : list) {
if (z.contains(owner.shard, last_proc_lt, last_proc_hash, mc_seqno)) {
return true;
}
}
list.emplace_back(owner.shard, mc_seqno, last_proc_lt, last_proc_hash);
return true;
}
bool MsgProcessedUptoCollection::insert_infty(ton::BlockSeqno mc_seqno, ton::LogicalTime last_proc_lt) {
return insert(mc_seqno, last_proc_lt, td::Bits256::ones().bits());
}
bool MsgProcessedUptoCollection::is_reduced() const {
if (!valid) {
return false;
}
for (auto it = list.begin(); it < list.end(); ++it) {
for (auto it2 = it + 1; it2 < list.end(); ++it2) {
if (it->contains(*it2) || it2->contains(*it)) {
return false;
}
}
}
return true;
}
bool MsgProcessedUptoCollection::contains(const MsgProcessedUpto& p_upto) const {
for (const auto& z : list) {
if (z.contains(p_upto)) {
return true;
}
}
return false;
}
bool MsgProcessedUptoCollection::contains(const MsgProcessedUptoCollection& other) const {
for (const auto& w : other.list) {
if (!contains(w)) {
return false;
}
}
return true;
}
const MsgProcessedUpto* MsgProcessedUptoCollection::is_simple_update_of(const MsgProcessedUptoCollection& other,
bool& ok) const {
ok = false;
if (!contains(other)) {
LOG(DEBUG) << "does not cointain the previous value";
return nullptr;
}
if (other.contains(*this)) {
LOG(DEBUG) << "coincides with the previous value";
ok = true;
return nullptr;
}
const MsgProcessedUpto* found = nullptr;
for (const auto& z : list) {
if (!other.contains(z)) {
if (found) {
LOG(DEBUG) << "has more than two new entries";
return found; // ok = false: update is not simple
}
found = &z;
}
}
ok = true;
return found;
}
ton::BlockSeqno MsgProcessedUptoCollection::min_mc_seqno() const {
ton::BlockSeqno min_mc_seqno = ~0U;
for (const auto& z : list) {
min_mc_seqno = std::min(min_mc_seqno, z.mc_seqno);
}
return min_mc_seqno;
}
bool MsgProcessedUptoCollection::compactify() {
std::sort(list.begin(), list.end());
std::size_t i, j, k = 0, m = 0, n = list.size();
std::vector<bool> mark(n, false);
assert(mark.size() == n);
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (j != i && !mark[j] && list[j].contains(list[i])) {
mark[i] = true;
++m;
break;
}
}
}
if (m) {
for (i = 0; i < n; i++) {
if (!mark[i]) {
list[k++] = list[i];
}
}
list.resize(k);
}
return true;
}
bool MsgProcessedUptoCollection::pack(vm::CellBuilder& cb) {
if (!compactify()) {
return false;
}
vm::Dictionary dict{96};
for (const auto& z : list) {
td::BitArray<96> key;
vm::CellBuilder cb2;
key.bits().store_uint(z.shard, 64);
(key.bits() + 64).store_uint(z.mc_seqno, 32);
if (!(cb2.store_long_bool(z.last_inmsg_lt) && cb2.store_bits_bool(z.last_inmsg_hash) &&
dict.set_builder(key, cb2, vm::Dictionary::SetMode::Add))) {
return false;
}
}
return std::move(dict).append_dict_to_bool(cb);
}
bool MsgProcessedUptoCollection::split(ton::ShardIdFull new_owner) {
if (!ton::shard_is_ancestor(owner, new_owner)) {
return false;
}
if (owner == new_owner) {
return true;
}
std::size_t n = list.size(), i, j = 0;
for (i = 0; i < n; i++) {
if (ton::shard_intersects(list[i].shard, new_owner.shard)) {
list[i].shard = ton::shard_intersection(list[i].shard, new_owner.shard);
if (j < i) {
list[j] = std::move(list[i]);
}
j++;
}
}
list.resize(j);
owner = new_owner;
return compactify();
}
bool MsgProcessedUptoCollection::combine_with(const MsgProcessedUptoCollection& other) {
if (!(other.owner == owner || ton::shard_is_sibling(other.owner, owner))) {
return false;
}
list.insert(list.end(), other.list.begin(), other.list.end());
if (owner != other.owner) {
owner = ton::shard_parent(owner);
}
return compactify();
}
bool MsgProcessedUpto::already_processed(const EnqueuedMsgDescr& msg) const {
// LOG(DEBUG) << "compare msg (" << msg.lt_ << "," << msg.hash_.to_hex() << ") against record's (" << last_inmsg_lt
// << "," << last_inmsg_hash.to_hex() << ")";
if (msg.lt_ > last_inmsg_lt) {
return false;
}
if (!ton::shard_contains(shard, msg.next_prefix_.account_id_prefix)) {
return false;
}
if (msg.lt_ == last_inmsg_lt && last_inmsg_hash < msg.hash_) {
return false;
}
if (ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
// ? enable this branch only if an extra boolean parameter is set ?
return true;
}
auto shard_end_lt = compute_shard_end_lt(msg.cur_prefix_);
// LOG(DEBUG) << "enqueued_lt = " << msg.enqueued_lt_ << " , shard_end_lt = " << shard_end_lt;
return msg.enqueued_lt_ < shard_end_lt;
}
bool MsgProcessedUptoCollection::already_processed(const EnqueuedMsgDescr& msg) const {
// LOG(DEBUG) << "checking message with cur_addr=" << msg.cur_prefix_.to_str()
// << " next_addr=" << msg.next_prefix_.to_str() << " against ProcessedUpto of neighbor " << owner.to_str();
if (!ton::shard_contains(owner, msg.next_prefix_)) {
return false;
}
for (const auto& rec : list) {
if (rec.already_processed(msg)) {
return true;
}
}
return false;
}
bool MsgProcessedUptoCollection::for_each_mcseqno(std::function<bool(ton::BlockSeqno)> func) const {
for (const auto& entry : list) {
if (!func(entry.mc_seqno)) {
return false;
}
}
return true;
}
// unpacks some fields from EnqueuedMsg
bool EnqueuedMsgDescr::unpack(vm::CellSlice& cs) {
block::gen::EnqueuedMsg::Record enq;
block::tlb::MsgEnvelope::Record_std env;
block::gen::CommonMsgInfo::Record_int_msg_info info;
if (!(tlb::unpack(cs, enq) && tlb::unpack_cell(enq.out_msg, env) && tlb::unpack_cell_inexact(env.msg, info))) {
return invalidate();
}
src_prefix_ = block::tlb::t_MsgAddressInt.get_prefix(std::move(info.src));
dest_prefix_ = block::tlb::t_MsgAddressInt.get_prefix(std::move(info.dest));
if (!(src_prefix_.is_valid() && dest_prefix_.is_valid())) {
return invalidate();
}
cur_prefix_ = interpolate_addr(src_prefix_, dest_prefix_, env.cur_addr);
next_prefix_ = interpolate_addr(src_prefix_, dest_prefix_, env.next_addr);
lt_ = info.created_lt;
enqueued_lt_ = enq.enqueued_lt;
hash_ = env.msg->get_hash().bits();
msg_ = std::move(env.msg);
msg_env_ = std::move(enq.out_msg);
return true;
}
bool EnqueuedMsgDescr::check_key(td::ConstBitPtr key) const {
return key.get_int(32) == next_prefix_.workchain && (key + 32).get_uint(64) == next_prefix_.account_id_prefix &&
hash_ == key + 96;
}
bool ParamLimits::deserialize(vm::CellSlice& cs) {
return cs.fetch_ulong(8) == 0xc3 // param_limits#c3
&& cs.fetch_uint_to(32, limits_[0]) // underload:uint32
&& cs.fetch_uint_to(32, limits_[1]) // soft_limit:uint32
&& cs.fetch_uint_to(32, limits_[3]) // hard_limit:uint32
&& limits_[0] <= limits_[1] // { underload <= soft_limit }
&& limits_[1] <= limits_[3] // { soft_limit <= hard_limit } = ParamLimits;
&& compute_medium_limit();
}
bool BlockLimits::deserialize(vm::CellSlice& cs) {
return cs.fetch_ulong(8) == 0x5d // block_limits#5d
&& bytes.deserialize(cs) // bytes:ParamLimits
&& gas.deserialize(cs) // gas:ParamLimits
&& lt_delta.deserialize(cs); // lt_delta:ParamLimits
}
int ParamLimits::classify(td::uint64 value) const {
int a = -1, b = limits_cnt;
while (b - a > 1) {
int c = (a + b) >> 1;
if (value >= limits_[c]) {
a = c;
} else {
b = c;
}
}
return a + 1;
}
bool ParamLimits::fits(unsigned cls, td::uint64 value) const {
return cls >= limits_cnt || value < limits_[cls];
}
int BlockLimits::classify_size(td::uint64 size) const {
return bytes.classify(size);
}
int BlockLimits::classify_gas(td::uint64 gas_value) const {
return gas.classify(gas_value);
}
int BlockLimits::classify_lt(ton::LogicalTime lt) const {
return lt_delta.classify(lt - start_lt);
}
int BlockLimits::classify(td::uint64 size, td::uint64 gas, ton::LogicalTime lt) const {
return std::max(std::max(classify_size(size), classify_gas(gas)), classify_lt(lt));
}
bool BlockLimits::fits(unsigned cls, td::uint64 size, td::uint64 gas_value, ton::LogicalTime lt) const {
return bytes.fits(cls, size) && gas.fits(cls, gas_value) && lt_delta.fits(cls, lt - start_lt);
}
td::uint64 BlockLimitStatus::estimate_block_size(const vm::NewCellStorageStat::Stat* extra) const {
auto sum = st_stat.get_total_stat();
if (extra) {
sum += *extra;
}
return 2000 + (sum.bits >> 3) + sum.cells * 12 + sum.internal_refs * 3 + sum.external_refs * 40 + accounts * 200 +
transactions * 200 + (extra ? 200 : 0);
}
int BlockLimitStatus::classify() const {
return limits.classify(estimate_block_size(), gas_used, cur_lt);
}
bool BlockLimitStatus::fits(unsigned cls) const {
return cls >= ParamLimits::limits_cnt ||
(limits.gas.fits(cls, gas_used) && limits.lt_delta.fits(cls, cur_lt - limits.start_lt) &&
limits.bytes.fits(cls, estimate_block_size()));
}
bool BlockLimitStatus::would_fit(unsigned cls, ton::LogicalTime end_lt, td::uint64 more_gas,
const vm::NewCellStorageStat::Stat* extra) const {
return cls >= ParamLimits::limits_cnt || (limits.gas.fits(cls, gas_used + more_gas) &&
limits.lt_delta.fits(cls, std::max(cur_lt, end_lt) - limits.start_lt) &&
limits.bytes.fits(cls, estimate_block_size(extra)));
}
// SETS: account_dict, shard_libraries_, mc_state_extra
// total_balance{,_extra}, total_validator_fees
// SETS: out_msg_queue, processed_upto_, ihr_pending (via unpack_out_msg_queue_info)
// SETS: utime_, lt_
td::Status ShardState::unpack_state(ton::BlockIdExt blkid, Ref<vm::Cell> prev_state_root) {
if (!blkid.is_valid()) {
return td::Status::Error(-666, "invalid block id supplied to ShardState::unpack");
}
if (prev_state_root.is_null()) {
return td::Status::Error(-666, "the root cell supplied for the shardchain state "s + blkid.to_str() + " is null");
}
block::gen::ShardStateUnsplit::Record state;
if (!tlb::unpack_cell(prev_state_root, state)) {
return td::Status::Error(-666, "cannot unpack header of shardchain state "s + blkid.to_str());
}
if ((unsigned)state.seq_no != blkid.seqno()) {
return td::Status::Error(
-666, PSTRING() << "shardchain state for " << blkid.to_str() << " has incorrect seqno " << state.seq_no);
}
auto shard1 = ton::ShardIdFull(block::ShardId{state.shard_id});
if (shard1 != blkid.shard_full()) {
return td::Status::Error(-666, "shardchain state for "s + blkid.to_str() +
" corresponds to incorrect workchain or shard " + shard1.to_str());
}
if (state.vert_seq_no) {
return td::Status::Error(
-666, "shardchain state for "s + blkid.to_str() + " has non-zero vert_seq_no, which is unsupported");
}
id_ = blkid;
root_ = std::move(prev_state_root);
before_split_ = state.before_split;
account_dict_ = std::make_unique<vm::AugmentedDictionary>(
vm::load_cell_slice(std::move(state.accounts)).prefetch_ref(), 256, block::tlb::aug_ShardAccounts);
// check that all keys in account_dict have correct prefixes
td::BitArray<64> acc_pfx{(long long)shard1.shard};
int acc_pfx_len = shard_prefix_length(shard1);
if (!account_dict_->has_common_prefix(acc_pfx.bits(), acc_pfx_len)) {
return td::Status::Error(-666, "account dictionary of previous state of "s + id_.to_str() + " does not have " +
acc_pfx.bits().to_hex(acc_pfx_len) + " as common key prefix");
}
// get overload / underload history
overload_history_ = state.r1.overload_history;
underload_history_ = state.r1.underload_history;
// get shard libraries
shard_libraries_ = std::make_unique<vm::Dictionary>(state.r1.libraries->prefetch_ref(), 256);
if (!shard_libraries_->is_empty() && !shard1.is_masterchain()) {
return td::Status::Error(-666,
"shardchain state "s + id_.to_str() +
" has a non-trivial shard libraries collection, but it is not in the masterchain");
}
mc_state_extra_ = state.custom->prefetch_ref();
vm::CellSlice cs{*state.r1.master_ref}; // master_ref:(Maybe BlkMasterInfo)
if ((int)cs.fetch_ulong(1) == 1) {
if (!(block::tlb::t_ExtBlkRef.unpack(cs, mc_blk_ref_, &mc_blk_lt_) && cs.empty_ext())) {
return td::Status::Error(-666, "cannot unpack master_ref in shardchain state of "s + id_.to_str());
}
mc_blk_seqno_ = mc_blk_ref_.seqno();
} else {
mc_blk_seqno_ = 0;
mc_blk_lt_ = 0;
mc_blk_ref_.invalidate();
}
min_ref_mc_seqno_ = state.min_ref_mc_seqno;
global_id_ = state.global_id;
utime_ = state.gen_utime;
lt_ = state.gen_lt;
if (!total_balance_.validate_unpack(state.r1.total_balance)) {
return td::Status::Error(
-666, "cannot unpack total_balance:CurrencyCollection from previous ShardState of "s + id_.to_str());
}
auto accounts_extra = account_dict_->get_root_extra();
CurrencyCollection old_total_balance;
if (!(accounts_extra.write().advance(5) && old_total_balance.fetch(accounts_extra.write()))) {
return td::Status::Error(
-666,
"cannot extract total account balance from ShardAccounts contained in previous ShardState of "s + id_.to_str());
}
if (old_total_balance != total_balance_) {
return td::Status::Error(-666, "invalid previous ShardState for "s + id_.to_str() + ": declared total balance " +
total_balance_.to_str() + " differs from " + old_total_balance.to_str() +
" obtained by summing over all Accounts");
}
if (!(total_validator_fees_.validate_unpack(state.r1.total_validator_fees) && !total_validator_fees_.has_extra())) {
return td::Status::Error(
-666, "cannot unpack total_validator_fees:CurrencyCollection from previous ShardState of "s + id_.to_str());
}
if (is_masterchain()) {
if (mc_state_extra_.is_null()) {
return td::Status::Error(-666, "ShardState of "s + id_.to_str() + " does not contain McStateExtra");
}
block::gen::McStateExtra::Record extra;
if (!tlb::unpack_cell(mc_state_extra_, extra)) {
return td::Status::Error(-666, "cannot unpack McStateExtra in ShardState of "s + id_.to_str());
}
if (!global_balance_.validate_unpack(extra.global_balance)) {
return td::Status::Error(-666, "ShardState of "s + id_.to_str() + " does not contain a valid global_balance");
}
if (extra.r1.flags & 1) {
if (extra.r1.block_create_stats->prefetch_ulong(8) != 0x17) {
return td::Status::Error(-666, "ShardState of "s + id_.to_str() + " does not contain a valid BlockCreateStats");
}
block_create_stats_ = std::make_unique<vm::Dictionary>(extra.r1.block_create_stats->prefetch_ref(), 256);
} else {
block_create_stats_ = std::make_unique<vm::Dictionary>(256);
}
}
return unpack_out_msg_queue_info(std::move(state.out_msg_queue_info));
}
// SETS: out_msg_queue, processed_upto_, ihr_pending
td::Status ShardState::unpack_out_msg_queue_info(Ref<vm::Cell> out_msg_queue_info) {
block::gen::OutMsgQueueInfo::Record qinfo;
if (!tlb::unpack_cell(std::move(out_msg_queue_info), qinfo)) {
return td::Status::Error(-666, "cannot unpack OutMsgQueueInfo in the state of "s + id_.to_str());
}
out_msg_queue_ =
std::make_unique<vm::AugmentedDictionary>(std::move(qinfo.out_queue), 352, block::tlb::aug_OutMsgQueue);
if (verbosity >= 3 * 0) {
LOG(DEBUG) << "unpacking ProcessedUpto of our previous block " << id_.to_str();
block::gen::t_ProcessedInfo.print(std::cerr, qinfo.proc_info);
}
if (!block::gen::t_ProcessedInfo.validate_csr(qinfo.proc_info)) {
return td::Status::Error(
-666, "ProcessedInfo in the state of "s + id_.to_str() + " is invalid according to automated validity checks");
}
if (!block::gen::t_IhrPendingInfo.validate_csr(qinfo.ihr_pending)) {
return td::Status::Error(
-666, "IhrPendingInfo in the state of "s + id_.to_str() + " is invalid according to automated validity checks");
}
processed_upto_ = block::MsgProcessedUptoCollection::unpack(ton::ShardIdFull(id_), std::move(qinfo.proc_info));
ihr_pending_ = std::make_unique<vm::Dictionary>(std::move(qinfo.ihr_pending), 320);
auto shard1 = id_.shard_full();
td::BitArray<64> pfx{(long long)shard1.shard};
int pfx_len = shard_prefix_length(shard1);
if (!ihr_pending_->has_common_prefix(pfx.bits(), pfx_len)) {
return td::Status::Error(-666, "IhrPendingInfo in the state of "s + id_.to_str() + " does not have " +
pfx.bits().to_hex(pfx_len) + " as common key prefix");
}
return td::Status::OK();
}
// UPDATES: prev_state_utime_, prev_state_lt_
bool ShardState::update_prev_utime_lt(ton::UnixTime& prev_utime, ton::LogicalTime& prev_lt) const {
prev_utime = std::max<ton::UnixTime>(prev_utime, utime_);
prev_lt = std::max<ton::LogicalTime>(prev_lt, lt_);
return true;
}
td::Status ShardState::check_before_split(bool req_before_split) const {
CHECK(id_.is_valid());
if (before_split_ != req_before_split) {
return td::Status::Error(PSTRING() << "previous state for " << id_.to_str() << " has before_split=" << before_split_
<< ", but we have after_split=" << req_before_split);
}
return td::Status::OK();
}
td::Status ShardState::check_global_id(int req_global_id) const {
if (global_id_ != req_global_id) {
return td::Status::Error(-666, PSTRING() << "global blockchain id mismatch in shard state of " << id_.to_str()
<< ": expected " << req_global_id << ", found " << global_id_);
}
return td::Status::OK();
}
td::Status ShardState::check_mc_blk_seqno(ton::BlockSeqno last_mc_block_seqno) const {
if (mc_blk_seqno_ > last_mc_block_seqno) {
return td::Status::Error(
-666, PSTRING() << "previous block refers to masterchain block with seqno " << mc_blk_seqno_
<< " larger than the latest known masterchain block seqno " << last_mc_block_seqno);
}
return td::Status::OK();
}
td::Status ShardState::unpack_state_ext(ton::BlockIdExt id, Ref<vm::Cell> state_root, int global_id,
ton::BlockSeqno prev_mc_block_seqno, bool after_split, bool clear_history,
std::function<bool(ton::BlockSeqno)> for_each_mcseqno_func) {
TRY_STATUS(unpack_state(id, std::move(state_root)));
TRY_STATUS(check_global_id(global_id));
TRY_STATUS(check_mc_blk_seqno(prev_mc_block_seqno));
TRY_STATUS(check_before_split(after_split));
clear_load_history_if(clear_history);
if (!for_each_mcseqno(std::move(for_each_mcseqno_func))) {
return td::Status::Error(
-666, "cannot perform necessary actions for each mc_seqno mentioned in ProcessedUpto of "s + id_.to_str());
}
return td::Status::OK();
}
td::Status ShardState::merge_with(ShardState& sib) {
// 1. check that the two states are valid and belong to sibling shards
if (!is_valid() || !sib.is_valid()) {
return td::Status::Error(-666, "cannot merge invalid or uninitialized states");
}
if (!ton::shard_is_sibling(id_.shard_full(), sib.id_.shard_full())) {
return td::Status::Error(-666, "cannot merge non-sibling states of "s + id_.to_str() + " and " + sib.id_.to_str());
}
ton::ShardIdFull shard = ton::shard_parent(id_.shard_full());
// 2. compute total_balance and total_validator_fees
total_balance_ += std::move(sib.total_balance_);
if (!total_balance_.is_valid()) {
return td::Status::Error(-667, "cannot add total_balance_extra of the two states being merged");
}
total_validator_fees_ += std::move(sib.total_validator_fees_);
// 3. merge account_dict with sibling_account_dict
LOG(DEBUG) << "merging account dictionaries";
if (!account_dict_->combine_with(*sib.account_dict_)) {
return td::Status::Error(-666, "cannot merge account dictionaries of the two ancestors");
}
sib.account_dict_.reset();
// 3.1. check that all keys in merged account_dict have correct prefixes
td::BitArray<64> pfx{(long long)shard.shard};
int pfx_len = shard_prefix_length(shard);
if (!account_dict_->has_common_prefix(pfx.bits(), pfx_len)) {
return td::Status::Error(-666, "merged account dictionary of previous states of "s + shard.to_str() +
" does not have " + pfx.bits().to_hex(pfx_len) + " as common key prefix");
}
// 3.2. check total balance of the new account_dict
auto accounts_extra = account_dict_->get_root_extra();
CurrencyCollection old_total_balance;
if (!(accounts_extra.write().advance(5) && old_total_balance.fetch(accounts_extra.write()))) {
return td::Status::Error(-666, "cannot extract total account balance from merged accounts dictionary");
}
if (old_total_balance != total_balance_) {
return td::Status::Error(
-666,
"invalid merged account dictionary: declared total balance differs from one obtained by summing over all "
"Accounts");
}
// 4. merge shard libraries
CHECK(shard_libraries_->is_empty() && sib.shard_libraries_->is_empty());
// 5. merge out_msg_queue
LOG(DEBUG) << "merging outbound message queues";
if (!out_msg_queue_->combine_with(*sib.out_msg_queue_)) {
return td::Status::Error(-666, "cannot merge outbound message queues of the two ancestor states");
}
sib.out_msg_queue_.reset();
// 6. merge processed_upto
LOG(DEBUG) << "merging ProcessedUpto structures";
if (!processed_upto_->combine_with(*sib.processed_upto_)) {
return td::Status::Error(-666, "cannot merge ProcessedUpto structures of the two ancestor states");
}
sib.processed_upto_.reset();
// 7. merge ihr_pending
LOG(DEBUG) << "merging IhrPendingInfo";
if (!ihr_pending_->combine_with(*sib.ihr_pending_)) {
return td::Status::Error(-666, "cannot merge IhrPendingInfo of the two ancestors");
}
sib.ihr_pending_.reset();
// 7.1. check whether all keys of the new ihr_pending have correct prefix
if (!ihr_pending_->has_common_prefix(pfx.bits(), pfx_len)) {
return td::Status::Error(-666, "merged IhrPendingInfo of the two previous states of "s + shard.to_str() +
" does not have " + pfx.bits().to_hex(pfx_len) + " as common key prefix");
}
// 8. compute merged utime_ and lt_
utime_ = std::max(utime_, sib.utime_);
lt_ = std::max(lt_, sib.lt_);
// 9. compute underload & overload history
underload_history_ = overload_history_ = 0;
// Anything else? add here
// ...
// 10. compute new root
if (!block::gen::t_ShardState.cell_pack_split_state(root_, std::move(root_), std::move(sib.root_))) {
return td::Status::Error(-667, "cannot construct a virtual split_state after a merge");
}
// 11. invalidate sibling, change id_ to the (virtual) common parent
sib.invalidate();
id_.id.shard = shard.shard;
id_.file_hash.set_zero();
id_.root_hash.set_zero();
return td::Status::OK();
}
td::Result<std::unique_ptr<vm::AugmentedDictionary>> ShardState::compute_split_out_msg_queue(
ton::ShardIdFull subshard) {
auto shard = id_.shard_full();
if (!ton::shard_is_parent(shard, subshard)) {
return td::Status::Error(-666, "cannot split subshard "s + subshard.to_str() + " from state of " + id_.to_str() +
" because it is not a parent");
}
CHECK(out_msg_queue_);
auto subqueue = std::make_unique<vm::AugmentedDictionary>(*out_msg_queue_);
int res = block::filter_out_msg_queue(*subqueue, shard, subshard);
if (res < 0) {
return td::Status::Error(-666, "error splitting OutMsgQueue of "s + id_.to_str());
}
LOG(DEBUG) << "OutMsgQueue split counter: " << res << " messages";
return std::move(subqueue);
}
td::Result<std::shared_ptr<block::MsgProcessedUptoCollection>> ShardState::compute_split_processed_upto(
ton::ShardIdFull subshard) {
if (!ton::shard_is_parent(id_.shard_full(), subshard)) {
return td::Status::Error(-666, "cannot split subshard "s + subshard.to_str() + " from state of " + id_.to_str() +
" because it is not a parent");
}
CHECK(processed_upto_);
auto sub_processed_upto = std::make_shared<block::MsgProcessedUptoCollection>(*processed_upto_);
if (!sub_processed_upto->split(subshard)) {
return td::Status::Error(-666, "error splitting ProcessedUpto of "s + id_.to_str());
}
return std::move(sub_processed_upto);
}
td::Status ShardState::split(ton::ShardIdFull subshard) {
if (!ton::shard_is_parent(id_.shard_full(), subshard)) {
return td::Status::Error(-666, "cannot split subshard "s + subshard.to_str() + " from state of " + id_.to_str() +
" because it is not a parent");
}
// Have to split:
// 1. account_dict
LOG(DEBUG) << "splitting account dictionary";
td::BitArray<64> pfx{(long long)subshard.shard};
int pfx_len = shard_prefix_length(subshard);
CHECK(account_dict_);
CHECK(account_dict_->cut_prefix_subdict(pfx.bits(), pfx_len));
CHECK(account_dict_->has_common_prefix(pfx.bits(), pfx_len));
// 2. out_msg_queue
LOG(DEBUG) << "splitting OutMsgQueue";
auto shard1 = id_.shard_full();
CHECK(ton::shard_is_parent(shard1, subshard));
CHECK(out_msg_queue_);
int res1 = block::filter_out_msg_queue(*out_msg_queue_, shard1, subshard);
if (res1 < 0) {
return td::Status::Error(-666, "error splitting OutMsgQueue of "s + id_.to_str());
}
LOG(DEBUG) << "split counters: " << res1;
// 3. processed_upto
LOG(DEBUG) << "splitting ProcessedUpto";
CHECK(processed_upto_);
if (!processed_upto_->split(subshard)) {
return td::Status::Error(-666, "error splitting ProcessedUpto of "s + id_.to_str());
}
// 4. ihr_pending
LOG(DEBUG) << "splitting IhrPending";
CHECK(ihr_pending_->cut_prefix_subdict(pfx.bits(), pfx_len));
CHECK(ihr_pending_->has_common_prefix(pfx.bits(), pfx_len));
// 5. adjust total_balance
LOG(DEBUG) << "splitting total_balance";
auto old_total_balance = total_balance_;
auto accounts_extra = account_dict_->get_root_extra();
if (!(accounts_extra.write().advance(5) && total_balance_.validate_unpack(accounts_extra))) {
LOG(ERROR) << "cannot unpack CurrencyCollection from the root of newly-split accounts dictionary";
return td::Status::Error(
-666, "error splitting total balance in account dictionary of shardchain state "s + id_.to_str());
}
LOG(DEBUG) << "split total balance from " << old_total_balance.to_str() << " to our share of "
<< total_balance_.to_str();
// 6. adjust total_fees
LOG(DEBUG) << "split total validator fees (current value is " << total_validator_fees_.to_str() << ")";
total_validator_fees_.grams = (total_validator_fees_.grams + is_right_child(subshard)) >> 1;
LOG(DEBUG) << "new total_validator_fees is " << total_validator_fees_.to_str();
// NB: if total_fees_extra will be allowed to be non-empty, split it here too
// 7. reset overload/underload history
overload_history_ = underload_history_ = 0;
// 999. anything else?
id_.id.shard = subshard.shard;
id_.file_hash.set_zero();
id_.root_hash.set_zero();
return td::Status::OK();
}
int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard) {
return out_queue.filter([subshard, old_shard](vm::CellSlice& cs, td::ConstBitPtr key, int key_len) -> int {
CHECK(key_len == 352);
LOG(DEBUG) << "scanning OutMsgQueue entry with key " << key.to_hex(key_len);
block::tlb::MsgEnvelope::Record_std env;
block::gen::CommonMsgInfo::Record_int_msg_info info;
if (!(cs.size_ext() == 0x10080 // (uint64) enqueued_lt:uint64 out_msg:^MsgEnvelope
&& tlb::unpack_cell(cs.prefetch_ref(), env) && tlb::unpack_cell_inexact(env.msg, info))) {
LOG(ERROR) << "cannot unpack OutMsgQueue entry with key " << key.to_hex(key_len);
return -1;
}
auto src_prefix = block::tlb::t_MsgAddressInt.get_prefix(info.src);
auto dest_prefix = block::tlb::t_MsgAddressInt.get_prefix(info.dest);
auto cur_prefix = block::interpolate_addr(src_prefix, dest_prefix, env.cur_addr);
if (!(src_prefix.is_valid() && dest_prefix.is_valid() && cur_prefix.is_valid())) {
LOG(ERROR) << "OutMsgQueue message with key " << key.to_hex(key_len)
<< " has invalid source or destination address";
return -1;
}
if (!ton::shard_contains(old_shard, cur_prefix)) {
LOG(ERROR) << "OutMsgQueue message with key " << key.to_hex(key_len)
<< " does not contain current address belonging to shard " << old_shard.to_str();
return -1;
}
return ton::shard_contains(subshard, cur_prefix);
});
}
bool CurrencyCollection::validate() const {
return is_valid() && td::sgn(grams) >= 0 && validate_extra();
}
bool CurrencyCollection::validate_extra() const {
if (extra.is_null()) {
return true;
}
vm::CellBuilder cb;
return cb.store_maybe_ref(extra) && block::tlb::t_ExtraCurrencyCollection.validate_ref(cb.finalize());
}
bool CurrencyCollection::add(const CurrencyCollection& a, const CurrencyCollection& b, CurrencyCollection& c) {
return (a.is_valid() && b.is_valid() && (c.grams = a.grams + b.grams).not_null() && c.grams->is_valid() &&
add_extra_currency(a.extra, b.extra, c.extra)) ||
c.invalidate();
}
bool CurrencyCollection::add(const CurrencyCollection& a, CurrencyCollection&& b, CurrencyCollection& c) {
return (a.is_valid() && b.is_valid() && (c.grams = a.grams + std::move(b.grams)).not_null() && c.grams->is_valid() &&
add_extra_currency(a.extra, std::move(b.extra), c.extra)) ||
c.invalidate();
}
CurrencyCollection& CurrencyCollection::operator+=(const CurrencyCollection& other) {
if (!is_valid()) {
return *this;
}
if (!(other.is_valid() && (grams += other.grams).not_null() && grams->is_valid() &&
add_extra_currency(extra, other.extra, extra))) {
invalidate();
}
return *this;
}
CurrencyCollection& CurrencyCollection::operator+=(CurrencyCollection&& other) {
if (!is_valid()) {
return *this;
}
if (!(other.is_valid() && (grams += std::move(other.grams)).not_null() && grams->is_valid() &&
add_extra_currency(extra, std::move(other.extra), extra))) {
invalidate();
}
return *this;
}
CurrencyCollection& CurrencyCollection::operator+=(td::RefInt256 other_grams) {
if (!is_valid()) {
return *this;
}
if (!(other_grams.not_null() && (grams += other_grams).not_null())) {
invalidate();
}
return *this;
}
CurrencyCollection CurrencyCollection::operator+(const CurrencyCollection& other) const {
CurrencyCollection res;
add(*this, other, res);
return res;
}
CurrencyCollection CurrencyCollection::operator+(CurrencyCollection&& other) const {
CurrencyCollection res;
add(*this, std::move(other), res);
return res;
}
CurrencyCollection CurrencyCollection::operator+(td::RefInt256 other_grams) {
if (!is_valid()) {
return *this;
}
auto sum = grams + other_grams;
if (sum.not_null()) {
return CurrencyCollection{std::move(sum), extra};
} else {
return CurrencyCollection{};
}
}
bool CurrencyCollection::sub(const CurrencyCollection& a, const CurrencyCollection& b, CurrencyCollection& c) {
return (a.is_valid() && b.is_valid() && (c.grams = a.grams - b.grams).not_null() && c.grams->is_valid() &&
td::sgn(c.grams) >= 0 && sub_extra_currency(a.extra, b.extra, c.extra)) ||
c.invalidate();
}
bool CurrencyCollection::sub(const CurrencyCollection& a, CurrencyCollection&& b, CurrencyCollection& c) {
return (a.is_valid() && b.is_valid() && (c.grams = a.grams - std::move(b.grams)).not_null() && c.grams->is_valid() &&
td::sgn(c.grams) >= 0 && sub_extra_currency(a.extra, std::move(b.extra), c.extra)) ||
c.invalidate();
}
CurrencyCollection& CurrencyCollection::operator-=(const CurrencyCollection& other) {
if (!is_valid()) {
return *this;
}
if (!(other.is_valid() && (grams -= other.grams).not_null() && grams->is_valid() && td::sgn(grams) >= 0 &&
sub_extra_currency(extra, other.extra, extra))) {
invalidate();
}
return *this;
}
CurrencyCollection& CurrencyCollection::operator-=(CurrencyCollection&& other) {
if (!is_valid()) {
return *this;
}
if (!(other.is_valid() && (grams -= std::move(other.grams)).not_null() && grams->is_valid() && td::sgn(grams) >= 0 &&
sub_extra_currency(extra, std::move(other.extra), extra))) {
invalidate();
}
return *this;
}
CurrencyCollection& CurrencyCollection::operator-=(td::RefInt256 other_grams) {
if (!is_valid()) {
return *this;
}
if (!(other_grams.not_null() && (grams -= other_grams).not_null() && td::sgn(grams) >= 0)) {
invalidate();
}
return *this;
}
CurrencyCollection CurrencyCollection::operator-(const CurrencyCollection& other) const {
CurrencyCollection res;
sub(*this, other, res);
return res;
}
CurrencyCollection CurrencyCollection::operator-(CurrencyCollection&& other) const {
CurrencyCollection res;
sub(*this, std::move(other), res);
return res;
}
CurrencyCollection CurrencyCollection::operator-(td::RefInt256 other_grams) const {
if (!(is_valid() && other_grams.not_null())) {
return {};
}
auto x = grams - other_grams;
if (td::sgn(x) >= 0) {
return CurrencyCollection{std::move(x), extra};
} else {
return {};
}
}
bool CurrencyCollection::operator==(const CurrencyCollection& other) const {
return is_valid() && other.is_valid() && !td::cmp(grams, other.grams) &&
(extra.not_null() == other.extra.not_null()) &&
(extra.is_null() || extra->get_hash() == other.extra->get_hash());
}
bool CurrencyCollection::operator>=(const CurrencyCollection& other) const {
Ref<vm::Cell> tmp;
return is_valid() && other.is_valid() && td::cmp(grams, other.grams) >= 0 &&
sub_extra_currency(extra, other.extra, tmp);
}
bool CurrencyCollection::store(vm::CellBuilder& cb) const {
return is_valid() && store_CurrencyCollection(cb, grams, extra);
}
bool CurrencyCollection::store_or_zero(vm::CellBuilder& cb) const {
return is_valid() ? store(cb) : cb.store_long_bool(0, 5);
}
bool CurrencyCollection::fetch(vm::CellSlice& cs) {
return block::tlb::t_CurrencyCollection.unpack_special(cs, *this, true) || invalidate();
}
bool CurrencyCollection::fetch_exact(vm::CellSlice& cs) {
return block::tlb::t_CurrencyCollection.unpack_special(cs, *this, false) || invalidate();
}
bool CurrencyCollection::unpack(Ref<vm::CellSlice> csr) {
return unpack_CurrencyCollection(std::move(csr), grams, extra) || invalidate();
}
bool CurrencyCollection::validate_unpack(Ref<vm::CellSlice> csr) {
return (csr.not_null() && block::tlb::t_CurrencyCollection.validate(*csr) &&
unpack_CurrencyCollection(std::move(csr), grams, extra)) ||
invalidate();
}
Ref<vm::CellSlice> CurrencyCollection::pack() const {
vm::CellBuilder cb;
if (store(cb)) {
return vm::load_cell_slice_ref(cb.finalize());
} else {
return {};
}
}
bool CurrencyCollection::show(std::ostream& os) const {
if (!is_valid()) {
os << "<invalid-cc>";
return false;
}
if (extra.not_null()) {
os << '(';
}
os << grams << "ng";
if (extra.not_null()) {
vm::Dictionary dict{extra, 32};
if (!dict.check_for_each([&os](Ref<vm::CellSlice> csr, td::ConstBitPtr key, int n) {
CHECK(n == 32);
int x = (int)key.get_int(n);
auto val = block::tlb::t_VarUIntegerPos_32.as_integer_skip(csr.write());
if (val.is_null() || !csr->empty_ext()) {
os << "+<invalid>.$" << x << "...)";
return false;
}
os << '+' << val << ".$" << x;
return true;
})) {
return false;
}
os << ')';
}
return true;
}
std::string CurrencyCollection::to_str() const {
std::ostringstream os;
show(os);
return os.str();
}
std::ostream& operator<<(std::ostream& os, const CurrencyCollection& cc) {
cc.show(os);
return os;
}
bool ValueFlow::set_zero() {
return from_prev_blk.set_zero() && to_next_blk.set_zero() && imported.set_zero() && exported.set_zero() &&
fees_collected.set_zero() && fees_imported.set_zero() && recovered.set_zero() && created.set_zero() &&
minted.set_zero();
}
bool ValueFlow::validate() const {
return is_valid() && from_prev_blk + imported + fees_imported + created + minted + recovered ==
to_next_blk + exported + fees_collected;
}
bool ValueFlow::store(vm::CellBuilder& cb) const {
vm::CellBuilder cb2;
return cb.store_long_bool(block::gen::ValueFlow::cons_tag[0], 32) // value_flow ^[
&& from_prev_blk.store(cb2) // from_prev_blk:CurrencyCollection
&& to_next_blk.store(cb2) // to_next_blk:CurrencyCollection
&& imported.store(cb2) // imported:CurrencyCollection
&& exported.store(cb2) // exported:CurrencyCollection
&& cb.store_ref_bool(cb2.finalize()) // ]
&& fees_collected.store(cb) // fees_collected:CurrencyCollection
&& fees_imported.store(cb2) // ^[ fees_imported:CurrencyCollection
&& recovered.store(cb2) // recovered:CurrencyCollection
&& created.store(cb2) // created:CurrencyCollection
&& minted.store(cb2) // minted:CurrencyCollection
&& cb.store_ref_bool(cb2.finalize()); // ] = ValueFlow;
}
bool ValueFlow::fetch(vm::CellSlice& cs) {
block::gen::ValueFlow::Record f;
if (!(tlb::unpack(cs, f) && from_prev_blk.validate_unpack(std::move(f.r1.from_prev_blk)) &&
to_next_blk.validate_unpack(std::move(f.r1.to_next_blk)) &&
imported.validate_unpack(std::move(f.r1.imported)) && exported.validate_unpack(std::move(f.r1.exported)) &&
fees_collected.validate_unpack(std::move(f.fees_collected)) &&
fees_imported.validate_unpack(std::move(f.r2.fees_imported)) &&
recovered.validate_unpack(std::move(f.r2.recovered)) && created.validate_unpack(std::move(f.r2.created)) &&
minted.validate_unpack(std::move(f.r2.minted)))) {
return invalidate();
}
return true;
}
bool ValueFlow::unpack(Ref<vm::CellSlice> csr) {
return (csr.not_null() && fetch(csr.write()) && csr->empty_ext()) || invalidate();
}
static inline bool say(std::ostream& os, const char* str) {
os << str;
return true;
}
bool ValueFlow::show_one(std::ostream& os, const char* str, const CurrencyCollection& cc) const {
return say(os, str) && cc.show(os);
}
bool ValueFlow::show(std::ostream& os) const {
if (!is_valid()) {
os << "<invalid-value-flow>";
return false;
}
return (say(os, "(value-flow ") && show_one(os, "from_prev_blk:", from_prev_blk) &&
show_one(os, " to_next_blk:", to_next_blk) && show_one(os, " imported:", imported) &&
show_one(os, " exported:", exported) && show_one(os, " fees_collected:", fees_collected) &&
show_one(os, " fees_imported:", fees_imported) && show_one(os, " recovered:", recovered) &&
show_one(os, " created:", created) && show_one(os, " minted:", minted) && say(os, ")")) ||
(say(os, "...<invalid-value-flow>)") && false);
}
std::string ValueFlow::to_str() const {
std::ostringstream os;
show(os);
return os.str();
}
std::ostream& operator<<(std::ostream& os, const ValueFlow& vflow) {
vflow.show(os);
return os;
}
bool DiscountedCounter::increase_by(unsigned count, ton::UnixTime now) {
if (!validate()) {
return false;
}
td::uint64 scaled = (td::uint64(count) << 32);
if (!total) {
last_updated = now;
total = count;
cnt2048 = scaled;
cnt65536 = scaled;
return true;
}
if (count > ~total || cnt2048 > ~scaled || cnt65536 > ~scaled) {
return false /* invalidate() */; // overflow
}
unsigned dt = (now >= last_updated ? now - last_updated : 0);
if (dt > 0) {
// more precise version of cnt2048 = llround(cnt2048 * exp(-dt / 2048.));
// (rounding error has absolute value < 1)
cnt2048 = (dt >= 48 * 2048 ? 0 : td::umulnexps32(cnt2048, dt << 5));
// more precise version of cnt65536 = llround(cnt65536 * exp(-dt / 65536.));
// (rounding error has absolute value < 1)
cnt65536 = td::umulnexps32(cnt65536, dt);
}
total += count;
cnt2048 += scaled;
cnt65536 += scaled;
last_updated = now;
return true;
}
bool DiscountedCounter::validate() {
if (!is_valid()) {
return false;
}
if (!total) {
if (cnt2048 | cnt65536) {
return invalidate();
}
} else if (!last_updated) {
return invalidate();
}
return true;
}
bool DiscountedCounter::fetch(vm::CellSlice& cs) {
valid = (cs.fetch_uint_to(32, last_updated) && cs.fetch_uint_to(64, total) && cs.fetch_uint_to(64, cnt2048) &&
cs.fetch_uint_to(64, cnt65536));
return validate() || invalidate();
}
bool DiscountedCounter::unpack(Ref<vm::CellSlice> csr) {
return (csr.not_null() && fetch(csr.write()) && csr->empty_ext()) || invalidate();
}
bool DiscountedCounter::store(vm::CellBuilder& cb) const {
return is_valid() && cb.store_long_bool(last_updated, 32) && cb.store_long_bool(total, 64) &&
cb.store_long_bool(cnt2048, 64) && cb.store_long_bool(cnt65536, 64);
}
Ref<vm::CellSlice> DiscountedCounter::pack() const {
vm::CellBuilder cb;
if (store(cb)) {
return vm::load_cell_slice_ref(cb.finalize());
} else {
return {};
}
}
bool DiscountedCounter::show(std::ostream& os) const {
if (!is_valid()) {
os << "<invalid-counter>";
return false;
}
os << "(counter last_updated:" << last_updated << " total:" << total << " cnt2048: " << (double)cnt2048 / (1LL << 32)
<< " cnt65536: " << (double)cnt65536 / (1LL << 32) << ")";
return true;
}
std::string DiscountedCounter::to_str() const {
std::ostringstream stream;
if (show(stream)) {
return stream.str();
} else {
return "<invalid-counter>";
}
}
bool fetch_CreatorStats(vm::CellSlice& cs, DiscountedCounter& mc_cnt, DiscountedCounter& shard_cnt) {
return cs.fetch_ulong(4) == 4 // creator_info#4
&& mc_cnt.fetch(cs) // mc_blocks:Counters
&& shard_cnt.fetch(cs); // shard_blocks:Counters
}
bool store_CreatorStats(vm::CellBuilder& cb, const DiscountedCounter& mc_cnt, const DiscountedCounter& shard_cnt) {
return cb.store_long_bool(4, 4) // creator_info#4
&& mc_cnt.store(cb) // mc_blocks:Counters
&& shard_cnt.store(cb); // shard_blocks:Counters
}
bool unpack_CreatorStats(Ref<vm::CellSlice> cs, DiscountedCounter& mc_cnt, DiscountedCounter& shard_cnt) {
if (cs.is_null()) {
return mc_cnt.set_zero() && shard_cnt.set_zero();
} else {
return fetch_CreatorStats(cs.write(), mc_cnt, shard_cnt) && cs->empty_ext();
}
}
/*
*
* Other block-related functions
*
*/
bool store_UInt7(vm::CellBuilder& cb, unsigned long long value) {
return block::tlb::t_VarUInteger_7.store_long(cb, (long long)value);
}
bool store_UInt7(vm::CellBuilder& cb, unsigned long long value1, unsigned long long value2) {
return store_UInt7(cb, value1) && store_UInt7(cb, value2);
}
bool store_Maybe_Grams(vm::CellBuilder& cb, td::RefInt256 value) {
if (value.is_null()) {
return cb.store_long_bool(0, 1);
} else {
return cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, std::move(value));
}
}
bool store_Maybe_Grams_nz(vm::CellBuilder& cb, td::RefInt256 value) {
if (value.is_null() || !value->sgn()) {
return cb.store_long_bool(0, 1);
} else {
return cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, std::move(value));
}
}
bool store_CurrencyCollection(vm::CellBuilder& cb, td::RefInt256 value, Ref<vm::Cell> extra) {
return block::tlb::t_CurrencyCollection.pack_special(cb, std::move(value), std::move(extra));
}
bool fetch_CurrencyCollection(vm::CellSlice& cs, td::RefInt256& value, Ref<vm::Cell>& extra, bool inexact) {
return block::tlb::t_CurrencyCollection.unpack_special(cs, value, extra, inexact);
}
bool unpack_CurrencyCollection(Ref<vm::CellSlice> csr, td::RefInt256& value, Ref<vm::Cell>& extra) {
if (csr.is_null()) {
return false;
} else if (csr->is_unique()) {
return block::tlb::t_CurrencyCollection.unpack_special(csr.write(), value, extra);
} else {
vm::CellSlice cs{*csr};
return block::tlb::t_CurrencyCollection.unpack_special(cs, value, extra);
}
}
bool check_one_library(Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, int n) {
assert(n == 256);
if (cs_ref->size_ext() != 0x10001) {
return false;
}
Ref<vm::Cell> cell = cs_ref->prefetch_ref();
const auto& cell_hash = cell->get_hash();
return !td::bitstring::bits_memcmp(cell_hash.bits(), key, n);
}
bool valid_library_collection(Ref<vm::Cell> cell, bool catch_errors) {
if (cell.is_null()) {
return true;
}
if (!catch_errors) {
vm::Dictionary dict{std::move(cell), 256};
return dict.check_for_each(check_one_library);
}
try {
vm::Dictionary dict{std::move(cell), 256};
return dict.check_for_each(check_one_library);
} catch (vm::VmError&) {
return false;
}
}
bool check_one_config_param(Ref<vm::CellSlice> cs_ref, td::ConstBitPtr key, td::ConstBitPtr addr, bool relax_par0) {
if (cs_ref->size_ext() != 0x10000) {
return false;
}
Ref<vm::Cell> cell = cs_ref->prefetch_ref();
int idx = (int)key.get_int(32);
if (!idx) {
auto cs = load_cell_slice(std::move(cell));
return cs.size_ext() == 256 && (relax_par0 || cs.fetch_bits(256) == addr);
} else if (idx < 0) {
return true;
}
bool ok = block::gen::ConfigParam{idx}.validate_ref(std::move(cell));
if (!ok) {
LOG(ERROR) << "configuration parameter #" << idx << " is invalid";
}
return ok;
}
const int mandatory_config_params[] = {18, 20, 21, 22, 23, 24, 25, 28, 34};
bool valid_config_data(Ref<vm::Cell> cell, const td::BitArray<256>& addr, bool catch_errors, bool relax_par0) {
using namespace std::placeholders;
if (cell.is_null()) {
return false;
}
if (!catch_errors) {
vm::Dictionary dict{std::move(cell), 32};
for (int x : mandatory_config_params) {
if (!dict.int_key_exists(x)) {
LOG(ERROR) << "mandatory configuration parameter #" << x << " is missing";
return false;
}
}
return dict.check_for_each(std::bind(check_one_config_param, _1, _2, addr.cbits(), relax_par0));
}
try {
vm::Dictionary dict{std::move(cell), 32};
for (int x : mandatory_config_params) {
if (!dict.int_key_exists(x)) {
LOG(ERROR) << "mandatory configuration parameter #" << x << " is missing";
return false;
}
}
return dict.check_for_each(std::bind(check_one_config_param, _1, _2, addr.cbits(), relax_par0));
} catch (vm::VmError&) {
return false;
}
}
bool add_extra_currency(Ref<vm::Cell> extra1, Ref<vm::Cell> extra2, Ref<vm::Cell>& res) {
if (extra2.is_null()) {
res = extra1;
return true;
} else if (extra1.is_null()) {
res = extra2;
return true;
} else {
return block::tlb::t_ExtraCurrencyCollection.add_values_ref(res, std::move(extra1), std::move(extra2));
}
}
bool sub_extra_currency(Ref<vm::Cell> extra1, Ref<vm::Cell> extra2, Ref<vm::Cell>& res) {
if (extra2.is_null()) {
res = extra1;
return true;
} else if (extra1.is_null()) {
res.clear();
return false;
} else {
return block::tlb::t_ExtraCurrencyCollection.sub_values_ref(res, std::move(extra1), std::move(extra2));
}
}
// combine d bits from dest, remaining 64 - d bits from src
ton::AccountIdPrefixFull interpolate_addr(const ton::AccountIdPrefixFull& src, const ton::AccountIdPrefixFull& dest,
int d) {
if (d <= 0) {
return src;
} else if (d >= 96) {
return dest;
} else if (d >= 32) {
unsigned long long mask = (std::numeric_limits<td::uint64>::max() >> (d - 32));
return ton::AccountIdPrefixFull{dest.workchain, (dest.account_id_prefix & ~mask) | (src.account_id_prefix & mask)};
} else {
int mask = (-1 >> d);
return ton::AccountIdPrefixFull{(dest.workchain & ~mask) | (src.workchain & mask), src.account_id_prefix};
}
}
bool interpolate_addr_to(const ton::AccountIdPrefixFull& src, const ton::AccountIdPrefixFull& dest, int d,
ton::AccountIdPrefixFull& res) {
res = interpolate_addr(src, dest, d);
return true;
}
// result: (transit_addr_dest_bits, nh_addr_dest_bits)
std::pair<int, int> perform_hypercube_routing(ton::AccountIdPrefixFull src, ton::AccountIdPrefixFull dest,
ton::ShardIdFull cur, int used_dest_bits) {
ton::AccountIdPrefixFull transit = interpolate_addr(src, dest, used_dest_bits);
if (!ton::shard_contains(cur, transit)) {
return {-1, -1};
}
if (ton::shard_contains(cur, dest)) {
// if destination is in this shard, set cur:=next_hop:=dest
return {96, 96};
}
if (transit.workchain == ton::masterchainId || dest.workchain == ton::masterchainId) {
return {used_dest_bits, 96}; // route messages to/from masterchain directly
}
if (transit.workchain != dest.workchain) {
return {used_dest_bits, 32};
}
unsigned long long x = cur.shard & (cur.shard - 1), y = cur.shard | (cur.shard - 1);
unsigned long long t = transit.account_id_prefix, q = dest.account_id_prefix ^ t;
int i = (td::count_leading_zeroes64(q) & -4); // top i bits match, next 4 bits differ
unsigned long long m = (std::numeric_limits<td::uint64>::max() >> i), h;
do {
m >>= 4;
h = t ^ (q & ~m);
i += 4;
} while (h >= x && h <= y);
return {28 + i, 32 + i};
}
bool compute_out_msg_queue_key(Ref<vm::Cell> msg_env, td::BitArray<352>& key) {
block::tlb::MsgEnvelope::Record_std env;
block::gen::CommonMsgInfo::Record_int_msg_info info;
if (!(tlb::unpack_cell(msg_env, env) && tlb::unpack_cell_inexact(env.msg, info))) {
return false;
}
auto src_prefix = block::tlb::t_MsgAddressInt.get_prefix(std::move(info.src));
auto dest_prefix = block::tlb::t_MsgAddressInt.get_prefix(std::move(info.dest));
auto next_hop = interpolate_addr(src_prefix, dest_prefix, env.next_addr);
key.bits().store_int(next_hop.workchain, 32);
(key.bits() + 32).store_int(next_hop.account_id_prefix, 64);
(key.bits() + 96).copy_from(env.msg->get_hash().bits(), 256);
return true;
}
bool unpack_block_prev_blk(Ref<vm::Cell> block_root, const ton::BlockIdExt& id, std::vector<ton::BlockIdExt>& prev,
ton::BlockIdExt& mc_blkid, bool& after_split, ton::BlockIdExt* fetch_blkid) {
return unpack_block_prev_blk_ext(std::move(block_root), id, prev, mc_blkid, after_split, fetch_blkid).is_ok();
}
td::Status unpack_block_prev_blk_try(Ref<vm::Cell> block_root, const ton::BlockIdExt& id,
std::vector<ton::BlockIdExt>& prev, ton::BlockIdExt& mc_blkid, bool& after_split,
ton::BlockIdExt* fetch_blkid) {
try {
return unpack_block_prev_blk_ext(std::move(block_root), id, prev, mc_blkid, after_split, fetch_blkid);
} catch (vm::VmError err) {
return td::Status::Error(std::string{"error while processing Merkle proof: "} + err.get_msg());
} catch (vm::VmVirtError err) {
return td::Status::Error(std::string{"error while processing Merkle proof: "} + err.get_msg());
}
}
td::Status unpack_block_prev_blk_ext(Ref<vm::Cell> block_root, const ton::BlockIdExt& id,
std::vector<ton::BlockIdExt>& prev, ton::BlockIdExt& mc_blkid, bool& after_split,
ton::BlockIdExt* fetch_blkid) {
block::gen::Block::Record blk;
block::gen::BlockInfo::Record info;
block::gen::ExtBlkRef::Record mcref; // _ ExtBlkRef = BlkMasterInfo;
ton::ShardIdFull shard;
if (!(tlb::unpack_cell(block_root, blk) && tlb::unpack_cell(blk.info, info) && !info.version &&
block::tlb::t_ShardIdent.unpack(info.shard.write(), shard) && !info.vert_seq_no &&
(!info.not_master || tlb::unpack_cell(info.master_ref, mcref)))) {
return td::Status::Error("cannot unpack block header");
}
if (fetch_blkid) {
fetch_blkid->id = ton::BlockId{shard, (unsigned)info.seq_no};
fetch_blkid->root_hash = block_root->get_hash().bits();
fetch_blkid->file_hash.clear();
} else {
ton::BlockId hdr_id{shard, (unsigned)info.seq_no};
if (id.id != hdr_id) {
return td::Status::Error("block header contains block id "s + hdr_id.to_str() + ", expected " + id.id.to_str());
}
if (id.root_hash != block_root->get_hash().bits()) {
return td::Status::Error("block header has incorrect root hash "s + block_root->get_hash().bits().to_hex(256) +
" instead of expected " + id.root_hash.to_hex());
}
}
if (info.not_master != !shard.is_masterchain()) {
return td::Status::Error("block has invalid not_master flag in its (Merkelized) header");
}
after_split = info.after_split;
block::gen::ExtBlkRef::Record prev1, prev2;
if (info.after_merge) {
auto cs = vm::load_cell_slice(std::move(info.prev_ref));
CHECK(cs.size_ext() == 0x20000); // prev_blks_info$_ prev1:^ExtBlkRef prev2:^ExtBlkRef = BlkPrevInfo 1;
if (!(tlb::unpack_cell(cs.prefetch_ref(0), prev1) && tlb::unpack_cell(cs.prefetch_ref(1), prev2))) {
return td::Status::Error("cannot unpack two previous block references from block header");
}
} else {
// prev_blk_info$_ prev:ExtBlkRef = BlkPrevInfo 0;
if (!(tlb::unpack_cell(std::move(info.prev_ref), prev1))) {
return td::Status::Error("cannot unpack previous block reference from block header");
}
}
prev.clear();
ton::BlockSeqno prev_seqno = prev1.seq_no;
if (!info.after_merge) {
prev.emplace_back(shard.workchain, info.after_split ? ton::shard_parent(shard.shard) : shard.shard, prev1.seq_no,
prev1.root_hash, prev1.file_hash);
if (info.after_split && !prev1.seq_no) {
return td::Status::Error("shardchains cannot be split immediately after initial state");
}
} else {
if (info.after_split) {
return td::Status::Error("shardchains cannot be simultaneously split and merged at the same block");
}
prev.emplace_back(shard.workchain, ton::shard_child(shard.shard, true), prev1.seq_no, prev1.root_hash,
prev1.file_hash);
prev.emplace_back(shard.workchain, ton::shard_child(shard.shard, false), prev2.seq_no, prev2.root_hash,
prev2.file_hash);
prev_seqno = std::max<unsigned>(prev1.seq_no, prev2.seq_no);
if (!prev1.seq_no || !prev2.seq_no) {
return td::Status::Error("shardchains cannot be merged immediately after initial state");
}
}
if (id.id.seqno != prev_seqno + 1) {
return td::Status::Error("new block has invalid seqno (not equal to one plus maximum of seqnos of its ancestors)");
}
if (shard.is_masterchain()) {
mc_blkid = prev.at(0);
} else {
mc_blkid = ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, mcref.seq_no, mcref.root_hash, mcref.file_hash};
}
return td::Status::OK();
}
td::Status check_block_header(Ref<vm::Cell> block_root, const ton::BlockIdExt& id, ton::Bits256* store_shard_hash_to) {
block::gen::Block::Record blk;
block::gen::BlockInfo::Record info;
ton::ShardIdFull shard;
if (!(tlb::unpack_cell(block_root, blk) && tlb::unpack_cell(blk.info, info) && !info.version &&
block::tlb::t_ShardIdent.unpack(info.shard.write(), shard) && !info.vert_seq_no)) {
return td::Status::Error("cannot unpack block header");
}
ton::BlockId hdr_id{shard, (unsigned)info.seq_no};
if (id.id != hdr_id) {
return td::Status::Error("block header contains block id "s + hdr_id.to_str() + ", expected " + id.id.to_str());
}
if (id.root_hash != block_root->get_hash().bits()) {
return td::Status::Error("block header has incorrect root hash "s + block_root->get_hash().bits().to_hex(256) +
" instead of expected " + id.root_hash.to_hex());
}
if (info.not_master != !shard.is_masterchain()) {
return td::Status::Error("block has invalid not_master flag in its (Merkelized) header");
}
if (store_shard_hash_to) {
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 td::Status::Error("invalid Merkle update in block header");
}
auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0);
*store_shard_hash_to = upd_hash.bits();
}
return td::Status::OK();
}
std::unique_ptr<vm::AugmentedDictionary> get_prev_blocks_dict(Ref<vm::Cell> state_root) {
block::gen::ShardStateUnsplit::Record info;
block::gen::McStateExtra::Record extra_info;
if (!(::tlb::unpack_cell(std::move(state_root), info) && info.custom->size_refs() &&
::tlb::unpack_cell(info.custom->prefetch_ref(), extra_info))) {
return {};
}
return std::make_unique<vm::AugmentedDictionary>(extra_info.r1.prev_blocks, 32, block::tlb::aug_OldMcBlocksInfo);
}
bool get_old_mc_block_id(vm::AugmentedDictionary* prev_blocks_dict, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt) {
return prev_blocks_dict && get_old_mc_block_id(*prev_blocks_dict, seqno, blkid, end_lt);
}
bool get_old_mc_block_id(vm::AugmentedDictionary& prev_blocks_dict, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt) {
return unpack_old_mc_block_id(prev_blocks_dict.lookup(td::BitArray<32>{seqno}), seqno, blkid, end_lt);
}
bool unpack_old_mc_block_id(Ref<vm::CellSlice> old_blk_info, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt) {
return old_blk_info.not_null() && old_blk_info.write().advance(1) &&
block::tlb::t_ExtBlkRef.unpack(std::move(old_blk_info), blkid, end_lt) && blkid.seqno() == seqno;
}
bool check_old_mc_block_id(vm::AugmentedDictionary* prev_blocks_dict, const ton::BlockIdExt& blkid) {
return prev_blocks_dict && check_old_mc_block_id(*prev_blocks_dict, blkid);
}
bool check_old_mc_block_id(vm::AugmentedDictionary& prev_blocks_dict, const ton::BlockIdExt& blkid) {
if (!blkid.id.is_masterchain_ext()) {
return false;
}
ton::BlockIdExt old_blkid;
return unpack_old_mc_block_id(prev_blocks_dict.lookup(td::BitArray<32>{blkid.id.seqno}), blkid.id.seqno, old_blkid) &&
old_blkid == blkid;
}
td::Result<Ref<vm::Cell>> get_block_transaction(Ref<vm::Cell> block_root, ton::WorkchainId workchain,
const ton::StdSmcAddress& addr, ton::LogicalTime lt) {
block::gen::Block::Record block;
block::gen::BlockInfo::Record info;
if (!(tlb::unpack_cell(std::move(block_root), block) && tlb::unpack_cell(std::move(block.info), info))) {
return td::Status::Error("cannot unpack block header");
}
Ref<vm::Cell> trans_root;
if (lt > info.start_lt && lt < info.end_lt) {
// lt belongs to this block
block::gen::BlockExtra::Record extra;
if (!(tlb::unpack_cell(block.extra, extra))) {
return td::Status::Error("cannot unpack block extra information");
}
vm::AugmentedDictionary account_blocks_dict{vm::load_cell_slice_ref(extra.account_blocks), 256,
block::tlb::aug_ShardAccountBlocks};
auto ab_csr = account_blocks_dict.lookup(addr);
if (ab_csr.not_null()) {
// account block for this account exists
block::gen::AccountBlock::Record acc_block;
if (!(tlb::csr_unpack(std::move(ab_csr), acc_block) && acc_block.account_addr == addr)) {
return td::Status::Error("cannot unpack AccountBlock");
}
vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), acc_block.transactions, 64,
block::tlb::aug_AccountTransactions};
return trans_dict.lookup_ref(td::BitArray<64>{static_cast<long long>(lt)});
}
}
return Ref<vm::Cell>{};
}
td::Result<Ref<vm::Cell>> get_block_transaction_try(Ref<vm::Cell> block_root, ton::WorkchainId workchain,
const ton::StdSmcAddress& addr, ton::LogicalTime lt) {
try {
return get_block_transaction(std::move(block_root), workchain, addr, lt);
} catch (vm::VmError err) {
return td::Status::Error(std::string{"error while extracting transaction from block : "} + err.get_msg());
} catch (vm::VmVirtError err) {
return td::Status::Error(std::string{"virtualization error while traversing transaction proof : "} + err.get_msg());
}
}
bool get_transaction_in_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell>& in_msg) {
block::gen::Transaction::Record trans;
if (!tlb::unpack_cell(std::move(trans_ref), trans)) {
return false;
} else {
in_msg = trans.r1.in_msg->prefetch_ref();
return true;
}
}
bool is_transaction_in_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell> msg) {
Ref<vm::Cell> imsg;
return get_transaction_in_msg(std::move(trans_ref), imsg) && imsg.not_null() == msg.not_null() &&
(imsg.is_null() || imsg->get_hash() == msg->get_hash());
}
bool is_transaction_out_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell> msg) {
block::gen::Transaction::Record trans;
vm::CellSlice cs;
unsigned long long created_lt;
if (!(trans_ref.not_null() && msg.not_null() && tlb::unpack_cell(std::move(trans_ref), trans) && cs.load_ord(msg) &&
block::tlb::t_CommonMsgInfo.get_created_lt(cs, created_lt))) {
return false;
}
if (created_lt <= trans.lt || created_lt > trans.lt + trans.outmsg_cnt) {
return false;
}
try {
auto o_msg =
vm::Dictionary{trans.r1.out_msgs, 15}.lookup_ref(td::BitArray<15>{(long long)(created_lt - trans.lt - 1)});
return o_msg.not_null() && o_msg->get_hash() == msg->get_hash();
} catch (vm::VmError&) {
return false;
}
}
// transaction$0111 account_addr:bits256 lt:uint64 ...
bool get_transaction_id(Ref<vm::Cell> trans_ref, ton::StdSmcAddress& account_addr, ton::LogicalTime& lt) {
if (trans_ref.is_null()) {
return false;
}
vm::CellSlice cs{vm::NoVmOrd(), trans_ref};
return cs.fetch_ulong(4) == 7 // transaction$0111
&& cs.fetch_bits_to(account_addr) // account_addr:bits256
&& cs.fetch_uint_to(64, lt); // lt:uint64
}
bool get_transaction_owner(Ref<vm::Cell> trans_ref, ton::StdSmcAddress& addr) {
ton::LogicalTime lt;
return get_transaction_id(std::move(trans_ref), addr, lt);
}
td::uint32 compute_validator_set_hash(ton::CatchainSeqno cc_seqno, ton::ShardIdFull from,
const std::vector<ton::ValidatorDescr>& nodes) {
/*
std::vector<tl_object_ptr<ton_api::test0_validatorSetItem>> s_vec;
for (auto& n : nodes) {
auto id = ValidatorFullId{n.key}.short_id();
s_vec.emplace_back(create_tl_object<ton_api::test0_validatorSetItem>(id, n.weight));
}
auto obj = create_tl_object<ton_api::test0_validatorSet>(cc_seqno, std::move(s_vec));
auto B = serialize_tl_object(obj, true);
return td::crc32c(B.as_slice());
*/
CHECK(nodes.size() <= 0xffffffff);
auto tot_size = 1 + 1 + 1 + nodes.size() * (8 + 2 + 8);
auto buff = std::make_unique<td::uint32[]>(tot_size);
td::TlStorerUnsafe storer(reinterpret_cast<unsigned char*>(buff.get()));
auto* begin = storer.get_buf();
storer.store_int(-1877581587); // magic inherited from test0.validatorSet
storer.store_int(cc_seqno);
storer.store_binary((td::uint32)nodes.size());
for (auto& n : nodes) {
storer.store_binary(n.key.as_bits256());
storer.store_long(n.weight);
storer.store_binary(n.addr);
}
auto* end = storer.get_buf();
CHECK(static_cast<size_t>(end - begin) == 4 * tot_size);
return td::crc32c(td::Slice(begin, end));
}
td::Result<Ref<vm::Cell>> get_config_data_from_smc(Ref<vm::Cell> acc_root) {
if (acc_root.is_null()) {
return td::Status::Error("configuration smart contract not found or it has no state, cannot extract configuration");
}
block::gen::Account::Record_account acc;
block::gen::AccountStorage::Record storage;
block::gen::StateInit::Record state;
if (!(tlb::unpack_cell(acc_root, acc) && tlb::csr_unpack(acc.storage, storage) &&
storage.state.write().fetch_ulong(1) == 1 && tlb::csr_unpack(storage.state, state) &&
state.data->have_refs(1))) {
return td::Status::Error("cannot extract persistent data from configuration smart contract state");
}
Ref<vm::Cell> data_cell = state.data->prefetch_ref();
auto res = vm::load_cell_slice(data_cell).prefetch_ref();
if (res.is_null()) {
return td::Status::Error(
"configuration smart contract does not contain a valid configuration in the first reference of its persistent "
"data");
}
return std::move(res);
}
td::Result<Ref<vm::Cell>> get_config_data_from_smc(Ref<vm::CellSlice> acc_csr) {
if (acc_csr.is_null()) {
return td::Status::Error("configuration smart contract not found, cannot extract configuration");
}
if (acc_csr->size_ext() != 0x10140) {
return td::Status::Error("configuration smart contract does not have a valid non-empty state");
}
return get_config_data_from_smc(acc_csr->prefetch_ref());
}
// when these parameters change, the block must be marked as a key block
bool important_config_parameters_changed(Ref<vm::Cell> old_cfg_root, Ref<vm::Cell> new_cfg_root, bool coarse) {
if (old_cfg_root->get_hash() == new_cfg_root->get_hash()) {
return false;
}
if (coarse) {
return true;
}
// for now, all parameters are "important"
// at least the parameters affecting the computations of validator sets must be considered important
// ...
return true;
}
bool is_public_library(td::ConstBitPtr key, Ref<vm::CellSlice> val) {
return val.not_null() && val->prefetch_ulong(1) == 1 && val->have_refs() &&
!key.compare(val->prefetch_ref()->get_hash().bits(), 256);
}
bool parse_hex_hash(const char* str, const char* end, td::Bits256& hash) {
if (end - str != 64) {
return false;
}
int y = 0;
for (int i = 0; i < 64; i++) {
int c = *str++, x = c - '0';
if (x < 0) {
return false;
} else if (x > 10) {
x = (c | 0x20) - ('a' - 10);
if (x < 10 || x > 16) {
return false;
}
}
y = (y << 4) | x;
if (i & 1) {
hash.data()[i >> 1] = (unsigned char)y;
y = 0;
}
}
return true;
}
bool parse_hex_hash(td::Slice str, td::Bits256& hash) {
return parse_hex_hash(str.begin(), str.end(), hash);
}
bool parse_block_id_ext(const char* str, const char* end, ton::BlockIdExt& blkid) {
blkid.invalidate();
if (!str || !end || str >= end || end - str > 255) {
return false;
}
if (*str != '(') {
return false;
}
if (!std::memchr(str, ')', end - str)) {
return false;
}
int wc, pos = 0;
unsigned seqno;
unsigned long long shard;
if (std::sscanf(str, "(%d,%llx,%u):%n", &wc, &shard, &seqno, &pos) < 3 || pos <= 0 || pos >= end - str) {
return false;
}
if (!shard || wc == ton::workchainInvalid) {
return false;
}
str += pos;
if (end - str != 64 * 2 + 1 || str[64] != ':') {
return false;
}
blkid.id = ton::BlockId{wc, shard, seqno};
return (parse_hex_hash(str, str + 64, blkid.root_hash) && parse_hex_hash(str + 65, end, blkid.file_hash)) ||
blkid.invalidate();
}
bool parse_block_id_ext(td::Slice str, ton::BlockIdExt& blkid) {
return parse_block_id_ext(str.begin(), str.end(), blkid);
}
} // namespace block