1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

493
crypto/block/Binlog.cpp Normal file
View file

@ -0,0 +1,493 @@
/*
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 "crypto/block/Binlog.h"
#include "td/utils/as.h"
#include "td/utils/misc.h"
#include "td/utils/port/path.h"
#include <sstream>
namespace block {
/*
*
* GENERIC BINLOG (move to separate file)
*
*/
BinlogBuffer::BinlogBuffer(std::unique_ptr<BinlogCallback> cb, std::size_t _max_size, td::FileFd fd)
: cb(std::move(cb))
, need_more_bytes(0)
, eptr(nullptr)
, log_rpos(0)
, log_cpos(0)
, log_wpos(0)
, fd(std::move(fd))
, replica(false)
, writing(false)
, dirty(false)
, created(false)
, ok(false) {
max_size = _max_size;
start = static_cast<unsigned char*>(std::malloc(max_size));
DCHECK(start);
rptr = wptr = cptr = start;
end = start + max_size;
}
unsigned char* BinlogBuffer::alloc_log_event_force(std::size_t size) {
unsigned char* res = alloc_log_event(size);
if (!res) {
throw LevAllocError{size};
}
return res;
}
unsigned char* BinlogBuffer::try_alloc_log_event(std::size_t size) {
if (!eptr) {
if (end - wptr >= (long)size) {
unsigned char* res = wptr;
wptr += size;
log_wpos += size;
return res;
}
eptr = wptr;
wptr = start;
if (rptr == eptr) {
rptr = start;
}
if (cptr == eptr) {
cptr = start;
}
}
if (rptr - wptr > (long)size) {
unsigned char* res = wptr;
wptr += size;
log_wpos += size;
return res;
}
return nullptr;
}
bool BinlogBuffer::flush(int mode) {
auto r_res = try_flush(mode);
if (r_res.is_ok()) {
return r_res.ok();
}
std::string msg = PSTRING() << "cannot flush binlog file " << binlog_name << " at position " << log_rpos << " "
<< r_res.error();
LOG(ERROR) << msg;
throw BinlogError{msg};
}
td::Result<bool> BinlogBuffer::try_flush(int mode) {
LOG(DEBUG) << "in flush: writing=" << writing << " r=" << rptr - start << " c=" << cptr - start
<< " w=" << wptr - start << "; rp=" << log_rpos << " cp=" << log_cpos << " wp=" << log_wpos;
if (!writing || rptr == cptr) {
return false; // nothing to flush
}
DCHECK(!fd.empty()); // must have an open binlog file
while (rptr != cptr) {
unsigned char* tptr = (cptr >= rptr ? cptr : eptr);
DCHECK(rptr <= tptr);
auto sz = tptr - rptr;
if (sz) {
LOG(INFO) << "writing " << sz << " bytes to binlog " << binlog_name << " at position " << log_rpos;
TRY_RESULT(res, fd.pwrite(td::Slice(rptr, sz), log_rpos));
if (static_cast<td::int64>(res) != sz) {
return td::Status::Error(PSLICE() << "written " << res << " bytes instead of " << sz);
}
log_rpos += sz;
rptr += sz;
}
if (rptr == eptr) {
rptr = start;
eptr = nullptr;
}
}
if (mode >= 3) {
LOG(INFO) << "syncing binlog " << binlog_name << " (position " << log_rpos << ")";
TRY_STATUS(fd.sync());
}
return true;
}
unsigned char* BinlogBuffer::alloc_log_event(std::size_t size) {
if (!writing) {
throw BinlogError{"cannot create new binlog event: binlog not open for writing"};
}
if (size >= max_size || size > max_event_size) {
return nullptr;
}
size = (size + 3) & -4;
unsigned char* res = try_alloc_log_event(size);
if (!res) {
flush();
return try_alloc_log_event(size);
} else {
return res;
}
}
bool BinlogBuffer::commit_range(unsigned long long pos_start, unsigned long long pos_end) {
// TODO: make something more clever, with partially committed/uncommitted segments in [cpos..wpos] range
if (pos_start != log_cpos || pos_end < pos_start || pos_end > log_wpos) {
return false;
}
if (!pos_start && pos_end >= pos_start + 4 && td::as<unsigned>(cptr) != 0x0442446b) {
throw BinlogError{"incorrect magic"};
}
long long size = pos_end - pos_start;
replay_range(cptr, pos_start, pos_end);
log_cpos = pos_end;
cptr += size;
if (eptr && cptr >= eptr) {
cptr -= eptr - start;
}
return true;
}
bool BinlogBuffer::rollback_range(unsigned long long pos_start, unsigned long long pos_end) {
if (pos_start < log_cpos || pos_end < pos_start || pos_end != log_wpos) {
return false;
}
long long size = pos_end - pos_start;
log_wpos = pos_end;
if (size >= wptr - start) {
wptr -= size;
} else {
DCHECK(eptr);
wptr += eptr - start - size;
}
return true;
}
void BinlogBuffer::NewBinlogEvent::commit() {
//LOG(DEBUG) << "in NewBinlogEvent::commit (status = " << status << ")";
if (!(status & 4)) {
throw BinlogError{"cannot commit new binlog event: already committed or rolled back"};
}
if (!bb.commit_range(pos, pos + size)) {
throw BinlogError{"cannot commit new binlog event: possibly some earlier log events are not committed yet"};
}
status = 1;
//LOG(DEBUG) << "after NewBinlogEvent::commit (status = " << status << ")";
}
void BinlogBuffer::NewBinlogEvent::rollback() {
if (!(status & 4)) {
throw BinlogError{"cannot roll back new binlog event: already committed or rolled back"};
}
if (!bb.rollback_range(pos, pos + size)) {
throw BinlogError{"cannot roll back new binlog event: possibly some later log event are already committed"};
}
status = 2;
}
BinlogBuffer::NewBinlogEvent::~NewBinlogEvent() {
if (status & 4) {
if (status == 5) {
status = 4;
commit();
} else if (status == 6) {
status = 4;
rollback();
} else {
LOG(ERROR) << "newly-allocated binlog event is neither committed nor rolled back (automatically rolling back)";
rollback();
}
}
}
void BinlogBuffer::replay_range(unsigned char* ptr, unsigned long long pos_start, unsigned long long pos_end) {
unsigned char* tptr = (ptr <= wptr ? wptr : eptr);
long long avail = tptr - ptr;
while (pos_start < pos_end) {
if (ptr == eptr) {
ptr = start;
tptr = wptr;
avail = tptr - ptr;
if (avail > (long long)(pos_end - pos_start)) {
avail = pos_end - pos_start;
}
}
int res = (avail >= 4 ? cb->replay_log_event(*this, reinterpret_cast<const unsigned*>(ptr),
td::narrow_cast<size_t>(avail), pos_start)
: -0x7ffffffc);
if (res <= 0 || res > avail) {
std::ostringstream ss;
ss << "cannot interpret newly-committed binlog event 0x" << std::hex
<< (avail >= 4 ? (unsigned)td::as<unsigned>(ptr) : 0u) << std::dec << ": error " << res;
throw BinlogError{ss.str()};
}
ptr += res;
pos_start += res;
avail -= res;
}
}
int BinlogBuffer::replay_pending(bool allow_partial) {
if (rptr == cptr) {
return 0;
}
unsigned char* tptr = (rptr <= cptr ? cptr : eptr);
long long avail = tptr - rptr;
DCHECK(tptr && avail >= 0);
while (rptr != cptr) {
int res = (avail >= 4 ? cb->replay_log_event(*this, reinterpret_cast<const unsigned*>(rptr),
td::narrow_cast<size_t>(avail), log_rpos)
: -0x7ffffffc);
if (res > 0) {
if (res > avail) {
throw BinlogError{"binlog event used more bytes than available"};
}
avail -= res;
log_rpos += res;
rptr += res;
if (rptr != eptr) {
continue;
}
rptr = start;
tptr = cptr;
avail = tptr - rptr;
continue;
}
long long prev_need = 0;
while (res < -0x40000000) {
long long need = res - 0x80000000;
need = (need + 3) & -4;
if (need > (long long)max_event_size) {
throw BinlogError{"binlog event requires too many bytes"};
}
if (need <= avail) {
throw BinlogError{"binlog event requires more bytes, but we already had them"};
}
if (need <= prev_need) {
throw BinlogError{"binlog event requires more bytes, but we already had them"};
}
prev_need = need;
long long total_avail = avail + (rptr > cptr ? cptr - start : 0);
if (need > total_avail) {
if (allow_partial) {
need_more_bytes = td::narrow_cast<size_t>(need - total_avail);
return 2;
} else {
throw BinlogError{"binlog event extends past end of buffer"};
}
}
if (need <= 1024) {
unsigned char tmp[1024];
std::memcpy(tmp, rptr, td::narrow_cast<size_t>(avail));
std::memcpy(tmp + avail, start, td::narrow_cast<size_t>(need - avail));
res = cb->replay_log_event(*this, reinterpret_cast<const unsigned*>(tmp), td::narrow_cast<size_t>(need),
log_rpos);
} else {
unsigned char* tmp = static_cast<unsigned char*>(std::malloc(td::narrow_cast<size_t>(need)));
std::memcpy(tmp, rptr, td::narrow_cast<size_t>(avail));
std::memcpy(tmp + avail, start, td::narrow_cast<size_t>(need - avail));
res = cb->replay_log_event(*this, reinterpret_cast<const unsigned*>(tmp), td::narrow_cast<size_t>(need),
log_rpos);
std::free(tmp);
}
if (res > need) {
throw BinlogError{"binlog event used more bytes than available"};
}
}
if (res < 0) {
return res;
}
if (!res) {
throw BinlogError{"unknown error while interpreting binlog event"};
}
if (res < avail) {
avail -= res;
log_rpos += res;
rptr += res;
continue;
}
DCHECK(eptr);
log_rpos += res;
rptr += res;
rptr = start + (rptr - eptr);
eptr = nullptr;
DCHECK(start <= rptr && rptr <= cptr && cptr <= wptr && wptr <= end);
}
return 1;
}
BinlogBuffer::~BinlogBuffer() {
if (start) {
if (writing) {
flush(2);
}
std::free(start);
}
}
td::Status BinlogBuffer::set_binlog(std::string new_binlog_name, int mode) {
if (!binlog_name.empty() || !fd.empty()) {
return td::Status::Error("binlog buffer already attached to a file");
}
td::int32 flags = td::FileFd::Read;
if ((mode & 1) != 0) {
flags |= td::FileFd::Write;
}
auto r_fd = td::FileFd::open(new_binlog_name, flags, 0640);
if (r_fd.is_error()) {
if (!(~mode & 3)) {
TRY_RESULT(new_fd, td::FileFd::open(new_binlog_name, flags | td::FileFd::CreateNew, 0640));
fd = std::move(new_fd);
created = true;
} else {
return r_fd.move_as_error();
}
} else {
fd = r_fd.move_as_ok();
}
replica = !(mode & 1);
if (!replica) {
TRY_STATUS(fd.lock(td::FileFd::LockFlags::Write, new_binlog_name, 100));
}
if (created) {
writing = true;
td::Status res;
try {
res = cb->init_new_binlog(*this);
} catch (BinlogBuffer::BinlogError& err) {
res = td::Status::Error(err.msg);
}
if (res.is_error()) {
fd.close();
td::unlink(new_binlog_name).ignore();
writing = false;
return res;
}
binlog_name = new_binlog_name;
ok = true;
return td::Status::OK();
}
binlog_name = new_binlog_name;
auto res = replay_binlog(replica);
if (res.is_error()) {
return res.move_as_error();
}
if (!replica) {
if (log_rpos != log_wpos || log_rpos != log_cpos || rptr != wptr || rptr != cptr) {
std::string msg = (PSLICE() << "error while interpreting binlog `" << binlog_name << "`: " << log_wpos - log_rpos
<< " bytes left uninterpreted at position " << log_rpos << ", truncated binlog?")
.c_str();
LOG(ERROR) << msg;
return td::Status::Error(msg);
}
//rptr = wptr = cptr = start;
//eptr = nullptr;
LOG(INFO) << "read and interpreted " << res.move_as_ok() << " bytes from binlog `" << binlog_name
<< "`, final position " << log_rpos << ", reopening in write mode";
writing = true;
if (!log_rpos) {
td::Status status;
try {
status = cb->init_new_binlog(*this);
} catch (BinlogBuffer::BinlogError& err) {
status = td::Status::Error(err.msg);
}
if (status.is_error()) {
fd.close();
td::unlink(new_binlog_name).ignore();
writing = false;
return status;
}
}
}
ok = true;
return td::Status::OK();
}
td::Result<long long> BinlogBuffer::replay_binlog(bool allow_partial) {
if (writing) {
return 0;
}
long long total = 0;
while (true) {
auto res = read_file();
if (res.is_error()) {
return res.move_as_error();
}
long long sz = res.move_as_ok();
total += sz;
try {
cptr = wptr;
log_cpos = log_wpos;
if (!log_rpos && rptr == start && wptr >= rptr + 4 && td::as<unsigned>(rptr) != 0x0442446b) {
throw BinlogError{"incorrect magic"};
}
int r = replay_pending(allow_partial || sz != 0);
if (r < 0 && r >= -0x40000000) {
throw InterpretError{(PSLICE() << "binlog error " << r).c_str()};
}
} catch (BinlogError err) {
LOG(ERROR) << "error reading binlog " << binlog_name << ": " << err.msg << " at position " << log_rpos;
return td::Status::Error(PSLICE() << "error reading binlog " << binlog_name << ": " << err.msg << " at position "
<< log_rpos);
} catch (InterpretError err) {
LOG(ERROR) << "error interpreting binlog " << binlog_name << ": " << err.msg << " at position " << log_rpos;
return td::Status::Error(PSLICE() << "error interpreting binlog " << binlog_name << ": " << err.msg
<< " at position " << log_rpos);
}
if (!sz) {
break;
}
};
return total;
}
td::Result<int> BinlogBuffer::read_file() {
unsigned char* ptr = wptr;
std::size_t sz = end - wptr;
if (rptr > wptr) {
DCHECK(eptr);
sz = rptr - wptr;
if (sz <= 4) {
return 0; // buffer full
}
sz -= 4;
} else if (!sz) {
DCHECK(!eptr);
if (rptr <= start + 4) {
return 0; // buffer full
}
eptr = end;
ptr = wptr = start;
sz = rptr - start - 4;
}
auto r_res = fd.pread(td::MutableSlice(ptr, sz), log_wpos);
if (r_res.is_error()) {
std::string msg = PSTRING() << "error reading binlog file `" << binlog_name << "` at position " << log_wpos << " : "
<< r_res.error();
LOG(ERROR) << msg;
return td::Status::Error(msg);
}
auto res = r_res.move_as_ok();
DCHECK(std::size_t(res) <= sz);
LOG(INFO) << "read " << res << " bytes from binlog `" << binlog_name << "` at position " << log_wpos;
log_wpos += res;
wptr += res;
return (int)res;
}
} // namespace block

157
crypto/block/Binlog.h Normal file
View file

@ -0,0 +1,157 @@
/*
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
*/
#pragma once
#include "td/utils/Status.h"
#include "td/utils/port/FileFd.h"
namespace block {
/*
*
* BINLOG (BUFFERS) -- move to separate file
*
*/
class BinlogBuffer;
class BinlogCallback {
public:
virtual ~BinlogCallback() = default;
virtual td::Status init_new_binlog(BinlogBuffer& bb) = 0;
virtual int replay_log_event(BinlogBuffer& bb, const unsigned* ptr, std::size_t len, unsigned long long pos) = 0;
};
class BinlogBuffer {
static constexpr std::size_t max_event_size = 0xfffc;
std::unique_ptr<BinlogCallback> cb;
std::size_t max_size;
std::size_t need_more_bytes;
unsigned char *start, *rptr, *cptr, *wptr, *eptr, *end;
unsigned long long log_rpos, log_cpos, log_wpos;
std::string binlog_name;
td::FileFd fd;
bool replica;
bool writing;
bool dirty;
bool created;
bool ok;
td::Result<int> read_file();
td::Result<long long> replay_binlog(bool allow_partial);
unsigned char* try_alloc_log_event(std::size_t size);
int replay_pending(bool allow_partial = false);
void replay_range(unsigned char* ptr, unsigned long long pos_start, unsigned long long pos_end);
td::Status set_fd(td::FileFd fd);
public:
struct LevAllocError {
std::size_t size;
explicit LevAllocError(std::size_t _size) : size(_size) {
}
};
struct InterpretError {
std::string msg;
explicit InterpretError(std::string _msg) : msg(_msg) {
}
};
struct BinlogError {
std::string msg;
explicit BinlogError(std::string _msg) : msg(_msg) {
}
};
BinlogBuffer(std::unique_ptr<BinlogCallback> _cb, std::size_t _max_size = (1 << 24), td::FileFd fd = {});
BinlogBuffer(const BinlogBuffer&) = delete;
BinlogBuffer& operator=(const BinlogBuffer&) = delete;
BinlogBuffer(BinlogBuffer&&) = delete;
BinlogBuffer& operator=(BinlogBuffer&&) = delete;
~BinlogBuffer();
td::Status set_binlog(std::string _binlog_name, int mode = 0);
unsigned char* alloc_log_event(std::size_t size);
unsigned char* alloc_log_event_force(std::size_t size);
bool flush(int mode = 0);
td::Result<bool> try_flush(int mode);
unsigned long long get_rpos() const {
return log_rpos;
}
//
class NewBinlogEvent {
protected:
BinlogBuffer& bb;
unsigned long long pos;
unsigned size;
int status;
public:
NewBinlogEvent(BinlogBuffer& _bb, unsigned long long _pos, unsigned _size)
: bb(_bb), pos(_pos), size(_size), status(4) {
}
~NewBinlogEvent();
unsigned long long get_log_pos() const {
return pos;
}
void commit();
void commit_later() {
if (status & 4) {
status = 5;
}
}
void rollback();
void rollback_later() {
if (status & 4) {
status = 6;
}
};
};
template <typename T>
class NewEvent : public NewBinlogEvent {
T* ptr;
protected:
friend class BinlogBuffer;
NewEvent(BinlogBuffer& _bb, unsigned long long _pos, unsigned _size, T* _ptr)
: NewBinlogEvent(_bb, _pos, _size), ptr(_ptr) {
}
public:
T* operator->() const {
return ptr;
}
T& operator*() const {
return *ptr;
}
void commit() {
NewBinlogEvent::commit();
ptr = nullptr;
}
void rollback() {
NewBinlogEvent::rollback();
ptr = nullptr;
}
};
template <typename T, typename... Args>
NewEvent<T> alloc(Args... args) {
unsigned long long pos = log_wpos;
return NewEvent<T>(*this, pos, sizeof(T), new (alloc_log_event_force(sizeof(T))) T(args...));
}
protected:
friend class NewBinlogEvent;
bool commit_range(unsigned long long pos_start, unsigned long long pos_end);
bool rollback_range(unsigned long long pos_start, unsigned long long pos_end);
};
} // namespace block

111
crypto/block/block-binlog.h Normal file
View file

@ -0,0 +1,111 @@
/*
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
*/
#pragma once
#include "td/utils/int_types.h"
#include "ton/ton-types.h"
#include <ctime>
namespace block {
namespace log {
#pragma pack(push, 4)
struct Start {
static constexpr unsigned tag = 0x0442446b;
static constexpr unsigned log_type = 0x290100;
unsigned tag_field;
unsigned type_field;
unsigned created_at;
unsigned char zerostate_root_hash[32];
Start(const RootHash& hash, unsigned _now = 0)
: tag_field(tag), type_field(log_type), created_at(_now ? _now : (unsigned)std::time(nullptr)) {
td::as<RootHash>(zerostate_root_hash) = hash;
}
};
struct SetZeroState {
static constexpr unsigned tag = 0x63ab3cd9;
unsigned tag_field;
unsigned flags;
long long file_size;
unsigned char file_hash[32];
unsigned char root_hash[32];
SetZeroState(const RootHash& rhash, const FileHash& fhash, unsigned long long _fsize, unsigned _flags = 0)
: tag_field(tag), flags(_flags), file_size(_fsize) {
td::as<FileHash>(file_hash) = fhash;
td::as<RootHash>(root_hash) = rhash;
}
};
struct NewBlock {
static constexpr unsigned tag = 0x19f4bc63;
unsigned tag_field;
unsigned flags; // lower 8 bits = authority
int workchain;
unsigned seqno;
unsigned long long shard;
long long file_size;
unsigned char file_hash[32];
unsigned char root_hash[32];
unsigned char last_bytes[8];
NewBlock(const ton::BlockId& block, const RootHash& rhash, const FileHash& fhash, unsigned long long _fsize,
unsigned _flags)
: tag_field(tag)
, flags(_flags)
, workchain(block.workchain)
, seqno(block.seqno)
, shard(block.shard)
, file_size(_fsize) {
td::as<FileHash>(file_hash) = fhash;
td::as<RootHash>(root_hash) = rhash;
td::as<unsigned long long>(last_bytes) = 0;
}
};
struct NewState {
static constexpr unsigned tag = 0x4190a21f;
unsigned tag_field;
unsigned flags; // lower 8 bits = authority
int workchain;
unsigned seqno;
unsigned long long shard;
long long file_size;
unsigned char file_hash[32];
unsigned char root_hash[32];
unsigned char last_bytes[8];
NewState(const ton::BlockId& state, const RootHash& rhash, const FileHash& fhash, unsigned long long _fsize,
unsigned _flags)
: tag_field(tag)
, flags(_flags)
, workchain(state.workchain)
, seqno(state.seqno)
, shard(state.shard)
, file_size(_fsize) {
td::as<FileHash>(file_hash) = fhash;
td::as<RootHash>(root_hash) = rhash;
td::as<unsigned long long>(last_bytes) = 0;
}
};
#pragma pack(pop)
} // namespace log
} // namespace block

View file

@ -0,0 +1,135 @@
/*
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
*/
#pragma once
#include <string>
#include <map>
#include "vm/cells.h"
#include "block/Binlog.h"
#include "block/block-db.h"
#include "block/block-binlog.h"
namespace block {
using td::Ref;
/*
*
* BLOCK DATABASE
*
*/
class BlockDbImpl;
class BlockBinlogCallback : public BinlogCallback {
BlockDbImpl& db;
td::Status init_new_binlog(BinlogBuffer& bb) override;
int replay_log_event(BinlogBuffer& bb, const unsigned* ptr, std::size_t len, unsigned long long log_pos) override;
int replay(const block::log::Start& lev, unsigned long long log_pos) const;
int replay(const block::log::SetZeroState& lev, unsigned long long log_pos) const;
int replay(const block::log::NewBlock& lev, unsigned long long log_pos) const;
int replay(const block::log::NewState& lev, unsigned long long log_pos) const;
template <typename T>
inline int try_interpret(const unsigned* ptr, std::size_t len, unsigned long long log_pos);
public:
BlockBinlogCallback(BlockDbImpl& _db) : db(_db) {
}
};
template <typename T>
inline int BlockBinlogCallback::try_interpret(const unsigned* ptr, std::size_t len, unsigned long long log_pos) {
if (len < sizeof(T)) {
return 0x80000000 + sizeof(T);
} else {
int res = replay(*reinterpret_cast<const T*>(ptr), log_pos);
return res >= 0 ? sizeof(T) : res;
}
}
class BlockDbImpl final : public BlockDb {
int status;
bool allow_uninit;
bool created;
int depth;
std::unique_ptr<ZerostateInfo> zstate;
std::string base_dir;
std::string binlog_name;
BinlogBuffer bb;
ton::Bits256 zstate_rhash, zstate_fhash;
unsigned created_at;
std::map<ton::FileHash, td::BufferSlice> file_cache;
std::map<ton::BlockId, Ref<FileInfo>> block_info;
std::map<ton::BlockId, Ref<FileInfo>> state_info;
//
td::Result<int> do_init();
public:
enum FMode {
chk_none = 0,
chk_if_exists = 1,
fail_if_exists = 2,
overwrite = 4,
chk_size_only = 16,
chk_file_hash = 32
};
static constexpr const char* default_binlog_name = "blockdb";
static constexpr const char* default_binlog_suffix = ".bin";
static constexpr int default_depth = 4;
BlockDbImpl(td::Result<int>& _res, std::string _base_dir, std::unique_ptr<ZerostateInfo> _zstate = nullptr,
bool _allow_uninit = false, int _depth = 4, std::string _binlog_name = "");
~BlockDbImpl();
bool ok() const {
return status >= 0;
}
bool initialized() const {
return status != 0;
}
bool init_ok() const {
return status > 0;
}
protected:
friend class BlockBinlogCallback;
td::Ref<FileInfo> zerostate;
td::Status init_from_zstate();
td::Status update_block_info(Ref<FileInfo> blk_info);
td::Status update_state_info(Ref<FileInfo> state);
private:
std::string compute_db_filename(const FileHash& file_hash) const;
std::string compute_db_tmp_filename(const FileHash& file_hash, int i, bool makedirs) const;
td::Status save_db_file(const FileHash& file_hash, const td::BufferSlice& data, int fmode = 0);
td::Status load_data(FileInfo& file_info, bool force = false);
// actor BlockDb implementation
void get_top_block_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) override;
void get_top_block_state_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) override;
void get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) override;
void get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) override;
void get_out_queue_info_by_id(ton::BlockId blk_id, td::Promise<td::Ref<OutputQueueInfoDescr>> promise) override;
void get_object_by_file_hash(FileHash file_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) override;
void get_object_by_root_hash(RootHash root_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) override;
void save_new_block(ton::BlockIdExt blk_id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) override;
void save_new_state(ton::BlockIdExt state_id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) override;
bool file_cache_insert(const FileHash& file_hash, const td::BufferSlice& data, int mode = 0);
};
} // namespace block

835
crypto/block/block-db.cpp Normal file
View file

