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

Initial commit of block generator for fisherman testing

This commit is contained in:
trinitil 2025-01-28 13:35:02 +04:00
parent ea0dc16163
commit 383a0c574a
14 changed files with 523 additions and 2 deletions

View file

@ -223,7 +223,7 @@ endif()
if (TON_ARCH AND NOT MSVC)
CHECK_CXX_COMPILER_FLAG( "-march=${TON_ARCH}" COMPILER_OPT_ARCH_SUPPORTED )
if (TON_ARCH STREQUAL "apple-m1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${TON_ARCH}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${TON_ARCH}")
elseif(COMPILER_OPT_ARCH_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TON_ARCH}")
elseif(NOT TON_ARCH STREQUAL "native")
@ -555,6 +555,17 @@ target_link_libraries(test-http PRIVATE tonhttp)
add_executable(test-emulator test/test-td-main.cpp emulator/test/emulator-tests.cpp)
target_link_libraries(test-emulator PRIVATE emulator)
add_executable(test-fisherman
test/fisherman/tests.cpp
test/fisherman/block_manipulator/factory.cpp
test/fisherman/block_reader.cpp
test/fisherman/utils.cpp
)
target_link_libraries(test-fisherman PRIVATE validator ton_validator validator ton_db)
add_executable(print-all-shard-states test/print-all-shard-states.cpp)
target_link_libraries(print-all-shard-states PRIVATE validator ton_db)
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
if (HAS_PARENT)
set(ALL_TEST_SOURCE

View file

@ -59,6 +59,7 @@ RocksDb RocksDb::clone() const {
return RocksDb{db_, options_};
}
// TODO: Add support for opening the database in read-only mode.
Result<RocksDb> RocksDb::open(std::string path, RocksDbOptions options) {
rocksdb::OptimisticTransactionDB *db;
{

View file

@ -0,0 +1 @@
#pragma once

View file

@ -0,0 +1 @@
#include "base.hpp"

View file

@ -0,0 +1,95 @@
- Некорректная работа твм, т.е. результат исполнения записанный в блоке не совпадает с "локальным" исполнением, в частности
-- некорректная проверка подписи (принята подпись, которая не была бы принята в реальной сети)
-- некорректная комиссия транзакции (одна из)
-- некорректные исходящие сообщения
-- некорректный апдейт стейта
- Непоследовательное исполнение сообщений (в блок включено сообщение с большим lt/hash чем сообщения оставшиеся в очереди)
- Несоблюдение лимитов блока (по газу, размеру, lt)
- Некорректные value-flow (уничтожаются/минтятся деньги из воздуха)
Подпись
1. Неверная подпись валидатора в заголовке.
2. Задвоенная подпись (или отсутствующая подпись при наличии).
В заголовке блока записано, что "N валидаторов подписались",
но по факту часть подписей повторяется, либо не совпадает с публичным ключом.
3. Подделка подписи внешнего сообщения. Внешнее сообщение якобы подписано кошельком пользователя, но фактически подпись неверна.
Газ / комиссии
1. Некорректная сумма комиссии в транзакции. В том числе и обнуление комиссии.
2. Неверное распределение комиссии между валидаторами, коллатором и пр.
Сумма собранной комиссии не совпадает с тем, сколько реально поступило на счета валидаторов/коллатора.
Исходящие сообщения
1. Неверная форма исходящего сообщения ???
Внутреннее сообщение, сформированное контрактом, имеет некорректную структуру.
2. Сообщение отправляется на несуществующий адрес (или адрес, не соответствующий данному шарду).
3. Неверная сумма, переданная в исходящем сообщении.
Контракт "списывает" у себя X TON, отправляет их в сообщении, но на балансе контракта фактически было меньше X.
Контракт "списывает" у себя X TON, а в сообщении указано Y TON.
Некорректный апдейт стейта
???
Некорректное исполнение сообщений
1. Включено сообщение с большим lt/hash, в то время как сообщения с меньшим lt не были исполнены.
2. Использование "просроченных" входящих сообщений (по timeout).
Несоблюдение лимитов блока
1. Превышение лимита по газу.
2. Превышение лимита по размеру блока.
Фактический размер сериализованного блока больше, чем что-то ???
3. Превышение лимита по количеству транзакций.
4. Превышение лимита по lt / изменению lt.
Некорректные value-flow
1. Баланс контракта увеличился на 1000 TON, хотя никаких транзакций перевода и минтинга нет.
2. Баланс контракта уменьшился на 1000 TON, хотя никаких транзакций перевода и сжигания нет.
3. Некоректоное изменение баланса при отправке сообщения.
Соответствие цепочки блоков шарда мастерчейну
1. Мастерчейн содержит более новый блок по seqno.
2. Шард содержит незарегистированную длинную цепочку.
3. В мастерчейне содердится другой последний зарегистрированный блок, нежели в шарде.
Ошибки в заголовке и структуре самого блока
1. Неверная ссылка на предыдущий блок.
2. Несогласованность с мастерчейном
Шард-блок утверждает, что он ссылается на определённые данные в мастерчейне, которые не совпадают с реальной историей.
3. Искажение timestamp.
Ставится время из далекого "будущего" или "прошлого".
4. Невалидный шард (например битово некорректный).
5. Блок является подшардом мастерчейна. workchain == -1, shard != ShardIdFull.
6. start_lt_ >= end_lt_.
Несоответствие глобальным настройкам сети ???
1. Сеть поменяла лимиты по gas, а блок генерируется со старыми лимитами.
Ошибки в медшардовых сообщениях
1. Неверный маршрут сообщения.
Указывается, что сообщение идёт в шард A, но на самом деле адрес принадлежит шару B.
Нарушение правил "консенсуса"
1. Блок подписан валидаторами, которые не являются валидаторами этого шарда.
2. Недостаточное число подписей.
Дополнительные идеи порчи блока на основе reject_query
- Хэш корневой ячейки блока не совпадает с тем, что заявлено в block_id.
- Внутренние структуры коллатора, например TopBlockDescrSet, Merkle proof и т.д. некорректны.
- Отметили "key block" в шардчейне, что запрещено.
- Поле info.after_merge/info.before_split/info.after_split true, но мы в мастерчейне.
- Поля info.after_merge и info.after_split оба установлены в true.
- Пустой префикс шардирования, но блок объявлен как "после сплита". info.after_split == true, но shard_pfx_len == 0.
- В BlockInfo есть поле vert_seqno_incr (!= 0).
- Указан другой публичный ключ создателя, нежели тот, что мы ожидаем.
- Для мастерчейн-блока ожидаем extra.custom->size_refs() != 0, но оказалось 0. И наоборот.
- Противоречие end_lt в header и gen_lt в ShardState. В заголовке стоит end_lt_, а в самом стейте gen_lt, и они не совпадают.
- Аналогично противоречение timestamp. info.gen_utime (во финальном стейте) != now_ (из заголовка).
- Внутри состояния (ShardState) поле seq_no и shard_id указывает на другой блок, нежели этот.
- Некорректный мерж ???
- Несуществующий workchain_id, который отсутствует в глобальном списке.
- Workchain есть, но active == false. Или wc_info_->basic == false.
Или wc_info_->enabled_since && wc_info_->enabled_since > config_->utime.
- При store_out_msg_queue_size_ == true ожидаем, что в стейте прописан out_msg_queue_size, а его нет.
Или размер не совпадает с найденным ns_.out_msg_queue_size_.value().
- is_key_block_ == true, а "важные" параметры конфигурации не менялись.
- При повторном вычислении транзакции выяснилось, что финальный хэш аккаунта не совпадает с тем, что заявлен.

View file

@ -0,0 +1,50 @@
#include "block_reader.hpp"
namespace test::fisherman {
BlockDataLoader::BlockDataLoader(const std::string &db_path) : scheduler_({1}) {
auto opts = ton::validator::ValidatorManagerOptions::create(ton::BlockIdExt{}, ton::BlockIdExt{});
scheduler_.run_in_context([&] {
root_db_actor_ = td::actor::create_actor<ton::validator::RootDb>(
"RootDbActor", td::actor::ActorId<ton::validator::ValidatorManager>(), db_path, opts);
});
}
BlockDataLoader::~BlockDataLoader() {
scheduler_.stop();
}
td::Result<td::Ref<ton::validator::BlockData>> BlockDataLoader::load_block_data(const ton::BlockIdExt &block_id) {
std::atomic<bool> done{false};
td::Result<td::Ref<ton::validator::BlockData>> block_data_result;
scheduler_.run_in_context([&] {
auto handle_promise = td::PromiseCreator::lambda([&](td::Result<ton::validator::ConstBlockHandle> handle_res) {
if (handle_res.is_error()) {
block_data_result = td::Result<td::Ref<ton::validator::BlockData>>(handle_res.move_as_error());
done = true;
return;
}
auto handle = handle_res.move_as_ok();
auto data_promise = td::PromiseCreator::lambda([&](td::Result<td::Ref<ton::validator::BlockData>> data_res) {
block_data_result = std::move(data_res);
done = true;
});
td::actor::send_closure(root_db_actor_, &ton::validator::RootDb::get_block_data, handle, std::move(data_promise));
});
td::actor::send_closure(root_db_actor_, &ton::validator::RootDb::get_block_by_seqno,
ton::AccountIdPrefixFull{block_id.id.workchain, block_id.id.shard}, block_id.id.seqno,
std::move(handle_promise));
});
while (!done) {
scheduler_.run(1);
}
return block_data_result;
}
} // namespace test::fisherman

View file

@ -0,0 +1,25 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include "validator/db/rootdb.hpp"
#include "td/actor/actor.h"
namespace test::fisherman {
// TODO: Verify that the database does not get corrupted when reading while the validator is running
class BlockDataLoader {
public:
explicit BlockDataLoader(const std::string &db_path);
~BlockDataLoader();
td::Result<td::Ref<ton::validator::BlockData>> load_block_data(const ton::BlockIdExt &block_id);
private:
td::actor::Scheduler scheduler_;
td::actor::ActorOwn<ton::validator::RootDb> root_db_actor_;
};
} // namespace test::fisherman

71
test/fisherman/tests.cpp Normal file
View file

@ -0,0 +1,71 @@
#include "block_reader.hpp"
#include "crypto/block/block-auto.h"
#include "utils.hpp"
using namespace test::fisherman;
auto main(int argc, char **argv) -> int {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " /path/to/rootdb config.json\n";
return 1;
}
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO)); // TODO: add to config
std::string db_path = argv[1];
std::string json_file_path = argv[2];
auto content_res = read_file_to_buffer(json_file_path);
if (content_res.is_error()) {
std::cerr << "Error reading JSON file: " << content_res.error().message().str() << std::endl;
return 1;
}
td::BufferSlice content = content_res.move_as_ok();
td::Parser parser(content.as_slice());
auto decode_result = do_json_decode(parser, 100);
if (decode_result.is_error()) {
std::cerr << "JSON parse error: " << decode_result.error().message().str() << std::endl;
return 1;
}
auto block_id_res = parse_block_id_from_json(decode_result.move_as_ok());
if (block_id_res.is_error()) {
std::cerr << "Error extracting BlockIdExt: " << block_id_res.error().message().str() << std::endl;
return 1;
}
ton::BlockIdExt block_id = block_id_res.move_as_ok();
BlockDataLoader loader(db_path);
auto block_data_result = loader.load_block_data(block_id);
if (block_data_result.is_error()) {
std::cerr << "Error loading block data: " << block_data_result.error().message().str() << std::endl;
return 1;
}
auto block_data = block_data_result.move_as_ok();
LOG(INFO) << "BlockId: " << block_data->block_id().to_str();
LOG(INFO) << "Block data size: " << block_data->data().size() << " bytes";
LOG(INFO) << "Cell has block record = " << block::gen::Block().validate_ref(10000000, block_data->root_cell()) << "\n";
std::ostringstream os;
block::gen::Block().print_ref(os, block_data->root_cell());
LOG(INFO) << "Block = " << os.str();
block::gen::Block::Record block_rec;
bool ok = block::gen::Block().cell_unpack(block_data->root_cell(), block_rec);
CHECK(ok);
block::gen::BlockInfo::Record info_rec;
block::gen::BlockInfo().cell_unpack(block_rec.info, info_rec);
LOG(INFO) << "start_lt = " << info_rec.start_lt << ", end_lt = " << info_rec.end_lt;
block::gen::ShardIdent::Record shard_rec;
block::gen::ShardIdent().unpack(info_rec.shard.write(), shard_rec);
LOG(INFO) << "workchain_id = " << shard_rec.workchain_id;
return 0;
}

