diff --git a/CMakeLists.txt b/CMakeLists.txt index 18bc0157..eb49b31e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -558,6 +558,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/transaction_corrupter.cpp test/fisherman/block_manipulator/factory.cpp test/fisherman/block_reader.cpp test/fisherman/utils.cpp diff --git a/test/fisherman/block_manipulator/factory.cpp b/test/fisherman/block_manipulator/factory.cpp index 17a49f32..474f1304 100644 --- a/test/fisherman/block_manipulator/factory.cpp +++ b/test/fisherman/block_manipulator/factory.cpp @@ -1,6 +1,7 @@ #include "factory.hpp" #include "header_corrupter.hpp" +#include "transaction_corrupter.hpp" namespace test::fisherman { @@ -22,6 +23,9 @@ auto ManipulatorFactory::createImpl(td::JsonValue jv) -> td::Result(HeaderCorrupter::Config::fromJson(std::move(json_config))); } + if (type == "TransactionCorrupter") { + return std::make_shared(TransactionCorrupter::Config::fromJson(std::move(json_config))); + } return td::Status::Error(400, PSLICE() << "Unknown manipulator type: " << type); } diff --git a/test/fisherman/block_manipulator/header_corrupter.hpp b/test/fisherman/block_manipulator/header_corrupter.hpp index 4978252d..43c9a84d 100644 --- a/test/fisherman/block_manipulator/header_corrupter.hpp +++ b/test/fisherman/block_manipulator/header_corrupter.hpp @@ -3,7 +3,6 @@ #include "base.hpp" #include "td/utils/JsonBuilder.h" -#include "block/block-auto.h" namespace test::fisherman { diff --git a/test/fisherman/block_manipulator/transaction_corrupter.cpp b/test/fisherman/block_manipulator/transaction_corrupter.cpp new file mode 100644 index 00000000..eff98faf --- /dev/null +++ b/test/fisherman/block_manipulator/transaction_corrupter.cpp @@ -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 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 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 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 diff --git a/test/fisherman/block_manipulator/transaction_corrupter.hpp b/test/fisherman/block_manipulator/transaction_corrupter.hpp new file mode 100644 index 00000000..49a1d0e3 --- /dev/null +++ b/test/fisherman/block_manipulator/transaction_corrupter.hpp @@ -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