From 383a0c574a71761326641965421dbf1cf4c0aa5e Mon Sep 17 00:00:00 2001 From: trinitil Date: Tue, 28 Jan 2025 13:35:02 +0400 Subject: [PATCH 1/4] Initial commit of block generator for fisherman testing --- CMakeLists.txt | 13 ++- tddb/td/db/RocksDb.cpp | 1 + test/fisherman/block_manipulator/base.hpp | 1 + test/fisherman/block_manipulator/factory.cpp | 1 + test/fisherman/block_manipulator/ideas.txt | 95 +++++++++++++++++ test/fisherman/block_reader.cpp | 50 +++++++++ test/fisherman/block_reader.hpp | 25 +++++ test/fisherman/tests.cpp | 71 +++++++++++++ test/fisherman/utils.cpp | 79 ++++++++++++++ test/fisherman/utils.hpp | 20 ++++ test/print-all-shard-states.cpp | 103 +++++++++++++++++++ validator/db/archive-manager.cpp | 2 +- validator/db/celldb.cpp | 60 +++++++++++ validator/db/celldb.hpp | 4 + 14 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 test/fisherman/block_manipulator/base.hpp create mode 100644 test/fisherman/block_manipulator/factory.cpp create mode 100644 test/fisherman/block_manipulator/ideas.txt create mode 100644 test/fisherman/block_reader.cpp create mode 100644 test/fisherman/block_reader.hpp create mode 100644 test/fisherman/tests.cpp create mode 100644 test/fisherman/utils.cpp create mode 100644 test/fisherman/utils.hpp create mode 100644 test/print-all-shard-states.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index af2640367..1e0388fa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/tddb/td/db/RocksDb.cpp b/tddb/td/db/RocksDb.cpp index f1aa64a5d..3d0ee63a1 100644 --- a/tddb/td/db/RocksDb.cpp +++ b/tddb/td/db/RocksDb.cpp @@ -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::open(std::string path, RocksDbOptions options) { rocksdb::OptimisticTransactionDB *db; { diff --git a/test/fisherman/block_manipulator/base.hpp b/test/fisherman/block_manipulator/base.hpp new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/fisherman/block_manipulator/base.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/test/fisherman/block_manipulator/factory.cpp b/test/fisherman/block_manipulator/factory.cpp new file mode 100644 index 000000000..6b58ff92c --- /dev/null +++ b/test/fisherman/block_manipulator/factory.cpp @@ -0,0 +1 @@ +#include "base.hpp" diff --git a/test/fisherman/block_manipulator/ideas.txt b/test/fisherman/block_manipulator/ideas.txt new file mode 100644 index 000000000..e2d4f0415 --- /dev/null +++ b/test/fisherman/block_manipulator/ideas.txt @@ -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, а "важные" параметры конфигурации не менялись. +- При повторном вычислении транзакции выяснилось, что финальный хэш аккаунта не совпадает с тем, что заявлен. diff --git a/test/fisherman/block_reader.cpp b/test/fisherman/block_reader.cpp new file mode 100644 index 000000000..445a55e5a --- /dev/null +++ b/test/fisherman/block_reader.cpp @@ -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( + "RootDbActor", td::actor::ActorId(), db_path, opts); + }); +} + +BlockDataLoader::~BlockDataLoader() { + scheduler_.stop(); +} + +td::Result> BlockDataLoader::load_block_data(const ton::BlockIdExt &block_id) { + std::atomic done{false}; + td::Result> block_data_result; + + scheduler_.run_in_context([&] { + auto handle_promise = td::PromiseCreator::lambda([&](td::Result handle_res) { + if (handle_res.is_error()) { + block_data_result = td::Result>(handle_res.move_as_error()); + done = true; + return; + } + auto handle = handle_res.move_as_ok(); + + auto data_promise = td::PromiseCreator::lambda([&](td::Result> 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 diff --git a/test/fisherman/block_reader.hpp b/test/fisherman/block_reader.hpp new file mode 100644 index 000000000..52d952d15 --- /dev/null +++ b/test/fisherman/block_reader.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#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> load_block_data(const ton::BlockIdExt &block_id); + + private: + td::actor::Scheduler scheduler_; + td::actor::ActorOwn root_db_actor_; +}; + +} // namespace test::fisherman diff --git a/test/fisherman/tests.cpp b/test/fisherman/tests.cpp new file mode 100644 index 000000000..f412e85d5 --- /dev/null +++ b/test/fisherman/tests.cpp @@ -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; +} diff --git a/test/fisherman/utils.cpp b/test/fisherman/utils.cpp new file mode 100644 index 000000000..40cdbc54a --- /dev/null +++ b/test/fisherman/utils.cpp @@ -0,0 +1,79 @@ +#include "utils.hpp" + +#include + +namespace test::fisherman { + +td::Result 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(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(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 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(seqno_signed), ton::RootHash::zero(), + ton::FileHash::zero()}; +} + +} // namespace test::fisherman diff --git a/test/fisherman/utils.hpp b/test/fisherman/utils.hpp new file mode 100644 index 000000000..753d95644 --- /dev/null +++ b/test/fisherman/utils.hpp @@ -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 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 parse_block_id_from_json(td::JsonValue jv); + +} // namespace test::fisherman diff --git a/test/print-all-shard-states.cpp b/test/print-all-shard-states.cpp new file mode 100644 index 000000000..d0a4784df --- /dev/null +++ b/test/print-all-shard-states.cpp @@ -0,0 +1,103 @@ +#include +#include + +#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 g_cell_db_actor; +static td::actor::ActorOwn g_loader_actor; // LoadCellActor хранить здесь + +// ============ 1) Актор, печатающий все ключи (если нужно) ============ +class PrintHashesActor : public td::actor::Actor { + public: + explicit PrintHashesActor(td::actor::ActorId 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 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( + "celldb_actor", + td::actor::ActorId(), // пустой + celldb_path, + opts + ); + + // Если захотите печатать все ключи: + auto printer_actor = td::actor::create_actor("printer", g_cell_db_actor.get()); + }); + + // Главный цикл + while (scheduler.run(0.1)) { + // do nothing + } + + // Останавливаем планировщик + scheduler.stop(); + + LOG(INFO) << "Done. Exiting."; + return 0; +} diff --git a/validator/db/archive-manager.cpp b/validator/db/archive-manager.cpp index d349f9d87..a598045ec 100644 --- a/validator/db/archive-manager.cpp +++ b/validator/db/archive-manager.cpp @@ -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); diff --git a/validator/db/celldb.cpp b/validator/db/celldb.cpp index 9dcecdb35..22a84305e 100644 --- a/validator/db/celldb.cpp +++ b/validator/db/celldb.cpp @@ -265,6 +265,48 @@ void CellDbIn::get_cell_db_reader(td::Promise> 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> CellDbIn::prepare_stats() { TD_PERF_COUNTER(celldb_prepare_stats); auto r_boc_stats = boc_->get_stats(); @@ -528,6 +570,15 @@ td::Result CellDbIn::get_block(KeyHash key_hash) { return DbEntry{obj.move_as_ok()}; } +void CellDbIn::get_block_id_async(KeyHash key_hash, td::Promise 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> 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 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(); diff --git a/validator/db/celldb.hpp b/validator/db/celldb.hpp index 5639b9748..bbb38f719 100644 --- a/validator/db/celldb.hpp +++ b/validator/db/celldb.hpp @@ -66,6 +66,8 @@ class CellDbIn : public CellDbBase { void load_cell(RootHash hash, td::Promise> promise); void store_cell(BlockIdExt block_id, td::Ref cell, td::Promise> promise); void get_cell_db_reader(td::Promise> promise); + void get_block_id_async(KeyHash key_hash, td::Promise 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> promise); + void get_block_id(CellDbIn::KeyHash key_hash, td::Promise promise); + void print_all_hashes(); CellDb(td::actor::ActorId root_db, std::string path, td::Ref opts) : root_db_(root_db), path_(path), opts_(opts) { From 4f9359457fb5a760d4f56f0aaea34be5087b20d8 Mon Sep 17 00:00:00 2001 From: trinitil Date: Tue, 28 Jan 2025 19:19:32 +0400 Subject: [PATCH 2/4] remove redundant comments --- test/print-all-shard-states.cpp | 24 ++---------------------- validator/db/celldb.cpp | 8 -------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/test/print-all-shard-states.cpp b/test/print-all-shard-states.cpp index d0a4784df..fe599f052 100644 --- a/test/print-all-shard-states.cpp +++ b/test/print-all-shard-states.cpp @@ -4,15 +4,11 @@ #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 g_cell_db_actor; static td::actor::ActorOwn g_loader_actor; // LoadCellActor хранить здесь -// ============ 1) Актор, печатающий все ключи (если нужно) ============ class PrintHashesActor : public td::actor::Actor { public: explicit PrintHashesActor(td::actor::ActorId cell_db) @@ -21,14 +17,13 @@ class PrintHashesActor : public td::actor::Actor { 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(); // завершить работу + stop(); } private: td::actor::ActorId 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"); @@ -46,18 +41,12 @@ ton::RootHash parse_hex_hash(const std::string &hex_str) { 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; @@ -66,19 +55,14 @@ int main(int argc, char* argv[]) { 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( "celldb_actor", td::actor::ActorId(), // пустой @@ -86,16 +70,12 @@ int main(int argc, char* argv[]) { opts ); - // Если захотите печатать все ключи: auto printer_actor = td::actor::create_actor("printer", g_cell_db_actor.get()); }); - // Главный цикл - while (scheduler.run(0.1)) { - // do nothing + while (scheduler.run(1)) { } - // Останавливаем планировщик scheduler.stop(); LOG(INFO) << "Done. Exiting."; diff --git a/validator/db/celldb.cpp b/validator/db/celldb.cpp index 22a84305e..57924d179 100644 --- a/validator/db/celldb.cpp +++ b/validator/db/celldb.cpp @@ -265,31 +265,24 @@ void CellDbIn::get_cell_db_reader(td::Promise> 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(); @@ -705,7 +698,6 @@ void CellDb::get_block_id(CellDbIn::KeyHash key_hash, td::Promise pr } 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); } From 8cb919a3916876d43861663949c12ad8fcb94f31 Mon Sep 17 00:00:00 2001 From: trinitil Date: Wed, 29 Jan 2025 12:02:08 +0400 Subject: [PATCH 3/4] add simple block manipulator as example --- CMakeLists.txt | 1 + test/fisherman/block_manipulator/base.hpp | 12 ++++++ test/fisherman/block_manipulator/factory.cpp | 29 ++++++++++++- test/fisherman/block_manipulator/factory.hpp | 16 +++++++ .../block_manipulator/header_corrupter.cpp | 21 +++++++++ .../block_manipulator/header_corrupter.hpp | 23 ++++++++++ test/fisherman/tests.cpp | 43 +++++++++++-------- 7 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 test/fisherman/block_manipulator/factory.hpp create mode 100644 test/fisherman/block_manipulator/header_corrupter.cpp create mode 100644 test/fisherman/block_manipulator/header_corrupter.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e0388fa8..18bc01570 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -557,6 +557,7 @@ target_link_libraries(test-emulator PRIVATE emulator) add_executable(test-fisherman test/fisherman/tests.cpp + test/fisherman/block_manipulator/header_corrupter.cpp test/fisherman/block_manipulator/factory.cpp test/fisherman/block_reader.cpp test/fisherman/utils.cpp diff --git a/test/fisherman/block_manipulator/base.hpp b/test/fisherman/block_manipulator/base.hpp index 6f70f09be..c65f581e3 100644 --- a/test/fisherman/block_manipulator/base.hpp +++ b/test/fisherman/block_manipulator/base.hpp @@ -1 +1,13 @@ #pragma once + +#include "crypto/block/block-auto.h" + +namespace test::fisherman { + +class BaseManipulator { + public: + virtual void modify(block::gen::Block::Record &block) = 0; + virtual ~BaseManipulator() = default; +}; + +} // namespace test::fisherman diff --git a/test/fisherman/block_manipulator/factory.cpp b/test/fisherman/block_manipulator/factory.cpp index 6b58ff92c..17a49f322 100644 --- a/test/fisherman/block_manipulator/factory.cpp +++ b/test/fisherman/block_manipulator/factory.cpp @@ -1 +1,28 @@ -#include "base.hpp" +#include "factory.hpp" + +#include "header_corrupter.hpp" + +namespace test::fisherman { + +auto ManipulatorFactory::create(td::JsonValue jv) -> std::shared_ptr { + auto res = createImpl(std::move(jv)); + if (res.is_error()) { + throw std::runtime_error("Error while creating manipulator: " + res.error().message().str()); + } + return res.move_as_ok(); +} + +auto ManipulatorFactory::createImpl(td::JsonValue jv) -> td::Result> { + CHECK(jv.type() == td::JsonValue::Type::Object); + + auto &obj = jv.get_object(); + TRY_RESULT(type, td::get_json_object_string_field(obj, "type", false)); + TRY_RESULT(json_config, td::get_json_object_field(obj, "config", td::JsonValue::Type::Object, false)); + + if (type == "HeaderCorrupter") { + return std::make_shared(HeaderCorrupter::Config::fromJson(std::move(json_config))); + } + return td::Status::Error(400, PSLICE() << "Unknown manipulator type: " << type); +} + +} // namespace test::fisherman diff --git a/test/fisherman/block_manipulator/factory.hpp b/test/fisherman/block_manipulator/factory.hpp new file mode 100644 index 000000000..912561627 --- /dev/null +++ b/test/fisherman/block_manipulator/factory.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "base.hpp" +#include "td/utils/JsonBuilder.h" + +namespace test::fisherman { + +class ManipulatorFactory { + public: + auto create(td::JsonValue jv) -> std::shared_ptr; + + private: + auto createImpl(td::JsonValue jv) -> td::Result>; +}; + +} // namespace test::fisherman diff --git a/test/fisherman/block_manipulator/header_corrupter.cpp b/test/fisherman/block_manipulator/header_corrupter.cpp new file mode 100644 index 000000000..f019d0708 --- /dev/null +++ b/test/fisherman/block_manipulator/header_corrupter.cpp @@ -0,0 +1,21 @@ +#include "header_corrupter.hpp" + +namespace test::fisherman { + +auto HeaderCorrupter::Config::fromJson(td::JsonValue jv) -> Config { + return Config{}; +} + +HeaderCorrupter::HeaderCorrupter(Config config) : config_(std::move(config)) { +} + +void HeaderCorrupter::modify(block::gen::Block::Record &block) { + block::gen::BlockInfo::Record info_rec; + bool ok = block::gen::BlockInfo().cell_unpack(block.info, info_rec); + CHECK(ok); + info_rec.after_merge = true; + info_rec.after_split = true; + block::gen::BlockInfo().cell_pack(block.info, info_rec); +} + +} // namespace test::fisherman diff --git a/test/fisherman/block_manipulator/header_corrupter.hpp b/test/fisherman/block_manipulator/header_corrupter.hpp new file mode 100644 index 000000000..1fe0f4a67 --- /dev/null +++ b/test/fisherman/block_manipulator/header_corrupter.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "base.hpp" +#include "td/utils/JsonBuilder.h" + +namespace test::fisherman { + +class HeaderCorrupter : public BaseManipulator { + public: + struct Config { + // TODO: add corruption field and method + + static auto fromJson(td::JsonValue jv) -> Config; + }; + + explicit HeaderCorrupter(Config config); + void modify(block::gen::Block::Record &block) final; + + private: + Config config_; +}; + +} // namespace test::fisherman diff --git a/test/fisherman/tests.cpp b/test/fisherman/tests.cpp index f412e85d5..4a7249c90 100644 --- a/test/fisherman/tests.cpp +++ b/test/fisherman/tests.cpp @@ -1,6 +1,7 @@ #include "block_reader.hpp" #include "crypto/block/block-auto.h" +#include "block_manipulator/factory.hpp" #include "utils.hpp" using namespace test::fisherman; @@ -30,42 +31,50 @@ auto main(int argc, char **argv) -> int { 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; + auto js = decode_result.move_as_ok(); + auto &js_obj = js.get_object(); + auto blk_id_obj_res = td::get_json_object_field(js_obj, "block_id", td::JsonValue::Type::Object, false); + CHECK(blk_id_obj_res.is_ok()); + auto blk_id_res = parse_block_id_from_json(blk_id_obj_res.move_as_ok()); + if (blk_id_res.is_error()) { + std::cerr << "Error extracting BlockIdExt: " << blk_id_res.error().message().str() << std::endl; return 1; } - ton::BlockIdExt block_id = block_id_res.move_as_ok(); + ton::BlockIdExt blk_id = blk_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; + auto blk_data_result = loader.load_block_data(blk_id); + if (blk_data_result.is_error()) { + std::cerr << "Error loading block data: " << blk_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"; + auto blk_data = blk_data_result.move_as_ok(); + LOG(INFO) << "BlockId: " << blk_data->block_id().to_str(); + LOG(INFO) << "Block data size: " << blk_data->data().size() << " bytes"; - LOG(INFO) << "Cell has block record = " << block::gen::Block().validate_ref(10000000, block_data->root_cell()) << "\n"; + LOG(INFO) << "Cell has block record = " << block::gen::Block().validate_ref(10000000, blk_data->root_cell()) << "\n"; std::ostringstream os; - block::gen::Block().print_ref(os, block_data->root_cell()); + block::gen::Block().print_ref(os, blk_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); + bool ok = block::gen::Block().cell_unpack(blk_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; + LOG(INFO) << "Block.info after_merge=" << info_rec.after_merge << ", after_split=" << info_rec.after_split; - block::gen::ShardIdent::Record shard_rec; - block::gen::ShardIdent().unpack(info_rec.shard.write(), shard_rec); - LOG(INFO) << "workchain_id = " << shard_rec.workchain_id; + auto manipulation_config = td::get_json_object_field(js_obj, "manipulation", td::JsonValue::Type::Object, false); + CHECK(manipulation_config.is_ok()); + ManipulatorFactory().create(manipulation_config.move_as_ok())->modify(block_rec); + + LOG(INFO) << "Block after manipulation:"; + block::gen::BlockInfo().cell_unpack(block_rec.info, info_rec); + LOG(INFO) << "Block.info after_merge=" << info_rec.after_merge << ", after_split=" << info_rec.after_split; return 0; } From ed10909dcd522051f4c6694f8611032821a4dd86 Mon Sep 17 00:00:00 2001 From: trinitil Date: Wed, 29 Jan 2025 12:03:11 +0400 Subject: [PATCH 4/4] add config for simple manipulation --- test/fisherman/configs/test.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/fisherman/configs/test.json diff --git a/test/fisherman/configs/test.json b/test/fisherman/configs/test.json new file mode 100644 index 000000000..d0e2bd7e7 --- /dev/null +++ b/test/fisherman/configs/test.json @@ -0,0 +1,11 @@ +{ + "block_id": { + "workchain_id": -1, + "shard_id": "8000000000000000", + "seqno": 27492934 + }, + "manipulation": { + "type": "HeaderCorrupter", + "config": {} + } +}