79
test/fisherman/utils.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "utils.hpp"
#include <fstream>
namespace test::fisherman {
td::Result<td::BufferSlice> read_file_to_buffer(const std::string &path) {
std::ifstream in(path, std::ios::binary);
if (!in.is_open()) {
return td::Status::Error("Cannot open file: " + path);
}
in.seekg(0, std::ios::end);
std::streamoff file_size = in.tellg();
if (file_size < 0) {
return td::Status::Error("Failed to get file size: " + path);
}
in.seekg(0, std::ios::beg);
auto size = static_cast<size_t>(file_size);
td::BufferWriter writer(size);
td::MutableSlice out_slice = writer.prepare_append();
if (out_slice.size() < size) {
return td::Status::Error("Not enough memory allocated in BufferWriter");
}
if (!in.read(reinterpret_cast<char *>(out_slice.data()), size)) {
return td::Status::Error("Failed to read file contents: " + path);
}
writer.confirm_append(size);
return writer.as_buffer_slice();
}
td::Result<ton::BlockIdExt> parse_block_id_from_json(td::JsonValue jv) {
using td::Result;
using td::Status;
if (jv.type() != td::JsonValue::Type::Object) {
return Status::Error("Root JSON is not an object");
}
auto &obj = jv.get_object();
auto res_wc = td::get_json_object_int_field(obj, PSLICE() << "workchain_id", false);
if (res_wc.is_error()) {
return Status::Error("Missing or invalid 'workchain_id'");
}
int32_t workchain_id = res_wc.move_as_ok();
auto res_shard_str = td::get_json_object_string_field(obj, PSLICE() << "shard_id", false);
if (res_shard_str.is_error()) {
return Status::Error("Missing or invalid 'shard_id'");
}
std::string shard_str = res_shard_str.move_as_ok();
uint64_t shard_id = 0;
try {
if (shard_str.starts_with("0x")) {
shard_str.erase(0, 2);
}
shard_id = std::stoull(shard_str, nullptr, 16);
} catch (...) {
return Status::Error("Failed to parse shard_id from: " + shard_str);
}
auto res_seqno = td::get_json_object_int_field(obj, PSLICE() << "seqno", false);
if (res_seqno.is_error()) {
return Status::Error("Missing or invalid 'seqno'");
}
int32_t seqno_signed = res_seqno.move_as_ok();
if (seqno_signed < 0) {
return Status::Error("seqno must be non-negative");
}
return ton::BlockIdExt{workchain_id, shard_id, static_cast<uint32_t>(seqno_signed), ton::RootHash::zero(),
ton::FileHash::zero()};
}
} // namespace test::fisherman

