1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00
This commit is contained in:
Trinitil 2025-02-15 14:07:51 +07:00 committed by GitHub
commit 607be9acd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 819 additions and 5 deletions

View file

@ -224,7 +224,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")
@ -556,6 +556,19 @@ 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/header_corrupter.cpp
test/fisherman/block_manipulator/transaction_corrupter.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

@ -23,9 +23,9 @@
namespace tlbc {
/*
*
*
* C++ CODE GENERATION
*
*
*/
CppIdentSet global_cpp_ids;
@ -3036,7 +3036,7 @@ void CppTypeCode::generate_store_enum_method(std::ostream& os, int options) {
<< minl << ");\n";
} else if (minl == maxl) {
if (exact) {
os << " return cb.store_long_rchk_bool(value, " << minl << ");\n";
os << " return cb.store_ulong_rchk_bool(value, " << minl << ");\n";
} else if (incremental_cons_tags && cons_num > (1 << (minl - 1))) {
os << " return cb.store_uint_less(" << cons_num << ", value);\n";
} else {

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,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

View file

@ -0,0 +1,32 @@
#include "factory.hpp"
#include "header_corrupter.hpp"
#include "transaction_corrupter.hpp"
namespace test::fisherman {
auto ManipulatorFactory::create(td::JsonValue jv) -> std::shared_ptr<BaseManipulator> {
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<std::shared_ptr<BaseManipulator>> {
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>(HeaderCorrupter::Config::fromJson(std::move(json_config)));
}
if (type == "TransactionCorrupter") {
return std::make_shared<TransactionCorrupter>(TransactionCorrupter::Config::fromJson(std::move(json_config)));
}
return td::Status::Error(400, PSLICE() << "Unknown manipulator type: " << type);
}
} // namespace test::fisherman

View file

@ -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<BaseManipulator>;
private:
auto createImpl(td::JsonValue jv) -> td::Result<std::shared_ptr<BaseManipulator>>;
};
} // namespace test::fisherman

View file

@ -0,0 +1,115 @@
#include "header_corrupter.hpp"
#include "block/block-auto.h"
namespace test::fisherman {
HeaderCorrupter::Config HeaderCorrupter::Config::fromJson(td::JsonValue jv) {
Config cfg;
CHECK(jv.type() == td::JsonValue::Type::Object);
auto &obj = jv.get_object();
cfg.distort_timestamp = td::get_json_object_bool_field(obj, "distort_timestamp", true, false).move_as_ok();
cfg.time_offset = td::get_json_object_int_field(obj, "time_offset", true, 999999999).move_as_ok();
cfg.mark_subshard_of_master =
td::get_json_object_bool_field(obj, "mark_subshard_of_master", true, false).move_as_ok();
cfg.invert_lt = td::get_json_object_bool_field(obj, "invert_lt", true, false).move_as_ok();
cfg.mark_keyblock_on_shard = td::get_json_object_bool_field(obj, "mark_keyblock_on_shard", true, false).move_as_ok();
cfg.force_after_merge_for_mc = td::get_json_object_bool_field(obj, "force_after_merge_for_mc", true, false).move_as_ok();
cfg.force_before_split_for_mc = td::get_json_object_bool_field(obj, "force_before_split_for_mc", true, false).move_as_ok();
cfg.force_after_split_for_mc = td::get_json_object_bool_field(obj, "force_after_split_for_mc", true, false).move_as_ok();
cfg.allow_both_after_merge_and_split =
td::get_json_object_bool_field(obj, "allow_both_after_merge_and_split", true, false).move_as_ok();
cfg.shard_pfx_zero_yet_after_split =
td::get_json_object_bool_field(obj, "shard_pfx_zero_yet_after_split", true, false).move_as_ok();
cfg.set_vert_seqno_incr = td::get_json_object_bool_field(obj, "set_vert_seqno_incr", true, false).move_as_ok();
return cfg;
}
HeaderCorrupter::HeaderCorrupter(Config config) : config_(std::move(config)) {
}
void HeaderCorrupter::modify(block::gen::Block::Record &block) {
block::gen::BlockInfo::Record info_rec;
CHECK(block::gen::t_BlockInfo.cell_unpack(block.info, info_rec));
// 1) distort_timestamp => сдвигаем info_rec.gen_utime
if (config_.distort_timestamp) {
info_rec.gen_utime += config_.time_offset;
}
// 2) mark_subshard_of_master => если workchain == -1, делаем shard_pfx_bits != 0, то есть блок "подшард" MC
if (config_.mark_subshard_of_master) {
block::gen::ShardIdent::Record shard_rec;
CHECK(block::gen::ShardIdent().unpack(info_rec.shard.write(), shard_rec));
CHECK(shard_rec.workchain_id == -1 && !info_rec.not_master);
if (shard_rec.shard_pfx_bits == 0) {
shard_rec.shard_pfx_bits = 10;
shard_rec.shard_prefix = 123456ULL;
}
vm::CellBuilder cb;
CHECK(block::gen::t_ShardIdent.pack(cb, shard_rec));
info_rec.shard.write() = cb.finalize();
}
// 3) invert_lt => start_lt >= end_lt
if (config_.invert_lt) {
if (info_rec.start_lt < info_rec.end_lt) {
auto tmp = info_rec.start_lt;
info_rec.start_lt = info_rec.end_lt;
info_rec.end_lt = tmp;
}
}
// 4) mark_keyblock_on_shard => если "not_master" = true, то проставим key_block = true
if (config_.mark_keyblock_on_shard) {
CHECK(info_rec.not_master);
info_rec.key_block = true;
}
// 5) force_after_merge / force_before_split / force_after_split для MC
if (config_.force_after_merge_for_mc) {
CHECK(!info_rec.not_master);
info_rec.after_merge = true;
}
if (config_.force_before_split_for_mc) {
CHECK(!info_rec.not_master);
info_rec.before_split = true;
}
if (config_.force_after_split_for_mc) {
CHECK(!info_rec.not_master);
info_rec.after_split = true;
}
// 6) allow_both_after_merge_and_split => ставим after_merge=1 и after_split=1
if (config_.allow_both_after_merge_and_split) {
info_rec.after_merge = true;
info_rec.after_split = true;
}
// 7) shard_pfx_zero_yet_after_split => shard_pfx_bits=0, after_split=1
if (config_.shard_pfx_zero_yet_after_split) {
info_rec.after_split = true;
block::gen::ShardIdent::Record shard_rec;
CHECK(block::gen::ShardIdent().unpack(info_rec.shard.write(), shard_rec));
shard_rec.shard_pfx_bits = 0;
vm::CellBuilder cb;
CHECK(block::gen::t_ShardIdent.pack(cb, shard_rec));
info_rec.shard.write() = cb.finalize();
}
// 8) set_vert_seqno_incr => vert_seqno_incr != 0 => ставим true
if (config_.set_vert_seqno_incr) {
info_rec.vert_seq_no = 1;
info_rec.vert_seqno_incr = true;
info_rec.prev_vert_ref = info_rec.prev_ref;
}
CHECK(block::gen::t_BlockInfo.cell_pack(block.info, info_rec));
}
} // namespace test::fisherman

View file

@ -0,0 +1,38 @@
#pragma once
#include "base.hpp"
#include "td/utils/JsonBuilder.h"
namespace test::fisherman {
class HeaderCorrupter : public BaseManipulator {
public:
struct Config {
bool distort_timestamp = false;
td::int32 time_offset = 1'000'000'000;
bool mark_subshard_of_master = false;
bool invert_lt = false;
bool mark_keyblock_on_shard = false;
bool force_after_merge_for_mc = false;
bool force_before_split_for_mc = false;
bool force_after_split_for_mc = false;
bool allow_both_after_merge_and_split = false;
bool shard_pfx_zero_yet_after_split = false;
bool set_vert_seqno_incr = false;
static Config fromJson(td::JsonValue jv);
};
explicit HeaderCorrupter(Config config);
void modify(block::gen::Block::Record &block) override;
private:
Config config_;
};
} // namespace test::fisherman

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,67 @@
#include "transaction_corrupter.hpp"
#include "block-parse.h"
namespace test::fisherman {
auto TransactionCorrupter::Config::fromJson(td::JsonValue jv) -> Config {
Config cfg;
CHECK(jv.type() == td::JsonValue::Type::Object);
auto& obj = jv.get_object();
cfg.transaction_fee_change = td::get_json_object_long_field(obj, "transaction_fee_change", false).move_as_ok();
return cfg;
}
TransactionCorrupter::TransactionCorrupter(Config config) : config_(std::move(config)) {
}
void TransactionCorrupter::modify(block::gen::Block::Record& block) {
block::gen::BlockExtra::Record block_extra_rec;
CHECK(block::gen::BlockExtra().cell_unpack(block.extra, block_extra_rec));
block::gen::ShardAccountBlocks::Record shard_account_blocks;
CHECK(block::gen::ShardAccountBlocks().cell_unpack(block_extra_rec.account_blocks, shard_account_blocks));
vm::AugmentedDictionary accounts_dict{shard_account_blocks.x, 256, block::tlb::aug_ShardAccountBlocks};
vm::AugmentedDictionary new_accounts_dict{256, block::tlb::aug_ShardAccountBlocks};
accounts_dict.check_for_each_extra(
[&](td::Ref<vm::CellSlice> account_slice, auto const&, td::ConstBitPtr account_key, int account_key_len) -> bool {
block::gen::AccountBlock::Record account_block_rec;
CHECK(block::gen::AccountBlock().unpack(account_slice.write(), account_block_rec));
vm::AugmentedDictionary tx_dict{vm::DictNonEmpty(), account_block_rec.transactions, 64,
block::tlb::aug_AccountTransactions};
vm::AugmentedDictionary new_tx_dict{64, block::tlb::aug_AccountTransactions};
CHECK(tx_dict.check_for_each_extra(
[&](td::Ref<vm::CellSlice> tx_slice, auto const&, td::ConstBitPtr tx_key, int tx_key_len) -> bool {
block::gen::Transaction::Record tx_rec;
if (block::gen::Transaction().cell_unpack(tx_slice.write().fetch_ref(), tx_rec)) {
block::CurrencyCollection tx_currency_rec;
CHECK(tx_currency_rec.validate_unpack(tx_rec.total_fees));
tx_currency_rec.grams += config_.transaction_fee_change;
tx_currency_rec.pack_to(tx_rec.total_fees);
td::Ref<vm::Cell> new_tx_cell;
CHECK(block::gen::Transaction().cell_pack(new_tx_cell, tx_rec));
new_tx_dict.set_ref(tx_key, tx_key_len, new_tx_cell, vm::Dictionary::SetMode::Add);
}
return true;
},
false));
account_block_rec.transactions.write() = vm::load_cell_slice(new_tx_dict.get_root_cell());
vm::CellBuilder cb2;
CHECK(block::gen::AccountBlock().pack(cb2, account_block_rec));
new_accounts_dict.set(account_key, account_key_len, cb2.finalize(), vm::Dictionary::SetMode::Add);
return true;
},
false);
shard_account_blocks.x = new_accounts_dict.get_root();
CHECK(block::gen::ShardAccountBlocks().cell_pack(block_extra_rec.account_blocks, shard_account_blocks));
CHECK(block::gen::BlockExtra().cell_pack(block.extra, block_extra_rec));
}
} // namespace test::fisherman

View file

@ -0,0 +1,25 @@
#pragma once
#include "base.hpp"
#include "td/utils/JsonBuilder.h"
namespace test::fisherman {
class TransactionCorrupter : public BaseManipulator {
public:
struct Config {
// TODO: add corruption fields and associate this manipulator with another to achieve more complex block corruptions
td::int64 transaction_fee_change;
static auto fromJson(td::JsonValue jv) -> Config;
};
explicit TransactionCorrupter(Config config);
void modify(block::gen::Block::Record &block) final;
private:
Config config_;
};
} // namespace test::fisherman

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

View file

@ -0,0 +1,11 @@
{
"block_id": {
"workchain_id": -1,
"shard_id": "8000000000000000",
"seqno": 27492934
},
"manipulation": {
"type": "HeaderCorrupter",
"config": {}
}
}

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

@ -0,0 +1,75 @@
#include "block_reader.hpp"
#include "crypto/block/block-auto.h"
#include "block_manipulator/factory.hpp"
#include "utils.hpp"
using namespace test::fisherman;
void print_block(const block::gen::Block::Record &block_rec) {
std::ostringstream os;
td::Ref<vm::Cell> block_cell_pack;
CHECK(block::gen::t_Block.cell_pack(block_cell_pack, block_rec));
block::gen::t_Block.print_ref(os, block_cell_pack);
LOG(INFO) << "Block = " << os.str();
}
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 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 blk_id = blk_id_res.move_as_ok();
BlockDataLoader loader(db_path);
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 blk_data = blk_data_result.move_as_ok();
block::gen::Block::Record block_rec;
CHECK(block::gen::t_Block.cell_unpack(blk_data->root_cell(), block_rec));
print_block(block_rec);
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:";
print_block(block_rec);
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,83 @@
#include <iostream>
#include <cstring>
#include "td/actor/actor.h"
#include "td/utils/logging.h"
#include "validator/db/celldb.hpp"
static td::actor::ActorOwn<ton::validator::CellDb> g_cell_db_actor;
static td::actor::ActorOwn<td::actor::Actor> g_loader_actor; // LoadCellActor хранить здесь
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_;
};
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;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " /path/to/celldb [64-hex-hash]\n";
return 1;
}
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();
}
td::actor::Scheduler scheduler({1}); // 1-thread
scheduler.run_in_context([&] {
auto opts = ton::validator::ValidatorManagerOptions::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()}
);
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(1)) {
}
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,41 @@ void CellDbIn::get_cell_db_reader(td::Promise<std::shared_ptr<vm::CellDbReader>>
promise.set_result(boc_->get_cell_db_reader());
}
void CellDbIn::print_all_hashes() {
LOG(INFO) << "Enumerating keys in CellDb...";
auto snapshot = cell_db_->snapshot();
auto status = snapshot->for_each([&](td::Slice raw_key, td::Slice raw_value) -> td::Status {
if (raw_key == "desczero") {
LOG(INFO) << "Found empty key: desczero";
return td::Status::OK();
}
if (raw_key.size() >= 4 && std::memcmp(raw_key.data(), "desc", 4) == 0) {
if (raw_key.size() == 4 + 44) {
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) << "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 +563,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 +693,14 @@ 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() {
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) {