mirror of
https://github.com/danog/ton.git
synced 2024-11-30 04:29:19 +01:00
f67f5d879b
- bugfix in func - vertseqno support in validator/collator/topsharddescr
2124 lines
79 KiB
C++
2124 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 (msg.same_workchain() && ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
|
|
// this branch is needed only for messages generated in the same shard
|
|
// (such messages could have been processed without a reference from the masterchain)
|
|
// ? 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());
|
|
}
|
|
id_ = blkid;
|
|
root_ = std::move(prev_state_root);
|
|
vert_seqno_ = state.vert_seq_no;
|
|
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 * 1) {
|
|
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;
|
|
// 10. compute vert_seqno
|
|
vert_seqno_ = std::max(vert_seqno_, sib.vert_seqno_);
|
|
// Anything else? add here
|
|
// ...
|
|
|
|
// 100. 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");
|
|
}
|
|
// 101. 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
|