20
test/fisherman/utils.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "td/utils/buffer.h"
#include "td/utils/Status.h"
#include "td/utils/JsonBuilder.h"
#include "ton/ton-types.h"
namespace test::fisherman {
td::Result<td::BufferSlice> read_file_to_buffer(const std::string &path);
/// \brief Parses a JsonValue object to build a BlockIdExt.
/// \param jv A td::JsonValue that should be a JSON object with fields:
/// "workchain_id" (int),
/// "shard_id" (hex string),
/// "seqno" (int >= 0).
/// \return A td::Result containing either the constructed BlockIdExt or an error status.
td::Result<ton::BlockIdExt> parse_block_id_from_json(td::JsonValue jv);
} // namespace test::fisherman

View file

@ -0,0 +1,103 @@
#include <iostream>
#include <cstring>
#include "td/actor/actor.h"
#include "td/utils/logging.h"
// TON / CellDb includes (проверьте свои пути)
#include "validator/db/celldb.hpp"
// ====================== GLOBAL STORAGE FOR ACTORS =====================
// Чтобы актор LoadCellActor не уничтожился, когда локальная переменная исчезнет.
static td::actor::ActorOwn<ton::validator::CellDb> g_cell_db_actor;
static td::actor::ActorOwn<td::actor::Actor> g_loader_actor; // LoadCellActor хранить здесь
// ============ 1) Актор, печатающий все ключи (если нужно) ============
class PrintHashesActor : public td::actor::Actor {
public:
explicit PrintHashesActor(td::actor::ActorId<ton::validator::CellDb> cell_db)
: cell_db_(cell_db) {}
void start_up() override {
LOG(INFO) << "PrintHashesActor: calling CellDb::print_all_hashes()";
td::actor::send_closure(cell_db_, &ton::validator::CellDb::print_all_hashes);
stop(); // завершить работу
}
private:
td::actor::ActorId<ton::validator::CellDb> cell_db_;
};
// ============ Helper: Парсим 64-hex-символов в ton::RootHash ============
ton::RootHash parse_hex_hash(const std::string &hex_str) {
if (hex_str.size() != 64) {
throw std::runtime_error("Root hash must be 64 hex chars");
}
auto r = td::hex_decode(hex_str);
if (r.is_error()) {
throw std::runtime_error("Invalid hex string: " + r.error().message().str());
}
auto data = r.move_as_ok();
if (data.size() != 32) {
throw std::runtime_error("Hash must be 32 bytes (64 hex characters).");
}
ton::RootHash root;
std::memcpy(root.as_slice().begin(), data.data(), 32);
return root;
}
// ============ MAIN ============
int main(int argc, char* argv[]) {
// Аргументы: path/to/celldb [64-hex-hash]
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " /path/to/celldb [64-hex-hash]\n";
return 1;
}
// Если нужно, включите логи
// td::Logger::instance().set_verbosity_level(3);
std::string celldb_path = argv[1];
bool load_hash = (argc > 2);
ton::RootHash cell_hash;
if (load_hash) {
cell_hash = parse_hex_hash(argv[2]);
LOG(INFO) << "We will load hash = " << cell_hash.to_hex();
}
// Создаём Scheduler
td::actor::Scheduler scheduler({1}); // 1-thread
// Запускаем инициализацию в run_in_context, чтобы всё делалось внутри Actor среды
scheduler.run_in_context([&] {
// 1) Строим opts (ValidatorManagerOptions)
auto opts = ton::validator::ValidatorManagerOptions::create(
// 2 аргумента, если у вас 2-param create
ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, 0, ton::RootHash::zero(), ton::FileHash::zero()},
ton::BlockIdExt{ton::masterchainId, ton::shardIdAll, 0, ton::RootHash::zero(), ton::FileHash::zero()}
);
// 2) Создаём CellDb
g_cell_db_actor = td::actor::create_actor<ton::validator::CellDb>(
"celldb_actor",
td::actor::ActorId<ton::validator::RootDb>(), // пустой
celldb_path,
opts
);
// Если захотите печатать все ключи:
auto printer_actor = td::actor::create_actor<PrintHashesActor>("printer", g_cell_db_actor.get());
});
// Главный цикл
while (scheduler.run(0.1)) {
// do nothing
}
// Останавливаем планировщик
scheduler.stop();
LOG(INFO) << "Done. Exiting.";
return 0;
}