@ -0,0 +1,835 @@
/*
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 "block-db.h"
#include "block-db-impl.h"
#include "block-binlog.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/misc.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
#include "td/utils/filesystem.h"
#include "vm/cellslice.h"
#include "vm/boc.h"
#include "vm/db/StaticBagOfCellsDb.h"
#include <limits>
namespace block {
//static constexpr std::string default_binlog_name = "blockdb";
//static constexpr std::string default_binlog_suffix = ".bin";
bool parse_hash_string(std::string arg, RootHash& res) {
if (arg.size() != 64) {
res.set_zero();
return false;
}
int f = 1;
unsigned char* ptr = res.data();
for (char c : arg) {
f <<= 4;
if (c >= '0' && c <= '9') {
f += c - '0';
} else {
c |= 0x20;
if (c >= 'a' && c <= 'f') {
f += c - ('a' - 10);
} else {
res.set_zero();
return false;
}
}
if (f >= 0x100) {
*ptr++ = (unsigned char)f;
f = 1;
}
}
return true;
}
td::Result<td::BufferSlice> load_binary_file(std::string filename, td::int64 max_size) {
//TODO: use td::read_file
auto res = [&]() -> td::Result<td::BufferSlice> {
TRY_RESULT(fd, td::FileFd::open(filename, td::FileFd::Read));
TRY_RESULT(stat, fd.stat());
if (!stat.is_reg_) {
return td::Status::Error("file is not regular");
}
td::int64 size = stat.size_;
if (!size) {
return td::Status::Error("file is empty");
}
if ((max_size && size > max_size) || static_cast<td::uint64>(size) > std::numeric_limits<std::size_t>::max()) {
return td::Status::Error("file is too long");
}
td::BufferSlice res(td::narrow_cast<std::size_t>(size));
TRY_RESULT(r, fd.read(res.as_slice()));
if (r != static_cast<td::uint64>(size)) {
return td::Status::Error(PSLICE() << "read " << r << " bytes out of " << size);
}
return std::move(res);
}();
LOG_IF(ERROR, res.is_error()) << "error reading file `" << filename << "` : " << res.error();
return res;
}
td::Status save_binary_file(std::string filename, const td::BufferSlice& data, unsigned long long max_size) {
//TODO: use td::write_file
auto status = [&]() {
if (max_size && data.size() > max_size) {
return td::Status::Error("contents too long");
}
auto size = data.size();
TRY_RESULT(to_file, td::FileFd::open(filename, td::FileFd::CreateNew | td::FileFd::Write));
TRY_RESULT(written, to_file.write(data));
if (written != static_cast<size_t>(size)) {
return td::Status::Error(PSLICE() << "written " << written << " bytes instead of " << size);
}
to_file.close();
return td::Status::OK();
}();
LOG_IF(ERROR, status.is_error()) << "error writing new file `" << filename << "` : " << status;
return status;
}
FileHash compute_file_hash(const td::BufferSlice& data) {
ton::Bits256 data_hash;
td::sha256(data, td::MutableSlice{data_hash.data(), 32});
return data_hash;
}
FileHash compute_file_hash(td::Slice data) {
ton::Bits256 data_hash;
td::sha256(data, td::MutableSlice{data_hash.data(), 32});
return data_hash;
}
/*
*
* ZEROSTATE CONFIGURATION
*
*/
td::Status ZerostateInfo::base_check() {
if (!has_data()) {
return td::Status::OK();
}
auto data_hash = compute_file_hash(data);
if (!has_file_hash()) {
file_hash = data_hash;
} else if (file_hash != data_hash) {
return td::Status::Error("zerostate file hash mismatch");
}
vm::BagOfCells boc;
auto res = boc.deserialize(data);
if (!res.is_ok() || boc.get_root_count() != 1) {
return td::Status::Error("zerostate is not a valid bag of cells"); // not a valid bag-of-Cells
}
data_hash = boc.get_root_cell()->get_hash().bits();
if (!has_root_hash()) {
root_hash = data_hash;
} else if (root_hash != data_hash) {
return td::Status::Error("zerostate root hash mismatch");
}
return td::Status::OK();
}
/*
*
* BLOCK DATABASE
*
*/
std::string compute_db_filename(std::string base_dir, const FileHash& file_hash, int depth) {
static const char hex_digits[] = "0123456789ABCDEF";
assert(depth >= 0 && depth <= 8);
std::string res = std::move(base_dir);
res.reserve(res.size() + 32 + depth * 3 + 4);
for (int i = 0; i < depth; i++) {
unsigned u = file_hash.data()[i];
res.push_back(hex_digits[u >> 4]);
res.push_back(hex_digits[u & 15]);
res.push_back('/');
}
for (int i = 0; i < 32; i++) {
unsigned u = file_hash.data()[i];
res.push_back(hex_digits[u >> 4]);
res.push_back(hex_digits[u & 15]);
}
res += ".boc";
return res;
}
std::string BlockDbImpl::compute_db_filename(const FileHash& file_hash) const {
return block::compute_db_filename(base_dir, file_hash, depth);
}
std::string compute_db_tmp_filename(std::string base_dir, const FileHash& file_hash, int i, bool makedirs, int depth) {
static const char hex_digits[] = "0123456789ABCDEF";
assert(depth >= 0 && depth <= 8);
std::string res = std::move(base_dir);
res.reserve(res.size() + 32 + depth * 3 + 4);
for (int j = 0; j < depth; j++) {
unsigned u = file_hash.data()[j];
res.push_back(hex_digits[u >> 4]);
res.push_back(hex_digits[u & 15]);
res.push_back('/');
if (makedirs) {
td::mkdir(res, 0755).ignore();
}
}
for (int j = 0; j < 32; j++) {
unsigned u = file_hash.data()[j];
res.push_back(hex_digits[u >> 4]);
res.push_back(hex_digits[u & 15]);
}
res += ".tmp";
if (i > 0) {
if (i < 10) {
res.push_back((char)('0' + i));
} else {
res.push_back((char)('0' + i / 10));
res.push_back((char)('0' + i % 10));
}
}
return res;
}
std::string BlockDbImpl::compute_db_tmp_filename(const FileHash& file_hash, int i, bool makedirs) const {
return block::compute_db_tmp_filename(base_dir, file_hash, i, makedirs, depth);
}
bool BlockDbImpl::file_cache_insert(const FileHash& file_hash, const td::BufferSlice& data, int mode) {
auto it = file_cache.find(file_hash);
if (it != file_cache.end()) {
// found
return true;
}
auto res = file_cache.emplace(file_hash, data.clone());
return res.second;
}
td::Status BlockDbImpl::save_db_file(const FileHash& file_hash, const td::BufferSlice& data, int fmode) {
if (fmode & FMode::chk_file_hash && file_hash != compute_file_hash(data)) {
return td::Status::Error("file hash passed for creation of a new file does not match contents");
}
std::string filename = compute_db_filename(file_hash);
bool overwrite = false;
auto r_stat = td::stat(filename);
if (r_stat.is_ok()) {
auto stat = r_stat.move_as_ok();
// file exists
if (fmode & FMode::fail_if_exists) {
return td::Status::Error(PSLICE() << "file " << filename << " cannot be created, it already exists");
}
if (!(fmode & (FMode::chk_if_exists | FMode::overwrite))) {
file_cache_insert(file_hash, data);
return td::Status::OK();
}
if (fmode & FMode::chk_if_exists) {
if (stat.size_ != (long long)data.size()) {
LOG(ERROR) << "file " << filename << " already exists with wrong content";
if (!(fmode & FMode::overwrite)) {
return td::Status::Error(PSLICE() << "file " << filename << " already exists with wrong content");
}
} else if (fmode & FMode::chk_size_only) {
file_cache_insert(file_hash, data);
return td::Status::OK();
} else {
auto res = load_binary_file(filename);
if (res.is_error()) {
return res.move_as_error();
}
auto old_contents = res.move_as_ok();
if (old_contents.size() != data.size() || old_contents.as_slice() != data.as_slice()) {
LOG(ERROR) << "file " << filename << " already exists with wrong content";
if (!(fmode & FMode::overwrite)) {
return td::Status::Error(PSLICE() << "file " << filename << " already exists with wrong content");
}
} else {
file_cache_insert(file_hash, data);
return td::Status::OK();
}
}
}
overwrite = true;
}
std::string tmp_filename;
for (int i = 0; i < 10; i++) {
tmp_filename = compute_db_tmp_filename(file_hash, i, true);
auto res = save_binary_file(tmp_filename, data);
if (res.is_ok()) {
break;
}
if (i == 9) {
return res;
}
}
auto rename_status = td::rename(tmp_filename, filename);
if (rename_status.is_error()) {
td::unlink(tmp_filename).ignore();
LOG(ERROR) << rename_status;
return rename_status;
}
if (overwrite) {
LOG(DEBUG) << "database file `" << filename << "` overwritten, " << data.size() << " bytes";
} else {
LOG(DEBUG) << "new database file `" << filename << "` created, " << data.size() << " bytes";
}
file_cache_insert(file_hash, data);
return td::Status::OK();
}
td::Result<td::actor::ActorOwn<BlockDb>> BlockDb::create_block_db(std::string base_dir,
std::unique_ptr<ZerostateInfo> zstate,
bool allow_uninit, int depth,
std::string binlog_name) {
using td::actor::ActorId;
using td::actor::ActorOwn;
td::Result<int> res;
ActorOwn<BlockDbImpl> actor =
td::actor::create_actor<BlockDbImpl>(td::actor::ActorOptions().with_name("BlockDB"), res, base_dir,
std::move(zstate), allow_uninit, depth, binlog_name);
if (res.is_error()) {
return std::move(res).move_as_error();
} else {
return std::move(actor);
}
}
BlockDbImpl::BlockDbImpl(td::Result<int>& _res, std::string _base_dir, std::unique_ptr<ZerostateInfo> _zstate,
bool _allow_uninit, int _depth, std::string _binlog_name)
: status(0)
, allow_uninit(_allow_uninit)
, created(false)
, depth(_depth)
, zstate(std::move(_zstate))
, base_dir(_base_dir)
, binlog_name(_binlog_name)
, bb(std::unique_ptr<BinlogCallback>(new BlockBinlogCallback(*this)))
, created_at(0) {
auto res = do_init();
status = (res.is_ok() && res.ok() > 0 ? res.ok() : -1);
if (res.is_error()) {
_res = std::move(res);
} else {
_res = res.move_as_ok();
}
}
td::Result<int> BlockDbImpl::do_init() {
if (base_dir.empty()) {
return td::Status::Error("block database cannot have empty base directory");
}
if (depth < 0 || depth >= 8) {
return td::Status::Error("block database directory tree depth must be in range 0..8");
}
if (base_dir.back() != '/') {
base_dir.push_back('/');
}
if (binlog_name.empty()) {
binlog_name = default_binlog_name;
}
bool f = true;
for (char c : binlog_name) {
if (c == '.') {
f = false;
} else if (c == '/') {
f = true;
}
}
if (f) {
binlog_name += default_binlog_suffix;
}
if (binlog_name.at(0) != '/') {
binlog_name = base_dir + binlog_name;
}
if (zstate) {
if (!zstate->has_data() && zstate->has_filename()) {
auto data = load_binary_file(zstate->filename, 1 << 20);
if (data.is_error()) {
return data.move_as_error();
}
zstate->data = data.move_as_ok();
}
auto res = zstate->base_check();
if (res.is_error()) {
return res;
}
}
try {
auto res = bb.set_binlog(binlog_name, allow_uninit ? 3 : 1);
if (res.is_error()) {
return res;
}
} catch (BinlogBuffer::BinlogError& err) {
return td::Status::Error(-2, std::string{"error while initializing block database binlog: "} + err.msg);
} catch (BinlogBuffer::InterpretError& err) {
return td::Status::Error(-3, std::string{"error while interpreting block database binlog: "} + err.msg);
}
return created;
}
BlockDbImpl::~BlockDbImpl() {
}
td::Status BlockDbImpl::init_from_zstate() {
if (!zstate) {
return td::Status::Error("no zero state provided, cannot initialize from scratch");
}
if (!zstate->has_data()) {
if (zstate->has_filename() || zstate->has_file_hash()) {
if (!zstate->has_filename()) {
zstate->filename = compute_db_filename(zstate->file_hash);
}
auto res = load_binary_file(zstate->filename, 1 << 20);
if (res.is_error()) {
return res.move_as_error();
}
zstate->data = res.move_as_ok();
} else {
return td::Status::Error("cannot load zero state for block DB creation");
}
}
auto res = zstate->base_check();
if (res.is_error()) {
return res;
}
assert(zstate->has_file_hash() && zstate->has_root_hash());
res = save_db_file(zstate->file_hash, zstate->data, FMode::chk_if_exists | FMode::chk_file_hash);
if (res.is_error()) {
return res;
}
return res;
}
td::Status BlockBinlogCallback::init_new_binlog(BinlogBuffer& bb) {
auto res = db.init_from_zstate();
if (res.is_error()) {
return res;
}
auto lev = bb.alloc<log::Start>(db.zstate->root_hash);
assert(!lev.get_log_pos());
auto lev2 = bb.alloc<log::SetZeroState>(db.zstate->root_hash, db.zstate->file_hash, db.zstate->data.size());
lev.commit();
lev2.commit(); // TODO: introduce multi-commit bb.commit(lev, lev2)
bb.flush(3);
db.created = true;
return td::Status::OK();
}
#define REPLAY_CASE(__T) \
case __T::tag: \
return try_interpret<__T>(ptr, len, log_pos);
int BlockBinlogCallback::replay_log_event(BinlogBuffer& bb, const unsigned* ptr, std::size_t len,
unsigned long long log_pos) {
assert(len >= 4);
LOG(DEBUG) << "replay_log_event(" << len << ", " << log_pos << ", " << *ptr << ")";
switch (*ptr) {
REPLAY_CASE(log::Start);
REPLAY_CASE(log::SetZeroState);
REPLAY_CASE(log::NewBlock);
REPLAY_CASE(log::NewState);
}
std::ostringstream ss;
ss << "unknown binlog event 0x" << std::hex << *ptr << std::dec;
LOG(ERROR) << ss.str() << " at position " << log_pos;
throw BinlogBuffer::InterpretError{ss.str()};
}
#undef REPLAY_CASE
int BlockBinlogCallback::replay(const log::Start& lev, unsigned long long log_pos) const {
LOG(DEBUG) << "in replay(Start{" << lev.tag_field << ", " << lev.type_field << ", " << lev.created_at << "})";
if (lev.type_field != lev.log_type) {
throw BinlogBuffer::InterpretError{(PSLICE() << "unsupported binlog type " << lev.type_field).str()};
}
if (log_pos) {
throw BinlogBuffer::InterpretError{"LEV_START can only be the very first record in a binlog"};
}
db.zstate_rhash = lev.zerostate_root_hash;
db.created_at = lev.created_at;
if (db.zstate) {
if (!db.zstate->has_root_hash()) {
db.zstate->root_hash = db.zstate_rhash;
} else if (db.zstate->root_hash != db.zstate_rhash) {
throw BinlogBuffer::InterpretError{PSTRING() << "zerostate hash mismatch: in binlog " << db.zstate_rhash.to_hex()
<< ", required " << db.zstate->root_hash.to_hex()};
}
}
return 0; // ok
}
int BlockBinlogCallback::replay(const log::SetZeroState& lev, unsigned long long log_pos) const {
LOG(DEBUG) << "in replay(SetZeroState)";
// LOG(DEBUG) << "db.zstate_rhash = " << db.zstate_rhash.to_hex();
if (db.zstate_rhash != td::ConstBitPtr{lev.root_hash}) {
throw BinlogBuffer::InterpretError{std::string{"SetZeroState: zerostate root hash mismatch: in binlog "} +
ton::Bits256{lev.root_hash}.to_hex() + ", required " + db.zstate_rhash.to_hex()};
}
db.zerostate = td::Ref<FileInfo>{true,
FileType::state,
ton::BlockId{ton::masterchainId, 1ULL << 63, 0},
0,
td::as<FileHash>(lev.file_hash),
td::as<RootHash>(lev.root_hash),
lev.file_size};
return 0; // ok
}
int BlockBinlogCallback::replay(const log::NewBlock& lev, unsigned long long log_pos) const {
LOG(DEBUG) << "in replay(NewBlock)";
if (!lev.seqno || lev.workchain == ton::workchainInvalid) {
return -1;
}
ton::BlockId blkid{lev.workchain, lev.shard, lev.seqno};
auto blk_info = td::Ref<FileInfo>{true,
FileType::block,
blkid,
lev.flags & 0xff,
td::as<FileHash>(lev.file_hash),
td::as<RootHash>(lev.root_hash),
lev.file_size};
auto res = db.update_block_info(blk_info);
if (res.is_error()) {
LOG(ERROR) << "cannot update block information in the local DB: " << res.to_string();
return -1;
} else {
return 0; // ok
}
}
int BlockBinlogCallback::replay(const log::NewState& lev, unsigned long long log_pos) const {
LOG(DEBUG) << "in replay(NewState)";
if (!lev.seqno || lev.workchain == ton::workchainInvalid) {
return -1;
}
ton::BlockId id{lev.workchain, lev.shard, lev.seqno};
auto state_info = td::Ref<FileInfo>{true,
FileType::state,
id,
lev.flags & 0xff,
td::as<FileHash>(lev.file_hash),
td::as<RootHash>(lev.root_hash),
lev.file_size};
auto res = db.update_state_info(state_info);
if (res.is_error()) {
LOG(ERROR) << "cannot update shardchain state information in the local DB: " << res.to_string();
return -1;
} else {
return 0; // ok
}
}
td::Status BlockDbImpl::update_block_info(Ref<FileInfo> blk_info) {
auto it = block_info.find(blk_info->blk.id);
if (it != block_info.end()) {
// already exists
if (it->second->blk.file_hash != blk_info->blk.file_hash || it->second->blk.root_hash != blk_info->blk.root_hash) {
return td::Status::Error(-666, std::string{"fatal error in block DB: block "} + blk_info->blk.id.to_str() +
" has two records with different file or root hashes");
} else {
return td::Status::OK();
}
} else {
auto id = blk_info->blk.id;
auto res = block_info.emplace(std::move(id), std::move(blk_info));
if (res.second) {
return td::Status::OK();
} else {
return td::Status::Error(-666, "cannot insert block information into DB");
}
}
}
td::Status BlockDbImpl::update_state_info(Ref<FileInfo> state) {
auto it = state_info.find(state->blk.id);
if (it != state_info.end()) {
// already exists
if (it->second->blk.root_hash != state->blk.root_hash) {
return td::Status::Error(-666, std::string{"fatal error in block DB: state for block "} + state->blk.id.to_str() +
" has two records with different root hashes");
} else {
return td::Status::OK();
}
} else {
auto id = state->blk.id;
auto res = state_info.emplace(std::move(id), std::move(state));
if (res.second) {
return td::Status::OK();
} else {
return td::Status::Error(-666, "cannot insert state information into DB");
}
}
}
void BlockDbImpl::get_top_block_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) {
LOG(DEBUG) << "in BlockDb::get_top_block_id()";
auto it = block_info.upper_bound(ton::BlockId{shard, std::numeric_limits<td::uint32>::max()});
if (it != block_info.begin() && ton::ShardIdFull{(--it)->first} == shard) {
promise(it->second->blk);
return;
}
if (shard.is_masterchain()) {
promise(ton::BlockIdExt{ton::BlockId{ton::masterchainId, 1ULL << 63, 0}});
return;
}
promise(td::Status::Error(-666));
}
void BlockDbImpl::get_top_block_state_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) {
LOG(DEBUG) << "in BlockDb::get_top_block_state_id()";
auto it = state_info.upper_bound(ton::BlockId{shard, std::numeric_limits<td::uint32>::max()});
if (it != state_info.begin() && ton::ShardIdFull{(--it)->first} == shard) {
promise(it->second->blk);
return;
}
if (shard.is_masterchain() && zerostate.not_null()) {
promise(zerostate->blk);
return;
}
promise(td::Status::Error(-666, "no state for given workchain found in database"));
}
void BlockDbImpl::get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) {
LOG(DEBUG) << "in BlockDb::get_block_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", " << blk_id.seqno
<< "}, " << need_data << ")";
auto it = block_info.find(blk_id);
if (it != block_info.end()) {
if (need_data && it->second->data.is_null()) {
LOG(DEBUG) << "loading data for block " << blk_id.to_str();
auto res = load_data(it->second.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
promise(it->second);
}
promise(td::Status::Error(-666, "block not found in database"));
}
void BlockDbImpl::get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) {
LOG(DEBUG) << "in BlockDb::get_state_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", " << blk_id.seqno
<< "}, " << need_data << ")";
auto it = state_info.find(blk_id);
if (it != state_info.end()) {
if (need_data && it->second->data.is_null()) {
LOG(DEBUG) << "loading data for state " << blk_id.to_str();
auto res = load_data(it->second.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
promise(it->second);
}
if (zerostate.not_null() && blk_id == zerostate->blk.id) {
LOG(DEBUG) << "get_state_by_id(): zerostate requested";
if (need_data && zerostate->data.is_null()) {
LOG(DEBUG) << "loading data for zerostate";
auto res = load_data(zerostate.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
promise(zerostate);
return;
}
promise(td::Status::Error(-666, "requested state not found in database"));
}
void BlockDbImpl::get_out_queue_info_by_id(ton::BlockId blk_id, td::Promise<td::Ref<OutputQueueInfoDescr>> promise) {
LOG(DEBUG) << "in BlockDb::get_out_queue_info_by_id({" << blk_id.workchain << ", " << blk_id.shard << ", "
<< blk_id.seqno << ")";
auto it = state_info.find(blk_id);
if (it == state_info.end()) {
promise(td::Status::Error(
-666, std::string{"cannot obtain output queue info for block "} + blk_id.to_str() + " : cannot load state"));
}
if (it->second->data.is_null()) {
LOG(DEBUG) << "loading data for state " << blk_id.to_str();
auto res = load_data(it->second.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
auto it2 = block_info.find(blk_id);
if (it2 == block_info.end()) {
promise(td::Status::Error(-666, std::string{"cannot obtain output queue info for block "} + blk_id.to_str() +
" : cannot load block description"));
}
vm::StaticBagOfCellsDbLazy::Options options;
auto res = vm::StaticBagOfCellsDbLazy::create(it->second->data.clone(), options);
if (res.is_error()) {
td::Status err = res.move_as_error();
LOG(ERROR) << "cannot deserialize state for block " << blk_id.to_str() << " : " << err.to_string();
promise(std::move(err));
return;
}
auto static_boc = res.move_as_ok();
auto rc = static_boc->get_root_count();
if (rc.is_error()) {
promise(rc.move_as_error());
return;
}
if (rc.move_as_ok() != 1) {
promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid"));
return;
}
auto res3 = static_boc->get_root_cell(0);
if (res3.is_error()) {
promise(res3.move_as_error());
return;
}
auto state_root = res3.move_as_ok();
if (it->second->blk.root_hash != state_root->get_hash().bits()) {
promise(td::Status::Error(
-668, std::string{"state for block "} + blk_id.to_str() + " is invalid : state root hash mismatch"));
}
vm::CellSlice cs = vm::load_cell_slice(state_root);
if (!cs.have(64, 1) || cs.prefetch_ulong(32) != 0x9023afde) {
promise(td::Status::Error(-668, std::string{"state for block "} + blk_id.to_str() + " is invalid"));
}
auto out_queue_info = cs.prefetch_ref();
promise(Ref<OutputQueueInfoDescr>{true, blk_id, it2->second->blk.root_hash.cbits(), state_root->get_hash().bits(),
std::move(out_queue_info)});
}
void BlockDbImpl::get_object_by_file_hash(FileHash file_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) {
if (zerostate.not_null() && zerostate->blk.file_hash == file_hash) {
if (need_data && zerostate->data.is_null()) {
auto res = load_data(zerostate.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
promise(zerostate);
return;
}
promise(td::Status::Error(-666));
}
void BlockDbImpl::get_object_by_root_hash(RootHash root_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) {
if (zerostate.not_null() && zerostate->blk.root_hash == root_hash) {
if (need_data && zerostate->data.is_null()) {
auto res = load_data(zerostate.write());
if (res.is_error()) {
promise(std::move(res));
return;
}
}
promise(zerostate);
return;
}
promise(td::Status::Error(-666));
}
void BlockDbImpl::save_new_block(ton::BlockIdExt id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) {
// TODO: add verification that data is a BoC with correct root hash, and that it is a Block corresponding to blk_id
// ...
// TODO: check whether we already have a block with blk_id
// ...
auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash);
if (save_res.is_error()) {
promise(std::move(save_res));
}
auto sz = data.size();
auto lev = bb.alloc<log::NewBlock>(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff);
if (sz <= 8) {
std::memcpy(lev->last_bytes, data.data(), sz);
} else {
std::memcpy(lev->last_bytes, data.data() + sz - 8, 8);
}
lev.commit();
bb.flush();
promise(td::Unit{});
}
void BlockDbImpl::save_new_state(ton::BlockIdExt id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) {
// TODO: add verification that data is a BoC with correct root hash, and that it is a Block corresponding to blk_id
// ...
// TODO: check whether we already have a block with blk_id
// ...
auto save_res = save_db_file(id.file_hash, data, FMode::chk_if_exists | FMode::overwrite | FMode::chk_file_hash);
if (save_res.is_error()) {
promise(std::move(save_res));
}
auto sz = data.size();
auto lev = bb.alloc<log::NewState>(id.id, id.root_hash, id.file_hash, data.size(), authority & 0xff);
if (sz <= 8) {
std::memcpy(lev->last_bytes, data.data(), sz);
} else {
std::memcpy(lev->last_bytes, data.data() + sz - 8, 8);
}
lev.commit();
bb.flush();
promise(td::Unit{});
}
td::Status BlockDbImpl::load_data(FileInfo& file_info, bool force) {
if (!file_info.data.is_null() && !force) {
return td::Status::OK();
}
if (file_info.blk.file_hash.is_zero()) {
return td::Status::Error("cannot load a block file without knowing its file hash");
}
auto it = file_cache.find(file_info.blk.file_hash);
if (it != file_cache.end() && !force) {
file_info.data = it->second.clone();
return td::Status::OK();
}
std::string filename = compute_db_filename(file_info.blk.file_hash);
auto res = load_binary_file(filename);
if (res.is_error()) {
return res.move_as_error();
}
file_info.data = res.move_as_ok();
file_cache_insert(file_info.blk.file_hash, file_info.data);
return td::Status::OK();
}
FileInfo FileInfo::clone() const {
return FileInfo{*this};
}
FileInfo::FileInfo(const FileInfo& other)
: td::CntObject()
, blk(other.blk)
, type(other.type)
, status(other.status)
, file_size(other.file_size)
, data(other.data.clone()) {
}
FileInfo* FileInfo::make_copy() const {
return new FileInfo(*this);
}
} // namespace block

164
crypto/block/block-db.h Normal file
View file

@ -0,0 +1,164 @@
/*
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
*/
#pragma once
#include "td/utils/int_types.h"
#include "td/utils/buffer.h"
#include "td/actor/actor.h"
#include "ton/ton-types.h"
#include "crypto/common/refcnt.hpp"
#include "crypto/vm/cells.h"
namespace block {
using FileHash = ton::Bits256;
using RootHash = ton::Bits256;
using td::Ref;
struct ZerostateInfo {
td::BufferSlice data;
std::string filename;
FileHash file_hash;
RootHash root_hash;
ZerostateInfo() {
file_hash.set_zero();
root_hash.set_zero();
}
ZerostateInfo(RootHash hash, std::string _filename) : filename(_filename), root_hash(std::move(hash)) {
file_hash.set_zero();
}
ZerostateInfo(std::string _filename) : filename(_filename) {
file_hash.set_zero();
root_hash.set_zero();
}
ZerostateInfo(RootHash hash, FileHash fhash) : file_hash(std::move(fhash)), root_hash(std::move(hash)) {
}
bool has_file_hash() const {
return !file_hash.is_zero();
}
bool has_root_hash() const {
return !root_hash.is_zero();
}
bool has_filename() const {
return !filename.empty();
}
bool has_data() const {
return !data.empty();
}
td::Status base_check();
};
enum class FileType {
unknown = 0,
unknown_boc = 1,
block = 2,
block_candidate = 3,
collated_data = 4,
block_signatures = 5,
state = 6,
out_queue = 7
};
struct FileInfo : public td::CntObject {
ton::BlockIdExt blk;
FileType type;
int status;
long long file_size;
td::BufferSlice data;
FileInfo() : type(FileType::unknown), status(0), file_size(-1) {
blk.file_hash.set_zero();
blk.root_hash.set_zero();
}
FileInfo(FileType _type, const ton::BlockId& _id, int _status, const FileHash& _fhash, long long _fsize = -1)
: blk(_id, _fhash), type(_type), status(_status), file_size(_fsize) {
blk.root_hash.set_zero();
}
FileInfo(FileType _type, const ton::BlockId& _id, int _status, const FileHash& _fhash, const RootHash& _rhash,
long long _fsize = -1)
: blk(_id, _rhash, _fhash), type(_type), status(_status), file_size(_fsize) {
}
FileInfo(FileType _type, const ton::BlockId& _id, int _status, const FileHash& _fhash, const RootHash& _rhash,
td::BufferSlice _data)
: blk(_id, _rhash, _fhash), type(_type), status(_status), file_size(_data.size()), data(std::move(_data)) {
}
FileInfo(FileInfo&& other) = default;
FileInfo clone() const;
FileInfo* make_copy() const override;
private:
FileInfo(const FileInfo& other);
};
struct OutputQueueInfoDescr : public td::CntObject {
ton::BlockId id;
RootHash block_hash;
RootHash state_hash;
RootHash output_queue_info_hash;
td::Ref<vm::Cell> queue_info;
OutputQueueInfoDescr(ton::BlockId _id, const RootHash& _bhash, const RootHash& _shash, Ref<vm::Cell> _qinfo)
: id(_id)
, block_hash(_bhash)
, state_hash(_shash)
, output_queue_info_hash(_qinfo->get_hash().bits())
, queue_info(std::move(_qinfo)) {
}
OutputQueueInfoDescr(ton::BlockId _id, td::ConstBitPtr _bhash, td::ConstBitPtr _shash, Ref<vm::Cell> _qinfo)
: id(_id)
, block_hash(_bhash)
, state_hash(_shash)
, output_queue_info_hash(_qinfo->get_hash().bits())
, queue_info(std::move(_qinfo)) {
}
};
class BlockDb : public td::actor::Actor {
public:
BlockDb() = default;
virtual ~BlockDb() = default;
static td::Result<td::actor::ActorOwn<BlockDb>> create_block_db(std::string _base_dir,
std::unique_ptr<ZerostateInfo> _zstate = nullptr,
bool _allow_uninit = false, int _depth = 4,
std::string _binlog_name = "");
// authority: 0 = standard (inclusion in mc block), 1 = validator (by 2/3 validator signatures)
virtual void get_top_block_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) = 0;
virtual void get_top_block_state_id(ton::ShardIdFull shard, int authority, td::Promise<ton::BlockIdExt> promise) = 0;
virtual void get_block_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) = 0;
virtual void get_state_by_id(ton::BlockId blk_id, bool need_data, td::Promise<td::Ref<FileInfo>> promise) = 0;
virtual void get_out_queue_info_by_id(ton::BlockId blk_id, td::Promise<td::Ref<OutputQueueInfoDescr>> promise) = 0;
virtual void get_object_by_file_hash(FileHash file_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) = 0;
virtual void get_object_by_root_hash(RootHash root_hash, bool need_data, bool force_file_load,
td::Promise<td::Ref<FileInfo>> promise) = 0;
virtual void save_new_block(ton::BlockIdExt blk_id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) = 0;
virtual void save_new_state(ton::BlockIdExt state_id, td::BufferSlice data, int authority,
td::Promise<td::Unit> promise) = 0;
};
bool parse_hash_string(std::string arg, RootHash& res);
FileHash compute_file_hash(const td::BufferSlice& data);
FileHash compute_file_hash(td::Slice data);
td::Result<td::BufferSlice> load_binary_file(std::string filename, td::int64 max_size = 0);
td::Status save_binary_file(std::string filename, const td::BufferSlice& data, unsigned long long max_size = 0);
std::string compute_db_filename(std::string base_dir, const FileHash& file_hash, int depth = 4);
std::string compute_db_tmp_filename(std::string base_dir, const FileHash& file_hash, int i = 0, bool makedirs = true,
int depth = 4);
} // namespace block

2135
crypto/block/block-parse.cpp Normal file

File diff suppressed because it is too large Load diff

1081
crypto/block/block-parse.h Normal file

File diff suppressed because it is too large Load diff

1923
crypto/block/block.cpp Normal file

File diff suppressed because it is too large Load diff

569
crypto/block/block.h Normal file
View file

@ -0,0 +1,569 @@
/*
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
*/
#pragma once
#include "common/refcnt.hpp"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/dict.h"
#include "vm/boc.h"
#include "vm/stack.hpp"
#include <ostream>
#include "tl/tlblib.hpp"
#include "td/utils/bits.h"
#include "td/utils/StringBuilder.h"
#include "ton/ton-types.h"
namespace block {
using td::Ref;
struct StdAddress {
ton::WorkchainId workchain{ton::workchainInvalid};
bool bounceable{true}; // addresses must be bounceable by default
bool testnet{false};
ton::StdSmcAddress addr;
StdAddress() = default;
StdAddress(ton::WorkchainId _wc, const ton::StdSmcAddress& _addr, bool _bounce = true, bool _testnet = false)
: workchain(_wc), bounceable(_bounce), testnet(_testnet), addr(_addr) {
}
StdAddress(ton::WorkchainId _wc, td::ConstBitPtr _addr, bool _bounce = true, bool _testnet = false)
: workchain(_wc), bounceable(_bounce), testnet(_testnet), addr(_addr) {
}
explicit StdAddress(std::string serialized);
explicit StdAddress(td::Slice from);
bool is_valid() const {
return workchain != ton::workchainInvalid;
}
bool invalidate() {
workchain = ton::workchainInvalid;
return false;
}
std::string rserialize(bool base64_url = false) const;
bool rserialize_to(td::MutableSlice to, bool base64_url = false) const;
bool rserialize_to(char to[48], bool base64_url = false) const;
bool rdeserialize(td::Slice from);
bool rdeserialize(std::string from);
bool rdeserialize(const char from[48]);
bool parse_addr(td::Slice acc_string);
bool operator==(const StdAddress& other) const;
static td::Result<StdAddress> parse(td::Slice acc_string);
};
inline td::StringBuilder& operator<<(td::StringBuilder& sb, const StdAddress& addr) {
return sb << addr.workchain << " : " << addr.addr.to_hex();
}
bool parse_std_account_addr(td::Slice acc_string, ton::WorkchainId& wc, ton::StdSmcAddress& addr,
bool* bounceable = nullptr, bool* testnet_only = nullptr);
struct ShardId {
ton::WorkchainId workchain_id;
int shard_pfx_len;
unsigned long long shard_pfx;
ShardId(ton::WorkchainId wc_id = ton::workchainInvalid)
: workchain_id(wc_id), shard_pfx_len(0), shard_pfx(1ULL << 63) {
}
ShardId(ton::WorkchainId wc_id, unsigned long long sh_pfx);
ShardId(ton::ShardIdFull ton_shard);
ShardId(ton::BlockId ton_block);
ShardId(const ton::BlockIdExt& ton_block);
ShardId(ton::WorkchainId wc_id, unsigned long long sh_pfx, int sh_pfx_len);
ShardId(vm::CellSlice& cs) {
deserialize(cs);
}
ShardId(Ref<vm::CellSlice> cs_ref) {
vm::CellSlice cs{*cs_ref};
deserialize(cs);
}
explicit operator ton::ShardIdFull() const {
return ton::ShardIdFull{workchain_id, shard_pfx};
}
bool operator==(const ShardId& other) const {
return workchain_id == other.workchain_id && shard_pfx == other.shard_pfx;
}
void invalidate() {
workchain_id = ton::workchainInvalid;
shard_pfx_len = 0;
}
bool is_valid() const {
return workchain_id != ton::workchainInvalid;
}
void show(std::ostream& os) const;
std::string to_str() const;
bool serialize(vm::CellBuilder& cb) const;
bool deserialize(vm::CellSlice& cs);
private:
void init();
};
struct EnqueuedMsgDescr {
ton::AccountIdPrefixFull src_prefix_, cur_prefix_, next_prefix_, dest_prefix_;
ton::LogicalTime lt_;
ton::LogicalTime enqueued_lt_;
ton::Bits256 hash_;
Ref<vm::Cell> msg_;
Ref<vm::Cell> msg_env_;
EnqueuedMsgDescr() = default;
EnqueuedMsgDescr(ton::AccountIdPrefixFull cur_pfx, ton::AccountIdPrefixFull next_pfx, ton::LogicalTime lt,
ton::LogicalTime enqueued_lt, td::ConstBitPtr hash)
: cur_prefix_(cur_pfx), next_prefix_(next_pfx), lt_(lt), enqueued_lt_(enqueued_lt), hash_(hash) {
}
bool is_valid() const {
return next_prefix_.is_valid();
}
bool check_key(td::ConstBitPtr key) const;
bool invalidate() {
next_prefix_.workchain = cur_prefix_.workchain = ton::workchainInvalid;
return false;
}
bool unpack(vm::CellSlice& cs);
};
using compute_shard_end_lt_func_t = std::function<ton::LogicalTime(ton::AccountIdPrefixFull)>;
struct MsgProcessedUpto {
ton::ShardId shard;
ton::BlockSeqno mc_seqno;
ton::LogicalTime last_inmsg_lt;
ton::Bits256 last_inmsg_hash;
compute_shard_end_lt_func_t compute_shard_end_lt;
MsgProcessedUpto() = default;
MsgProcessedUpto(ton::ShardId _shard, ton::BlockSeqno _mcseqno, ton::LogicalTime _lt, td::ConstBitPtr _hash)
: shard(_shard), mc_seqno(_mcseqno), last_inmsg_lt(_lt), last_inmsg_hash(_hash) {
}
bool operator<(const MsgProcessedUpto& other) const & {
return shard < other.shard || (shard == other.shard && mc_seqno < other.mc_seqno);
}
bool contains(const MsgProcessedUpto& other) const &;
bool contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
ton::BlockSeqno other_mc_seqno) const &;
// NB: this is for checking whether we have already imported an internal message
bool already_processed(const EnqueuedMsgDescr& msg) const;
};
struct MsgProcessedUptoCollection {
ton::ShardIdFull owner;
bool valid{false};
std::vector<MsgProcessedUpto> list;
MsgProcessedUptoCollection(ton::ShardIdFull _owner) : owner(_owner) {
}
MsgProcessedUptoCollection(ton::ShardIdFull _owner, Ref<vm::CellSlice> cs_ref);
static std::unique_ptr<MsgProcessedUptoCollection> unpack(ton::ShardIdFull _owner, Ref<vm::CellSlice> cs_ref);
bool is_valid() const {
return valid;
}
bool insert(ton::BlockSeqno mc_seqno, ton::LogicalTime last_proc_lt, td::ConstBitPtr last_proc_hash);
bool insert_infty(ton::BlockSeqno mc_seqno, ton::LogicalTime last_proc_lt = ~0ULL);
bool compactify();
bool pack(vm::CellBuilder& cb);
bool is_reduced() const;
bool contains(const MsgProcessedUpto& other) const;
bool contains(const MsgProcessedUptoCollection& other) const;
const MsgProcessedUpto* is_simple_update_of(const MsgProcessedUptoCollection& other, bool& ok) const;
ton::BlockSeqno min_mc_seqno() const;
bool split(ton::ShardIdFull new_owner);
bool combine_with(const MsgProcessedUptoCollection& other);
// NB: this is for checking whether we have already imported an internal message
bool already_processed(const EnqueuedMsgDescr& msg) const;
bool for_each_mcseqno(std::function<bool(ton::BlockSeqno)>) const;
};
struct ParamLimits {
enum { limits_cnt = 4 };
enum { cl_underload = 0, cl_normal = 1, cl_soft = 2, cl_medium = 3, cl_hard = 4 };
ParamLimits() = default;
ParamLimits(td::uint32 underload, td::uint32 soft_lim, td::uint32 hard_lim)
: limits_{underload, soft_lim, (soft_lim + hard_lim) / 2, hard_lim} {
}
td::uint32 underload() const {
return limits_[0];
}
td::uint32 soft() const {
return limits_[1];
}
td::uint32 hard() const {
return limits_[3];
}
bool compute_medium_limit() {
limits_[2] = soft() + ((hard() - soft()) >> 1);
return true;
}
bool deserialize(vm::CellSlice& cs);
int classify(td::uint64 value) const;
bool fits(unsigned cls, td::uint64 value) const;
private:
std::array<td::uint32, limits_cnt> limits_;
};
struct BlockLimits {
ParamLimits bytes, gas, lt_delta;
ton::LogicalTime start_lt{0};
const vm::CellUsageTree* usage_tree{nullptr};
bool deserialize(vm::CellSlice& cs);
int classify_size(td::uint64 size) const;
int classify_gas(td::uint64 gas) const;
int classify_lt(ton::LogicalTime lt) const;
int classify(td::uint64 size, td::uint64 gas, ton::LogicalTime lt) const;
bool fits(unsigned cls, td::uint64 size, td::uint64 gas, ton::LogicalTime lt) const;
};
struct BlockLimitStatus {
const BlockLimits& limits;
ton::LogicalTime cur_lt;
td::uint64 gas_used{};
vm::NewCellStorageStat st_stat;
unsigned accounts{}, transactions{};
BlockLimitStatus(const BlockLimits& limits_, ton::LogicalTime lt = 0)
: limits(limits_), cur_lt(std::max(limits_.start_lt, lt)) {
}
void reset() {
cur_lt = limits.start_lt;
st_stat.set_zero();
transactions = accounts = 0;
gas_used = 0;
}
td::uint64 estimate_block_size(const vm::NewCellStorageStat::Stat* extra = nullptr) const;
int classify() const;
bool fits(unsigned cls) const;
bool would_fit(unsigned cls, ton::LogicalTime end_lt, td::uint64 more_gas,
const vm::NewCellStorageStat::Stat* extra = nullptr) const;
bool add_cell(Ref<vm::Cell> cell) {
st_stat.add_cell(std::move(cell));
return true;
}
bool add_proof(Ref<vm::Cell> cell) {
st_stat.add_proof(std::move(cell), limits.usage_tree);
return true;
}
bool update_lt(ton::LogicalTime lt) {
cur_lt = std::max(lt, cur_lt);
return true;
}
bool update_gas(td::uint64 more_gas) {
gas_used += more_gas;
return true;
}
bool add_transaction(unsigned cnt = 1) {
transactions += cnt;
return true;
}
bool add_account(unsigned cnt = 1) {
accounts += cnt;
return true;
}
};
namespace tlb {
struct CurrencyCollection;
} // namespace tlb
struct CurrencyCollection {
using type_class = block::tlb::CurrencyCollection;
td::RefInt256 grams;
Ref<vm::Cell> extra;
CurrencyCollection() = default;
explicit CurrencyCollection(td::RefInt256 _grams, Ref<vm::Cell> _extra = {})
: grams(std::move(_grams)), extra(std::move(_extra)) {
}
explicit CurrencyCollection(long long _grams, Ref<vm::Cell> _extra = {})
: grams(true, _grams), extra(std::move(_extra)) {
}
bool set_zero() {
grams = td::RefInt256{true, 0};
extra.clear();
return true;
}
static CurrencyCollection zero() {
return CurrencyCollection(td::RefInt256{true, 0});
}
bool is_valid() const {
return grams.not_null();
}
bool is_zero() const {
return is_valid() && extra.is_null() && !td::sgn(grams);
}
bool has_extra() const {
return extra.not_null();
}
bool invalidate() {
extra.clear();
grams.clear();
return false;
}
bool validate() const;
bool validate_extra() const;
bool operator==(const CurrencyCollection& other) const;
bool operator!=(const CurrencyCollection& other) const {
return !operator==(other);
}
bool operator==(td::RefInt256 other_grams) const {
return is_valid() && !has_extra() && !td::cmp(grams, other_grams);
}
bool operator!=(td::RefInt256 other_grams) const {
return !operator==(std::move(other_grams));
}
bool operator>=(const CurrencyCollection& other) const;
bool operator<=(const CurrencyCollection& other) const {
return other >= *this;
}
static bool add(const CurrencyCollection& a, const CurrencyCollection& b, CurrencyCollection& c);
static bool add(const CurrencyCollection& a, CurrencyCollection&& b, CurrencyCollection& c);
CurrencyCollection& operator+=(const CurrencyCollection& other);
CurrencyCollection& operator+=(CurrencyCollection&& other);
CurrencyCollection& operator+=(td::RefInt256 other_grams);
CurrencyCollection operator+(const CurrencyCollection& other) const;
CurrencyCollection operator+(CurrencyCollection&& other) const;
CurrencyCollection operator+(td::RefInt256 other_grams);
static bool sub(const CurrencyCollection& a, const CurrencyCollection& b, CurrencyCollection& c);
static bool sub(const CurrencyCollection& a, CurrencyCollection&& b, CurrencyCollection& c);
CurrencyCollection& operator-=(const CurrencyCollection& other);
CurrencyCollection& operator-=(CurrencyCollection&& other);
CurrencyCollection& operator-=(td::RefInt256 other_grams);
CurrencyCollection operator-(const CurrencyCollection& other) const;
CurrencyCollection operator-(CurrencyCollection&& other) const;
CurrencyCollection operator-(td::RefInt256 other_grams) const;
bool store(vm::CellBuilder& cb) const;
bool store_or_zero(vm::CellBuilder& cb) const;
bool fetch(vm::CellSlice& cs);
bool fetch_exact(vm::CellSlice& cs);
bool unpack(Ref<vm::CellSlice> csr);
bool validate_unpack(Ref<vm::CellSlice> csr);
Ref<vm::CellSlice> pack() const;
bool pack_to(Ref<vm::CellSlice>& csr) const {
return (csr = pack()).not_null();
}
Ref<vm::Tuple> as_vm_tuple() const {
if (is_valid()) {
return vm::make_tuple_ref(grams, vm::StackEntry::maybe(extra));
} else {
return {};
}
}
bool show(std::ostream& os) const;
std::string to_str() const;
};
std::ostream& operator<<(std::ostream& os, const CurrencyCollection& cc);
struct ShardState {
enum { verbosity = 0 };
ton::BlockIdExt id_;
Ref<vm::Cell> root_;
int global_id_;
ton::UnixTime utime_;
ton::LogicalTime lt_;
ton::BlockSeqno mc_blk_seqno_, min_ref_mc_seqno_;
ton::BlockIdExt mc_blk_ref_;
ton::LogicalTime mc_blk_lt_;
bool before_split_{false};
std::unique_ptr<vm::AugmentedDictionary> account_dict_;
std::unique_ptr<vm::Dictionary> shard_libraries_;
Ref<vm::Cell> mc_state_extra_;
td::uint64 overload_history_{0}, underload_history_{0};
CurrencyCollection total_balance_, total_validator_fees_, global_balance_;
std::unique_ptr<vm::AugmentedDictionary> out_msg_queue_;
std::unique_ptr<vm::Dictionary> ihr_pending_;
std::shared_ptr<block::MsgProcessedUptoCollection> processed_upto_;
bool is_valid() const {
return id_.is_valid();
}
bool is_masterchain() const {
return id_.is_masterchain();
}
bool invalidate() {
id_.invalidate();
return false;
}
td::Status unpack_state(ton::BlockIdExt id, Ref<vm::Cell> state_root);
td::Status 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);
td::Status merge_with(ShardState& sib);
td::Result<std::unique_ptr<vm::AugmentedDictionary>> compute_split_out_msg_queue(ton::ShardIdFull subshard);
td::Result<std::shared_ptr<block::MsgProcessedUptoCollection>> compute_split_processed_upto(
ton::ShardIdFull subshard);
td::Status split(ton::ShardIdFull subshard);
td::Status unpack_out_msg_queue_info(Ref<vm::Cell> out_msg_queue_info);
bool clear_load_history() {
overload_history_ = underload_history_ = 0;
return true;
}
bool clear_load_history_if(bool cond) {
return !cond || clear_load_history();
}
td::Status check_before_split(bool before_split) const;
td::Status check_global_id(int req_global_id) const;
td::Status check_mc_blk_seqno(ton::BlockSeqno last_mc_block_seqno) const;
bool update_prev_utime_lt(ton::UnixTime& prev_utime, ton::LogicalTime& prev_lt) const;
bool for_each_mcseqno(std::function<bool(ton::BlockSeqno)> func) const {
return processed_upto_ && processed_upto_->for_each_mcseqno(std::move(func));
}
};
struct ValueFlow {
struct SetZero {};
CurrencyCollection from_prev_blk, to_next_blk, imported, exported, fees_collected, fees_imported, recovered, created,
minted;
ValueFlow() = default;
ValueFlow(SetZero)
: from_prev_blk{0}
, to_next_blk{0}
, imported{0}
, exported{0}
, fees_collected{0}
, fees_imported{0}
, recovered{0}
, created{0}
, minted{0} {
}
bool is_valid() const {
return from_prev_blk.is_valid() && minted.is_valid();
}
bool validate() const;
bool invalidate() {
return from_prev_blk.invalidate();
}
bool set_zero();
bool store(vm::CellBuilder& cb) const;
bool fetch(vm::CellSlice& cs);
bool unpack(Ref<vm::CellSlice> csr);
bool show(std::ostream& os) const;
std::string to_str() const;
private:
bool show_one(std::ostream& os, const char* str, const CurrencyCollection& cc) const;
};
std::ostream& operator<<(std::ostream& os, const ValueFlow& vflow);
struct BlkProofLink {
ton::BlockIdExt from, to;
bool is_key{false}, is_fwd{false};
Ref<vm::Cell> dest_proof, shard_proof, proof;
ton::CatchainSeqno cc_seqno{0};
td::uint32 validator_set_hash{0};
std::vector<ton::BlockSignature> signatures;
BlkProofLink(ton::BlockIdExt _from, ton::BlockIdExt _to, bool _iskey = false)
: from(_from), to(_to), is_key(_iskey), is_fwd(to.seqno() > from.seqno()) {
}
};
struct BlkProofChain {
ton::BlockIdExt from, to;
int mode;
std::vector<BlkProofLink> links;
std::size_t link_count() const {
return links.size();
}
BlkProofChain(ton::BlockIdExt _from, ton::BlockIdExt _to, int _mode) : from(_from), to(_to), mode(_mode) {
}
};
int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard);
std::ostream& operator<<(std::ostream& os, const ShardId& shard_id);
bool pack_std_smc_addr_to(char result[48], bool base64_url, ton::WorkchainId wc, const ton::StdSmcAddress& addr,
bool bounceable, bool testnet);
std::string pack_std_smc_addr(bool base64_url, ton::WorkchainId wc, const ton::StdSmcAddress& addr, bool bounceable,
bool testnet);
bool unpack_std_smc_addr(const char packed[48], ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet);
bool unpack_std_smc_addr(td::Slice packed, ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet);
bool unpack_std_smc_addr(std::string packed, ton::WorkchainId& wc, ton::StdSmcAddress& addr, bool& bounceable,
bool& testnet);
bool store_UInt7(vm::CellBuilder& cb, unsigned long long value);
bool store_UInt7(vm::CellBuilder& cb, unsigned long long value1, unsigned long long value2);
bool store_Maybe_Grams(vm::CellBuilder& cb, td::RefInt256 value);
bool store_Maybe_Grams_nz(vm::CellBuilder& cb, td::RefInt256 value);
bool store_CurrencyCollection(vm::CellBuilder& cb, td::RefInt256 value, Ref<vm::Cell> extra);
bool fetch_CurrencyCollection(vm::CellSlice& cs, td::RefInt256& value, Ref<vm::Cell>& extra, bool inexact = false);
bool unpack_CurrencyCollection(Ref<vm::CellSlice> csr, td::RefInt256& value, Ref<vm::Cell>& extra);
bool valid_library_collection(Ref<vm::Cell> cell, bool catch_errors = true);
bool valid_config_data(Ref<vm::Cell> cell, const td::BitArray<256>& addr, bool catch_errors = true,
bool relax_par0 = false);
bool add_extra_currency(Ref<vm::Cell> extra1, Ref<vm::Cell> extra2, Ref<vm::Cell>& res);
bool sub_extra_currency(Ref<vm::Cell> extra1, Ref<vm::Cell> extra2, Ref<vm::Cell>& res);
ton::AccountIdPrefixFull interpolate_addr(const ton::AccountIdPrefixFull& src, const ton::AccountIdPrefixFull& dest,
int used_dest_bits);
bool interpolate_addr_to(const ton::AccountIdPrefixFull& src, const ton::AccountIdPrefixFull& dest, int used_dest_bits,
ton::AccountIdPrefixFull& res);
// 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 = 0);
bool compute_out_msg_queue_key(Ref<vm::Cell> msg_env, td::BitArray<352>& key);
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 = nullptr);
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 = nullptr);
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 = nullptr);
std::unique_ptr<vm::AugmentedDictionary> get_prev_blocks_dict(Ref<vm::Cell> state_root);
bool get_old_mc_block_id(vm::AugmentedDictionary* prev_blocks_dict, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt = nullptr);
bool get_old_mc_block_id(vm::AugmentedDictionary& prev_blocks_dict, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt = nullptr);
bool unpack_old_mc_block_id(Ref<vm::CellSlice> old_blk_info, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt = nullptr);
bool check_old_mc_block_id(vm::AugmentedDictionary* prev_blocks_dict, const ton::BlockIdExt& blkid);
bool check_old_mc_block_id(vm::AugmentedDictionary& prev_blocks_dict, const ton::BlockIdExt& blkid);
td::Result<Ref<vm::Cell>> get_block_transaction(Ref<vm::Cell> block_root, ton::WorkchainId workchain,
const ton::StdSmcAddress& addr, ton::LogicalTime lt);
td::Result<Ref<vm::Cell>> get_block_transaction_try(Ref<vm::Cell> block_root, ton::WorkchainId workchain,
const ton::StdSmcAddress& addr, ton::LogicalTime lt);
bool get_transaction_in_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell>& in_msg);
bool is_transaction_in_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell> msg);
bool is_transaction_out_msg(Ref<vm::Cell> trans_ref, Ref<vm::Cell> msg);
bool get_transaction_id(Ref<vm::Cell> trans_ref, ton::StdSmcAddress& account_addr, ton::LogicalTime& lt);
bool get_transaction_owner(Ref<vm::Cell> trans_ref, ton::StdSmcAddress& addr);
td::uint32 compute_validator_set_hash(ton::CatchainSeqno cc_seqno, ton::ShardIdFull from,
const std::vector<ton::ValidatorDescr>& nodes);
td::Result<Ref<vm::Cell>> get_config_data_from_smc(Ref<vm::Cell> acc_root);
td::Result<Ref<vm::Cell>> get_config_data_from_smc(Ref<vm::CellSlice> acc_csr);
bool important_config_parameters_changed(Ref<vm::Cell> old_cfg_root, Ref<vm::Cell> new_cfg_root, bool coarse = false);
bool is_public_library(td::ConstBitPtr key, Ref<vm::CellSlice> val);
bool parse_hex_hash(const char* str, const char* end, td::Bits256& hash);
bool parse_hex_hash(td::Slice str, td::Bits256& hash);
bool parse_block_id_ext(const char* str, const char* end, ton::BlockIdExt& blkid);
bool parse_block_id_ext(td::Slice str, ton::BlockIdExt& blkid);
} // namespace block