View file

@ -890,7 +890,7 @@ void ArchiveManager::start_up() {
td::WalkPath::run(db_root_ + "/archive/states/", [&](td::CSlice fname, td::WalkPath::Type t) -> void {
if (t == td::WalkPath::Type::NotDir) {
LOG(ERROR) << "checking file " << fname;
LOG(DEBUG) << "checking file " << fname;
auto pos = fname.rfind(TD_DIR_SLASH);
if (pos != td::Slice::npos) {
fname.remove_prefix(pos + 1);

View file

@ -265,6 +265,48 @@ void CellDbIn::get_cell_db_reader(td::Promise<std::shared_ptr<vm::CellDbReader>>
promise.set_result(boc_->get_cell_db_reader());
}
// In celldb.cpp (somewhere after CellDbIn is declared and defined)
void CellDbIn::print_all_hashes() {
LOG(INFO) << "Enumerating keys in CellDb...";
// Create a snapshot of RocksDB so we can iterate it
auto snapshot = cell_db_->snapshot();
// snapshot->for_each(...) calls our lambda for each (key, value) pair in the DB
auto status = snapshot->for_each([&](td::Slice raw_key, td::Slice raw_value) -> td::Status {
// Special check: in official CellDb code, the "empty" key is "desczero"
if (raw_key == "desczero") {
LOG(INFO) << "Found empty key: desczero";
return td::Status::OK();
}
// Check if the key starts with "desc"
if (raw_key.size() >= 4 && std::memcmp(raw_key.data(), "desc", 4) == 0) {
if (raw_key.size() == 4 + 44) {
// Slice out the 32-byte hash
KeyHash khash;
LOG(INFO) << "raw_key: " << raw_key.substr(4, 44);
auto hash_part = td::base64_decode(raw_key.substr(4, 44)).move_as_ok();
std::memcpy(khash.as_slice().begin(), hash_part.data(), 32);
auto block = get_block(khash).move_as_ok();
// LOG(INFO) << raw_key.str();
LOG(INFO) << "Found key: hash=" << block.root_hash << " d: " << block.root_hash.to_hex();
LOG(INFO) << "Block_id = " << block.block_id.to_str();
} else {
LOG(INFO) << "Found key with \"desc\" prefix but not 48 bytes: key.size()=" << raw_key.size();
}
}
return td::Status::OK();
});
if (status.is_error()) {
LOG(ERROR) << "Iteration error: " << status.error().message();
} else {
LOG(INFO) << "Done enumerating CellDb keys.";
}
}
std::vector<std::pair<std::string, std::string>> CellDbIn::prepare_stats() {
TD_PERF_COUNTER(celldb_prepare_stats);
auto r_boc_stats = boc_->get_stats();
@ -528,6 +570,15 @@ td::Result<CellDbIn::DbEntry> CellDbIn::get_block(KeyHash key_hash) {
return DbEntry{obj.move_as_ok()};
}
void CellDbIn::get_block_id_async(KeyHash key_hash, td::Promise<BlockIdExt> promise) {
auto result = get_block(key_hash);
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(result.move_as_ok().block_id);
}
}
void CellDbIn::set_block(KeyHash key_hash, DbEntry e) {
const auto key = get_key(key_hash);
cell_db_->set(td::as_slice(key), e.release()).ensure();
@ -649,6 +700,15 @@ void CellDb::get_cell_db_reader(td::Promise<std::shared_ptr<vm::CellDbReader>> p
td::actor::send_closure(cell_db_, &CellDbIn::get_cell_db_reader, std::move(promise));
}
void CellDb::get_block_id(CellDbIn::KeyHash key_hash, td::Promise<BlockIdExt> promise) {
td::actor::send_closure(cell_db_, &CellDbIn::get_block_id_async, key_hash, std::move(promise));
}
void CellDb::print_all_hashes() {
// The underlying RocksDB tasks happen in the CellDbIn actor, so we forward:
td::actor::send_closure(cell_db_, &CellDbIn::print_all_hashes);
}
void CellDb::start_up() {
CellDbBase::start_up();
boc_ = vm::DynamicBagOfCellsDb::create();

View file

@ -66,6 +66,8 @@ class CellDbIn : public CellDbBase {
void load_cell(RootHash hash, td::Promise<td::Ref<vm::DataCell>> promise);
void store_cell(BlockIdExt block_id, td::Ref<vm::Cell> cell, td::Promise<td::Ref<vm::DataCell>> promise);
void get_cell_db_reader(td::Promise<std::shared_ptr<vm::CellDbReader>> promise);
void get_block_id_async(KeyHash key_hash, td::Promise<BlockIdExt> promise);
void print_all_hashes();
void migrate_cell(td::Bits256 hash);
@ -204,6 +206,8 @@ class CellDb : public CellDbBase {
in_memory_boc_ = std::move(in_memory_boc);
}
void get_cell_db_reader(td::Promise<std::shared_ptr<vm::CellDbReader>> promise);
void get_block_id(CellDbIn::KeyHash key_hash, td::Promise<BlockIdExt> promise);
void print_all_hashes();
CellDb(td::actor::ActorId<RootDb> root_db, std::string path, td::Ref<ValidatorManagerOptions> opts)
: root_db_(root_db), path_(path), opts_(opts) {