662
crypto/block/block.tlb Normal file
View file

@ -0,0 +1,662 @@
unit$_ = Unit;
true$_ = True;
// EMPTY False;
bool_false$0 = Bool;
bool_true$1 = Bool;
bool_false$0 = BoolFalse;
bool_true$1 = BoolTrue;
nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
bit$_ (## 1) = Bit;
/*
*
* FROM hashmap.tlb
*
*/
// ordinary Hashmap / HashmapE, with fixed length keys
//
hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
{n = (~m) + l} node:(HashmapNode m X) = Hashmap n X;
hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X;
hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X)
right:^(Hashmap n X) = HashmapNode (n + 1) X;
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m;
hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m;
hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m;
unary_zero$0 = Unary ~0;
unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1);
hme_empty$0 {n:#} {X:Type} = HashmapE n X;
hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X;
// true#_ = True;
_ {n:#} _:(Hashmap n True) = BitstringSet n;
// HashmapAug, hashmap with an extra value
// (augmentation) of type Y at every node
//
ahm_edge#_ {n:#} {X:Type} {Y:Type} {l:#} {m:#}
label:(HmLabel ~l n) {n = (~m) + l}
node:(HashmapAugNode m X Y) = HashmapAug n X Y;
ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y;
ahmn_fork#_ {n:#} {X:Type} {Y:Type} left:^(HashmapAug n X Y)
right:^(HashmapAug n X Y) extra:Y = HashmapAugNode (n + 1) X Y;
ahme_empty$0 {n:#} {X:Type} {Y:Type} extra:Y
= HashmapAugE n X Y;
ahme_root$1 {n:#} {X:Type} {Y:Type} root:^(HashmapAug n X Y)
extra:Y = HashmapAugE n X Y;
// VarHashmap / VarHashmapE, with variable-length keys
//
vhm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
{n = (~m) + l} node:(VarHashmapNode m X)
= VarHashmap n X;
vhmn_leaf$00 {n:#} {X:Type} value:X = VarHashmapNode n X;
vhmn_fork$01 {n:#} {X:Type} left:^(VarHashmap n X)
right:^(VarHashmap n X) value:(Maybe X)
= VarHashmapNode (n + 1) X;
vhmn_cont$1 {n:#} {X:Type} branch:Bit child:^(VarHashmap n X)
value:X = VarHashmapNode (n + 1) X;
// nothing$0 {X:Type} = Maybe X;
// just$1 {X:Type} value:X = Maybe X;
vhme_empty$0 {n:#} {X:Type} = VarHashmapE n X;
vhme_root$1 {n:#} {X:Type} root:^(VarHashmap n X)
= VarHashmapE n X;
//
// PfxHashmap / PfxHashmapE, with variable-length keys
// constituting a prefix code
//
phm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
{n = (~m) + l} node:(PfxHashmapNode m X)
= PfxHashmap n X;
phmn_leaf$0 {n:#} {X:Type} value:X = PfxHashmapNode n X;
phmn_fork$1 {n:#} {X:Type} left:^(PfxHashmap n X)
right:^(PfxHashmap n X) = PfxHashmapNode (n + 1) X;
phme_empty$0 {n:#} {X:Type} = PfxHashmapE n X;
phme_root$1 {n:#} {X:Type} root:^(PfxHashmap n X)
= PfxHashmapE n X;
/*
*
* END hashmap.tlb
*
*/
//
// TON BLOCK LAYOUT
//
addr_none$00 = MsgAddressExt;
addr_extern$01 len:(## 9) external_address:(bits len)
= MsgAddressExt;
anycast_info$_ depth:(#<= 30) { depth >= 1 }
rewrite_pfx:(bits depth) = Anycast;
addr_std$10 anycast:(Maybe Anycast)
workchain_id:int8 address:bits256 = MsgAddressInt;
addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
_ _:MsgAddressInt = MsgAddress;
_ _:MsgAddressExt = MsgAddress;
//
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8))
= VarUInteger n;
var_int$_ {n:#} len:(#< n) value:(int (len * 8))
= VarInteger n;
nanograms$_ amount:(VarUInteger 16) = Grams;
//
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
= ExtraCurrencyCollection;
currencies$_ grams:Grams other:ExtraCurrencyCollection
= CurrencyCollection;
//
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddressInt dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
import_fee:Grams = CommonMsgInfo;
ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddress dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
tick_tock$_ tick:Bool tock:Bool = TickTock;
_ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
code:(Maybe ^Cell) data:(Maybe ^Cell)
library:(HashmapE 256 SimpleLib) = StateInit;
simple_lib$_ public:Bool root:^Cell = SimpleLib;
message$_ {X:Type} info:CommonMsgInfo
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = Message X;
message$_ {X:Type} info:CommonMsgInfoRelaxed
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = MessageRelaxed X;
//
interm_addr_regular$0 use_dest_bits:(#<= 96)
= IntermediateAddress;
interm_addr_simple$10 workchain_id:int8 addr_pfx:uint64
= IntermediateAddress;
interm_addr_ext$11 workchain_id:int32 addr_pfx:uint64
= IntermediateAddress;
msg_envelope#4 cur_addr:IntermediateAddress
next_addr:IntermediateAddress fwd_fee_remaining:Grams
msg:^(Message Any) = MsgEnvelope;
//
msg_import_ext$000 msg:^(Message Any) transaction:^Transaction
= InMsg;
msg_import_ihr$010 msg:^(Message Any) transaction:^Transaction
ihr_fee:Grams proof_created:^Cell = InMsg;
msg_import_imm$011 in_msg:^MsgEnvelope
transaction:^Transaction fwd_fee:Grams = InMsg;
msg_import_fin$100 in_msg:^MsgEnvelope
transaction:^Transaction fwd_fee:Grams = InMsg;
msg_import_tr$101 in_msg:^MsgEnvelope out_msg:^MsgEnvelope
transit_fee:Grams = InMsg;
msg_discard_fin$110 in_msg:^MsgEnvelope transaction_id:uint64
fwd_fee:Grams = InMsg;
msg_discard_tr$111 in_msg:^MsgEnvelope transaction_id:uint64
fwd_fee:Grams proof_delivered:^Cell = InMsg;
//
import_fees$_ fees_collected:Grams
value_imported:CurrencyCollection = ImportFees;
_ (HashmapAugE 256 InMsg ImportFees) = InMsgDescr;
msg_export_ext$000 msg:^(Message Any)
transaction:^Transaction = OutMsg;
msg_export_imm$010 out_msg:^MsgEnvelope
transaction:^Transaction reimport:^InMsg = OutMsg;
msg_export_new$001 out_msg:^MsgEnvelope
transaction:^Transaction = OutMsg;
msg_export_tr$011 out_msg:^MsgEnvelope
imported:^InMsg = OutMsg;
msg_export_deq$110 out_msg:^MsgEnvelope // out_msg_hash:bits256 ?
import_block_lt:uint64 = OutMsg;
msg_export_tr_req$111 out_msg:^MsgEnvelope
imported:^InMsg = OutMsg;
msg_export_deq_imm$100 out_msg:^MsgEnvelope
reimport:^InMsg = OutMsg;
_ enqueued_lt:uint64 out_msg:^MsgEnvelope = EnqueuedMsg;
_ (HashmapAugE 256 OutMsg CurrencyCollection) = OutMsgDescr;
_ (HashmapAugE 352 EnqueuedMsg uint64) = OutMsgQueue;
processed_upto$_ last_msg_lt:uint64 last_msg_hash:bits256 = ProcessedUpto;
// key is [ shard:uint64 mc_seqno:uint32 ]
_ (HashmapE 96 ProcessedUpto) = ProcessedInfo;
ihr_pending$_ import_lt:uint64 = IhrPendingSince;
_ (HashmapE 320 IhrPendingSince) = IhrPendingInfo;
_ out_queue:OutMsgQueue proc_info:ProcessedInfo
ihr_pending:IhrPendingInfo = OutMsgQueueInfo;
//
storage_used$_ cells:(VarUInteger 7) bits:(VarUInteger 7)
public_cells:(VarUInteger 7) = StorageUsed;
storage_used_short$_ cells:(VarUInteger 7)
bits:(VarUInteger 7) = StorageUsedShort;
storage_info$_ used:StorageUsed last_paid:uint32
due_payment:(Maybe Grams) = StorageInfo;
account_none$0 = Account;
account$1 addr:MsgAddressInt storage_stat:StorageInfo
storage:AccountStorage = Account;
account_storage$_ last_trans_lt:uint64
balance:CurrencyCollection state:AccountState
= AccountStorage;
account_uninit$00 = AccountState;
account_active$1 _:StateInit = AccountState;
account_frozen$01 state_hash:bits256 = AccountState;
acc_state_uninit$00 = AccountStatus;
acc_state_frozen$01 = AccountStatus;
acc_state_active$10 = AccountStatus;
acc_state_nonexist$11 = AccountStatus;
/* duplicates
tick_tock$_ tick:Bool tock:Bool = TickTock;
_ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
code:(Maybe ^Cell) data:(Maybe ^Cell)
library:(Maybe ^Cell) = StateInit;
*/
account_descr$_ account:^Account last_trans_hash:bits256
last_trans_lt:uint64 = ShardAccount;
depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection = DepthBalanceInfo;
_ (HashmapAugE 256 ShardAccount DepthBalanceInfo) = ShardAccounts;
transaction$0111 account_addr:bits256 lt:uint64
prev_trans_hash:bits256 prev_trans_lt:uint64 now:uint32
outmsg_cnt:uint15
orig_status:AccountStatus end_status:AccountStatus
^[ in_msg:(Maybe ^(Message Any)) out_msgs:(HashmapE 15 ^(Message Any)) ]
total_fees:CurrencyCollection state_update:^(HASH_UPDATE Account)
description:^TransactionDescr = Transaction;
!merkle_update#02 {X:Type} old_hash:bits256 new_hash:bits256
old:^X new:^X = MERKLE_UPDATE X;
update_hashes#72 {X:Type} old_hash:bits256 new_hash:bits256
= HASH_UPDATE X;
acc_trans#5 account_addr:bits256
transactions:(HashmapAug 64 ^Transaction CurrencyCollection)
state_update:^(HASH_UPDATE Account)
= AccountBlock;
_ (HashmapAugE 256 AccountBlock CurrencyCollection) = ShardAccountBlocks;
//
tr_phase_storage$_ storage_fees_collected:Grams
storage_fees_due:(Maybe Grams)
status_change:AccStatusChange
= TrStoragePhase;
acst_unchanged$0 = AccStatusChange; // x -> x
acst_frozen$10 = AccStatusChange; // init -> frozen
acst_deleted$11 = AccStatusChange; // frozen -> deleted
tr_phase_credit$_ due_fees_collected:(Maybe Grams)
credit:CurrencyCollection = TrCreditPhase;
tr_phase_compute_skipped$0 reason:ComputeSkipReason
= TrComputePhase;
tr_phase_compute_vm$1 success:Bool msg_state_used:Bool
account_activated:Bool gas_fees:Grams
^[ gas_used:(VarUInteger 7)
gas_limit:(VarUInteger 7) gas_credit:(Maybe (VarUInteger 3))
mode:int8 exit_code:int32 exit_arg:(Maybe int32)
vm_steps:uint32
vm_init_state_hash:bits256 vm_final_state_hash:bits256 ]
= TrComputePhase;
cskip_no_state$00 = ComputeSkipReason;
cskip_bad_state$01 = ComputeSkipReason;
cskip_no_gas$10 = ComputeSkipReason;
tr_phase_action$_ success:Bool valid:Bool no_funds:Bool
status_change:AccStatusChange
total_fwd_fees:(Maybe Grams) total_action_fees:(Maybe Grams)
result_code:int32 result_arg:(Maybe int32) tot_actions:uint16
spec_actions:uint16 skipped_actions:uint16 msgs_created:uint16
action_list_hash:bits256 tot_msg_size:StorageUsedShort
= TrActionPhase;
tr_phase_bounce_negfunds$00 = TrBouncePhase;
tr_phase_bounce_nofunds$01 msg_size:StorageUsedShort
req_fwd_fees:Grams = TrBouncePhase;
tr_phase_bounce_ok$1 msg_size:StorageUsedShort
msg_fees:Grams fwd_fees:Grams = TrBouncePhase;
//
trans_ord$0000 credit_first:Bool
storage_ph:(Maybe TrStoragePhase)
credit_ph:(Maybe TrCreditPhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool bounce:(Maybe TrBouncePhase)
destroyed:Bool
= TransactionDescr;
trans_storage$0001 storage_ph:TrStoragePhase
= TransactionDescr;
trans_tick_tock$001 is_tock:Bool storage:TrStoragePhase
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool = TransactionDescr;
//
split_merge_info$_ cur_shard_pfx_len:(## 6)
acc_split_depth:(## 6) this_addr:bits256 sibling_addr:bits256
= SplitMergeInfo;
trans_split_prepare$0100 split_info:SplitMergeInfo
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool
= TransactionDescr;
trans_split_install$0101 split_info:SplitMergeInfo
prepare_transaction:^Transaction
installed:Bool = TransactionDescr;
trans_merge_prepare$0110 split_info:SplitMergeInfo
storage_ph:TrStoragePhase aborted:Bool
= TransactionDescr;
trans_merge_install$0111 split_info:SplitMergeInfo
prepare_transaction:^Transaction
credit_ph:(Maybe TrCreditPhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool destroyed:Bool
= TransactionDescr;
smc_info#076ef1ea actions:uint16 msgs_sent:uint16
unixtime:uint32 block_lt:uint64 trans_lt:uint64
rand_seed:bits256 balance_remaining:CurrencyCollection
myself:MsgAddressInt = SmartContractInfo;
//
//
out_list_empty$_ = OutList 0;
out_list$_ {n:#} prev:^(OutList n) action:OutAction
= OutList (n + 1);
action_send_msg#0ec3c86d mode:(## 8)
out_msg:^(MessageRelaxed Any) = OutAction;
action_set_code#ad4de08e new_code:^Cell = OutAction;
action_reserve_currency#36e6b809 mode:(## 8)
currency:CurrencyCollection = OutAction;
out_list_node$_ prev:^Cell action:OutAction = OutListNode;
//
//
shard_ident$00 shard_pfx_bits:(#<= 60)
workchain_id:int32 shard_prefix:uint64 = ShardIdent;
ext_blk_ref$_ end_lt:uint64
seq_no:uint32 root_hash:bits256 file_hash:bits256
= ExtBlkRef;
block_id_ext$_ shard_id:ShardIdent seq_no:uint32
root_hash:bits256 file_hash:bits256 = BlockIdExt;
master_info$_ master:ExtBlkRef = BlkMasterInfo;
shard_state#9023afe2 global_id:int32
shard_id:ShardIdent
seq_no:uint32 vert_seq_no:#
gen_utime:uint32 gen_lt:uint64
min_ref_mc_seqno:uint32
out_msg_queue_info:^OutMsgQueueInfo
before_split:(## 1)
accounts:^ShardAccounts
^[ overload_history:uint64 underload_history:uint64
total_balance:CurrencyCollection
total_validator_fees:CurrencyCollection
libraries:(HashmapE 256 LibDescr)
master_ref:(Maybe BlkMasterInfo) ]
custom:(Maybe ^McStateExtra)
= ShardStateUnsplit;
_ ShardStateUnsplit = ShardState;
split_state#5f327da5 left:^ShardStateUnsplit right:^ShardStateUnsplit = ShardState;
shared_lib_descr$00 lib:^Cell publishers:(Hashmap 256 True)
= LibDescr;
block_info#9bc7a987 version:uint32
not_master:(## 1)
after_merge:(## 1) before_split:(## 1)
after_split:(## 1)
want_split:Bool want_merge:Bool
key_block:Bool vert_seqno_incr:(## 1)
flags:(## 8)
seq_no:# vert_seq_no:# { vert_seq_no >= vert_seqno_incr }
{ prev_seq_no:# } { ~prev_seq_no + 1 = seq_no }
shard:ShardIdent gen_utime:uint32
start_lt:uint64 end_lt:uint64
gen_validator_list_hash_short:uint32
gen_catchain_seqno:uint32
min_ref_mc_seqno:uint32
prev_key_block_seqno:uint32
master_ref:not_master?^BlkMasterInfo
prev_ref:^(BlkPrevInfo after_merge)
prev_vert_ref:vert_seq_no?^(BlkPrevInfo 0)
= BlockInfo;
prev_blk_info$_ prev:ExtBlkRef = BlkPrevInfo 0;
prev_blks_info$_ prev1:^ExtBlkRef prev2:^ExtBlkRef = BlkPrevInfo 1;
block#11ef55aa global_id:int32
info:^BlockInfo value_flow:^ValueFlow
state_update:^(MERKLE_UPDATE ShardState)
extra:^BlockExtra = Block;
block_extra in_msg_descr:^InMsgDescr
out_msg_descr:^OutMsgDescr
account_blocks:^ShardAccountBlocks
rand_seed:bits256
created_by:bits256
custom:(Maybe ^McBlockExtra) = BlockExtra;
//
value_flow ^[ from_prev_blk:CurrencyCollection
to_next_blk:CurrencyCollection
imported:CurrencyCollection
exported:CurrencyCollection ]
fees_collected:CurrencyCollection
^[
fees_imported:CurrencyCollection
recovered:CurrencyCollection
created:CurrencyCollection
minted:CurrencyCollection
] = ValueFlow;
//
//
bt_leaf$0 {X:Type} leaf:X = BinTree X;
bt_fork$1 {X:Type} left:^(BinTree X) right:^(BinTree X)
= BinTree X;
fsm_none$0 = FutureSplitMerge;
fsm_split$10 split_utime:uint32 interval:uint32 = FutureSplitMerge;
fsm_merge$11 merge_utime:uint32 interval:uint32 = FutureSplitMerge;
shard_descr#b seq_no:uint32 reg_mc_seqno:uint32
start_lt:uint64 end_lt:uint64
root_hash:bits256 file_hash:bits256
before_split:Bool before_merge:Bool
want_split:Bool want_merge:Bool
nx_cc_updated:Bool flags:(## 3) { flags = 0 }
next_catchain_seqno:uint32 next_validator_shard:uint64
min_ref_mc_seqno:uint32 gen_utime:uint32
split_merge_at:FutureSplitMerge
fees_collected:CurrencyCollection
funds_created:CurrencyCollection = ShardDescr;
_ (HashmapE 32 ^(BinTree ShardDescr)) = ShardHashes;
bta_leaf$0 {X:Type} {Y:Type} extra:Y leaf:X = BinTreeAug X Y;
bta_fork$1 {X:Type} {Y:Type} left:^(BinTreeAug X Y)
right:^(BinTreeAug X Y) extra:Y = BinTreeAug X Y;
_ fees:CurrencyCollection create:CurrencyCollection = ShardFeeCreated;
_ (HashmapAugE 96 ShardFeeCreated ShardFeeCreated) = ShardFees;
_ config_addr:bits256 config:^(Hashmap 32 ^Cell)
= ConfigParams;
validator_info$_
validator_list_hash_short:uint32
catchain_seqno:uint32
nx_cc_updated:Bool
= ValidatorInfo;
validator_base_info$_
validator_list_hash_short:uint32
catchain_seqno:uint32
= ValidatorBaseInfo;
_ key:Bool max_end_lt:uint64 = KeyMaxLt;
_ key:Bool blk_ref:ExtBlkRef = KeyExtBlkRef;
_ (HashmapAugE 32 KeyExtBlkRef KeyMaxLt) = OldMcBlocksInfo;
masterchain_state_extra#cc26
shard_hashes:ShardHashes
config:ConfigParams
^[ flags:(## 16) { flags = 0 }
validator_info:ValidatorInfo
prev_blocks:OldMcBlocksInfo
after_key_block:Bool
last_key_block:(Maybe ExtBlkRef) ]
global_balance:CurrencyCollection
= McStateExtra;
ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits
ed25519_signature#5 R:bits256 s:bits256 = CryptoSignatureSimple; // 516 bits
_ CryptoSignatureSimple = CryptoSignature;
sig_pair$_ node_id_short:bits256 sign:CryptoSignature = CryptoSignaturePair; // 256+x ~ 772 bits
certificate#4 temp_key:SigPubKey valid_since:uint32 valid_until:uint32 = Certificate; // 356 bits
certificate_env#a419b7d certificate:Certificate = CertificateEnv; // 384 bits
signed_certificate$_ certificate:Certificate certificate_signature:CryptoSignature
= SignedCertificate; // 356+516 = 872 bits
// certificate_signature is the signature of CertificateEnv (with embedded certificate) with persistent key
chained_signature#f signed_cert:^SignedCertificate temp_key_signature:CryptoSignatureSimple
= CryptoSignature; // 4+(356+516)+516 = 520 bits+ref (1392 bits total)
// temp_key_signature is the signature of whatever was originally intended to be signed with temp_key from certificate
masterchain_block_extra#cca5
key_block:(## 1)
shard_hashes:ShardHashes
shard_fees:ShardFees
^[ prev_blk_signatures:(HashmapE 16 CryptoSignaturePair)
recover_create_msg:(Maybe ^InMsg)
mint_msg:(Maybe ^InMsg) ]
config:key_block?ConfigParams
= McBlockExtra;
//
// CONFIGURATION PARAMETERS
//
validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
validators#11 utime_since:uint32 utime_until:uint32
total:(## 16) main:(## 16) { main <= total } { main >= 1 }
list:(Hashmap 16 ValidatorDescr) = ValidatorSet;
_ config_addr:bits256 = ConfigParam 0;
_ elector_addr:bits256 = ConfigParam 1;
_ minter_addr:bits256 = ConfigParam 2; // ConfigParam 0 is used if absent
_ fee_collector_addr:bits256 = ConfigParam 3; // ConfigParam 1 is used if absent
_ dns_root_addr:bits256 = ConfigParam 4; // root TON DNS resolver
_ mint_new_price:Grams mint_add_price:Grams = ConfigParam 6;
_ to_mint:ExtraCurrencyCollection = ConfigParam 7;
capabilities#c4 version:uint32 capabilities:uint64 = GlobalVersion;
_ GlobalVersion = ConfigParam 8; // all zero if absent
_ mandatory_params:(Hashmap 32 True) = ConfigParam 9;
wfmt_basic#1 vm_version:int32 vm_mode:uint64 = WorkchainFormat 1;
wfmt_ext#0 min_addr_len:(## 12) max_addr_len:(## 12) addr_len_step:(## 12)
{ min_addr_len >= 64 } { min_addr_len <= max_addr_len }
{ max_addr_len <= 1023 } { addr_len_step <= 1023 }
workchain_type_id:(## 32) { workchain_type_id >= 1 }
= WorkchainFormat 0;
workchain#a6 enabled_since:uint32 actual_min_split:(## 8)
min_split:(## 8) max_split:(## 8) { actual_min_split <= min_split }
//workchain#a5 enabled_since:uint32 min_split:(## 8) max_split:(## 8)
// { min_split <= max_split } { max_split <= 60 }
basic:(## 1) active:Bool accept_msgs:Bool flags:(## 13) { flags = 0 }
zerostate_root_hash:bits256 zerostate_file_hash:bits256
version:uint32 format:(WorkchainFormat basic)
= WorkchainDescr;
_ workchains:(HashmapE 32 WorkchainDescr) = ConfigParam 12;
block_grams_created#6b masterchain_block_fee:Grams basechain_block_fee:Grams
= BlockCreateFees;
_ BlockCreateFees = ConfigParam 14;
_ validators_elected_for:uint32 elections_start_before:uint32
elections_end_before:uint32 stake_held_for:uint32
= ConfigParam 15;
_ max_validators:(## 16) max_main_validators:(## 16) min_validators:(## 16)
{ max_validators >= max_main_validators }
{ max_main_validators >= min_validators }
{ min_validators >= 1 }
= ConfigParam 16;
_ min_stake:Grams max_stake:Grams min_total_stake:Grams max_stake_factor:uint32 = ConfigParam 17;
_#cc utime_since:uint32 bit_price_ps:uint64 cell_price_ps:uint64
mc_bit_price_ps:uint64 mc_cell_price_ps:uint64 = StoragePrices;
_ (Hashmap 32 StoragePrices) = ConfigParam 18;
gas_prices#dd gas_price:uint64 gas_limit:uint64 gas_credit:uint64
block_gas_limit:uint64 freeze_due_limit:uint64 delete_due_limit:uint64
= GasLimitsPrices;
config_mc_gas_prices#_ GasLimitsPrices = ConfigParam 20;
config_gas_prices#_ GasLimitsPrices = ConfigParam 21;
param_limits#c3 underload:# soft_limit:# { underload <= soft_limit }
hard_limit:# { soft_limit <= hard_limit } = ParamLimits;
block_limits#5d bytes:ParamLimits gas:ParamLimits lt_delta:ParamLimits
= BlockLimits;
config_mc_block_limits#_ BlockLimits = ConfigParam 22;
config_block_limits#_ BlockLimits = ConfigParam 23;
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
msg_forward_prices#ea lump_price:uint64 bit_price:uint64 cell_price:uint64
ihr_price_factor:uint32 first_frac:uint16 next_frac:uint16 = MsgForwardPrices;
// used for messages to/from masterchain
config_mc_fwd_prices#_ MsgForwardPrices = ConfigParam 24;
// used for all other messages
config_fwd_prices#_ MsgForwardPrices = ConfigParam 25;
catchain_config#c1 mc_catchain_lifetime:uint32 shard_catchain_lifetime:uint32
shard_validators_lifetime:uint32 shard_validators_num:uint32 = CatchainConfig;
consensus_config#d6 round_candidates:# { round_candidates >= 1 }
next_candidate_delay_ms:uint32 consensus_timeout_ms:uint32
fast_attempts:uint32 attempt_duration:uint32 catchain_max_deps:uint32
max_block_bytes:uint32 max_collated_bytes:uint32 = ConsensusConfig;
_ CatchainConfig = ConfigParam 28;
_ ConsensusConfig = ConfigParam 29;
_ fundamental_smc_addr:(HashmapE 256 True) = ConfigParam 31;
_ prev_validators:ValidatorSet = ConfigParam 32;
_ prev_temp_validators:ValidatorSet = ConfigParam 33;
_ cur_validators:ValidatorSet = ConfigParam 34;
_ cur_temp_validators:ValidatorSet = ConfigParam 35;
_ next_validators:ValidatorSet = ConfigParam 36;
_ next_temp_validators:ValidatorSet = ConfigParam 37;
validator_temp_key#3 adnl_addr:bits256 temp_public_key:SigPubKey seqno:# valid_until:uint32 = ValidatorTempKey;
signed_temp_key#4 key:^ValidatorTempKey signature:CryptoSignature = ValidatorSignedTempKey;
_ (HashmapE 256 ValidatorSignedTempKey) = ConfigParam 39;
//
// PROOFS
//
block_signatures_pure#_ sig_count:uint32 sig_weight:uint64
signatures:(HashmapE 16 CryptoSignaturePair) = BlockSignaturesPure;
block_signatures#11 validator_info:ValidatorBaseInfo pure_signatures:BlockSignaturesPure = BlockSignatures;
block_proof#c3 proof_for:BlockIdExt root:^Cell signatures:(Maybe ^BlockSignatures) = BlockProof;
chain_empty$_ = ProofChain 0;
chain_link$_ {n:#} root:^Cell prev:n?^(ProofChain n) = ProofChain (n + 1);
top_block_descr#d5 proof_for:BlockIdExt signatures:(Maybe ^BlockSignatures)
len:(## 8) { len >= 1 } { len <= 8 } chain:(ProofChain len) = TopBlockDescr;
//
// COLLATED DATA
//
top_block_descr_set#4ac789f3 collection:(HashmapE 96 ^TopBlockDescr) = TopBlockDescrSet;

View file

@ -0,0 +1,294 @@
/*
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 "check-proof.h"
#include "block/block.h"
#include "block/block-parse.h"
#include "block/block-auto.h"
#include "block/mc-config.h"
#include "ton/ton-shard.h"
#include "vm/cells/MerkleProof.h"
namespace block {
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to,
bool check_state_hash) {
ton::RootHash vhash{root->get_hash().bits()};
if (vhash != blkid.root_hash) {
return td::Status::Error(PSTRING() << " block header for block " << blkid.to_str() << " has incorrect root hash "
<< vhash.to_hex() << " instead of " << blkid.root_hash.to_hex());
}
std::vector<ton::BlockIdExt> prev;
ton::BlockIdExt mc_blkid;
bool after_split;
TRY_STATUS(block::unpack_block_prev_blk_try(root, blkid, prev, mc_blkid, after_split));
block::gen::Block::Record blk;
block::gen::BlockInfo::Record info;
if (!(tlb::unpack_cell(root, blk) && tlb::unpack_cell(blk.info, info))) {
return td::Status::Error(std::string{"cannot unpack header for block "} + blkid.to_str());
}
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);
if (!check_state_hash) {
*store_shard_hash_to = upd_hash.bits();
} else if (store_shard_hash_to->compare(upd_hash.bits())) {
return td::Status::Error(PSTRING() << "state hash mismatch in block header of " << blkid.to_str()
<< " : header declares " << upd_hash.bits().to_hex(256) << " expected "
<< store_shard_hash_to->to_hex());
}
}
return td::Status::OK();
}
td::Result<td::Bits256> check_state_proof(ton::BlockIdExt blkid, td::Slice proof) {
TRY_RESULT(proof_root, vm::std_boc_deserialize(proof));
auto virt_root = vm::MerkleProof::virtualize(std::move(proof_root), 1);
if (virt_root.is_null()) {
return td::Status::Error("account state proof is invalid");
}
td::Bits256 state_hash;
TRY_STATUS(check_block_header_proof(std::move(virt_root), blkid, &state_hash));
return state_hash;
}
td::Result<Ref<vm::Cell>> check_extract_state_proof(ton::BlockIdExt blkid, td::Slice proof, td::Slice data) {
try {
TRY_RESULT(state_hash, check_state_proof(blkid, proof));
TRY_RESULT(state_root, vm::std_boc_deserialize(data));
auto state_virt_root = vm::MerkleProof::virtualize(std::move(state_root), 1);
if (state_virt_root.is_null()) {
return td::Status::Error("account state proof is invalid");
}
if (state_hash != state_virt_root->get_hash().bits()) {
return td::Status::Error("root hash mismatch in the shardchain state proof");
}
return std::move(state_virt_root);
} catch (vm::VmError& err) {
return td::Status::Error(PSLICE() << "error scanning shard state proof: " << err.get_msg());
} catch (vm::VmVirtError& err) {
return td::Status::Error(PSLICE() << "virtualization error scanning shard state proof: " << err.get_msg());
}
}
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof) {
if (blk == shard_blk) {
if (!shard_proof.empty()) {
LOG(WARNING) << "Unexpected non-empty shard proof";
}
return td::Status::OK();
}
if (!blk.is_masterchain() || !blk.is_valid_full()) {
return td::Status::Error(PSLICE() << "reference block " << blk.to_str()
<< " for a getAccountState query must belong to the masterchain");
}
TRY_RESULT_PREFIX(P_roots, vm::std_boc_deserialize_multi(std::move(shard_proof)),
"cannot deserialize shard configuration proof");
if (P_roots.size() != 2) {
return td::Status::Error("shard configuration proof must have exactly two roots");
}
try {
auto mc_state_root = vm::MerkleProof::virtualize(std::move(P_roots[1]), 1);
if (mc_state_root.is_null()) {
return td::Status::Error("shard configuration proof is invalid");
}
ton::Bits256 mc_state_hash = mc_state_root->get_hash().bits();
TRY_STATUS_PREFIX(
check_block_header_proof(vm::MerkleProof::virtualize(std::move(P_roots[0]), 1), blk, &mc_state_hash, true),
"error in shard configuration block header proof :");
block::gen::ShardStateUnsplit::Record sstate;
if (!(tlb::unpack_cell(mc_state_root, sstate))) {
return td::Status::Error("cannot unpack masterchain state header");
}
auto shards_dict = block::ShardConfig::extract_shard_hashes_dict(std::move(mc_state_root));
if (!shards_dict) {
return td::Status::Error("cannot extract shard configuration dictionary from proof");
}
vm::CellSlice cs;
ton::ShardIdFull true_shard;
if (!block::ShardConfig::get_shard_hash_raw_from(*shards_dict, cs, shard_blk.shard_full(), true_shard)) {
return td::Status::Error(PSLICE() << "masterchain state contains no information for shard "
<< shard_blk.shard_full().to_str());
}
auto shard_info = block::McShardHash::unpack(cs, true_shard);
if (shard_info.is_null()) {
return td::Status::Error(PSLICE() << "cannot unpack information for shard " << shard_blk.shard_full().to_str()
<< " from masterchain state");
}
if (shard_info->top_block_id() != shard_blk) {
return td::Status::Error(PSLICE() << "shard configuration mismatch: expected to find block " << shard_blk.to_str()
<< " , found " << shard_info->top_block_id().to_str());
}
} catch (vm::VmError err) {
return td::Status::Error(PSLICE() << "error while traversing shard configuration proof : " << err.get_msg());
} catch (vm::VmVirtError err) {
return td::Status::Error(PSLICE() << "virtualization error while traversing shard configuration proof : "
<< err.get_msg());
}
return td::Status::OK();
}
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
td::Ref<vm::Cell> root, ton::LogicalTime* last_trans_lt, ton::Bits256* last_trans_hash) {
TRY_RESULT_PREFIX(Q_roots, vm::std_boc_deserialize_multi(std::move(proof)), "cannot deserialize account proof");
if (Q_roots.size() != 2) {
return td::Status::Error(PSLICE() << "account state proof must have exactly two roots");
}
if (last_trans_lt) {
last_trans_hash->set_zero();
}
try {
auto state_root = vm::MerkleProof::virtualize(std::move(Q_roots[1]), 1);
if (state_root.is_null()) {
return td::Status::Error("account state proof is invalid");
}
ton::Bits256 state_hash = state_root->get_hash().bits();
TRY_STATUS_PREFIX(
check_block_header_proof(vm::MerkleProof::virtualize(std::move(Q_roots[0]), 1), shard_blk, &state_hash, true),
"error in account shard block header proof : ");
block::gen::ShardStateUnsplit::Record sstate;
if (!(tlb::unpack_cell(std::move(state_root), sstate))) {
return td::Status::Error("cannot unpack state header");
}
vm::AugmentedDictionary accounts_dict{vm::load_cell_slice(sstate.accounts).prefetch_ref(), 256,
block::tlb::aug_ShardAccounts};
auto acc_csr = accounts_dict.lookup(addr.addr);
if (acc_csr.not_null()) {
if (root.is_null()) {
return td::Status::Error(PSLICE() << "account state proof shows that account state for " << addr
<< " must be non-empty, but it actually is empty");
}
block::gen::ShardAccount::Record acc_info;
if (!tlb::csr_unpack(std::move(acc_csr), acc_info)) {
return td::Status::Error("cannot unpack ShardAccount from proof");
}
if (acc_info.account->get_hash().bits().compare(root->get_hash().bits(), 256)) {
return td::Status::Error(PSLICE() << "account state hash mismatch: Merkle proof expects "
<< acc_info.account->get_hash().bits().to_hex(256)
<< " but received data has " << root->get_hash().bits().to_hex(256));
}
if (last_trans_hash) {
*last_trans_hash = acc_info.last_trans_hash;
}
if (last_trans_lt) {
*last_trans_lt = acc_info.last_trans_lt;
}
} else if (root.not_null()) {
return td::Status::Error(PSLICE() << "account state proof shows that account state for " << addr
<< " must be empty, but it is not");
}
} catch (vm::VmError err) {
return td::Status::Error(PSLICE() << "error while traversing account proof : " << err.get_msg());
} catch (vm::VmVirtError err) {
return td::Status::Error(PSLICE() << "virtualization error while traversing account proof : " << err.get_msg());
}
return td::Status::OK();
}
td::Result<AccountState::Info> AccountState::validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const {
TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state");
LOG(INFO) << "got account state for " << addr << " with respect to blocks " << blk.to_str()
<< (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str());
if (blk != ref_blk && ref_blk.id.seqno != ~0U) {
return td::Status::Error(PSLICE() << "obtained getAccountState() for a different reference block " << blk.to_str()
<< " instead of requested " << ref_blk.to_str());
}
if (!shard_blk.is_valid_full()) {
return td::Status::Error(PSLICE() << "shard block id " << shard_blk.to_str() << " in answer is invalid");
}
if (!ton::shard_contains(shard_blk.shard_full(), ton::extract_addr_prefix(addr.workchain, addr.addr))) {
return td::Status::Error(PSLICE() << "received data from shard block " << shard_blk.to_str()
<< " that cannot contain requested account");
}
TRY_STATUS(block::check_shard_proof(blk, shard_blk, shard_proof.as_slice()));
Info res;
TRY_STATUS(
block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt, &res.last_trans_hash));
res.root = std::move(root);
return res;
}
td::Result<Transaction::Info> Transaction::validate() {
if (root.is_null()) {
return td::Status::Error("transactions are expected to be non-empty");
}
if (hash != root->get_hash().bits()) {
return td::Status::Error(PSLICE() << "transaction hash mismatch: expected " << hash.to_hex() << ", found "
<< root->get_hash().bits().to_hex(256));
}
block::gen::Transaction::Record trans;
if (!tlb::unpack_cell(root, trans)) {
return td::Status::Error("cannot unpack transaction #");
}
if (trans.lt != lt) {
return td::Status::Error(PSLICE() << "transaction lt mismatch: expected " << lt << ", found " << trans.lt);
}
Info res;
res.blkid = blkid;
res.prev_trans_lt = trans.prev_trans_lt;
res.prev_trans_hash = trans.prev_trans_hash;
res.transaction = root;
return std::move(res);
}
td::Result<TransactionList::Info> TransactionList::validate() const {
if (blkids.empty()) {
return td::Status::Error("transaction list must be non-empty");
}
auto R = vm::std_boc_deserialize_multi(std::move(transactions_boc));
if (R.is_error()) {
return td::Status::Error("cannot deserialize transactions BoC");
}
auto list = R.move_as_ok();
if (list.size() != blkids.size()) {
return td::Status::Error(PSLICE() << "transaction list size " << list.size()
<< " must be equal to the size of block id list " << blkids.size());
}
size_t c = 0;
Info res;
auto current_lt = lt;
auto current_hash = hash;
for (auto& root : list) {
const auto& blkid = blkids[c++];
Transaction transaction;
transaction.blkid = std::move(blkid);
transaction.lt = current_lt;
transaction.hash = current_hash;
transaction.root = root;
TRY_RESULT(info, transaction.validate());
current_lt = info.prev_trans_lt;
current_hash = info.prev_trans_hash;
res.transactions.push_back(std::move(info));
}
return std::move(res);
}
} // namespace block

View file

@ -0,0 +1,80 @@
/*
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
*/
#pragma once
#include "vm/cells.h"
#include "block/block.h"
namespace block {
using td::Ref;
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid,
ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false);
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof);
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
td::Ref<vm::Cell> root, ton::LogicalTime* last_trans_lt = nullptr,
ton::Bits256* last_trans_hash = nullptr);
td::Result<td::Bits256> check_state_proof(ton::BlockIdExt blkid, td::Slice proof);
td::Result<Ref<vm::Cell>> check_extract_state_proof(ton::BlockIdExt blkid, td::Slice proof, td::Slice data);
struct AccountState {
ton::BlockIdExt blk;
ton::BlockIdExt shard_blk;
td::BufferSlice shard_proof;
td::BufferSlice proof;
td::BufferSlice state;
struct Info {
td::Ref<vm::Cell> root;
ton::LogicalTime last_trans_lt = 0;
ton::Bits256 last_trans_hash;
};
td::Result<Info> validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const;
};
struct Transaction {
ton::BlockIdExt blkid;
ton::LogicalTime lt;
ton::Bits256 hash;
td::Ref<vm::Cell> root;
struct Info {
ton::BlockIdExt blkid;
ton::LogicalTime prev_trans_lt;
ton::Bits256 prev_trans_hash;
td::Ref<vm::Cell> transaction;
};
td::Result<Info> validate();
};
struct TransactionList {
ton::LogicalTime lt;
ton::Bits256 hash;
std::vector<ton::BlockIdExt> blkids;
td::BufferSlice transactions_boc;
struct Info {
std::vector<Transaction::Info> transactions;
};
td::Result<Info> validate() const;
};
} // namespace block

View file

@ -0,0 +1,780 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <map>
#include <functional>
#include <limits>
#include <getopt.h>
#include "vm/stack.hpp"
#include "vm/boc.h"
#include "fift/Fift.h"
#include "fift/Dictionary.h"
#include "fift/SourceLookup.h"
#include "fift/words.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Parser.h"
#include "td/utils/port/path.h"
#include "td/utils/port/signals.h"
#include "block.h"
#include "block-parse.h"
#include "block-auto.h"
#include "mc-config.h"
#define PDO(__op) \
if (!(__op)) { \
ok = false; \
}
#define THRERR(__msg) \
if (!ok) { \
throw fift::IntError{__msg}; \
}
#define RETERR \
if (!ok) { \
return false; \
}
using td::Ref;
int verbosity;
enum { wc_master = -1, wc_base = 0 };
constexpr int wc_undef = std::numeric_limits<int>::min();
int workchain_id = wc_undef;
int global_id = 0;
typedef td::BitArray<256> hash_t;
struct SmcDescr {
hash_t addr;
int split_depth;
bool preinit_only;
td::RefInt256 gram_balance;
Ref<vm::DataCell> state_init; // StateInit
Ref<vm::DataCell> account; // Account
SmcDescr(const hash_t& _addr) : addr(_addr), split_depth(0), preinit_only(false) {
}
};
std::map<hash_t, SmcDescr> smart_contracts;
td::RefInt256 total_smc_balance{true, 0}, max_total_smc_balance;
struct PublicLibDescr {
Ref<vm::Cell> root;
std::set<hash_t> publishers;
PublicLibDescr(Ref<vm::Cell> _root) : root(std::move(_root)) {
}
};
std::map<hash_t, PublicLibDescr> public_libraries;
hash_t config_addr;
Ref<vm::Cell> config_param_root;
bool config_addr_set;
vm::Dictionary config_dict{32};
ton::UnixTime now;
bool set_config_smc(const SmcDescr& smc) {
if (config_addr_set || smc.preinit_only || workchain_id != wc_master || smc.split_depth) {
return false;
}
vm::CellSlice cs = load_cell_slice(smc.state_init);
bool ok = true;
PDO(block::gen::t_Maybe_natwidth_5.skip(cs) && block::gen::t_Maybe_TickTock.skip(cs) &&
block::gen::t_Maybe_Ref_Cell.skip(cs));
RETERR;
Ref<vm::Cell> data;
PDO(cs.fetch_ulong(1) == 1 && cs.fetch_ref_to(data));
THRERR("config smart contract must have non-empty data");
vm::CellSlice cs2 = load_cell_slice(data);
PDO(cs2.fetch_ref_to(data));
THRERR("first reference in config smart contract data must point to initial configuration");
PDO(block::valid_config_data(data, smc.addr));
THRERR("invalid smart contract configuration data");
config_addr = smc.addr;
config_param_root = std::move(data);
config_addr_set = true;
if (verbosity > 2) {
std::cerr << "set smart contract " << config_addr << " as the configuration smart contract with configuration:\n";
load_cell_slice(config_param_root).print_rec(std::cerr);
}
return true;
}
void interpret_set_workchain(vm::Stack& stack) {
workchain_id = stack.pop_smallint_range(0x7fffffff, -0x7fffffff);
}
void interpret_get_workchain(vm::Stack& stack) {
stack.push_smallint(workchain_id);
}
void interpret_set_global_id(vm::Stack& stack) {
global_id = stack.pop_smallint_range(0x7fffffff, -0x7fffffff);
}
void interpret_get_global_id(vm::Stack& stack) {
stack.push_smallint(global_id);
}
void interpret_get_verbosity(vm::Stack& stack) {
stack.push_smallint(GET_VERBOSITY_LEVEL());
}
void interpret_set_verbosity(vm::Stack& stack) {
int x = stack.pop_smallint_range(15);
SET_VERBOSITY_LEVEL(x);
}
void interpret_set_config_smartcontract(vm::Stack& stack) {
if (workchain_id != wc_master) {
throw fift::IntError{"configuration smart contract may be selected in masterchain only"};
}
if (config_addr_set) {
throw fift::IntError{"configuration smart contract already selected"};
}
td::RefInt256 int_addr = stack.pop_int_finite();
hash_t addr;
if (!int_addr->export_bits(addr.bits(), 256, false)) {
throw fift::IntError{"not a valid smart-contract address"};
}
auto it = smart_contracts.find(addr);
if (it == smart_contracts.end()) {
throw fift::IntError{"unknown smart contract"};
}
const SmcDescr& smc = it->second;
assert(smc.addr == addr);
if (smc.preinit_only) {
throw fift::IntError{"configuration smart contract must be completely initialized"};
}
if (!set_config_smc(smc)) {
throw fift::IntError{"invalid configuration smart contract"};
}
}
bool is_empty_cell(Ref<vm::Cell> cell) {
bool is_special;
auto cs = load_cell_slice_special(std::move(cell), is_special);
return !is_special && cs.empty_ext();
}
bool add_public_library(hash_t lib_addr, hash_t smc_addr, Ref<vm::Cell> lib_root) {
if (lib_root.is_null() || lib_root->get_hash().as_array() != lib_addr.as_array()) {
return false;
}
auto ins = public_libraries.emplace(lib_addr, lib_root);
PublicLibDescr& lib = ins.first->second;
lib.publishers.insert(smc_addr);
if (verbosity > 2) {
std::cerr << "added " << (ins.second ? "new " : "") << "public library " << lib_addr << " with publisher "
<< smc_addr << std::endl;
}
return true;
}
td::RefInt256 create_smartcontract(td::RefInt256 smc_addr, Ref<vm::Cell> code, Ref<vm::Cell> data,
Ref<vm::Cell> library, td::RefInt256 balance, int special, int split_depth,
int mode) {
if (is_empty_cell(code)) {
code.clear();
}
if (is_empty_cell(data)) {
data.clear();
}
if (is_empty_cell(library)) {
library.clear();
}
bool ok = true;
if (library.not_null()) {
PDO(block::valid_library_collection(library, false));
THRERR("not a valid library collection");
}
vm::CellBuilder cb;
if (!split_depth) {
PDO(cb.store_long_bool(0, 1));
} else {
PDO(cb.store_long_bool(1, 1) && cb.store_ulong_rchk_bool(split_depth, 5));
}
THRERR("invalid split_depth for a smart contract");
if (!special) {
PDO(cb.store_long_bool(0, 1));
} else {
PDO(cb.store_long_bool(1, 1) && cb.store_ulong_rchk_bool(special, 2));
}
THRERR("invalid special TickTock argument for a smart contract");
PDO(cb.store_maybe_ref(std::move(code)) && cb.store_maybe_ref(std::move(data)) && cb.store_maybe_ref(library));
THRERR("cannot store smart-contract code, data or library");
Ref<vm::DataCell> state_init = cb.finalize();
hash_t addr;
if (smc_addr.is_null()) {
addr = state_init->get_hash().as_array();
smc_addr = td::RefInt256{true};
PDO(smc_addr.write().import_bits(addr.data(), 0, 256, false));
} else if (mode == 1) {
throw fift::IntError{"cannot create uninitialized smart contracts with specified addresses"};
} else {
PDO(smc_addr->export_bits(addr.data(), 0, 256, false));
}
THRERR("cannot initialize smart-contract address");
if (verbosity > 2) {
std::cerr << "smart-contract address is ";
std::cerr << addr << " = " << smc_addr << std::endl;
}
PDO(mode || !sgn(balance));
THRERR("cannot set non-zero balance to smart contract unless it is initialized");
PDO(sgn(balance) >= 0);
THRERR("balance cannot be negative");
if (!mode) {
return smc_addr; // compute address only
}
auto it = smart_contracts.find(addr);
if (it != smart_contracts.end()) {
std::cerr << "smart contract " << addr << " already defined\n";
throw fift::IntError{"smart contract already exists"};
}
auto ins = smart_contracts.emplace(addr, addr);
assert(ins.second);
SmcDescr& smc = ins.first->second;
smc.split_depth = split_depth;
smc.preinit_only = (mode == 1);
smc.gram_balance = balance;
total_smc_balance += balance;
if (mode > 1) {
smc.state_init = std::move(state_init);
}
if (max_total_smc_balance.not_null() && total_smc_balance > max_total_smc_balance) {
throw fift::IntError{"total smart-contract balance exceeds limit"};
}
cb.reset();
PDO(cb.store_long_bool(0, 64) // account_storage$_ last_trans_lt:uint64
&& block::tlb::t_Grams.store_integer_value(cb, *balance) // balance.grams:Grams
&& cb.store_long_bool(0, 1)); // balance.other:ExtraCurrencyCollection
if (mode == 1) {
PDO(block::gen::t_AccountState.pack_account_uninit(cb));
} else {
PDO(block::gen::t_AccountState.pack_account_active(cb, vm::load_cell_slice_ref(smc.state_init)));
}
THRERR("cannot create smart-contract AccountStorage");
Ref<vm::DataCell> storage = cb.finalize();
vm::CellStorageStat stats;
PDO(stats.compute_used_storage(Ref<vm::Cell>(storage)));
if (verbosity > 2) {
std::cerr << "storage is:\n";
vm::load_cell_slice(storage).print_rec(std::cerr);
std::cerr << "stats: bits=" << stats.bits << ", cells=" << stats.cells << std::endl;
std::cerr << "block::gen::AccountStorage.validate_ref() = " << block::gen::t_AccountStorage.validate_ref(storage)
<< std::endl;
std::cerr << "block::tlb::AccountStorage.validate_ref() = " << block::tlb::t_AccountStorage.validate_ref(storage)
<< std::endl;
}
PDO(block::gen::t_AccountStorage.validate_ref(storage));
THRERR("AccountStorage of created smart-contract is invalid (?)");
cb.reset(); // build Account
PDO(cb.store_long_bool(1, 1)); // account$1
int ctor = 3; // addr_var$11
if (workchain_id >= -128 && workchain_id <= 127) {
ctor = 2; // addr_std$10
}
PDO(cb.store_long_bool(ctor, 2)); // addr_std$10 or addr_var$11
if (split_depth) {
PDO(cb.store_long_bool(1, 1) // just$1
&& cb.store_ulong_rchk_bool(split_depth, 5) // depth:(## 5)
&& cb.store_bits_bool(addr.cbits(), split_depth)); // rewrite pfx:(depth * Bit)
} else {
PDO(cb.store_long_bool(0, 1)); // nothing$0
}
PDO(cb.store_long_rchk_bool(workchain_id, ctor == 2 ? 8 : 32) && cb.store_bits_bool(addr.cbits(), 256));
THRERR("Cannot serialize addr:MsgAddressInt of the new smart contract");
// storage_stat:StorageInfo -> storage_stat.used:StorageUsed
PDO(block::store_UInt7(cb, stats.cells) // cells:(VarUInteger 7)
&& block::store_UInt7(cb, stats.bits) // bits:(VarUInteger 7)
&& block::store_UInt7(cb, stats.public_cells)); // public_cells:(VarUInteger 7)
THRERR("Cannot serialize used:StorageUsed of the new smart contract");
PDO(cb.store_long_bool(0, 33)); // last_paid:uint32 due_payment:(Maybe Grams)
PDO(cb.append_data_cell_bool(storage)); // storage:AccountStorage
THRERR("Cannot create Account of the new smart contract");
smc.account = cb.finalize();
if (verbosity > 2) {
std::cerr << "account is:\n";
vm::load_cell_slice(smc.account).print_rec(std::cerr);
std::cerr << "block::gen::Account.validate_ref() = " << block::gen::t_Account.validate_ref(smc.account)
<< std::endl;
std::cerr << "block::tlb::Account.validate_ref() = " << block::tlb::t_Account.validate_ref(smc.account)
<< std::endl;
}
PDO(block::gen::t_Account.validate_ref(smc.account));
THRERR("Account of created smart contract is invalid (?)");
if (library.not_null()) {
vm::Dictionary dict{std::move(library), 256};
ok &= dict.check_for_each([addr](Ref<vm::CellSlice> cs, td::ConstBitPtr key, int n) -> bool {
return !cs->prefetch_ulong(1) || add_public_library(key, addr, cs->prefetch_ref());
});
THRERR("Error processing libraries published by new smart contract");
}
return smc_addr;
}
// stores accounts:ShardAccounts
bool store_accounts(vm::CellBuilder& cb) {
vm::AugmentedDictionary dict{256, block::tlb::aug_ShardAccounts};
for (const auto& smc_pair : smart_contracts) {
const SmcDescr& smc = smc_pair.second;
CHECK(smc_pair.first == smc.addr);
vm::CellBuilder cb;
bool ok = cb.store_ref_bool(smc.account) // account_descr$_ acc:^Account
&& cb.store_zeroes_bool(256 + 64) // last_trans_hash:bits256 last_trans_lt:uint64
&& dict.set_builder(smc.addr.cbits(), 256, cb, vm::Dictionary::SetMode::Add);
CHECK(ok);
}
return std::move(dict).append_dict_to_bool(cb);
}
// stores libraries:(HashmapE 256 LibDescr)
bool store_public_libraries(vm::CellBuilder& cb) {
vm::Dictionary dict{256};
bool ok = true;
vm::CellBuilder empty_cb;
for (const auto& lib_pair : public_libraries) {
const PublicLibDescr pl = lib_pair.second;
PDO(pl.root->get_hash().as_array() == lib_pair.first.as_array());
vm::Dictionary publishers{256};
for (const auto& publisher : pl.publishers) {
PDO(publishers.set_builder(publisher.cbits(), 256, empty_cb, vm::Dictionary::SetMode::Add));
}
Ref<vm::Cell> root = std::move(publishers).extract_root_cell();
PDO(root.not_null());
THRERR("public library has an empty or invalid set of publishers");
vm::CellBuilder value_cb; // LibDescr
PDO(value_cb.store_long_bool(0, 2) && value_cb.store_ref_bool(pl.root) &&
value_cb.append_cellslice_bool(vm::load_cell_slice(std::move(root))));
THRERR("cannot create LibDescr for a public library");
PDO(dict.set_builder(lib_pair.first.cbits(), 256, value_cb, vm::Dictionary::SetMode::Add));
THRERR("cannot insert LibDescr of a public library into the public library collection");
}
PDO(std::move(dict).append_dict_to_bool(cb));
return ok;
}
// stores config:ConfigParams
bool store_config_params(vm::CellBuilder& cb) {
return config_addr_set && config_param_root.not_null() &&
cb.store_bits_bool(config_addr.cbits(), 256) // _ config_addr:bits256
&& cb.store_ref_bool(config_param_root); // config:^(Hashmap 32 ^Cell)
}
// stores hash of initial masterchain validator set computed from configuration parameter 34
bool store_validator_list_hash(vm::CellBuilder& cb) {
Ref<vm::Cell> vset_cell = config_dict.lookup_ref(td::BitArray<32>{34});
auto res = block::Config::unpack_validator_set(std::move(vset_cell));
if (res.is_error()) {
LOG(ERROR) << "cannot unpack current validator set: " << res.move_as_error().to_string();
return false;
}
auto vset = res.move_as_ok();
LOG_CHECK(vset) << "unpacked validator set is empty";
auto ccvc = block::Config::unpack_catchain_validators_config(config_dict.lookup_ref(td::BitArray<32>{28}));
ton::ShardIdFull shard{ton::masterchainId};
auto nodes = block::Config::do_compute_validator_set(ccvc, shard, *vset, now, 0);
LOG_CHECK(!nodes.empty()) << "validator node list in unpacked validator set is empty";
auto vset_hash = block::compute_validator_set_hash(0, shard, std::move(nodes));
LOG(DEBUG) << "initial validator set hash is " << vset_hash;
return cb.store_long_bool(vset_hash, 32);
}
// stores custom:(Maybe ^McStateExtra)
bool store_custom(vm::CellBuilder& cb) {
if (workchain_id != wc_master) {
return cb.store_long_bool(0, 1); // nothing
}
vm::CellBuilder cb2, cb3;
bool ok = true;
PDO(cb2.store_long_bool(0xcc26, 16) // masterchain_state_extra#cc26
&& cb2.store_long_bool(0, 1) // shard_hashes:ShardHashes = (HashmapE 32 ^(BinTree ShardDescr))
&& store_config_params(cb2) // config:ConfigParams
&& cb3.store_long_bool(0, 16) // ^[ flags:(## 16) { flags = 0 }
&& store_validator_list_hash(cb3) // validator_list_hash_short:uint32
&& cb3.store_long_bool(0, 32) // catchain_seqno:uint32
&& cb3.store_bool_bool(true) // nx_cc_updated:Bool
&& cb3.store_zeroes_bool(1 + 65) // prev_blocks:OldMcBlocksInfo
&& cb3.store_long_bool(2, 1 + 1) // after_key_block:Bool last_key_block:(Maybe ...)
&& cb2.store_ref_bool(cb3.finalize()) // ]
&& block::CurrencyCollection{total_smc_balance}.store(cb2) // global_balance:CurrencyCollection
&& cb.store_long_bool(1, 1) // just
&& cb.store_ref_bool(cb2.finalize()));
return ok;
}
Ref<vm::Cell> create_state() {
vm::CellBuilder cb, cb2;
now = static_cast<ton::UnixTime>(time(0));
bool ok = true;
PDO(workchain_id != wc_undef);
THRERR("workchain_id is unset, cannot generate state");
PDO(workchain_id != wc_master || config_addr_set);
THRERR("configuration smart contract must be selected");
PDO(cb.store_long_bool(0x9023afe2, 32) // shard_state#9023afe2
&& cb.store_long_bool(global_id, 32)); // global_id:int32
PDO(cb.store_long_bool(0, 8) && cb.store_long_bool(workchain_id, 32) &&
cb.store_long_bool(0, 64) // shard_id:ShardIdent
&& cb.store_long_bool(0, 32) // seq_no:#
&& cb.store_zeroes_bool(32) // vert_seq_no:#
&& cb.store_long_bool(now, 32) // gen_utime:uint32
&& cb.store_zeroes_bool(64) // gen_lt:uint64
&& cb.store_ones_bool(32) // min_ref_mc_seqno:uint32
&& cb2.store_zeroes_bool(1 + 64 + 2) // OutMsgQueueInfo
&& cb.store_ref_bool(cb2.finalize()) // out_msg_queue_info:^OutMsgQueueInfo
&& cb.store_long_bool(0, 1) // before_split:Bool
&& store_accounts(cb2) // accounts:^ShardAccounts
&& cb.store_ref_bool(cb2.finalize()) // ...
&& cb2.store_zeroes_bool(128) // ^[ overload_history:uint64 underload_history:uint64
&& block::CurrencyCollection{total_smc_balance}.store(cb2) // total_balance:CurrencyCollection
&& block::tlb::t_CurrencyCollection.null_value(cb2) // total_validator_fees:CurrencyCollection
&& store_public_libraries(cb2) // libraries:(Hashmap 256 LibDescr)
&& cb2.store_long_bool(0, 1) // master_ref:(Maybe BlkMasterInfo)
&& cb.store_ref_bool(cb2.finalize()) // ]
&& store_custom(cb)); // custom:(Maybe ^McStateExtra)
THRERR("cannot create blockchain state");
Ref<vm::Cell> cell = cb.finalize();
if (verbosity > 2) {
std::cerr << "shard_state is:\n";
vm::load_cell_slice(cell).print_rec(std::cerr);
std::cerr << "pretty-printed shard_state is:\n";
block::gen::t_ShardState.print_ref(std::cerr, cell);
std::cerr << "\n";
std::cerr << "block::gen::ShardState.validate_ref() = " << block::gen::t_ShardState.validate_ref(cell) << std::endl;
std::cerr << "block::tlb::ShardState.validate_ref() = " << block::tlb::t_ShardState.validate_ref(cell) << std::endl;
block::gen::ShardStateUnsplit::Record data;
bool ok1 = tlb::unpack_cell(cell, data);
std::cerr << "block::gen::ShardState.unpack_cell() = " << ok1 << std::endl;
if (ok1) {
std::cerr << "shard_id = " << data.shard_id
<< "; out_msg_queue_info = " << load_cell_slice(data.out_msg_queue_info)
<< "; total_balance = " << data.r1.total_balance << std::endl;
}
}
PDO(block::gen::t_ShardState.validate_ref(cell));
PDO(block::tlb::t_ShardState.validate_ref(cell));
THRERR("created an invalid ShardState record");
return cell;
}
// code (cell)
// data (cell)
// library (cell)
// balance (int)
// split_depth (int 0..32)
// special (int 0..3, +2 = tick, +1 = tock)
// [ address (uint256) ]
// mode (0 = compute address only, 1 = create uninit, 2 = create complete; +4 = with specified address)
// --> 256-bit address
void interpret_register_smartcontract(vm::Stack& stack) {
if (workchain_id == wc_undef) {
throw fift::IntError{"cannot register a smartcontract unless the workchain is specified first"};
}
td::RefInt256 spec_addr;
int mode = stack.pop_smallint_range(2 + 4); // allowed modes: 0 1 2 4 5 6
if (mode == 3) {
throw fift::IntError{"invalid mode"};
}
if (mode & 4) {
spec_addr = stack.pop_int_finite();
mode &= ~4;
}
int special = stack.pop_smallint_range(3);
if (special && workchain_id != wc_master) {
throw fift::IntError{"cannot create special smartcontracts outside of the masterchain"};
}
int split_depth = stack.pop_smallint_range(32);
td::RefInt256 balance = stack.pop_int_finite();
if (sgn(balance) < 0) {
throw fift::IntError{"initial balance of a smartcontract cannot be negative"};
}
if (sgn(balance) > 0 && !mode) {
throw fift::IntError{"cannot set non-zero balance if an account is not created"};
}
Ref<vm::Cell> library = stack.pop_cell();
Ref<vm::Cell> data = stack.pop_cell();
Ref<vm::Cell> code = stack.pop_cell();
td::RefInt256 addr = create_smartcontract(std::move(spec_addr), std::move(code), std::move(data), std::move(library),
std::move(balance), special, split_depth, mode);
if (addr.is_null()) {
throw fift::IntError{"internal error while creating smartcontract"};
}
stack.push(std::move(addr));
}
void interpret_create_state(vm::Stack& stack) {
if (!global_id) {
throw fift::IntError{
"(global) blockchain id must be set to a non-zero value: negative for test chains, positive for production"};
}
Ref<vm::Cell> state = create_state();
if (state.is_null()) {
throw fift::IntError{"could not create blockchain state"};
}
stack.push(std::move(state));
}
void interpret_get_config_dict(vm::Stack& stack) {
Ref<vm::Cell> value = config_dict.get_root_cell();
if (value.is_null()) {
stack.push_bool(false);
} else {
stack.push_cell(std::move(value));
stack.push_bool(true);
}
}
void interpret_get_config_param(vm::Stack& stack) {
int x = stack.pop_smallint_range(0x7fffffff, 0x80000000);
Ref<vm::Cell> value = config_dict.lookup_ref(td::BitArray<32>{x});
if (value.is_null()) {
stack.push_bool(false);
} else {
stack.push_cell(std::move(value));
stack.push_bool(true);
}
}
void interpret_set_config_param(vm::Stack& stack) {
int x = stack.pop_smallint_range(0x7fffffff, 0x80000000);
Ref<vm::Cell> value = stack.pop_cell();
if (verbosity > 2 && x >= 0) {
std::cerr << "setting configuration parameter #" << x << " to ";
// vm::load_cell_slice(value).print_rec(std::cerr);
block::gen::ConfigParam{x}.print_ref(std::cerr, value);
std::cerr << std::endl;
}
if (x >= 0 && !block::gen::ConfigParam{x}.validate_ref(value)) {
throw fift::IntError{"invalid value for indicated configuration parameter"};
}
if (!config_dict.set_ref(td::BitArray<32>{x}, std::move(value))) {
throw fift::IntError{"cannot set value of configuration parameter (value too long?)"};
}
}
void interpret_is_shard_state(vm::Stack& stack) {
Ref<vm::Cell> cell = stack.pop_cell();
if (verbosity > 4) {
std::cerr << "custom shard state is:\n";
vm::load_cell_slice(cell).print_rec(std::cerr);
std::cerr << "pretty-printed custom shard state is:\n";
block::gen::t_ShardState.print_ref(std::cerr, cell);
}
stack.push_bool(block::gen::t_ShardState.validate_ref(std::move(cell)));
}
void interpret_is_workchain_descr(vm::Stack& stack) {
Ref<vm::Cell> cell = stack.pop_cell();
if (verbosity > 4) {
std::cerr << "WorkchainDescr is:\n";
vm::load_cell_slice(cell).print_rec(std::cerr);
std::cerr << "pretty-printed WorkchainDescr is:\n";
block::gen::t_WorkchainDescr.print_ref(std::cerr, cell);
}
stack.push_bool(block::gen::t_WorkchainDescr.validate_ref(std::move(cell)));
}
void init_words_custom(fift::Dictionary& d) {
d.def_stack_word("verb@ ", interpret_get_verbosity);
d.def_stack_word("verb! ", interpret_set_verbosity);
d.def_stack_word("wcid@ ", interpret_get_workchain);
d.def_stack_word("wcid! ", interpret_set_workchain);
d.def_stack_word("globalid@ ", interpret_get_global_id);
d.def_stack_word("globalid! ", interpret_set_global_id);
d.def_stack_word("config@ ", interpret_get_config_param);
d.def_stack_word("config! ", interpret_set_config_param);
d.def_stack_word("(configdict) ", interpret_get_config_dict);
d.def_stack_word("register_smc ", interpret_register_smartcontract);
d.def_stack_word("set_config_smc ", interpret_set_config_smartcontract);
d.def_stack_word("create_state ", interpret_create_state);
d.def_stack_word("isShardState? ", interpret_is_shard_state);
d.def_stack_word("isWorkchainDescr? ", interpret_is_workchain_descr);
}
void usage(const char* progname) {
std::cerr
<< "Creates initial state for a TON blockchain, using configuration defined by Fift-language source files\n";
std::cerr
<< "usage: " << progname
<< " [-i] [-n] [-I <source-include-path>] {-L <library-fif-file>} <source-file1-fif> <source-file2-fif> ...\n";
std::cerr << "\t-n\tDo not preload preamble files `Fift.fif` and `CreateState.fif`\n"
"\t-i\tForce interactive mode even if explicit source file names are indicated\n"
"\t-I<source-search-path>\tSets colon-separated library source include path. If not indicated, "
"$FIFTPATH is used instead.\n"
"\t-L<library-fif-file>\tPre-loads a library source file\n"
"\t-v<verbosity-level>\tSet verbosity level\n";
std::exit(2);
}
void parse_include_path_set(std::string include_path_set, std::vector<std::string>& res) {
td::Parser parser(include_path_set);
while (!parser.empty()) {
auto path = parser.read_till_nofail(':');
if (!path.empty()) {
res.push_back(path.str());
}
parser.skip_nofail(':');
}
}
void preload_preamble(fift::Fift& fift, std::string filename, bool standard = true) {
auto status = fift.interpret_file(filename, "");
if (status.is_error()) {
LOG(ERROR) << "Error interpreting " << (standard ? "standard" : "application-specific") << " preamble file `"
<< filename << "`: " << status.error().message()
<< "\nCheck that correct include path is set by -I or by FIFTPATH environment variable, or disable "
"standard preamble by -n.\n";
std::exit(2);
}
}
int main(int argc, char* const argv[]) {
td::set_default_failure_signal_handler().ensure();
bool interactive = false;
bool fift_preload = true, no_env = false, script_mode = false;
std::vector<std::string> library_source_files, source_list;
std::vector<std::string> source_include_path;
std::string ton_db_path;
fift::Fift::Config config;
int i;
int new_verbosity_level = VERBOSITY_NAME(INFO);
while (!script_mode && (i = getopt(argc, argv, "hinsI:L:v:")) != -1) {
switch (i) {
case 'i':
interactive = true;
break;
case 'n':
fift_preload = false;
break;
case 'I':
LOG(ERROR) << source_include_path;
parse_include_path_set(optarg, source_include_path);
no_env = true;
break;
case 's':
script_mode = true;
break;
case 'L':
library_source_files.emplace_back(optarg);
break;
case 'v':
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
break;
case 'h':
default:
usage(argv[0]);
}
}
SET_VERBOSITY_LEVEL(new_verbosity_level);
while (optind < argc) {
source_list.emplace_back(argv[optind++]);
if (script_mode) {
break;
}
}
if (!no_env) {
const char* path = std::getenv("FIFTPATH");
if (path) {
parse_include_path_set(path ? path : "/usr/lib/fift", source_include_path);
}
}
std::string current_dir;
auto r_current_dir = td::realpath(".");
if (r_current_dir.is_ok()) {
current_dir = r_current_dir.move_as_ok();
source_include_path.push_back(current_dir);
}
config.source_lookup = fift::SourceLookup(std::make_unique<fift::OsFileLoader>());
for (auto& path : source_include_path) {
config.source_lookup.add_include_path(path);
}
fift::init_words_common(config.dictionary);
fift::init_words_vm(config.dictionary);
fift::init_words_ton(config.dictionary);
init_words_custom(config.dictionary);
if (script_mode) {
fift::import_cmdline_args(config.dictionary, source_list.empty() ? "" : source_list[0], argc - optind,
argv + optind);
}
fift::Fift fift(std::move(config));
if (fift_preload) {
preload_preamble(fift, "Fift.fif", true);
preload_preamble(fift, "CreateState.fif", false);
}
for (auto source : library_source_files) {
auto status = fift.interpret_file(source, "");
if (status.is_error()) {
std::cerr << "Error interpreting preloaded file `" << source << "`: " << status.error().to_string() << std::endl;
std::exit(2);
}
}
if (source_list.empty() && !interactive) {
std::cerr << "No Fift source files specified" << std::endl;
std::exit(2);
}
for (const auto& source : source_list) {
auto status = fift.interpret_file(source, current_dir);
if (status.is_error()) {
std::cerr << "Error interpreting file `" << source << "`: " << status.error().to_string() << std::endl;
std::exit(2);
}
}
if (interactive) {
fift.interpret_istream(std::cin, current_dir).ensure();
}
// show_total_cells();
}

1817
crypto/block/mc-config.cpp Normal file

File diff suppressed because it is too large Load diff

617
crypto/block/mc-config.h Normal file
View file

@ -0,0 +1,617 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "common/refcnt.hpp"
#include "vm/db/StaticBagOfCellsDb.h"
#include "vm/dict.h"
#include "ton/ton-types.h"
#include "ton/ton-shard.h"
#include "common/bitstring.h"
#include "block.h"
#include <vector>
#include <limits>
#include <map>
#include <set>
#include <cstring>
namespace block {
using td::Ref;
struct ValidatorDescr {
ton::Ed25519_PublicKey pubkey;
td::Bits256 adnl_addr;
td::uint64 weight;
td::uint64 cum_weight;
ValidatorDescr(const td::Bits256& _pubkey, td::uint64 _weight, td::uint64 _cum_weight)
: pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) {
adnl_addr.set_zero();
}
ValidatorDescr(const td::Bits256& _pubkey, td::uint64 _weight, td::uint64 _cum_weight, const td::Bits256& _adnl_addr)
: pubkey(_pubkey), adnl_addr(_adnl_addr), weight(_weight), cum_weight(_cum_weight) {
}
ValidatorDescr(const ton::Ed25519_PublicKey& _pubkey, td::uint64 _weight, td::uint64 _cum_weight)
: pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) {
adnl_addr.set_zero();
}
bool operator<(td::uint64 wt_pos) const & {
return cum_weight < wt_pos;
}
};
struct ValidatorSet {
unsigned utime_since;
unsigned utime_until;
int total;
int main;
td::uint64 total_weight;
std::vector<ValidatorDescr> list;
ValidatorSet() = default;
ValidatorSet(unsigned _since, unsigned _until, int _total, int _main = 0)
: utime_since(_since), utime_until(_until), total(_total), main(_main > 0 ? _main : _total), total_weight(0) {
}
const ValidatorDescr& operator[](unsigned i) const {
return list[i];
}
const ValidatorDescr& at_weight(td::uint64 weight_pos) const;
std::vector<ton::ValidatorDescr> export_validator_set() const;
};
#pragma pack(push, 1)
// this structure is hashed with SHA512 to produce pseudo-random bit stream in do_compute_validator_set()
// NB: all integers (including 256-bit seed) are actually big-endian
struct validator_set_descr {
unsigned char seed[32]; // seed for validator set computation, set to zero if none
td::uint64 shard;
td::int32 workchain;
td::uint32 cc_seqno;
validator_set_descr() = default;
validator_set_descr(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, bool flag)
: shard(td::bswap64(shard_id.shard))
, workchain(td::bswap32(shard_id.workchain))
, cc_seqno(td::bswap32(cc_seqno_)) {
}
validator_set_descr(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_)
: validator_set_descr(shard_id, cc_seqno_, false) {
std::memset(seed, 0, 32);
}
validator_set_descr(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, const unsigned char seed_[32])
: validator_set_descr(shard_id, cc_seqno_, false) {
std::memcpy(seed, seed_, 32);
}
validator_set_descr(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, td::ConstBitPtr seed_)
: validator_set_descr(shard_id, cc_seqno_, false) {
td::BitPtr{seed}.copy_from(seed_, 256);
}
validator_set_descr(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, const td::Bits256& seed_)
: validator_set_descr(shard_id, cc_seqno_, false) {
td::BitPtr{seed}.copy_from(seed_.cbits(), 256);
}
void incr_seed();
void hash_to(unsigned char hash_buffer[64]) const;
};
#pragma pack(pop)
class ValidatorSetPRNG {
validator_set_descr data;
union {
unsigned char hash[64];
td::uint64 hash_longs[8];
};
int pos{0}, limit{0};
public:
ValidatorSetPRNG() = default;
ValidatorSetPRNG(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_) : data(shard_id, cc_seqno_) {
}
ValidatorSetPRNG(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, const unsigned char seed_[32])
: data(shard_id, cc_seqno_, seed_) {
}
ValidatorSetPRNG(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, td::ConstBitPtr seed_)
: data(shard_id, cc_seqno_, std::move(seed_)) {
}
ValidatorSetPRNG(ton::ShardIdFull shard_id, ton::CatchainSeqno cc_seqno_, const td::Bits256& seed_)
: data(shard_id, cc_seqno_, seed_) {
}
td::uint64 next_ulong();
td::uint64 next_ranged(td::uint64 range); // integer in 0 .. range-1
ValidatorSetPRNG& operator>>(td::uint64& x) {
x = next_ulong();
return *this;
}
};
class McShardHashI : public td::CntObject {
public:
enum class FsmState { fsm_none, fsm_split, fsm_merge };
virtual ton::BlockIdExt top_block_id() const = 0;
virtual ton::LogicalTime start_lt() const = 0;
virtual ton::LogicalTime end_lt() const = 0;
virtual ton::UnixTime fsm_utime() const = 0;
virtual FsmState fsm_state() const = 0;
virtual ton::ShardIdFull shard() const = 0;
virtual bool before_split() const = 0;
virtual bool before_merge() const = 0;
};
struct McShardHash : public McShardHashI {
ton::BlockIdExt blk_;
ton::LogicalTime start_lt_, end_lt_;
ton::UnixTime gen_utime_{0};
ton::UnixTime fsm_utime_{0};
ton::UnixTime fsm_interval_{0};
ton::BlockSeqno min_ref_mc_seqno_{std::numeric_limits<ton::BlockSeqno>::max()};
ton::BlockSeqno reg_mc_seqno_{std::numeric_limits<ton::BlockSeqno>::max()};
FsmState fsm_{FsmState::fsm_none};
bool disabled_{false};
bool before_split_{false}, before_merge_{false}, want_split_{false}, want_merge_{false};
ton::CatchainSeqno next_catchain_seqno_{std::numeric_limits<ton::CatchainSeqno>::max()};
ton::ShardId next_validator_shard_{ton::shardIdAll};
CurrencyCollection fees_collected_, funds_created_;
McShardHash(const ton::BlockId& id, ton::LogicalTime start_lt, ton::LogicalTime end_lt, ton::UnixTime gen_utime,
const ton::BlockHash& root_hash, const ton::FileHash& file_hash, CurrencyCollection fees_collected = {},
CurrencyCollection funds_created = {},
ton::BlockSeqno reg_mc_seqno = std::numeric_limits<ton::BlockSeqno>::max(),
ton::BlockSeqno min_ref_mc_seqno = std::numeric_limits<ton::BlockSeqno>::max(),
ton::CatchainSeqno cc_seqno = std::numeric_limits<ton::CatchainSeqno>::max(), ton::ShardId val_shard = 0,
bool nx_cc_updated = false, bool before_split = false, bool before_merge = false, bool want_split = false,
bool want_merge = false)
: blk_(id, root_hash, file_hash)
, start_lt_(start_lt)
, end_lt_(end_lt)
, gen_utime_(gen_utime)
, min_ref_mc_seqno_(min_ref_mc_seqno)
, reg_mc_seqno_(reg_mc_seqno)
, before_split_(before_split)
, before_merge_(before_merge)
, want_split_(want_split)
, want_merge_(want_merge)
, next_catchain_seqno_(cc_seqno)
, next_validator_shard_(val_shard ? val_shard : id.shard)
, fees_collected_(fees_collected)
, funds_created_(funds_created) {
}
McShardHash(const ton::BlockIdExt& blk, ton::LogicalTime start_lt, ton::LogicalTime end_lt)
: blk_(blk), start_lt_(start_lt), end_lt_(end_lt) {
}
McShardHash(const McShardHash&) = default;
bool is_valid() const {
return blk_.is_valid();
}
ton::BlockIdExt top_block_id() const override final {
return blk_;
}
// ZeroStateIdExt zero_state() const override;
ton::LogicalTime start_lt() const override final {
return start_lt_;
}
ton::LogicalTime end_lt() const override final {
return end_lt_;
}
ton::UnixTime fsm_utime() const override final {
return fsm_utime_;
}
ton::UnixTime fsm_utime_end() const {
return fsm_utime_ + fsm_interval_;
}
ton::UnixTime created_at() const {
return gen_utime_;
}
FsmState fsm_state() const override final {
return fsm_;
}
bool is_fsm_none() const {
return fsm_ == FsmState::fsm_none;
}
bool is_fsm_split() const {
return fsm_ == FsmState::fsm_split;
}
bool is_fsm_merge() const {
return fsm_ == FsmState::fsm_merge;
}
ton::ShardIdFull shard() const override final {
return ton::ShardIdFull(blk_);
}
ton::WorkchainId workchain() const {
return blk_.id.workchain;
}
bool contains(const ton::AccountIdPrefixFull& pfx) const {
return ton::shard_contains(shard(), pfx);
}
bool before_split() const override final {
return before_split_;
}
bool before_merge() const override final {
return before_merge_;
}
bool is_disabled() const {
return disabled_;
}
void disable() {
blk_.invalidate();
disabled_ = true;
}
ton::BlockSeqno seqno() const {
return blk_.id.seqno;
}
bool set_reg_mc_seqno(ton::BlockSeqno reg_mc_seqno) {
reg_mc_seqno_ = reg_mc_seqno;
return true;
}
// compares all fields except fsm*, before_merge_, nx_cc_updated_, next_catchain_seqno_, fees_collected_
bool basic_info_equal(const McShardHash& other, bool compare_fees = false, bool compare_reg_seqno = true) const;
void clear_fsm() {
fsm_ = FsmState::fsm_none;
}
void set_fsm(FsmState fsm, ton::UnixTime fsm_utime, ton::UnixTime fsm_interval);
void set_fsm_split(ton::UnixTime fsm_utime, ton::UnixTime fsm_interval) {
set_fsm(FsmState::fsm_split, fsm_utime, fsm_interval);
}
void set_fsm_merge(ton::UnixTime fsm_utime, ton::UnixTime fsm_interval) {
set_fsm(FsmState::fsm_merge, fsm_utime, fsm_interval);
}
bool fsm_equal(const McShardHash& other) const {
return fsm_ == other.fsm_ &&
(is_fsm_none() || (fsm_utime_ == other.fsm_utime_ && fsm_interval_ == other.fsm_interval_));
}
bool pack(vm::CellBuilder& cb) const;
static Ref<McShardHash> unpack(vm::CellSlice& cs, ton::ShardIdFull id);
static Ref<McShardHash> from_block(Ref<vm::Cell> block_root, const ton::FileHash& _fhash, bool init_fees = false);
McShardHash* make_copy() const override {
return new McShardHash(*this);
}
};
struct McShardDescr final : public McShardHash {
Ref<vm::Cell> block_root;
Ref<vm::Cell> state_root;
Ref<vm::Cell> outmsg_root;
std::unique_ptr<vm::AugmentedDictionary> out_msg_queue;
std::shared_ptr<block::MsgProcessedUptoCollection> processed_upto;
McShardDescr(const ton::BlockId& id, ton::LogicalTime start_lt, ton::LogicalTime end_lt, ton::UnixTime gen_utime,
const ton::BlockHash& root_hash, const ton::FileHash& file_hash, CurrencyCollection fees_collected = {},
CurrencyCollection funds_created = {},
ton::BlockSeqno reg_mc_seqno = std::numeric_limits<ton::BlockSeqno>::max(),
ton::BlockSeqno min_ref_mc_seqno = std::numeric_limits<ton::BlockSeqno>::max(),
ton::CatchainSeqno cc_seqno = std::numeric_limits<ton::CatchainSeqno>::max(),
ton::ShardId val_shard = ton::shardIdAll, bool nx_cc_updated = false, bool before_split = false,
bool before_merge = false, bool want_split = false, bool want_merge = false)
: McShardHash(id, start_lt, end_lt, gen_utime, root_hash, file_hash, fees_collected, funds_created, reg_mc_seqno,
min_ref_mc_seqno, cc_seqno, val_shard, nx_cc_updated, before_split, before_merge, want_split,
want_merge) {
}
McShardDescr(const ton::BlockIdExt& blk, ton::LogicalTime start_lt, ton::LogicalTime end_lt)
: McShardHash(blk, start_lt, end_lt) {
}
McShardDescr(const McShardHash& shard_hash) : McShardHash(shard_hash) {
}
McShardDescr(const McShardDescr& other);
McShardDescr(McShardDescr&& other) = default;
McShardDescr& operator=(const McShardDescr& other);
McShardDescr& operator=(McShardDescr&& other) = default;
bool set_queue_root(Ref<vm::Cell> queue_root);
void disable();
static Ref<McShardDescr> from_block(Ref<vm::Cell> block_root, Ref<vm::Cell> state_root, const ton::FileHash& _fhash,
bool init_fees = false);
static Ref<McShardDescr> from_state(ton::BlockIdExt blkid, Ref<vm::Cell> state_root);
};
struct StoragePrices {
ton::UnixTime valid_since{0};
td::uint64 bit_price{0};
td::uint64 cell_price{0};
td::uint64 mc_bit_price{0};
td::uint64 mc_cell_price{0};
StoragePrices() = default;
StoragePrices(ton::UnixTime _valid_since, td::uint64 _bprice, td::uint64 _cprice, td::uint64 _mc_bprice,
td::uint64 _mc_cprice)
: valid_since(_valid_since)
, bit_price(_bprice)
, cell_price(_cprice)
, mc_bit_price(_mc_bprice)
, mc_cell_price(_mc_cprice) {
}
};
struct CatchainValidatorsConfig {
td::uint32 mc_cc_lifetime, shard_cc_lifetime, shard_val_lifetime, shard_val_num;
CatchainValidatorsConfig(td::uint32 mc_cc_lt_, td::uint32 sh_cc_lt_, td::uint32 sh_val_lt_, td::uint32 sh_val_num_)
: mc_cc_lifetime(mc_cc_lt_)
, shard_cc_lifetime(sh_cc_lt_)
, shard_val_lifetime(sh_val_lt_)
, shard_val_num(sh_val_num_) {
}
};
struct WorkchainInfo : public td::CntObject {
ton::WorkchainId workchain{ton::workchainInvalid};
ton::UnixTime enabled_since;
td::uint32 actual_min_split;
td::uint32 min_split, max_split;
bool basic;
bool active;
bool accept_msgs;
int flags;
td::uint32 version;
ton::RootHash zerostate_root_hash;
ton::FileHash zerostate_file_hash;
int min_addr_len, max_addr_len, addr_len_step;
bool is_valid() const {
return workchain != ton::workchainInvalid;
}
bool is_valid_addr_len(int addr_len) const {
return addr_len >= min_addr_len && addr_len <= max_addr_len &&
(addr_len == min_addr_len || addr_len == max_addr_len ||
(addr_len_step > 0 && !((addr_len - min_addr_len) % addr_len_step)));
}
bool unpack(ton::WorkchainId wc, vm::CellSlice& cs);
};
using WorkchainSet = std::map<td::int32, Ref<WorkchainInfo>>;
class ShardConfig {
Ref<vm::Cell> shard_hashes_;
Ref<McShardHash> mc_shard_hash_;
std::unique_ptr<vm::Dictionary> shard_hashes_dict_;
std::set<ton::ShardIdFull> shards_updated_;
bool valid_{false};
public:
ShardConfig() = default;
ShardConfig(const ShardConfig& other);
ShardConfig(ShardConfig&& other) = default;
ShardConfig(Ref<vm::Cell> shard_hashes, Ref<McShardHash> mc_shard_hash = {})
: shard_hashes_(std::move(shard_hashes)), mc_shard_hash_(std::move(mc_shard_hash)) {
init();
}
bool is_valid() const {
return valid_;
}
bool unpack(Ref<vm::Cell> shard_hashes, Ref<McShardHash> mc_shard_hash = {});
bool unpack(Ref<vm::CellSlice> shard_hashes, Ref<McShardHash> mc_shard_hash = {});
Ref<vm::CellSlice> get_root_csr() const;
bool has_workchain(ton::WorkchainId workchain) const;
std::vector<ton::WorkchainId> get_workchains() const;
Ref<McShardHash> get_shard_hash(ton::ShardIdFull id, bool exact = true) const;
bool contains(ton::BlockIdExt blkid) const;
bool get_shard_hash_raw(vm::CellSlice& cs, ton::ShardIdFull id, ton::ShardIdFull& true_id, bool exact = true) const;
ton::LogicalTime get_shard_end_lt(ton::AccountIdPrefixFull acc) const;
ton::LogicalTime get_shard_end_lt_ext(ton::AccountIdPrefixFull acc, ton::ShardIdFull& actual_shard) const;
static bool get_shard_hash_raw_from(vm::Dictionary& shard_hashes_dict, vm::CellSlice& cs, ton::ShardIdFull id,
ton::ShardIdFull& true_id, bool exact = true, Ref<vm::Cell>* leaf = nullptr);
std::vector<ton::BlockId> get_shard_hash_ids(bool skip_mc = false) const;
std::vector<ton::BlockId> get_shard_hash_ids(const std::function<bool(ton::ShardIdFull, bool)>& filter) const;
std::vector<ton::BlockId> get_intersecting_shard_hash_ids(ton::ShardIdFull myself) const;
std::vector<ton::BlockId> get_neighbor_shard_hash_ids(ton::ShardIdFull myself) const;
std::vector<ton::BlockId> get_proper_neighbor_shard_hash_ids(ton::ShardIdFull myself) const;
static std::unique_ptr<vm::Dictionary> extract_shard_hashes_dict(Ref<vm::Cell> mc_state_root);
bool process_shard_hashes(std::function<int(McShardHash&)> func);
bool process_sibling_shard_hashes(std::function<int(McShardHash&, const McShardHash*)> func);
// may become non-static const in the future
static bool is_neighbor(ton::ShardIdFull x, ton::ShardIdFull y);
Ref<McShardHash> get_mc_hash() const {
return mc_shard_hash_;
}
void set_mc_hash(Ref<McShardHash> mc_shard_hash) {
mc_shard_hash_ = std::move(mc_shard_hash);
}
ton::CatchainSeqno get_shard_cc_seqno(ton::ShardIdFull shard) const;
block::compute_shard_end_lt_func_t get_compute_shard_end_lt_func() const {
return std::bind(&ShardConfig::get_shard_end_lt, *this, std::placeholders::_1);
}
bool new_workchain(ton::WorkchainId workchain, ton::BlockSeqno reg_mc_seqno, const ton::RootHash& zerostate_root_hash,
const ton::FileHash& zerostate_file_hash);
td::Result<bool> update_shard_block_info(Ref<McShardHash> new_info, const std::vector<ton::BlockIdExt>& old_blkids);
td::Result<bool> update_shard_block_info2(Ref<McShardHash> new_info1, Ref<McShardHash> new_info2,
const std::vector<ton::BlockIdExt>& old_blkids);
td::Result<bool> may_update_shard_block_info(Ref<McShardHash> new_info,
const std::vector<ton::BlockIdExt>& old_blkids,
ton::LogicalTime lt_limit = std::numeric_limits<ton::LogicalTime>::max(),
Ref<McShardHash>* ancestor = nullptr) const;
private:
bool init();
bool do_update_shard_info(Ref<McShardHash> new_info);
bool do_update_shard_info2(Ref<McShardHash> new_info1, Ref<McShardHash> new_info2);
bool set_shard_info(ton::ShardIdFull shard, Ref<vm::Cell> value);
};
class Config {
enum {
default_mc_catchain_lifetime = 200,
default_shard_catchain_lifetime = 200,
default_shard_validators_lifetime = 3000,
default_shard_validators_num = 7
};
public:
enum { needValidatorSet = 16, needSpecialSmc = 32, needWorkchainInfo = 256 };
int mode{0};
ton::BlockIdExt block_id;
private:
td::BitArray<256> config_addr;
Ref<vm::Cell> config_root;
std::unique_ptr<vm::Dictionary> config_dict;
std::unique_ptr<ValidatorSet> cur_validators_;
std::unique_ptr<vm::Dictionary> workchains_dict_;
WorkchainSet workchains_;
protected:
std::unique_ptr<vm::Dictionary> special_smc_dict;
public:
static constexpr ton::LogicalTime get_lt_align() {
return 1000000;
}
static constexpr ton::LogicalTime get_max_lt_growth() {
return 10 * get_lt_align() - 1;
}
Ref<vm::Cell> get_config_param(int idx) const;
Ref<vm::Cell> get_config_param(int idx, int idx2) const;
Ref<vm::Cell> operator[](int idx) const {
return get_config_param(idx);
}
Ref<vm::Cell> get_root_cell() const {
return config_root;
}
bool is_masterchain() const {
return block_id.is_masterchain();
}
bool set_block_id_ext(const ton::BlockIdExt& block_id_ext);
td::Result<std::vector<ton::StdSmcAddress>> get_special_smartcontracts(bool without_config = false) const;
bool is_special_smartcontract(const ton::StdSmcAddress& addr) const;
static td::Result<std::unique_ptr<ValidatorSet>> unpack_validator_set(Ref<vm::Cell> valset_root);
td::Result<std::vector<StoragePrices>> get_storage_prices() const;
static CatchainValidatorsConfig unpack_catchain_validators_config(Ref<vm::Cell> cell);
CatchainValidatorsConfig get_catchain_validators_config() const;
td::Status visit_validator_params() const;
td::Result<std::unique_ptr<BlockLimits>> get_block_limits(bool is_masterchain = false) const;
auto get_mc_block_limits() const {
return get_block_limits(true);
}
static td::Result<std::pair<WorkchainSet, std::unique_ptr<vm::Dictionary>>> unpack_workchain_list_ext(
Ref<vm::Cell> cell);
static td::Result<WorkchainSet> unpack_workchain_list(Ref<vm::Cell> cell);
const WorkchainSet& get_workchain_list() const {
return workchains_;
}
const ValidatorSet* get_cur_validator_set() const {
return cur_validators_.get();
}
ton::ValidatorSessionConfig get_consensus_config() const;
bool foreach_config_param(std::function<bool(int, Ref<vm::Cell>)> scan_func) const;
Ref<WorkchainInfo> get_workchain_info(ton::WorkchainId workchain_id) const;
std::vector<ton::ValidatorDescr> compute_validator_set(ton::ShardIdFull shard, const block::ValidatorSet& vset,
ton::UnixTime time, ton::CatchainSeqno cc_seqno) const;
std::vector<ton::ValidatorDescr> compute_validator_set(ton::ShardIdFull shard, ton::UnixTime time,
ton::CatchainSeqno cc_seqno) const;
std::vector<ton::ValidatorDescr> compute_total_validator_set(int next) const;
static std::vector<ton::ValidatorDescr> do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf,
ton::ShardIdFull shard,
const block::ValidatorSet& vset, ton::UnixTime time,
ton::CatchainSeqno cc_seqno);
static td::Result<std::unique_ptr<Config>> unpack_config(Ref<vm::Cell> config_root,
const td::Bits256& config_addr = td::Bits256::zero(),
int mode = 0);
static td::Result<std::unique_ptr<Config>> unpack_config(Ref<vm::CellSlice> config_csr, int mode = 0);
static td::Result<std::unique_ptr<Config>> extract_from_state(Ref<vm::Cell> mc_state_root, int mode = 0);
static td::Result<std::unique_ptr<Config>> extract_from_key_block(Ref<vm::Cell> key_block_root, int mode = 0);
protected:
Config(int _mode) : mode(_mode) {
config_addr.set_zero();
}
Config(Ref<vm::Cell> config_root, const td::Bits256& config_addr = td::Bits256::zero(), int _mode = 0);
td::Status unpack_wrapped(Ref<vm::CellSlice> config_csr);
td::Status unpack(Ref<vm::CellSlice> config_csr);
td::Status unpack_wrapped();
td::Status unpack();
};
class ConfigInfo : public Config, public ShardConfig {
public:
enum {
needStateRoot = 1,
needLibraries = 2,
needStateExtraRoot = 4,
needShardHashes = 8,
needAccountsRoot = 64,
needPrevBlocks = 128
};
int vert_seqno{-1};
int global_id_{0};
ton::UnixTime utime{0};
ton::LogicalTime lt{0};
ton::BlockSeqno min_ref_mc_seqno_{std::numeric_limits<ton::BlockSeqno>::max()};
ton::CatchainSeqno cc_seqno_{std::numeric_limits<ton::CatchainSeqno>::max()};
int shard_cc_updated{-1};
bool nx_cc_updated;
bool is_key_state_{false};
private:
Ref<vm::Cell> state_root;
Ref<vm::Cell> lib_root_;
Ref<vm::Cell> state_extra_root_;
Ref<vm::CellSlice> accounts_root;
ton::ZeroStateIdExt zerostate_id_;
ton::BlockIdExt last_key_block_;
ton::LogicalTime last_key_block_lt_;
Ref<vm::Cell> shard_hashes;
std::unique_ptr<vm::Dictionary> shard_hashes_dict;
std::unique_ptr<vm::AugmentedDictionary> accounts_dict;
std::unique_ptr<vm::AugmentedDictionary> prev_blocks_dict_;
std::unique_ptr<vm::Dictionary> libraries_dict_;
public:
bool set_block_id_ext(const ton::BlockIdExt& block_id_ext);
bool rotated_all_shards() const {
return nx_cc_updated;
}
int get_global_blockchain_id() const {
return global_id_;
}
ton::ZeroStateIdExt get_zerostate_id() const {
return zerostate_id_;
}
Ref<vm::Cell> lookup_library(const ton::Bits256& root_hash) const {
return lookup_library(root_hash.bits());
}
Ref<vm::Cell> lookup_library(td::ConstBitPtr root_hash) const;
Ref<vm::Cell> get_libraries_root() const {
return lib_root_;
}
bool is_key_state() const {
return is_key_state_;
}
Ref<vm::Cell> get_state_extra_root() const {
return state_extra_root_;
}
ton::CatchainSeqno get_shard_cc_seqno(ton::ShardIdFull shard) const;
bool get_last_key_block(ton::BlockIdExt& blkid, ton::LogicalTime& blklt, bool strict = false) const;
bool get_old_mc_block_id(ton::BlockSeqno seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt = nullptr) const;
bool check_old_mc_block_id(const ton::BlockIdExt& blkid, bool strict = false) const;
// returns block with min seqno and req_lt <= block.end_lt
bool get_mc_block_by_lt(ton::LogicalTime lt, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt = nullptr) const;
bool get_prev_key_block(ton::BlockSeqno req_seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt = nullptr) const;
bool get_next_key_block(ton::BlockSeqno req_seqno, ton::BlockIdExt& blkid, ton::LogicalTime* end_lt = nullptr) const;
td::Result<std::vector<std::pair<ton::StdSmcAddress, int>>> get_special_ticktock_smartcontracts(
int tick_tock = 3) const;
int get_smc_tick_tock(td::ConstBitPtr smc_addr) const;
std::unique_ptr<vm::AugmentedDictionary> create_accounts_dict() const;
const vm::AugmentedDictionary& get_accounts_dict() const;
std::vector<ton::ValidatorDescr> compute_validator_set_cc(ton::ShardIdFull shard, const block::ValidatorSet& vset,
ton::UnixTime time,
ton::CatchainSeqno* cc_seqno_delta = nullptr) const;
std::vector<ton::ValidatorDescr> compute_validator_set_cc(ton::ShardIdFull shard, ton::UnixTime time,
ton::CatchainSeqno* cc_seqno_delta = nullptr) const;
static td::Result<std::unique_ptr<ConfigInfo>> extract_config(std::shared_ptr<vm::StaticBagOfCellsDb> static_boc,
int mode = 0);
static td::Result<std::unique_ptr<ConfigInfo>> extract_config(Ref<vm::Cell> mc_state_root, int mode = 0);
private:
ConfigInfo(Ref<vm::Cell> mc_state_root, int _mode = 0);
td::Status unpack_wrapped();
td::Status unpack();
void reset_mc_hash();
void cleanup();
};
} // namespace block

View file

@ -0,0 +1,221 @@
/*
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 "output-queue-merger.h"
namespace block {
/*
*
* OUTPUT QUEUE MERGER
*
*/
bool OutputQueueMerger::MsgKeyValue::operator<(const MsgKeyValue& other) const {
return lt < other.lt ||
(lt == other.lt && td::bitstring::bits_memcmp(key.cbits() + 96, other.key.cbits() + 96, 256) < 0);
}
bool OutputQueueMerger::MsgKeyValue::less(const std::unique_ptr<MsgKeyValue>& he1,
const std::unique_ptr<MsgKeyValue>& he2) {
return *he1 < *he2;
}
bool OutputQueueMerger::MsgKeyValue::greater(const std::unique_ptr<MsgKeyValue>& he1,
const std::unique_ptr<MsgKeyValue>& he2) {
return *he2 < *he1;
}
OutputQueueMerger::MsgKeyValue::MsgKeyValue(td::ConstBitPtr key_pfx, int key_pfx_len, int _src, Ref<vm::Cell> node)
: source(_src) {
unpack_node(key_pfx, key_pfx_len, std::move(node));
}
OutputQueueMerger::MsgKeyValue::MsgKeyValue(int _src, Ref<vm::Cell> node) : source(_src) {
unpack_node(td::ConstBitPtr{nullptr}, 0, std::move(node));
}
bool OutputQueueMerger::MsgKeyValue::invalidate() {
msg.clear();
lt = 0;
source = -1;
return false;
}
ton::LogicalTime OutputQueueMerger::MsgKeyValue::get_node_lt(Ref<vm::Cell> node, int key_pfx_len) {
if (node.is_null() || (unsigned)key_pfx_len > (unsigned)max_key_len) {
return std::numeric_limits<td::uint64>::max();
}
vm::dict::LabelParser label{std::move(node), max_key_len - key_pfx_len, vm::dict::LabelParser::chk_size};
if (!label.is_valid()) {
return std::numeric_limits<td::uint64>::max();
}
label.skip_label();
return label.remainder->prefetch_ulong(64);
}
bool OutputQueueMerger::MsgKeyValue::unpack_node(td::ConstBitPtr key_pfx, int key_pfx_len, Ref<vm::Cell> node) {
if (node.is_null() || (unsigned)key_pfx_len >= (unsigned)max_key_len) {
return invalidate();
}
if (!key_pfx.is_null()) {
td::bitstring::bits_memcpy(key.bits(), key_pfx, key_pfx_len);
}
vm::dict::LabelParser label{std::move(node), max_key_len - key_pfx_len, vm::dict::LabelParser::chk_size};
if (!label.is_valid()) {
return invalidate();
}
label.extract_label_to(key.bits() + key_pfx_len);
key_len = key_pfx_len + label.l_bits;
msg = std::move(label.remainder);
if (!msg.write().fetch_uint_to(64, lt)) {
return invalidate();
}
if (is_fork() && msg->size_ext() != 0x20000) {
return invalidate();
}
return true;
}
bool OutputQueueMerger::MsgKeyValue::replace_with_child(bool child_idx) {
if (!is_fork() || msg.is_null() || msg->size_ext() != 0x20000) {
return false;
}
key[key_len] = child_idx;
return unpack_node(td::ConstBitPtr{nullptr}, key_len + 1, msg->prefetch_ref(child_idx));
}
bool OutputQueueMerger::MsgKeyValue::replace_by_prefix(td::ConstBitPtr req_pfx, int req_pfx_len) {
do {
if (td::bitstring::bits_memcmp(req_pfx, key.cbits(), std::min(req_pfx_len, key_len))) {
return false;
}
if (key_len >= req_pfx_len) {
return true;
}
} while (replace_with_child(req_pfx[key_len]));
return false;
}
bool OutputQueueMerger::MsgKeyValue::split(MsgKeyValue& second) {
if (!is_fork() || msg.is_null()) {
return false;
}
unsigned long long keep_lt = lt;
unsigned long long left_lt = get_node_lt(msg->prefetch_ref(0), key_len + 1);
bool sw = (left_lt == lt);
second.source = source;
key[key_len] = sw;
if (!second.unpack_node(key.cbits(), key_len + 1, msg->prefetch_ref(sw))) {
return false;
}
key[key_len] = 1 - sw;
if (!unpack_node(td::ConstBitPtr{nullptr}, key_len + 1, msg->prefetch_ref(1 - sw))) {
return false;
}
if (lt != keep_lt || second.lt < keep_lt) {
return false;
}
return true;
}
bool OutputQueueMerger::add_root(int src, Ref<vm::Cell> outmsg_root) {
if (outmsg_root.is_null()) {
return true;
}
//block::gen::HashmapAug{352, block::gen::t_EnqueuedMsg, block::gen::t_uint64}.print_ref(std::cerr, outmsg_root);
auto kv = std::make_unique<MsgKeyValue>(src, std::move(outmsg_root));
if (kv->replace_by_prefix(common_pfx.cbits(), common_pfx_len)) {
heap.push_back(std::move(kv));
}
return true;
}
OutputQueueMerger::OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector<block::McShardDescr> _neighbors)
: queue_for(_queue_for), neighbors(std::move(_neighbors)), eof(false), failed(false) {
init();
}
void OutputQueueMerger::init() {
common_pfx.bits().store_int(queue_for.workchain, 32);
int l = queue_for.pfx_len();
td::bitstring::bits_store_long_top(common_pfx.bits() + 32, queue_for.shard, l);
common_pfx_len = 32 + l;
int i = 0;
for (block::McShardDescr& neighbor : neighbors) {
if (!neighbor.is_disabled()) {
LOG(DEBUG) << "adding " << (neighbor.outmsg_root.is_null() ? "" : "non-") << "empty output queue for neighbor #"
<< i << " (" << neighbor.blk_.to_str() << ")";
add_root(i++, neighbor.outmsg_root);
} else {
LOG(DEBUG) << "skipping output queue for disabled neighbor #" << i;
i++;
}
}
std::make_heap(heap.begin(), heap.end(), MsgKeyValue::greater);
eof = heap.empty();
if (!eof) {
load();
}
}
OutputQueueMerger::MsgKeyValue* OutputQueueMerger::cur() {
return eof ? nullptr : msg_list.at(pos).get();
}
std::unique_ptr<OutputQueueMerger::MsgKeyValue> OutputQueueMerger::extract_cur() {
return eof ? std::unique_ptr<MsgKeyValue>{} : std::move(msg_list.at(pos));
}
bool OutputQueueMerger::next() {
if (eof) {
return false;
} else if (++pos < msg_list.size() || load()) {
return true;
} else {
eof = true;
return false;
}
}
bool OutputQueueMerger::load() {
if (heap.empty() || failed) {
return false;
}
unsigned long long lt = heap[0]->lt;
std::size_t orig_size = msg_list.size();
do {
while (heap[0]->is_fork()) {
auto other = std::make_unique<MsgKeyValue>();
if (!heap[0]->split(*other)) {
failed = true;
return false;
}
heap.push_back(std::move(other));
std::push_heap(heap.begin(), heap.end(), MsgKeyValue::greater);
}
assert(heap[0]->lt == lt);
std::pop_heap(heap.begin(), heap.end(), MsgKeyValue::greater);
msg_list.push_back(std::move(heap.back()));
heap.pop_back();
} while (!heap.empty() && heap[0]->lt <= lt);
std::sort(msg_list.begin() + orig_size, msg_list.end(), MsgKeyValue::less);
return true;
}
} // namespace block

View file

@ -0,0 +1,80 @@
/*
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
*/
#pragma once
#include "ton/ton-types.h"
#include "vm/cells/CellSlice.h"
#include "block/mc-config.h"
namespace block {
using td::Ref;
struct OutputQueueMerger {
struct MsgKeyValue {
static constexpr int max_key_len = 32 + 64 + 256;
Ref<vm::CellSlice> msg;
unsigned long long lt;
int source;
int key_len{0};
td::BitArray<max_key_len> key;
MsgKeyValue() = default;
MsgKeyValue(int src, Ref<vm::Cell> node);
MsgKeyValue(td::ConstBitPtr key_pfx, int key_pfx_len, int src, Ref<vm::Cell> node);
bool operator<(const MsgKeyValue& other) const;
bool is_fork() const {
return key_len < max_key_len;
}
bool invalidate();
static bool less(const std::unique_ptr<MsgKeyValue>& he1, const std::unique_ptr<MsgKeyValue>& he2);
static bool greater(const std::unique_ptr<MsgKeyValue>& he1, const std::unique_ptr<MsgKeyValue>& he2);
protected:
friend struct OutputQueueMerger;
static ton::LogicalTime get_node_lt(Ref<vm::Cell> node, int key_pfx_len);
bool replace_with_child(bool child_idx);
bool replace_by_prefix(td::ConstBitPtr req_pfx, int req_pfx_len);
bool unpack_node(td::ConstBitPtr key_pfx, int key_pfx_len, Ref<vm::Cell> node);
bool split(MsgKeyValue& second);
};
//
ton::ShardIdFull queue_for;
std::vector<std::unique_ptr<MsgKeyValue>> msg_list;
std::vector<block::McShardDescr> neighbors;
public:
OutputQueueMerger(ton::ShardIdFull _queue_for, std::vector<block::McShardDescr> _neighbors);
bool is_eof() const {
return eof;
}
MsgKeyValue* cur();
std::unique_ptr<MsgKeyValue> extract_cur();
bool next();
private:
td::BitArray<32 + 64> common_pfx;
int common_pfx_len;
std::vector<std::unique_ptr<MsgKeyValue>> heap;
std::size_t pos{0};
bool eof;
bool failed;
void init();
bool add_root(int src, Ref<vm::Cell> outmsg_root);
bool load();
};
} // namespace block

248
crypto/block/test-block.cpp Normal file
View file

@ -0,0 +1,248 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "block/block.h"
#include "vm/boc.h"
#include <iostream>
#include "block-db.h"
#include "block-auto.h"
#include "block-parse.h"
#include "vm/cp0.h"
#include <getopt.h>
using td::Ref;
int verbosity;
struct IntError {
std::string err_msg;
IntError(std::string _msg) : err_msg(_msg) {
}
IntError(const char* _msg) : err_msg(_msg) {
}
};
td::Ref<vm::Cell> load_boc(std::string filename) {
std::cerr << "loading bag-of-cell file " << filename << std::endl;
auto bytes_res = block::load_binary_file(filename);
if (bytes_res.is_error()) {
throw IntError{PSTRING() << "cannot load file `" << filename << "` : " << bytes_res.move_as_error()};
}
vm::BagOfCells boc;
auto res = boc.deserialize(bytes_res.move_as_ok());
if (res.is_error()) {
throw IntError{PSTRING() << "cannot deserialize bag-of-cells " << res.move_as_error()};
}
if (res.move_as_ok() <= 0 || boc.get_root_cell().is_null()) {
throw IntError{"cannot deserialize bag-of-cells "};
}
return boc.get_root_cell();
}
void test1() {
block::ShardId id{ton::masterchainId}, id2{ton::basechainId, 0x11efULL << 48};
std::cout << '[' << id << "][" << id2 << ']' << std::endl;
vm::CellBuilder cb;
cb << id << id2;
std::cout << "ShardIdent.pack() = " << block::tlb::t_ShardIdent.pack(cb, {12, 3, 0x3aeULL << 52}) << std::endl;
std::cout << cb << std::endl;
auto cref = cb.finalize();
td::Ref<vm::CellSlice> cs{true, cref}, cs2;
block::ShardId id3{cs.write()}, id4, id5;
cs >> id4 >> id5;
std::cout << '[' << id3 << "][" << id4 << "][" << id5 << ']' << std::endl;
vm::CellSlice csl{std::move(cref)};
std::cout << "ShardIdent.get_size() = " << block::tlb::t_ShardIdent.get_size(csl) << std::endl;
std::cout << "MsgAddress.get_size() = " << block::tlb::t_MsgAddress.get_size(csl) << std::endl;
std::cout << "Grams.get_size() = " << block::tlb::t_Grams.get_size(csl) << std::endl;
std::cout << "Grams.as_integer() = " << block::tlb::t_Grams.as_integer(csl) << std::endl;
(csl + 8).print_rec(std::cout);
std::cout << "Grams.get_size() = " << block::tlb::t_Grams.get_size(csl + 8) << std::endl;
std::cout << "Grams.as_integer() = " << block::tlb::t_Grams.as_integer(csl + 8) << std::endl;
vm::CellSlice csl2{csl};
block::gen::ShardIdent::Record sh_id;
for (int i = 0; i < 3; i++) {
std::cout << csl2 << std::endl;
bool ok = tlb::unpack(csl2, sh_id);
std::cout << "block::gen::ShardIdent.unpack() = " << ok << std::endl;
if (ok) {
std::cout << " (shard_ident shard_pfx_bits:" << sh_id.shard_pfx_bits << " workchain_id:" << sh_id.workchain_id
<< " shard_prefix:" << std::hex << sh_id.shard_prefix << std::dec << ")" << std::endl;
}
}
block::tlb::ShardIdent::Record shard_id;
for (int i = 0; i < 3; i++) {
std::cout << "ShardIdent.validate() = " << block::tlb::t_ShardIdent.validate(csl) << std::endl;
csl.print_rec(std::cerr);
csl.dump(std::cerr, 7);
std::cout << "ShardIdent.unpack() = " << block::tlb::t_ShardIdent.unpack(csl, shard_id) << std::endl;
if (shard_id.is_valid()) {
std::cout << "shard_pfx_bits:" << shard_id.shard_pfx_bits << " workchain_id:" << shard_id.workchain_id
<< " shard_prefix:" << shard_id.shard_prefix << std::endl;
}
}
std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl;
std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl;
std::cout << "ShardIdent.skip_validate() = " << block::tlb::t_ShardIdent.validate_skip(csl) << std::endl;
using namespace td::literals;
std::cout << "Grams.store_intval(239) = " << block::tlb::t_Grams.store_integer_value(cb, "239"_i256) << std::endl;
std::cout << "Grams.store_intval(17239) = " << block::tlb::t_Grams.store_integer_value(cb, "17239"_i256) << std::endl;
std::cout << "Grams.store_intval(-17) = " << block::tlb::t_Grams.store_integer_value(cb, "-17"_i256) << std::endl;
std::cout << "Grams.store_intval(0) = " << block::tlb::t_Grams.store_integer_value(cb, "0"_i256) << std::endl;
std::cout << cb << std::endl;
cs = td::Ref<vm::CellSlice>{true, cb.finalize()};
std::cout << "Grams.store_intval(666) = " << block::tlb::t_Grams.store_integer_value(cb, "666"_i256) << std::endl;
std::cout << cb << std::endl;
cs2 = td::Ref<vm::CellSlice>{true, cb.finalize()};
std::cout << "Grams.validate(cs) = " << block::tlb::t_Grams.validate(*cs) << std::endl;
std::cout << "Grams.validate(cs2) = " << block::tlb::t_Grams.validate(*cs2) << std::endl;
//
block::gen::SplitMergeInfo::Record data;
block::gen::Grams::Record data2;
std::cout << "block::gen::Grams.validate(cs) = " << block::gen::t_Grams.validate(*cs) << std::endl;
std::cout << "block::gen::Grams.validate(cs2) = " << block::gen::t_Grams.validate(*cs2) << std::endl;
std::cout << "[cs = " << cs << "]" << std::endl;
bool ok = tlb::csr_unpack_inexact(cs, data);
std::cout << "block::gen::SplitMergeInfo.unpack(cs, data) = " << ok << std::endl;
if (ok) {
std::cout << " cur_shard_pfx_len = " << data.cur_shard_pfx_len << "; acc_split_depth = " << data.acc_split_depth
<< "; this_addr = " << data.this_addr << "; sibling_addr = " << data.sibling_addr << std::endl;
}
ok = tlb::csr_unpack_inexact(cs, data2);
std::cout << "block::gen::Grams.unpack(cs, data2) = " << ok << std::endl;
if (ok) {
std::cout << " amount = " << data2.amount << std::endl;
block::gen::VarUInteger::Record data3;
ok = tlb::csr_type_unpack(data2.amount, block::gen::t_VarUInteger_16, data3);
std::cout << " block::gen::VarUInteger16.unpack(amount, data3) = " << ok << std::endl;
if (ok) {
std::cout << " len = " << data3.len << "; value = " << data3.value << std::endl;
vm::CellBuilder cb;
std::cout << " block::gen::VarUInteger16.pack(cb, data3) = "
<< tlb::type_pack(cb, block::gen::t_VarUInteger_16, data3) << std::endl;
std::cout << " cb = " << cb.finalize() << std::endl;
}
}
/*
{
vm::CellBuilder cb;
td::BitArray<256> hash;
std::memset(hash.data(), 0x69, 32);
bool ok = tlb::pack(
cb, block::gen::Test::Record{1000000000000, {170239, -888, {239017, "1000000000000000000"_ri256}, hash}, 17});
std::cout << " block::gen::Test::pack(cb, {1000000000000, ...}) = " << ok << std::endl;
std::cout << " cb = " << cb << std::endl;
auto cell = cb.finalize();
vm::CellSlice cs{cell};
cs.print_rec(std::cout);
block::gen::Test::Record data;
std::cout << " block::gen::Test::validate_ref(cell) = " << block::gen::t_Test.validate_ref(cell) << std::endl;
ok = tlb::unpack(cs, data);
std::cout << " block::gen::Test::unpack(cs, data) = " << ok << std::endl;
if (ok) {
std::cout << "a:" << data.a << " b:" << data.r1.b << " c:" << data.r1.c << " d:" << data.r1.r1.d
<< " e:" << data.r1.r1.e << " f:" << data.r1.f << " g:" << data.g << std::endl;
}
std::cout << " block::gen::Test::print_ref(cell) = ";
block::gen::t_Test.print_ref(std::cout, cell, 2);
block::gen::t_CurrencyCollection.print_ref(std::cout, cell, 2);
std::cout << std::endl;
}
*/
std::cout << "Grams.add_values() = " << block::tlb::t_Grams.add_values(cb, cs.write(), cs2.write()) << std::endl;
std::cout << cb << std::endl;
std::cout << "block::gen::t_HashmapAug_64_...print_type() = "
<< block::gen::t_HashmapAug_64_Ref_Transaction_CurrencyCollection << std::endl;
}
void test2(vm::CellSlice& cs) {
std::cout << "Bool.validate() = " << block::tlb::t_Bool.validate(cs) << std::endl;
std::cout << "UInt16.validate() = " << block::tlb::t_uint16.validate(cs) << std::endl;
std::cout << "HashmapE(32,UInt16).validate() = " << block::tlb::HashmapE(32, block::tlb::t_uint16).validate(cs)
<< std::endl;
std::cout << "block::gen::HashmapE(32,UInt16).validate() = "
<< block::gen::HashmapE{32, block::gen::t_uint16}.validate(cs) << std::endl;
}
void usage() {
std::cout << "usage: test-block [<boc-file>]\n\tor test-block -h\n";
std::exit(2);
}
int main(int argc, char* const argv[]) {
int i;
int new_verbosity_level = VERBOSITY_NAME(INFO);
auto zerostate = std::make_unique<block::ZerostateInfo>();
while ((i = getopt(argc, argv, "hv:")) != -1) {
switch (i) {
case 'v':
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
break;
case 'h':
usage();
std::exit(2);
default:
usage();
std::exit(2);
}
}
SET_VERBOSITY_LEVEL(new_verbosity_level);
try {
bool done = false;
while (optind < argc) {
auto boc = load_boc(argv[optind++]);
if (boc.is_null()) {
std::cerr << "(invalid boc)" << std::endl;
std::exit(2);
} else {
done = true;
vm::CellSlice cs{vm::NoVm(), boc};
cs.print_rec(std::cout);
std::cout << std::endl;
block::gen::t_Block.print_ref(std::cout, boc);
std::cout << std::endl;
if (!block::gen::t_Block.validate_ref(boc)) {
std::cout << "(invalid Block)" << std::endl;
} else {
std::cout << "(valid Block)" << std::endl;
}
}
}
if (!done) {
test1();
}
} catch (IntError& err) {
std::cerr << "caught internal error " << err.err_msg << std::endl;
return 1;
} catch (vm::VmError& err) {
std::cerr << "caught vm error " << err.get_msg() << std::endl;
return 1;
}
return 0;
}

2089
crypto/block/transaction.cpp Normal file

File diff suppressed because it is too large Load diff

401
crypto/block/transaction.h Normal file
View file

@ -0,0 +1,401 @@
/*
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
*/
#pragma once
#include "common/refcnt.hpp"
#include "common/refint.h"
#include "vm/cells.h"
#include "vm/cellslice.h"
#include "vm/dict.h"
#include "vm/boc.h"
#include <ostream>
#include "tl/tlblib.hpp"
#include "td/utils/bits.h"
#include "ton/ton-types.h"
#include "block/block.h"
#include "block/mc-config.h"
namespace block {
using td::Ref;
using LtCellRef = std::pair<ton::LogicalTime, Ref<vm::Cell>>;
struct Account;
struct Transaction;
struct CollatorError {
std::string msg;
CollatorError(std::string _msg) : msg(_msg) {
}
CollatorError(const char* _msg) : msg(_msg) {
}
std::string get_msg() const {
return msg;
}
};
static inline bool operator<(const LtCellRef& a, const LtCellRef& b) {
return a.first < b.first;
}
struct LtCellCompare {
bool operator()(const LtCellRef& a, const LtCellRef& b) {
return a.first < b.first;
}
};
struct NewOutMsg {
ton::LogicalTime lt;
Ref<vm::Cell> msg;
Ref<vm::Cell> trans;
NewOutMsg(ton::LogicalTime _lt, Ref<vm::Cell> _msg, Ref<vm::Cell> _trans)
: lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) {
}
bool operator<(const NewOutMsg& other) const & {
return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash());
}
bool operator>(const NewOutMsg& other) const & {
return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash());
}
};
struct StoragePhaseConfig {
const std::vector<block::StoragePrices>* pricing{nullptr};
td::RefInt256 freeze_due_limit;
td::RefInt256 delete_due_limit;
StoragePhaseConfig() = default;
StoragePhaseConfig(const std::vector<block::StoragePrices>* _pricing, td::RefInt256 freeze_limit = {},
td::RefInt256 delete_limit = {})
: pricing(_pricing), freeze_due_limit(freeze_limit), delete_due_limit(delete_limit) {
}
};
struct StoragePhase {
td::RefInt256 fees_collected;
td::RefInt256 fees_due;
ton::UnixTime last_paid_updated;
bool frozen{false};
bool deleted{false};
bool is_special{false};
};
struct ComputePhaseConfig {
td::uint64 gas_price;
td::uint64 gas_limit;
td::uint64 gas_credit;
static constexpr td::uint64 gas_infty = (1ULL << 63) - 1;
td::RefInt256 gas_price256;
td::RefInt256 max_gas_threshold;
std::unique_ptr<vm::Dictionary> libraries;
Ref<vm::Cell> global_config;
td::BitArray<256> block_rand_seed;
ComputePhaseConfig(td::uint64 _gas_price = 0, td::uint64 _gas_limit = 0, td::uint64 _gas_credit = 0)
: gas_price(_gas_price), gas_limit(_gas_limit), gas_credit(_gas_credit) {
compute_threshold();
}
void compute_threshold();
td::uint64 gas_bought_for(td::RefInt256 nanograms) const;
td::RefInt256 compute_gas_price(td::uint64 gas_used) const;
void set_gas_price(td::uint64 _gas_price) {
gas_price = _gas_price;
compute_threshold();
}
Ref<vm::Cell> lookup_library(td::ConstBitPtr key) const;
Ref<vm::Cell> lookup_library(const td::Bits256& key) const {
return lookup_library(key.bits());
}
Ref<vm::Cell> get_lib_root() const {
return libraries ? libraries->get_root_cell() : Ref<vm::Cell>{};
}
};
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
struct MsgPrices {
td::uint64 lump_price;
td::uint64 bit_price;
td::uint64 cell_price;
td::uint32 ihr_factor;
td::uint32 first_frac;
td::uint32 next_frac;
td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const;
std::pair<td::uint64, td::uint64> compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
bool ihr_disabled = false) const;
MsgPrices() = default;
MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf)
: lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) {
}
td::RefInt256 get_first_part(td::RefInt256 total) const;
td::uint64 get_first_part(td::uint64 total) const;
td::RefInt256 get_next_part(td::RefInt256 total) const;
};
struct ActionPhaseConfig {
int max_actions{255};
MsgPrices fwd_std;
MsgPrices fwd_mc; // from/to masterchain
const WorkchainSet* workchains{nullptr};
const MsgPrices& fetch_msg_prices(bool is_masterchain) const {
return is_masterchain ? fwd_mc : fwd_std;
}
};
struct CreditPhase {
td::RefInt256 due_fees_collected;
block::CurrencyCollection credit;
// td::RefInt256 credit;
// Ref<vm::Cell> credit_extra;
};
struct ComputePhase {
enum { sk_none, sk_no_state, sk_bad_state, sk_no_gas };
int skip_reason{sk_none};
bool success{false};
bool msg_state_used{false};
bool account_activated{false};
bool out_of_gas{false};
bool accepted{false};
td::RefInt256 gas_fees;
td::uint64 gas_used, gas_max, gas_limit, gas_credit;
int mode;
int exit_code;
int exit_arg;
int vm_steps;
ton::Bits256 vm_init_state_hash, vm_final_state_hash;
Ref<vm::Cell> in_msg;
Ref<vm::Cell> new_data;
Ref<vm::Cell> actions;
};
struct ActionPhase {
bool success{false};
bool valid{false};
bool no_funds{false};
bool code_changed{false};
bool action_list_invalid{false};
bool acc_delete_req{false};
enum { acst_unchanged = 0, acst_frozen = 2, acst_deleted = 3 };
int acc_status_change{acst_unchanged};
td::RefInt256 total_fwd_fees; // all fees debited from the account
td::RefInt256 total_action_fees; // fees credited to validators in this action phase
int result_code;
int result_arg;
int tot_actions;
int spec_actions;
int skipped_actions;
int msgs_created;
Ref<vm::Cell> new_code;
td::BitArray<256> action_list_hash;
block::CurrencyCollection remaining_balance, reserved_balance;
// td::RefInt256 remaining_balance;
// Ref<vm::Cell> remaining_extra;
// td::RefInt256 reserved_balance;
// Ref<vm::Cell> reserved_extra;
std::vector<Ref<vm::Cell>> action_list; // processed in reverse order
std::vector<Ref<vm::Cell>> out_msgs;
ton::LogicalTime end_lt;
unsigned long long tot_msg_bits{0}, tot_msg_cells{0};
};
struct BouncePhase {
bool ok{false};
bool nofunds{false};
unsigned long long msg_bits, msg_cells;
unsigned long long fwd_fees, fwd_fees_collected;
Ref<vm::Cell> out_msg;
};
struct Account {
enum { acc_nonexist = 0, acc_uninit = 1, acc_frozen = 2, acc_active = 3, acc_deleted = 4 };
int status{acc_nonexist}, orig_status{acc_nonexist};
bool is_special{false};
bool tick{false};
bool tock{false};
bool created{false};
bool split_depth_set_{false};
unsigned char split_depth_{0};
int verbosity{3 * 0};
ton::UnixTime now_{0};
ton::WorkchainId workchain{ton::workchainInvalid};
td::BitArray<32> addr_rewrite; // rewrite (anycast) data, split_depth bits
ton::StdSmcAddress addr; // rewritten address (by replacing a prefix of `addr_orig` with `addr_rewrite`)
ton::StdSmcAddress addr_orig; // address indicated in smart-contract data
Ref<vm::CellSlice> my_addr; // address as stored in the smart contract (MsgAddressInt)
Ref<vm::CellSlice> my_addr_exact; // exact address without anycast info
ton::LogicalTime last_trans_end_lt_;
ton::LogicalTime last_trans_lt_;
ton::Bits256 last_trans_hash_;
ton::LogicalTime block_lt;
ton::UnixTime last_paid;
vm::CellStorageStat storage_stat;
block::CurrencyCollection balance;
// td::RefInt256 balance;
// Ref<vm::Cell> extra_balance;
td::RefInt256 due_payment;
Ref<vm::Cell> orig_total_state; // ^Account
Ref<vm::Cell> total_state; // ^Account
Ref<vm::CellSlice> inner_state; // StateInit
ton::Bits256 state_hash; // hash of StateInit for frozen accounts
Ref<vm::Cell> code, data, library, orig_library;
std::vector<LtCellRef> transactions;
Account() = default;
Account(ton::WorkchainId wc, td::ConstBitPtr _addr) : workchain(wc), addr(_addr) {
}
Account(ton::WorkchainId wc, td::ConstBitPtr _addr, int depth)
: split_depth_set_(true), split_depth_((unsigned char)depth), workchain(wc), addr(_addr) {
}
block::CurrencyCollection get_balance() const {
return balance;
}
bool set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr);
bool unpack(Ref<vm::CellSlice> account, Ref<vm::CellSlice> extra, ton::UnixTime now, bool special = false);
bool init_new(ton::UnixTime now);
bool recompute_tmp_addr(Ref<vm::CellSlice>& tmp_addr, int split_depth, td::ConstBitPtr orig_addr_rewrite) const;
td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const;
bool is_masterchain() const {
return workchain == ton::masterchainId;
}
bool belongs_to_shard(ton::ShardIdFull shard) const;
bool store_acc_status(vm::CellBuilder& cb, int status) const;
bool store_acc_status(vm::CellBuilder& cb) const {
return store_acc_status(cb, status);
}
void push_transaction(Ref<vm::Cell> trans_root, ton::LogicalTime trans_lt);
bool libraries_changed() const;
bool create_account_block(vm::CellBuilder& cb); // stores an AccountBlock with all transactions
protected:
friend struct Transaction;
bool set_split_depth(int split_depth);
bool check_split_depth(int split_depth) const;
bool init_rewrite_addr(int split_depth, td::ConstBitPtr orig_addr_rewrite);
private:
bool unpack_address(vm::CellSlice& addr_cs);
bool unpack_storage_info(vm::CellSlice& cs);
bool unpack_state(vm::CellSlice& cs);
bool parse_maybe_anycast(vm::CellSlice& cs);
bool store_maybe_anycast(vm::CellBuilder& cb) const;
bool compute_my_addr(bool force = false);
};
struct Transaction {
static constexpr unsigned max_msg_bits = (1 << 21), max_msg_cells = (1 << 13);
enum {
tr_none,
tr_ord,
tr_storage,
tr_tick,
tr_tock,
tr_split_prepare,
tr_split_install,
tr_merge_prepare,
tr_merge_install
};
int trans_type{tr_none};
bool was_deleted{false};
bool was_frozen{false};
bool was_activated{false};
bool was_created{false};
bool bounce_enabled{false};
bool in_msg_extern{false};
bool use_msg_state{false};
bool is_first{false};
bool orig_addr_rewrite_set{false};
bool new_tick;
bool new_tock;
signed char new_split_depth{-1};
ton::UnixTime now;
int acc_status;
int verbosity{3 * 0};
int in_msg_type{0};
const Account& account; // only `commit` method modifies the account
Ref<vm::CellSlice> my_addr, my_addr_exact; // almost the same as in account.*
ton::LogicalTime start_lt, end_lt;
block::CurrencyCollection balance;
block::CurrencyCollection msg_balance_remaining;
td::RefInt256 due_payment;
td::RefInt256 in_fwd_fee, msg_fwd_fees;
block::CurrencyCollection total_fees{0};
ton::UnixTime last_paid;
Ref<vm::Cell> root;
Ref<vm::Cell> new_total_state;
Ref<vm::CellSlice> new_inner_state;
// Ref<vm::Cell> extra_balance;
// Ref<vm::Cell> msg_extra;
Ref<vm::Cell> new_code, new_data, new_library;
Ref<vm::Cell> in_msg, in_msg_state;
Ref<vm::CellSlice> in_msg_body;
Ref<vm::Cell> in_msg_library;
td::BitArray<256> frozen_hash;
td::BitArray<32> orig_addr_rewrite;
std::vector<Ref<vm::Cell>> out_msgs;
std::unique_ptr<StoragePhase> storage_phase;
std::unique_ptr<CreditPhase> credit_phase;
std::unique_ptr<ComputePhase> compute_phase;
std::unique_ptr<ActionPhase> action_phase;
std::unique_ptr<BouncePhase> bounce_phase;
vm::CellStorageStat new_storage_stat;
Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now,
Ref<vm::Cell> _inmsg = {});
bool unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg);
bool check_in_msg_state_hash();
bool prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect = true);
bool prepare_credit_phase();
bool compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg);
Ref<vm::Stack> prepare_vm_stack(ComputePhase& cp);
std::vector<Ref<vm::Cell>> compute_vm_libraries(const ComputePhaseConfig& cfg);
bool prepare_compute_phase(const ComputePhaseConfig& cfg);
bool prepare_action_phase(const ActionPhaseConfig& cfg);
bool prepare_bounce_phase(const ActionPhaseConfig& cfg);
bool compute_state();
bool serialize();
td::uint64 gas_used() const {
return compute_phase ? compute_phase->gas_used : 0;
}
td::Result<vm::NewCellStorageStat::Stat> estimate_block_storage_profile_incr(
const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const;
bool update_block_storage_profile(vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const;
bool would_fit(unsigned cls, const block::BlockLimitStatus& blk_lim_st) const;
bool update_limits(block::BlockLimitStatus& blk_lim_st) const;
Ref<vm::Cell> commit(Account& _account); // _account should point to the same account
LtCellRef extract_out_msg(unsigned i);
NewOutMsg extract_out_msg_ext(unsigned i);
void extract_out_msgs(std::vector<LtCellRef>& list);
private:
Ref<vm::Tuple> prepare_vm_c7(const ComputePhaseConfig& cfg) const;
bool prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const;
int try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
int try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
int try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
bool check_replace_src_addr(Ref<vm::CellSlice>& src_addr) const;
bool check_rewrite_dest_addr(Ref<vm::CellSlice>& dest_addr, const ActionPhaseConfig& cfg,
bool* is_mc = nullptr) const;
bool serialize_storage_phase(vm::CellBuilder& cb);
bool serialize_credit_phase(vm::CellBuilder& cb);
bool serialize_compute_phase(vm::CellBuilder& cb);
bool serialize_action_phase(vm::CellBuilder& cb);
bool serialize_bounce_phase(vm::CellBuilder& cb);
bool unpack_msg_state(bool lib_only = false);
};
} // namespace block