diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e52f969..4ac5e85a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,7 @@ add_subdirectory(tl-utils) add_subdirectory(adnl) add_subdirectory(crypto) add_subdirectory(lite-client) +add_subdirectory(emulator) #BEGIN tonlib add_subdirectory(tonlib) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index fab75bfc..202e420b 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -274,6 +274,10 @@ add_library(ton_crypto STATIC ${TON_CRYPTO_SOURCE}) target_include_directories(ton_crypto PUBLIC $ $) target_link_libraries(ton_crypto PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils tddb_utils) +if (USE_EMSCRIPTEN) + target_link_options(ton_crypto PRIVATE -fexceptions) + target_compile_options(ton_crypto PRIVATE -fexceptions) +endif() if (NOT WIN32) find_library(DL dl) if (DL) diff --git a/crypto/block/block-parse.cpp b/crypto/block/block-parse.cpp index c62854d4..e9eb8209 100644 --- a/crypto/block/block-parse.cpp +++ b/crypto/block/block-parse.cpp @@ -1000,7 +1000,7 @@ bool Account::skip_copy_depth_balance(vm::CellBuilder& cb, vm::CellSlice& cs) co } const Account t_Account, t_AccountE{true}; -const RefTo t_Ref_Account; +const RefTo t_Ref_AccountE{true}; bool ShardAccount::extract_account_state(Ref cs_ref, Ref& acc_state) { if (cs_ref.is_null()) { diff --git a/crypto/block/block-parse.h b/crypto/block/block-parse.h index 25476a64..ad4faec0 100644 --- a/crypto/block/block-parse.h +++ b/crypto/block/block-parse.h @@ -536,7 +536,7 @@ struct Account final : TLB_Complex { }; extern const Account t_Account, t_AccountE; -extern const RefTo t_Ref_Account; +extern const RefTo t_Ref_AccountE; struct AccountStatus final : TLB { enum { acc_state_uninit, acc_state_frozen, acc_state_active, acc_state_nonexist }; @@ -572,7 +572,7 @@ struct ShardAccount final : TLB_Complex { return cs.advance_ext(0x140, 1); } bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override { - return cs.advance(0x140) && t_Ref_Account.validate_skip(ops, cs, weak); + return cs.advance(0x140) && t_Ref_AccountE.validate_skip(ops, cs, weak); } static bool unpack(vm::CellSlice& cs, Record& info) { return info.unpack(cs); diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index 8d98197f..8662e243 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -366,7 +366,7 @@ trans_merge_install$0111 split_info:SplitMergeInfo smc_info#076ef1ea actions:uint16 msgs_sent:uint16 unixtime:uint32 block_lt:uint64 trans_lt:uint64 rand_seed:bits256 balance_remaining:CurrencyCollection - myself:MsgAddressInt = SmartContractInfo; + myself:MsgAddressInt global_config:(Maybe Cell) = SmartContractInfo; // // out_list_empty$_ = OutList 0; diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index ad5999e5..aca21475 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -632,7 +632,6 @@ class Config { static td::Result> unpack_param_dict(vm::Dictionary& dict); static td::Result> unpack_param_dict(Ref dict_root); - protected: Config(int _mode) : mode(_mode) { config_addr.set_zero(); } diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index adda48a5..7607ef81 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -20,6 +20,7 @@ #include "block/block.h" #include "block/block-parse.h" #include "block/block-auto.h" +#include "crypto/openssl/rand.hpp" #include "td/utils/bits.h" #include "td/utils/uint128.h" #include "ton/ton-shard.h" @@ -513,6 +514,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain()); } +namespace transaction { Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg) : trans_type(ttype) @@ -745,6 +747,7 @@ bool Transaction::prepare_credit_phase() { total_fees += std::move(collected); return true; } +} // namespace transaction bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit) { @@ -837,6 +840,7 @@ td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const { : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; } +namespace transaction { bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) { // Compute gas limits if (account.is_special) { @@ -1057,13 +1061,21 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { std::unique_ptr logger; auto vm_log = vm::VmLog(); if (cfg.with_vm_log) { - logger = std::make_unique(); + size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256; + logger = std::make_unique(log_max_size); vm_log.log_interface = logger.get(); vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); + if (cfg.vm_log_verbosity > 1) { + vm_log.log_mask |= vm::VmLog::ExecLocation; + if (cfg.vm_log_verbosity > 2) { + vm_log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining; + } + } } vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)}; vm.set_max_data_depth(cfg.max_vm_data_depth); vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo + vm.set_chksig_always_succeed(cfg.ignore_chksig); // vm.incr_stack_trace(1); // enable stack dump after each step LOG(DEBUG) << "starting VM"; @@ -1338,6 +1350,7 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c ap.spec_actions++; return 0; } +} // namespace transaction // msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms // ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms @@ -1372,6 +1385,7 @@ td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const { return (std::move(total) * next_frac) >> 16; } +namespace transaction { bool Transaction::check_replace_src_addr(Ref& src_addr) const { int t = (int)src_addr->prefetch_ulong(2); if (!t && src_addr->size_ext() == 2) { @@ -1978,6 +1992,7 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { bp.ok = true; return true; } +} // namespace transaction /* * @@ -2033,6 +2048,7 @@ static td::optional try_update_storage_stat(const vm::CellS return new_stat; } +namespace transaction { bool Transaction::compute_state() { if (new_total_state.not_null()) { return true; @@ -2460,6 +2476,7 @@ void Transaction::extract_out_msgs(std::vector& list) { list.emplace_back(start_lt + i + 1, std::move(out_msgs[i])); } } +} // namespace transaction void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) { transactions.emplace_back(trans_lt, std::move(trans_root)); @@ -2503,4 +2520,82 @@ bool Account::libraries_changed() const { } } +td::Status FetchConfigParams::fetch_config_params(const block::Config& config, + Ref* old_mparams, + std::vector* storage_prices, + block::StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, + td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, + ton::UnixTime now) { + *old_mparams = config.get_config_param(9); + { + auto res = config.get_storage_prices(); + if (res.is_error()) { + return res.move_as_error(); + } + *storage_prices = res.move_as_ok(); + } + if (rand_seed->is_zero()) { + // generate rand seed + prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); + LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); + } + TRY_RESULT(size_limits, config.get_size_limits_config()); + { + // compute compute_phase_cfg / storage_phase_cfg + auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21); + if (cell.is_null()) { + return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); + } + if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, + storage_phase_cfg->delete_due_limit)) { + return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); + } + compute_phase_cfg->block_rand_seed = *rand_seed; + compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; + compute_phase_cfg->global_config = config.get_root_cell(); + compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); + } + { + // compute action_phase_cfg + block::gen::MsgForwardPrices::Record rec; + auto cell = config.get_config_param(24); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_mc = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + cell = config.get_config_param(25); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_std = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + action_phase_cfg->workchains = &config.get_workchain_list(); + action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0); + action_phase_cfg->size_limits = size_limits; + } + { + // fetch block_grams_created + auto cell = config.get_config_param(14); + if (cell.is_null()) { + *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); + } else { + block::gen::BlockCreateFees::Record create_fees; + if (!(tlb::unpack_cell(cell, create_fees) && + block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && + block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { + return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); + } + } + } + return td::Status::OK(); +} + } // namespace block diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 2560c010..2e4463bd 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -35,7 +35,10 @@ using td::Ref; using LtCellRef = std::pair>; struct Account; + +namespace transaction { struct Transaction; +} // namespace transaction struct CollatorError { std::string msg; @@ -106,9 +109,11 @@ struct ComputePhaseConfig { std::unique_ptr libraries; Ref global_config; td::BitArray<256> block_rand_seed; + bool ignore_chksig{false}; bool with_vm_log{false}; td::uint16 max_vm_data_depth = 512; std::unique_ptr suspended_addresses; + int vm_log_verbosity = 0; ComputePhaseConfig(td::uint64 _gas_price = 0, td::uint64 _gas_limit = 0, td::uint64 _gas_credit = 0) : gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_gas_limit), gas_credit(_gas_credit) { compute_threshold(); @@ -273,7 +278,7 @@ struct Account { bool create_account_block(vm::CellBuilder& cb); // stores an AccountBlock with all transactions protected: - friend struct Transaction; + friend struct transaction::Transaction; bool set_split_depth(int split_depth); bool check_split_depth(int split_depth) const; bool forget_split_depth(); @@ -288,6 +293,7 @@ struct Account { bool compute_my_addr(bool force = false); }; +namespace transaction { struct Transaction { enum { tr_none, @@ -390,5 +396,20 @@ struct Transaction { bool serialize_bounce_phase(vm::CellBuilder& cb); bool unpack_msg_state(bool lib_only = false); }; +} // namespace transaction + +struct FetchConfigParams { +static td::Status fetch_config_params(const block::Config& config, + Ref* old_mparams, + std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, + ComputePhaseConfig* compute_phase_cfg, + ActionPhaseConfig* action_phase_cfg, + td::RefInt256* masterchain_create_fee, + td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, + ton::UnixTime now); +}; } // namespace block diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 9a273a08..713caf9c 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -54,7 +54,11 @@ td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref td::Ref prepare_vm_c7(SmartContract::Args args) { td::BitArray<256> rand_seed; - rand_seed.as_slice().fill(0); + if (args.rand_seed) { + rand_seed = args.rand_seed.unwrap(); + } else { + rand_seed.as_slice().fill(0); + } td::RefInt256 rand_seed_int{true}; rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false); @@ -96,7 +100,7 @@ td::Ref prepare_vm_c7(SmartContract::Args args) { } SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref stack, td::Ref c7, - vm::GasLimits gas, bool ignore_chksig, td::Ref libraries) { + vm::GasLimits gas, bool ignore_chksig, td::Ref libraries, int vm_log_verbosity) { auto gas_credit = gas.gas_credit; vm::init_op_cp0(); vm::DictionaryBase::get_empty_dictionary(); @@ -109,15 +113,12 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { - log.log_options.level = 4; - log.log_options.fix_newlines = true; - log.log_mask |= vm::VmLog::DumpStack; - } else { - log.log_options.level = 0; - log.log_mask = 0; + vm::VmLog log{&logger, td::LogOptions(VERBOSITY_NAME(DEBUG), true, false)}; + if (vm_log_verbosity > 1) { + log.log_mask |= vm::VmLog::ExecLocation; + if (vm_log_verbosity > 2) { + log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining; + } } SmartContract::Answer res; @@ -137,13 +138,13 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref= VERBOSITY_NAME(DEBUG)) { LOG(DEBUG) << "VM log\n" << logger.res; std::ostringstream os; @@ -153,6 +154,7 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref{}); + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, args.vm_log_verbosity_level); state_ = res.new_state; return res; } @@ -237,7 +239,7 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const { CHECK(args.method_id); args.stack.value().write().push_smallint(args.method_id.unwrap()); return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, - args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}); + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}, args.vm_log_verbosity_level); } SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const { diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h index 95dd2e84..6da74bc6 100644 --- a/crypto/smc-envelope/SmartContract.h +++ b/crypto/smc-envelope/SmartContract.h @@ -50,6 +50,7 @@ class SmartContract : public td::CntObject { td::int32 code; td::int64 gas_used; td::ConstBitPtr missing_library{0}; + std::string vm_log; static int output_actions_count(td::Ref list); }; @@ -59,9 +60,11 @@ class SmartContract : public td::CntObject { td::optional> c7; td::optional> stack; td::optional now; + td::optional> rand_seed; bool ignore_chksig{false}; td::uint64 amount{0}; td::uint64 balance{0}; + int vm_log_verbosity_level{0}; td::optional address; td::optional> config; @@ -100,6 +103,10 @@ class SmartContract : public td::CntObject { this->stack = std::move(stack); return std::move(*this); } + Args&& set_rand_seed(td::BitArray<256> rand_seed) { + this->rand_seed = std::move(rand_seed); + return std::move(*this); + } Args&& set_ignore_chksig(bool ignore_chksig) { this->ignore_chksig = ignore_chksig; return std::move(*this); @@ -124,6 +131,10 @@ class SmartContract : public td::CntObject { this->libraries = libraries; return std::move(*this); } + Args&& set_vm_verbosity_level(int vm_log_verbosity_level) { + this->vm_log_verbosity_level = vm_log_verbosity_level; + return std::move(*this); + } td::Result get_method_id() const { if (!method_id) { diff --git a/crypto/vm/log.h b/crypto/vm/log.h index 30ace2c4..b62ada5e 100644 --- a/crypto/vm/log.h +++ b/crypto/vm/log.h @@ -31,7 +31,7 @@ namespace vm { struct VmLog { td::LogInterface *log_interface{td::log_interface}; td::LogOptions log_options{td::log_options}; - enum { DumpStack = 2 }; + enum { DumpStack = 2, ExecLocation = 4, GasRemaining = 8 }; int log_mask{1}; static VmLog Null() { VmLog res; diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp index 6668c6d5..3baba279 100644 --- a/crypto/vm/vm.cpp +++ b/crypto/vm/vm.cpp @@ -433,18 +433,24 @@ void VmState::change_gas_limit(long long new_limit) { int VmState::step() { CHECK(code.not_null() && stack.not_null()); - //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); - //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; + if (log.log_mask & vm::VmLog::DumpStack) { + std::stringstream ss; + stack->dump(ss, 3); + VM_LOG(this) << "stack:" << ss.str(); + } if (stack_trace) { stack->dump(std::cerr, 3); } ++steps; if (code->size()) { + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << code->get_base_cell()->get_hash().to_hex() << " offset: " << code->cur_pos(); return dispatch->dispatch(this, code.write()); } else if (code->size_refs()) { VM_LOG(this) << "execute implicit JMPREF"; + auto ref_cell = code->prefetch_ref(); + VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << ref_cell->get_hash().to_hex() << " offset: 0"; gas.consume_chk(implicit_jmpref_gas_price); - Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; + Ref cont = Ref{true, load_cell_slice_ref(std::move(ref_cell)), get_cp()}; return jump(std::move(cont)); } else { VM_LOG(this) << "execute implicit RET"; @@ -465,6 +471,7 @@ int VmState::run() { try { try { res = step(); + VM_LOG_MASK(this, vm::VmLog::GasRemaining) << "gas remaining: " << gas.gas_remaining; gas.check(); } catch (vm::CellBuilder::CellWriteError) { throw VmError{Excno::cell_ov}; diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt new file mode 100644 index 00000000..43881c7d --- /dev/null +++ b/emulator/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +if (NOT OPENSSL_FOUND) + find_package(OpenSSL REQUIRED) +endif() + +set(EMULATOR_STATIC_SOURCE + transaction-emulator.cpp + tvm-emulator.hpp +) + +set(EMULATOR_HEADERS + transaction-emulator.h + emulator-extern.h +) + +set(EMULATOR_SOURCE + emulator-extern.cpp +) + +set(EMULATOR_EMSCRIPTEN_SOURCE + transaction-emscripten.cpp +) + +include(GenerateExportHeader) + +add_library(emulator_static STATIC ${EMULATOR_STATIC_SOURCE}) +target_link_libraries(emulator_static PUBLIC ton_crypto ton_block smc-envelope) + +add_library(emulator SHARED ${EMULATOR_SOURCE} ${EMULATOR_HEADERS}) +target_link_libraries(emulator PUBLIC emulator_static) +generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h) +target_include_directories(emulator PUBLIC + $ + $) +if (APPLE) + set_target_properties(emulator PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/emulator_export_list") +endif() + +if (USE_EMSCRIPTEN) + add_executable(emulator-emscripten ${EMULATOR_EMSCRIPTEN_SOURCE}) + target_link_libraries(emulator-emscripten PUBLIC emulator) + target_link_options(emulator-emscripten PRIVATE -sEXPORTED_RUNTIME_METHODS=_malloc,free,UTF8ToString,stringToUTF8,allocate,ALLOC_NORMAL,lengthBytesUTF8) + target_link_options(emulator-emscripten PRIVATE -sEXPORTED_FUNCTIONS=_emulate,_free,_run_get_method) + target_link_options(emulator-emscripten PRIVATE -sEXPORT_NAME=EmulatorModule) + target_link_options(emulator-emscripten PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0) + target_link_options(emulator-emscripten PRIVATE -Oz) + target_link_options(emulator-emscripten PRIVATE -sIGNORE_MISSING_MAIN=1) + target_link_options(emulator-emscripten PRIVATE -sAUTO_NATIVE_LIBRARIES=0) + target_link_options(emulator-emscripten PRIVATE -sMODULARIZE=1) + target_link_options(emulator-emscripten PRIVATE -sENVIRONMENT=web) + target_link_options(emulator-emscripten PRIVATE -sFILESYSTEM=0) + target_link_options(emulator-emscripten PRIVATE -fexceptions) + target_compile_options(emulator-emscripten PRIVATE -fexceptions) +endif() diff --git a/emulator/README.md b/emulator/README.md new file mode 100644 index 00000000..d4e2244e --- /dev/null +++ b/emulator/README.md @@ -0,0 +1,32 @@ +# Emulator + +Emulator is a shared library containing the following functionality: +- Emulating blockchain transactions +- Emulating TVM - get methods and sending external and internal messages. + +## Transaction Emulator + +To emulate transaction you need the following data: + +- Account state of type *ShardAccount*. +- Global config of type *(Hashmap 32 ^Cell)*. +- Inbound message of type *MessageAny*. + +Optionally you can set emulation parameters: +- *ignore_chksig* - whether CHKSIG instructions are set to always succeed. Default: *false* +- *lt* - logical time of emulation. Default: next block's lt after the account's last transaction block. +- *unixtime* - unix time of emulation. Default: current system time +- *rand_seed* - random seed. Default: generated randomly +- *libs* - shared libraries. If your smart contract uses shared libraries (located in masterchain), you should set this parameter. + +Emulator output contains: +- Transaction object (*Transaction*) +- New account state (*ShardAccount*) +- Actions cell (*OutList n*) +- TVM log + +## TVM Emulator + +TVM emulator is intended to run get methods or emulate sending message on TVM level. It is initialized with smart contract code and data cells. +- To run get method you pass *initial stack* and *method id* (as integer). +- To emulate sending message you pass *message body* and in case of internal message *amount* in nanograms. diff --git a/emulator/StringLog.h b/emulator/StringLog.h new file mode 100644 index 00000000..724182c4 --- /dev/null +++ b/emulator/StringLog.h @@ -0,0 +1,27 @@ +#ifndef TON_STRINGLOG_H +#define TON_STRINGLOG_H + +#include "td/utils/logging.h" +#include + +class StringLog : public td::LogInterface { + public: + StringLog() { + } + + void append(td::CSlice new_slice, int log_level) override { + str.append(new_slice.str()); + } + + void rotate() override { + } + + std::string get_string() const { + return str; + } + + private: + std::string str; +}; + +#endif //TON_STRINGLOG_H \ No newline at end of file diff --git a/emulator/emulator-emscripten.cpp b/emulator/emulator-emscripten.cpp new file mode 100644 index 00000000..f0f2c903 --- /dev/null +++ b/emulator/emulator-emscripten.cpp @@ -0,0 +1,189 @@ +#include "emulator-extern.h" +#include "td/utils/logging.h" +#include "td/utils/JsonBuilder.h" +#include "td/utils/misc.h" +#include "td/utils/optional.h" +#include "StringLog.h" +#include +#include "crypto/common/bitstring.h" + +struct TransactionEmulationParams { + uint32_t utime; + uint64_t lt; + td::optional rand_seed_hex; + bool ignore_chksig; +}; + +td::Result decode_transaction_emulation_params(const char* json) { + TransactionEmulationParams params; + + std::string json_str(json); + TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str))); + auto &obj = input_json.get_object(); + + TRY_RESULT(utime_field, td::get_json_object_field(obj, "utime", td::JsonValue::Type::Number, false)); + TRY_RESULT(utime, td::to_integer_safe(utime_field.get_number())); + params.utime = utime; + + TRY_RESULT(lt_field, td::get_json_object_field(obj, "lt", td::JsonValue::Type::String, false)); + TRY_RESULT(lt, td::to_integer_safe(lt_field.get_string())); + params.lt = lt; + + TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", true)); + if (rand_seed_str.size() > 0) { + params.rand_seed_hex = rand_seed_str; + } + + TRY_RESULT(ignore_chksig, td::get_json_object_bool_field(obj, "ignore_chksig", false)); + params.ignore_chksig = ignore_chksig; + + return params; +} + +struct GetMethodParams { + std::string code; + std::string data; + int verbosity; + td::optional libs; + std::string address; + uint32_t unixtime; + uint64_t balance; + std::string rand_seed_hex; + int64_t gas_limit; + int method_id; +}; + +td::Result decode_get_method_params(const char* json) { + GetMethodParams params; + + std::string json_str(json); + TRY_RESULT(input_json, td::json_decode(td::MutableSlice(json_str))); + auto &obj = input_json.get_object(); + + TRY_RESULT(code, td::get_json_object_string_field(obj, "code", false)); + params.code = code; + + TRY_RESULT(data, td::get_json_object_string_field(obj, "data", false)); + params.data = data; + + TRY_RESULT(verbosity, td::get_json_object_int_field(obj, "verbosity", false)); + params.verbosity = verbosity; + + TRY_RESULT(libs, td::get_json_object_string_field(obj, "libs", true)); + if (libs.size() > 0) { + params.libs = libs; + } + + TRY_RESULT(address, td::get_json_object_string_field(obj, "address", false)); + params.address = address; + + TRY_RESULT(unixtime_field, td::get_json_object_field(obj, "unixtime", td::JsonValue::Type::Number, false)); + TRY_RESULT(unixtime, td::to_integer_safe(unixtime_field.get_number())); + params.unixtime = unixtime; + + TRY_RESULT(balance_field, td::get_json_object_field(obj, "balance", td::JsonValue::Type::String, false)); + TRY_RESULT(balance, td::to_integer_safe(balance_field.get_string())); + params.balance = balance; + + TRY_RESULT(rand_seed_str, td::get_json_object_string_field(obj, "rand_seed", false)); + params.rand_seed_hex = rand_seed_str; + + TRY_RESULT(gas_limit_field, td::get_json_object_field(obj, "gas_limit", td::JsonValue::Type::String, false)); + TRY_RESULT(gas_limit, td::to_integer_safe(gas_limit_field.get_string())); + params.gas_limit = gas_limit; + + TRY_RESULT(method_id, td::get_json_object_int_field(obj, "method_id", false)); + params.method_id = method_id; + + return params; +} + +extern "C" { + +const char *emulate(const char *config, const char* libs, int verbosity, const char* account, const char* message, const char* params) { + StringLog logger; + + td::log_interface = &logger; + SET_VERBOSITY_LEVEL(verbosity_DEBUG); + + auto decoded_params_res = decode_transaction_emulation_params(params); + if (decoded_params_res.is_error()) { + return strdup(R"({"fail":true,"message":"Can't decode other params"})"); + } + auto decoded_params = decoded_params_res.move_as_ok(); + + auto em = transaction_emulator_create(config, verbosity); + + bool rand_seed_set = true; + if (decoded_params.rand_seed_hex) { + rand_seed_set = transaction_emulator_set_rand_seed(em, decoded_params.rand_seed_hex.unwrap().c_str()); + } + + if (!transaction_emulator_set_libs(em, libs) || + !transaction_emulator_set_lt(em, decoded_params.lt) || + !transaction_emulator_set_unixtime(em, decoded_params.utime) || + !transaction_emulator_set_ignore_chksig(em, decoded_params.ignore_chksig) || + !rand_seed_set) { + transaction_emulator_destroy(em); + return strdup(R"({"fail":true,"message":"Can't set params"})"); + } + + auto tx = transaction_emulator_emulate_transaction(em, account, message); + + transaction_emulator_destroy(em); + + const char* output = nullptr; + { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("output", td::JsonRaw(td::Slice(tx))); + json_obj("logs", logger.get_string()); + json_obj.leave(); + output = strdup(jb.string_builder().as_cslice().c_str()); + } + free((void*) tx); + + return output; +} + +const char *run_get_method(const char *params, const char* stack, const char* config) { + StringLog logger; + + td::log_interface = &logger; + SET_VERBOSITY_LEVEL(verbosity_DEBUG); + + auto decoded_params_res = decode_get_method_params(params); + if (decoded_params_res.is_error()) { + return strdup(R"({"fail":true,"message":"Can't decode params"})"); + } + auto decoded_params = decoded_params_res.move_as_ok(); + + auto tvm = tvm_emulator_create(decoded_params.code.c_str(), decoded_params.data.c_str(), decoded_params.verbosity); + + if ((decoded_params.libs && !tvm_emulator_set_libraries(tvm, decoded_params.libs.value().c_str())) || + !tvm_emulator_set_c7(tvm, decoded_params.address.c_str(), decoded_params.unixtime, + decoded_params.balance, decoded_params.rand_seed_hex.c_str(), config) || + (decoded_params.gas_limit > 0 && !tvm_emulator_set_gas_limit(tvm, decoded_params.gas_limit))) { + tvm_emulator_destroy(tvm); + return strdup(R"({"fail":true,"message":"Can't set params"})"); + } + + auto res = tvm_emulator_run_get_method(tvm, decoded_params.method_id, stack); + + tvm_emulator_destroy(tvm); + + const char* output = nullptr; + { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("output", td::JsonRaw(td::Slice(res))); + json_obj("logs", logger.get_string()); + json_obj.leave(); + output = strdup(jb.string_builder().as_cslice().c_str()); + } + free((void*) res); + + return output; +} + +} \ No newline at end of file diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp new file mode 100644 index 00000000..01b9d5c7 --- /dev/null +++ b/emulator/emulator-extern.cpp @@ -0,0 +1,435 @@ +#include "emulator-extern.h" +#include "td/utils/base64.h" +#include "td/utils/Status.h" +#include "td/utils/JsonBuilder.h" +#include "td/utils/logging.h" +#include "td/utils/Variant.h" +#include "td/utils/overloaded.h" +#include "transaction-emulator.h" +#include "tvm-emulator.hpp" +#include "crypto/vm/stack.hpp" + +td::Result> boc_b64_to_cell(const char *boc) { + TRY_RESULT_PREFIX(boc_decoded, td::base64_decode(td::Slice(boc)), "Can't decode base64 boc: "); + return vm::std_boc_deserialize(boc_decoded); +} + +td::Result cell_to_boc_b64(td::Ref cell) { + TRY_RESULT_PREFIX(boc, vm::std_boc_serialize(std::move(cell), vm::BagOfCells::Mode::WithCRC32C), "Can't serialize cell: "); + return td::base64_encode(boc.as_slice()); +} + +const char *success_response(std::string&& transaction, std::string&& new_shard_account, std::string&& vm_log, td::optional&& actions) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("transaction", std::move(transaction)); + json_obj("shard_account", std::move(new_shard_account)); + json_obj("vm_log", std::move(vm_log)); + if (actions) { + json_obj("actions", actions.unwrap()); + } else { + json_obj("actions", td::JsonNull()); + } + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *error_response(std::string&& error) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonFalse()); + json_obj("error", std::move(error)); + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *external_not_accepted_response(std::string&& vm_log, int vm_exit_code) { + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonFalse()); + json_obj("error", "External message not accepted by smart contract"); + json_obj("vm_log", std::move(vm_log)); + json_obj("vm_exit_code", vm_exit_code); + json_obj.leave(); + return strdup(jb.string_builder().as_cslice().c_str()); +} + +#define ERROR_RESPONSE(error) return error_response(error) + +td::Result decode_config(const char* config_boc) { + TRY_RESULT_PREFIX(config_params_cell, boc_b64_to_cell(config_boc), "Can't deserialize config params boc: "); + auto global_config = block::Config(config_params_cell, td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc); + TRY_STATUS_PREFIX(global_config.unpack(), "Can't unpack config params: "); + return global_config; +} + +void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity) { + auto global_config_res = decode_config(config_params_boc); + if (global_config_res.is_error()) { + LOG(ERROR) << global_config_res.move_as_error().message(); + return nullptr; + } + + return new emulator::TransactionEmulator(global_config_res.move_as_ok(), vm_log_verbosity); +} + +const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc) { + auto emulator = static_cast(transaction_emulator); + + auto message_cell_r = boc_b64_to_cell(message_boc); + if (message_cell_r.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message boc: " << message_cell_r.move_as_error()); + } + auto message_cell = message_cell_r.move_as_ok(); + auto message_cs = vm::load_cell_slice(message_cell); + int msg_tag = block::gen::t_CommonMsgInfo.get_tag(message_cs); + + auto shard_account_cell = boc_b64_to_cell(shard_account_boc); + if (shard_account_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize shard account boc: " << shard_account_cell.move_as_error()); + } + auto shard_account_slice = vm::load_cell_slice(shard_account_cell.ok_ref()); + block::gen::ShardAccount::Record shard_account; + if (!tlb::unpack(shard_account_slice, shard_account)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack shard account cell"); + } + + td::Ref addr_slice; + auto account_slice = vm::load_cell_slice(shard_account.account); + if (block::gen::t_Account.get_tag(account_slice) == block::gen::Account::account_none) { + if (msg_tag == block::gen::CommonMsgInfo::ext_in_msg_info) { + block::gen::CommonMsgInfo::Record_ext_in_msg_info info; + if (!tlb::unpack(message_cs, info)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack inbound external message"); + } + addr_slice = std::move(info.dest); + } + else if (msg_tag == block::gen::CommonMsgInfo::int_msg_info) { + block::gen::CommonMsgInfo::Record_int_msg_info info; + if (!tlb::unpack(message_cs, info)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack inbound internal message"); + } + addr_slice = std::move(info.dest); + } else { + ERROR_RESPONSE(PSTRING() << "Only ext in and int message are supported"); + } + } else { + block::gen::Account::Record_account account_record; + if (!tlb::unpack(account_slice, account_record)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack account cell"); + } + addr_slice = std::move(account_record.addr); + } + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(addr_slice, wc, addr)) { + ERROR_RESPONSE(PSTRING() << "Can't extract account address"); + } + + auto account = block::Account(wc, addr.bits()); + ton::UnixTime now = (unsigned)std::time(nullptr); + bool is_special = wc == ton::masterchainId && emulator->get_config().is_special_smartcontract(addr); + if (!account.unpack(vm::load_cell_slice_ref(shard_account_cell.move_as_ok()), td::Ref(), now, is_special)) { + ERROR_RESPONSE(PSTRING() << "Can't unpack shard account"); + } + + auto result = emulator->emulate_transaction(std::move(account), message_cell, 0, 0, block::transaction::Transaction::tr_ord); + if (result.is_error()) { + ERROR_RESPONSE(PSTRING() << "Emulate transaction failed: " << result.move_as_error()); + } + auto emulation_result = result.move_as_ok(); + + auto external_not_accepted = dynamic_cast(emulation_result.get()); + if (external_not_accepted) { + return external_not_accepted_response(std::move(external_not_accepted->vm_log), external_not_accepted->vm_exit_code); + } + + auto emulation_success = dynamic_cast(*emulation_result); + auto trans_boc_b64 = cell_to_boc_b64(std::move(emulation_success.transaction)); + if (trans_boc_b64.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize Transaction to boc " << trans_boc_b64.move_as_error()); + } + + auto new_shard_account_cell = vm::CellBuilder().store_ref(emulation_success.account.total_state) + .store_bits(emulation_success.account.last_trans_hash_.as_bitslice()) + .store_long(emulation_success.account.last_trans_lt_).finalize(); + auto new_shard_account_boc_b64 = cell_to_boc_b64(std::move(new_shard_account_cell)); + if (new_shard_account_boc_b64.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize ShardAccount to boc " << new_shard_account_boc_b64.move_as_error()); + } + + td::optional actions_boc_b64; + if (emulation_success.actions.not_null()) { + auto actions_boc_b64_result = cell_to_boc_b64(std::move(emulation_success.actions)); + if (actions_boc_b64_result.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't serialize actions list cell to boc " << actions_boc_b64_result.move_as_error()); + } + actions_boc_b64 = actions_boc_b64_result.move_as_ok(); + } + + return success_response(trans_boc_b64.move_as_ok(), new_shard_account_boc_b64.move_as_ok(), std::move(emulation_success.vm_log), std::move(actions_boc_b64)); +} + +bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_unixtime(unixtime); + + return true; +} + +bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_lt(lt); + + return true; +} + +bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex) { + auto emulator = static_cast(transaction_emulator); + + auto rand_seed_hex_slice = td::Slice(rand_seed_hex); + if (rand_seed_hex_slice.size() != 64) { + LOG(ERROR) << "Rand seed expected as 64 characters hex string"; + return false; + } + auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice); + if (rand_seed_bytes.is_error()) { + LOG(ERROR) << "Can't decode hex rand seed"; + return false; + } + td::BitArray<256> rand_seed; + rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok()); + + emulator->set_rand_seed(rand_seed); + return true; +} + +bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig) { + auto emulator = static_cast(transaction_emulator); + + emulator->set_ignore_chksig(ignore_chksig); + + return true; +} + +bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc) { + auto emulator = static_cast(transaction_emulator); + + auto global_config_res = decode_config(config_boc); + if (global_config_res.is_error()) { + LOG(ERROR) << global_config_res.move_as_error().message(); + return false; + } + + emulator->set_config(global_config_res.move_as_ok()); + + return true; +} + +bool transaction_emulator_set_libs(void *transaction_emulator, const char* shardchain_libs_boc) { + auto emulator = static_cast(transaction_emulator); + + if (shardchain_libs_boc != nullptr) { + auto shardchain_libs_cell = boc_b64_to_cell(shardchain_libs_boc); + if (shardchain_libs_cell.is_error()) { + LOG(ERROR) << "Can't deserialize shardchain libraries boc: " << shardchain_libs_cell.move_as_error(); + return false; + } + emulator->set_libs(vm::Dictionary(shardchain_libs_cell.move_as_ok(), 256)); + } + + return true; +} + +void transaction_emulator_destroy(void *transaction_emulator) { + delete static_cast(transaction_emulator); +} + +bool emulator_set_verbosity_level(int verbosity_level) { + if (0 <= verbosity_level && verbosity_level <= VERBOSITY_NAME(NEVER)) { + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity_level); + return true; + } + return false; +} + +void *tvm_emulator_create(const char *code, const char *data, int vm_log_verbosity) { + auto code_cell = boc_b64_to_cell(code); + if (code_cell.is_error()) { + LOG(ERROR) << "Can't deserialize code boc: " << code_cell.move_as_error(); + return nullptr; + } + auto data_cell = boc_b64_to_cell(data); + if (data_cell.is_error()) { + LOG(ERROR) << "Can't deserialize code boc: " << data_cell.move_as_error(); + return nullptr; + } + + auto emulator = new emulator::TvmEmulator(code_cell.move_as_ok(), data_cell.move_as_ok()); + emulator->set_vm_verbosity_level(vm_log_verbosity); + return emulator; +} + +bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc) { + vm::Dictionary libs{256}; + auto libs_cell = boc_b64_to_cell(libs_boc); + if (libs_cell.is_error()) { + LOG(ERROR) << "Can't deserialize libraries boc: " << libs_cell.move_as_error(); + return false; + } + libs = vm::Dictionary(libs_cell.move_as_ok(), 256); + + auto emulator = static_cast(tvm_emulator); + emulator->set_libraries(std::move(libs)); + + return true; +} + +bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config_boc) { + auto emulator = static_cast(tvm_emulator); + auto std_address = block::StdAddress::parse(td::Slice(address)); + if (std_address.is_error()) { + LOG(ERROR) << "Can't parse address: " << std_address.move_as_error(); + return false; + } + + auto config_params_cell = boc_b64_to_cell(config_boc); + if (config_params_cell.is_error()) { + LOG(ERROR) << "Can't deserialize config params boc: " << config_params_cell.move_as_error(); + return false; + } + auto global_config = std::make_shared(config_params_cell.move_as_ok(), td::Bits256::zero(), block::Config::needWorkchainInfo | block::Config::needSpecialSmc); + auto unpack_res = global_config->unpack(); + if (unpack_res.is_error()) { + LOG(ERROR) << "Can't unpack config params"; + return false; + } + + auto rand_seed_hex_slice = td::Slice(rand_seed_hex); + if (rand_seed_hex_slice.size() != 64) { + LOG(ERROR) << "Rand seed expected as 64 characters hex string"; + return false; + } + auto rand_seed_bytes = td::hex_decode(rand_seed_hex_slice); + if (rand_seed_bytes.is_error()) { + LOG(ERROR) << "Can't decode hex rand seed"; + return false; + } + td::BitArray<256> rand_seed; + rand_seed.as_slice().copy_from(rand_seed_bytes.move_as_ok()); + + emulator->set_c7(std_address.move_as_ok(), unixtime, balance, rand_seed, std::const_pointer_cast(global_config)); + + return true; +} + +bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit) { + auto emulator = static_cast(tvm_emulator); + emulator->set_gas_limit(gas_limit); + return true; +} + +const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc) { + auto stack_cell = boc_b64_to_cell(stack_boc); + if (stack_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack cell: " << stack_cell.move_as_error().to_string()); + } + auto stack_cs = vm::load_cell_slice(stack_cell.move_as_ok()); + td::Ref stack; + if (!vm::Stack::deserialize_to(stack_cs, stack)) { + ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack"); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->run_get_method(method_id, stack); + + vm::CellBuilder stack_cb; + if (!result.stack->serialize(stack_cb)) { + ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack"); + } + auto result_stack_boc = cell_to_boc_b64(stack_cb.finalize()); + if (result_stack_boc.is_error()) { + ERROR_RESPONSE(PSTRING() << "Couldn't serialize stack cell: " << result_stack_boc.move_as_error().to_string()); + } + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("stack", result_stack_boc.move_as_ok()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc) { + auto message_body_cell = boc_b64_to_cell(message_body_boc); + if (message_body_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error()); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->send_external_message(message_body_cell.move_as_ok()); + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("accepted", td::JsonBool(result.accepted)); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); + json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount) { + auto message_body_cell = boc_b64_to_cell(message_body_boc); + if (message_body_cell.is_error()) { + ERROR_RESPONSE(PSTRING() << "Can't deserialize message body boc: " << message_body_cell.move_as_error()); + } + + auto emulator = static_cast(tvm_emulator); + auto result = emulator->send_internal_message(message_body_cell.move_as_ok(), amount); + + td::JsonBuilder jb; + auto json_obj = jb.enter_object(); + json_obj("success", td::JsonTrue()); + json_obj("gas_used", std::to_string(result.gas_used)); + json_obj("vm_exit_code", result.code); + json_obj("accepted", td::JsonBool(result.accepted)); + json_obj("vm_log", result.vm_log); + if (result.missing_library.is_null()) { + json_obj("missing_library", td::JsonNull()); + } else { + json_obj("missing_library", td::Bits256(result.missing_library).to_hex()); + } + json_obj("actions", cell_to_boc_b64(result.actions).move_as_ok()); + json_obj("new_code", cell_to_boc_b64(result.new_state.code).move_as_ok()); + json_obj("new_data", cell_to_boc_b64(result.new_state.data).move_as_ok()); + json_obj.leave(); + + return strdup(jb.string_builder().as_cslice().c_str()); +} + +void tvm_emulator_destroy(void *tvm_emulator) { + delete static_cast(tvm_emulator); +} diff --git a/emulator/emulator-extern.h b/emulator/emulator-extern.h new file mode 100644 index 00000000..ad5972de --- /dev/null +++ b/emulator/emulator-extern.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include "emulator_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates TransactionEmulator object + * @param config_params_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @param vm_log_verbosity Verbosity level of VM log. 0 - log truncated to last 256 characters. 1 - unlimited length log. + * 2 - for each command prints its cell hash and offset. 3 - for each command log prints all stack values. + * @return Pointer to TransactionEmulator or nullptr in case of error + */ +EMULATOR_EXPORT void *transaction_emulator_create(const char *config_params_boc, int vm_log_verbosity); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param unixtime Unix timestamp + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_unixtime(void *transaction_emulator, uint32_t unixtime); + +/** + * @brief Set lt for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param lt Logical time + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt); + +/** + * @brief Set rand seed for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param rand_seed_hex Hex string of length 64 + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_rand_seed(void *transaction_emulator, const char* rand_seed_hex); + +/** + * @brief Set ignore_chksig flag for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param ignore_chksig Whether emulation should always succeed on CHKSIG operation + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_ignore_chksig(void *transaction_emulator, bool ignore_chksig); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param config_boc Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_config(void *transaction_emulator, const char* config_boc); + +/** + * @brief Set unixtime for emulation + * @param transaction_emulator Pointer to TransactionEmulator object + * @param libs_boc Base64 encoded BoC serialized shared libraries dictionary (HashmapE 256 ^Cell). + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool transaction_emulator_set_libs(void *transaction_emulator, const char* libs_boc); + +/** + * @brief Emulate transaction + * @param transaction_emulator Pointer to TransactionEmulator object + * @param shard_account_boc Base64 encoded BoC serialized ShardAccount + * @param message_boc Base64 encoded BoC serialized inbound Message (internal or external) + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * // and optional fields "vm_exit_code" and "vm_log" in case external message was not accepted. + * } + * Or success: + * { + * "success": true, + * "transaction": "Base64 encoded Transaction boc", + * "shard_account": "Base64 encoded new ShardAccount boc", + * "vm_log": "execute DUP...", + * "actions": "Base64 encoded compute phase actions boc (OutList n)" + * } + */ +EMULATOR_EXPORT const char *transaction_emulator_emulate_transaction(void *transaction_emulator, const char *shard_account_boc, const char *message_boc); + +/** + * @brief Destroy TransactionEmulator object + * @param transaction_emulator Pointer to TransactionEmulator object + */ +EMULATOR_EXPORT void transaction_emulator_destroy(void *transaction_emulator); + +/** + * @brief Set global verbosity level of the library + * @param verbosity_level New verbosity level (0 - never, 1 - error, 2 - warning, 3 - info, 4 - debug) + */ +EMULATOR_EXPORT bool emulator_set_verbosity_level(int verbosity_level); + +/** + * @brief Create TVM emulator + * @param code_boc Base64 encoded BoC serialized smart contract code cell + * @param data_boc Base64 encoded BoC serialized smart contract data cell + * @param vm_log_verbosity Verbosity level of VM log + * @return Pointer to TVM emulator object + */ +EMULATOR_EXPORT void *tvm_emulator_create(const char *code_boc, const char *data_boc, int vm_log_verbosity); + +/** + * @brief Set libraries for TVM emulator + * @param libs_boc Base64 encoded BoC serialized libraries dictionary (HashmapE 256 ^Cell). + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_libraries(void *tvm_emulator, const char *libs_boc); + +/** + * @brief Set c7 parameters + * @param tvm_emulator Pointer to TVM emulator + * @param address Adress of smart contract + * @param unixtime Unix timestamp + * @param balance Smart contract balance + * @param rand_seed_hex Random seed as hex string of length 64 + * @param config Base64 encoded BoC serialized Config dictionary (Hashmap 32 ^Cell) + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_c7(void *tvm_emulator, const char *address, uint32_t unixtime, uint64_t balance, const char *rand_seed_hex, const char *config); + +/** + * @brief Set TVM gas limit + * @param tvm_emulator Pointer to TVM emulator + * @param gas_limit Gas limit + * @return true in case of success, false in case of error + */ +EMULATOR_EXPORT bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit); + +/** + * @brief Run get method + * @param tvm_emulator Pointer to TVM emulator + * @param method_id Integer method id + * @param stack_boc Base64 encoded BoC serialized stack (VmStack) + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true + * "vm_log": "...", + * "vm_exit_code": 0, + * "stack": "Base64 encoded BoC serialized stack (VmStack)", + * "missing_library": null, + * "gas_used": 1212 + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_run_get_method(void *tvm_emulator, int method_id, const char *stack_boc); + +/** + * @brief Send external message + * @param tvm_emulator Pointer to TVM emulator + * @param message_body_boc Base64 encoded BoC serialized message body cell. + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true, + * "new_code": "Base64 boc decoded new code cell", + * "new_data": "Base64 boc decoded new data cell", + * "accepted": true, + * "vm_exit_code": 0, + * "vm_log": "...", + * "missing_library": null, + * "gas_used": 1212, + * "actions": "Base64 boc decoded actions cell of type (OutList n)" + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_send_external_message(void *tvm_emulator, const char *message_body_boc); + +/** + * @brief Send internal message + * @param tvm_emulator Pointer to TVM emulator + * @param message_body_boc Base64 encoded BoC serialized message body cell. + * @param amount Amount of nanograms attached with internal message. + * @return Json object with error: + * { + * "success": false, + * "error": "Error description" + * } + * Or success: + * { + * "success": true, + * "new_code": "Base64 boc decoded new code cell", + * "new_data": "Base64 boc decoded new data cell", + * "accepted": true, + * "vm_exit_code": 0, + * "vm_log": "...", + * "missing_library": null, + * "gas_used": 1212, + * "actions": "Base64 boc decoded actions cell of type (OutList n)" + * } + */ +EMULATOR_EXPORT const char *tvm_emulator_send_internal_message(void *tvm_emulator, const char *message_body_boc, uint64_t amount); + +/** + * @brief Destroy TVM emulator object + * @param tvm_emulator Pointer to TVM emulator object + */ +EMULATOR_EXPORT void tvm_emulator_destroy(void *tvm_emulator); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/emulator/emulator_export_list b/emulator/emulator_export_list new file mode 100644 index 00000000..b53a4114 --- /dev/null +++ b/emulator/emulator_export_list @@ -0,0 +1,17 @@ +_transaction_emulator_create +_transaction_emulator_set_lt +_transaction_emulator_set_rand_seed +_transaction_emulator_set_ignore_chksig +_transaction_emulator_set_config +_transaction_emulator_set_libs +_transaction_emulator_emulate_transaction +_transaction_emulator_destroy +_emulator_set_verbosity_level +_tvm_emulator_create +_tvm_emulator_set_libraries +_tvm_emulator_set_c7 +_tvm_emulator_set_gas_limit +_tvm_emulator_run_get_method +_tvm_emulator_send_external_message +_tvm_emulator_send_internal_message +_tvm_emulator_destroy diff --git a/emulator/transaction-emulator.cpp b/emulator/transaction-emulator.cpp new file mode 100644 index 00000000..8611378a --- /dev/null +++ b/emulator/transaction-emulator.cpp @@ -0,0 +1,254 @@ +#include +#include "transaction-emulator.h" +#include "crypto/common/refcnt.hpp" +#include "vm/cp0.h" + +using td::Ref; +using namespace std::string_literals; + +namespace emulator { +td::Result> TransactionEmulator::emulate_transaction( + block::Account&& account, td::Ref msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type) { + + td::Ref old_mparams; + std::vector storage_prices; + block::StoragePhaseConfig storage_phase_cfg{&storage_prices}; + block::ComputePhaseConfig compute_phase_cfg; + block::ActionPhaseConfig action_phase_cfg; + td::RefInt256 masterchain_create_fee, basechain_create_fee; + + if (!utime) { + utime = unixtime_; + } + if (!utime) { + utime = (unsigned)std::time(nullptr); + } + + auto fetch_res = block::FetchConfigParams::fetch_config_params(config_, &old_mparams, + &storage_prices, &storage_phase_cfg, + &rand_seed_, &compute_phase_cfg, + &action_phase_cfg, &masterchain_create_fee, + &basechain_create_fee, account.workchain, utime); + if(fetch_res.is_error()) { + return fetch_res.move_as_error_prefix("cannot fetch config params "); + } + + vm::init_op_cp0(); + + if (!lt) { + lt = lt_; + } + if (!lt) { + lt = (account.last_trans_lt_ / block::ConfigInfo::get_lt_align() + 1) * block::ConfigInfo::get_lt_align(); // next block after account_.last_trans_lt_ + } + + compute_phase_cfg.libraries = std::make_unique(libraries_); + compute_phase_cfg.ignore_chksig = ignore_chksig_; + compute_phase_cfg.with_vm_log = true; + compute_phase_cfg.vm_log_verbosity = vm_log_verbosity_; + + auto res = create_transaction(msg_root, &account, utime, lt, trans_type, + &storage_phase_cfg, &compute_phase_cfg, + &action_phase_cfg); + if(res.is_error()) { + return res.move_as_error_prefix("cannot run message on account "); + } + std::unique_ptr trans = res.move_as_ok(); + + if (!trans->compute_phase->accepted && trans->in_msg_extern) { + auto vm_log = trans->compute_phase->vm_log; + auto vm_exit_code = trans->compute_phase->exit_code; + return std::make_unique(std::move(vm_log), vm_exit_code); + } + + if (!trans->serialize()) { + return td::Status::Error(-669,"cannot serialize new transaction for smart contract "s + trans->account.addr.to_hex()); + } + + auto trans_root = trans->commit(account); + if (trans_root.is_null()) { + return td::Status::Error(PSLICE() << "cannot commit new transaction for smart contract"); + } + + return std::make_unique(std::move(trans_root), std::move(account), std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions)); +} + +td::Result TransactionEmulator::emulate_transaction(block::Account&& account, td::Ref original_trans) { + + block::gen::Transaction::Record record_trans; + if (!tlb::unpack_cell(original_trans, record_trans)) { + return td::Status::Error("Failed to unpack Transaction"); + } + + ton::LogicalTime lt = record_trans.lt; + ton::UnixTime utime = record_trans.now; + account.now_ = utime; + td::Ref msg_root = record_trans.r1.in_msg->prefetch_ref(); + int tag = block::gen::t_TransactionDescr.get_tag(vm::load_cell_slice(record_trans.description)); + + int trans_type = block::transaction::Transaction::tr_none; + switch (tag) { + case block::gen::TransactionDescr::trans_ord: { + trans_type = block::transaction::Transaction::tr_ord; + break; + } + case block::gen::TransactionDescr::trans_storage: { + trans_type = block::transaction::Transaction::tr_storage; + break; + } + case block::gen::TransactionDescr::trans_tick_tock: { + block::gen::TransactionDescr::Record_trans_tick_tock tick_tock; + if (!tlb::unpack_cell(record_trans.description, tick_tock)) { + return td::Status::Error("Failed to unpack tick tock transaction description"); + } + trans_type = tick_tock.is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick; + break; + } + case block::gen::TransactionDescr::trans_split_prepare: { + trans_type = block::transaction::Transaction::tr_split_prepare; + break; + } + case block::gen::TransactionDescr::trans_split_install: { + trans_type = block::transaction::Transaction::tr_split_install; + break; + } + case block::gen::TransactionDescr::trans_merge_prepare: { + trans_type = block::transaction::Transaction::tr_merge_prepare; + break; + } + case block::gen::TransactionDescr::trans_merge_install: { + trans_type = block::transaction::Transaction::tr_merge_install; + break; + } + } + + TRY_RESULT(emulation, emulate_transaction(std::move(account), msg_root, utime, lt, trans_type)); + + auto emulation_result = dynamic_cast(*emulation); + if (td::Bits256(emulation_result.transaction->get_hash().bits()) != td::Bits256(original_trans->get_hash().bits())) { + return td::Status::Error("transaction hash mismatch"); + } + + if (!check_state_update(emulation_result.account, record_trans)) { + return td::Status::Error("account hash mismatch"); + } + + return emulation_result; +} + +td::Result TransactionEmulator::emulate_transactions_chain(block::Account&& account, std::vector>&& original_transactions) { + + std::vector> emulated_transactions; + for (const auto& original_trans : original_transactions) { + if (original_trans.is_null()) { + continue; + } + + TRY_RESULT(emulation_result, emulate_transaction(std::move(account), original_trans)); + emulated_transactions.push_back(std::move(emulation_result.transaction)); + account = std::move(emulation_result.account); + } + + return TransactionEmulator::EmulationChain{ std::move(emulated_transactions), std::move(account) }; +} + +bool TransactionEmulator::check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans) { + block::gen::HASH_UPDATE::Record hash_update; + return tlb::type_unpack_cell(trans.state_update, block::gen::t_HASH_UPDATE_Account, hash_update) && + hash_update.new_hash == account.total_state->get_hash().bits(); +} + +td::Result> TransactionEmulator::create_transaction( + td::Ref msg_root, block::Account* acc, + ton::UnixTime utime, ton::LogicalTime lt, int trans_type, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg) { + bool external{false}, ihr_delivered{false}, need_credit_phase{false}; + + if (msg_root.not_null()) { + auto cs = vm::load_cell_slice(msg_root); + external = block::gen::t_CommonMsgInfo.get_tag(cs); + } + + if (trans_type == block::transaction::Transaction::tr_ord) { + need_credit_phase = !external; + } else if (trans_type == block::transaction::Transaction::tr_merge_install) { + need_credit_phase = true; + } + + std::unique_ptr trans = + std::make_unique(*acc, trans_type, lt, utime, msg_root); + + if (msg_root.not_null() && !trans->unpack_input_msg(ihr_delivered, action_phase_cfg)) { + if (external) { + // inbound external message was not accepted + return td::Status::Error(-701,"inbound external message rejected by account "s + acc->addr.to_hex() + + " before smart-contract execution"); + } + return td::Status::Error(-669,"cannot unpack input message for a new transaction"); + } + + if (trans->bounce_enabled) { + if (!trans->prepare_storage_phase(*storage_phase_cfg, true)) { + return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + if (need_credit_phase && !trans->prepare_credit_phase()) { + return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + } else { + if (need_credit_phase && !trans->prepare_credit_phase()) { + return td::Status::Error(-669,"cannot create credit phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + if (!trans->prepare_storage_phase(*storage_phase_cfg, true, need_credit_phase)) { + return td::Status::Error(-669,"cannot create storage phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + } + + if (!trans->prepare_compute_phase(*compute_phase_cfg)) { + return td::Status::Error(-669,"cannot create compute phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + if (!trans->compute_phase->accepted) { + if (!external && trans->compute_phase->skip_reason == block::ComputePhase::sk_none) { + return td::Status::Error(-669,"new ordinary transaction for smart contract "s + acc->addr.to_hex() + + " has not been accepted by the smart contract (?)"); + } + } + + if (trans->compute_phase->success && !trans->prepare_action_phase(*action_phase_cfg)) { + return td::Status::Error(-669,"cannot create action phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + if (trans->bounce_enabled && !trans->compute_phase->success && !trans->prepare_bounce_phase(*action_phase_cfg)) { + return td::Status::Error(-669,"cannot create bounce phase of a new transaction for smart contract "s + acc->addr.to_hex()); + } + + return trans; +} + +void TransactionEmulator::set_unixtime(ton::UnixTime unixtime) { + unixtime_ = unixtime; +} + +void TransactionEmulator::set_lt(ton::LogicalTime lt) { + lt_ = lt; +} + +void TransactionEmulator::set_rand_seed(td::BitArray<256>& rand_seed) { + rand_seed_ = rand_seed; +} + +void TransactionEmulator::set_ignore_chksig(bool ignore_chksig) { + ignore_chksig_ = ignore_chksig; +} + +void TransactionEmulator::set_config(block::Config &&config) { + config_ = std::forward(config); +} + +void TransactionEmulator::set_libs(vm::Dictionary &&libs) { + libraries_ = std::forward(libs); +} + +} // namespace emulator diff --git a/emulator/transaction-emulator.h b/emulator/transaction-emulator.h new file mode 100644 index 00000000..fe0e22cb --- /dev/null +++ b/emulator/transaction-emulator.h @@ -0,0 +1,83 @@ +#pragma once +#include "crypto/common/refcnt.hpp" +#include "ton/ton-types.h" +#include "crypto/vm/cells.h" +#include "block/transaction.h" +#include "block/block-auto.h" +#include "block/block-parse.h" +#include "block/mc-config.h" + +namespace emulator { +class TransactionEmulator { + block::Config config_; + vm::Dictionary libraries_; + int vm_log_verbosity_; + ton::UnixTime unixtime_; + ton::LogicalTime lt_; + td::BitArray<256> rand_seed_; + bool ignore_chksig_; + +public: + TransactionEmulator(block::Config&& config, int vm_log_verbosity = 0) : + config_(std::move(config)), libraries_(256), vm_log_verbosity_(vm_log_verbosity), + unixtime_(0), lt_(0), rand_seed_(0), ignore_chksig_(false) { + } + + struct EmulationResult { + std::string vm_log; + + EmulationResult(std::string vm_log_) : vm_log(vm_log_) {} + virtual ~EmulationResult() = default; + }; + + struct EmulationSuccess: EmulationResult { + td::Ref transaction; + block::Account account; + td::Ref actions; + + EmulationSuccess(td::Ref transaction_, block::Account account_, std::string vm_log_, td::Ref actions_) : + EmulationResult(vm_log_), transaction(transaction_), account(account_) , actions(actions_) + {} + }; + + struct EmulationExternalNotAccepted: EmulationResult { + int vm_exit_code; + + EmulationExternalNotAccepted(std::string vm_log_, int vm_exit_code_) : + EmulationResult(vm_log_), vm_exit_code(vm_exit_code_) + {} + }; + + struct EmulationChain { + std::vector> transactions; + block::Account account; + }; + + const block::Config& get_config() { + return config_; + } + + td::Result> emulate_transaction( + block::Account&& account, td::Ref msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type); + + td::Result emulate_transaction(block::Account&& account, td::Ref original_trans); + td::Result emulate_transactions_chain(block::Account&& account, std::vector>&& original_transactions); + + void set_unixtime(ton::UnixTime unixtime); + void set_lt(ton::LogicalTime lt); + void set_rand_seed(td::BitArray<256>& rand_seed); + void set_ignore_chksig(bool ignore_chksig); + void set_config(block::Config &&config); + void set_libs(vm::Dictionary &&libs); + +private: + bool check_state_update(const block::Account& account, const block::gen::Transaction::Record& trans); + + td::Result> create_transaction( + td::Ref msg_root, block::Account* acc, + ton::UnixTime utime, ton::LogicalTime lt, int trans_type, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg); +}; +} // namespace emulator diff --git a/emulator/tvm-emulator.hpp b/emulator/tvm-emulator.hpp new file mode 100644 index 00000000..dfbc20b1 --- /dev/null +++ b/emulator/tvm-emulator.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "smc-envelope/SmartContract.h" + +namespace emulator { +class TvmEmulator { + ton::SmartContract smc_; + ton::SmartContract::Args args_; +public: + using Answer = ton::SmartContract::Answer; + + TvmEmulator(td::Ref code, td::Ref data): smc_({code, data}) { + } + + void set_vm_verbosity_level(int vm_log_verbosity) { + args_.set_vm_verbosity_level(vm_log_verbosity); + } + + void set_libraries(vm::Dictionary&& libraries) { + args_.set_libraries(libraries); + } + + void set_gas_limit(int64_t limit) { + args_.set_limits(vm::GasLimits(limit)); + } + + void set_c7(block::StdAddress address, uint32_t unixtime, uint64_t balance, td::BitArray<256> rand_seed, std::shared_ptr config) { + args_.set_address(address); + args_.set_now(unixtime); + args_.set_balance(balance); + args_.set_rand_seed(rand_seed); + args_.set_config(config); + } + + Answer run_get_method(int method_id, td::Ref stack) { + return smc_.run_get_method(args_.set_stack(stack).set_method_id(method_id)); + } + + Answer send_external_message(td::Ref message_body) { + return smc_.send_external_message(message_body, args_); + } + + Answer send_internal_message(td::Ref message_body, uint64_t amount) { + return smc_.send_internal_message(message_body, args_.set_amount(amount)); + } +}; +} \ No newline at end of file diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index 10a5896c..c9a7df3d 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -257,6 +257,7 @@ getBip39Hints prefix:string = Bip39Hints; //raw.init initial_account_state:raw.initialAccountState = Ok; raw.getAccountState account_address:accountAddress = raw.FullAccountState; +raw.getAccountStateByTransaction account_address:accountAddress transaction_id:internal.transactionId = raw.FullAccountState; raw.getTransactions private_key:InputKey account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; raw.getTransactionsV2 private_key:InputKey account_address:accountAddress from_transaction_id:internal.transactionId count:# try_decode_messages:Bool = raw.Transactions; raw.sendMessage body:bytes = Ok; @@ -278,9 +279,13 @@ guessAccountRevision initial_account_state:InitialAccountState workchain_id:int3 guessAccount public_key:string rwallet_init_public_key:string = AccountRevisionList; getAccountState account_address:accountAddress = FullAccountState; +getAccountStateByTransaction account_address:accountAddress transaction_id:internal.transactionId = FullAccountState; +getShardAccountCell account_address:accountAddress = tvm.Cell; +getShardAccountCellByTransaction account_address:accountAddress transaction_id:internal.transactionId = tvm.Cell; createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action initial_account_state:InitialAccountState = query.Info; -getConfigParam mode:# id:ton.blockIdExt param:# = ConfigInfo; +getConfigParam mode:# param:# = ConfigInfo; +getConfigAll mode:# = ConfigInfo; msg.decrypt input_key:InputKey data:msg.dataEncryptedArray = msg.DataDecryptedArray; msg.decryptWithProof proof:bytes data:msg.dataEncrypted = msg.Data; @@ -292,6 +297,7 @@ query.estimateFees id:int53 ignore_chksig:Bool = query.Fees; query.getInfo id:int53 = query.Info; smc.load account_address:accountAddress = smc.Info; +smc.loadByTransaction account_address:accountAddress transaction_id:internal.transactionId = smc.Info; smc.forget id:int53 = Ok; smc.getCode id:int53 = tvm.Cell; smc.getData id:int53 = tvm.Cell; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 075c5a3a..a9570f6a 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/tonlib/CMakeLists.txt b/tonlib/CMakeLists.txt index f27e00f2..69b5318a 100644 --- a/tonlib/CMakeLists.txt +++ b/tonlib/CMakeLists.txt @@ -60,7 +60,7 @@ target_include_directories(tonlib PUBLIC $/.. $ ) -target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope) +target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope emulator_static) target_link_libraries(tonlib PUBLIC tdutils tl_tonlib_api) if (TONLIB_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android @@ -136,7 +136,7 @@ if (WIN32) set(WINGETOPT_TARGET wingetopt) endif() install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block smc-envelope ${WINGETOPT_TARGET} - tdutils tl_tonlib_api tonlib lite-client-common tddb_utils Tonlib EXPORT Tonlib + tdutils tl_tonlib_api tonlib lite-client-common tddb_utils emulator_static Tonlib EXPORT Tonlib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index bdfea9f5..c5e47fca 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -36,6 +36,8 @@ #include "smc-envelope/PaymentChannel.h" #include "smc-envelope/SmartContractCode.h" +#include "emulator/transaction-emulator.h" + #include "auto/tl/tonlib_api.hpp" #include "block/block-auto.h" #include "block/check-proof.h" @@ -74,6 +76,14 @@ struct GetAccountState { using ReturnType = td::unique_ptr; }; +struct GetAccountStateByTransaction { + block::StdAddress address; + std::int64_t lt; + td::Bits256 hash; + //td::optional public_key; + using ReturnType = td::unique_ptr; +}; + struct RemoteRunSmcMethod { block::StdAddress address; td::optional block_id; @@ -416,6 +426,19 @@ class AccountState { get_wallet_revision()); } + td::Result> to_shardAccountCell() const { + auto account_root = raw_.info.root; + if (account_root.is_null()) { + block::gen::Account().cell_pack_account_none(account_root); + } + auto cell = vm::CellBuilder().store_ref(account_root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).finalize(); + return tonlib_api::make_object(to_bytes(cell)); + } + + td::Result> to_shardAccountCellSlice() const { + return vm::CellBuilder().store_ref(raw_.info.root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).as_cellslice_ref(); + } + //NB: Order is important! Used during guessAccountRevision enum WalletType { Empty, @@ -940,7 +963,7 @@ class Query { } while (list.not_null()); return i; } -}; // namespace tonlib +}; td::Result to_balance_or_throw(td::Ref balance_ref) { vm::CellSlice balance_slice = *balance_ref; @@ -1633,6 +1656,305 @@ class GetShardBlockProof : public td::actor::Actor { std::vector> links_; }; +auto to_lite_api(const tonlib_api::ton_blockIdExt& blk) -> td::Result>; +auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) -> tonlib_api_ptr; + +class RunEmulator : public td::actor::Actor { + public: + RunEmulator(ExtClientRef ext_client_ref, int_api::GetAccountStateByTransaction request, + td::actor::ActorShared<> parent, td::Promise>&& promise) + : request_(std::move(request)), parent_(std::move(parent)), promise_(std::move(promise)) { + client_.set_client(ext_client_ref); + } + + private: + struct FullBlockId { + ton::BlockIdExt id; + ton::BlockIdExt mc; + ton::BlockIdExt prev; + ton::Bits256 rand_seed; + }; + + ExtClient client_; + int_api::GetAccountStateByTransaction request_; + td::actor::ActorShared<> parent_; + td::Promise> promise_; + + std::map> actors_; + td::int64 actor_id_{1}; + + FullBlockId block_id_; + td::Ref mc_state_root_; // ^ShardStateUnsplit + td::unique_ptr account_state_; + std::vector> transactions_; // std::vector<^Transaction> + + size_t count_{0}; + size_t count_transactions_{0}; + bool incomplete_{true}; + bool stopped_{false}; + + void get_block_id(td::Promise&& promise) { + auto shard_id = ton::shard_prefix(request_.address.addr, 60); + auto query = ton::lite_api::liteServer_lookupBlock(0b111111010, ton::create_tl_lite_block_id_simple({request_.address.workchain, shard_id, 0}), request_.lt, 0); + client_.send_query(std::move(query), promise.wrap([self = this, shard_id](td::Result> header_r) -> td::Result { + + TRY_RESULT(header, std::move(header_r)); + ton::BlockIdExt block_id = ton::create_block_id(header->id_); + TRY_RESULT(root, vm::std_boc_deserialize(std::move(header->header_proof_))); + + try { + auto virt_root = vm::MerkleProof::virtualize(root, 1); + if (virt_root.is_null()) { + return td::Status::Error("block header proof is not a valid Merkle proof"); + } + + ton::RootHash vhash{virt_root->get_hash().bits()}; + if (ton::RootHash{virt_root->get_hash().bits()} != block_id.root_hash) { + return td::Status::Error("block header has incorrect root hash"); + } + + std::vector prev_blocks; + ton::BlockIdExt mc_block_id; + bool after_split; + td::Status status = block::unpack_block_prev_blk_ext(virt_root, block_id, prev_blocks, mc_block_id, after_split); + if (status.is_error()) { + return status.move_as_error(); + } + + ton::BlockIdExt prev_block; + if (prev_blocks.size() == 1 || ton::shard_is_ancestor(prev_blocks[0].id.shard, shard_id)) { + prev_block = std::move(prev_blocks[0]); + } else { + prev_block = std::move(prev_blocks[1]); + } + + block::gen::Block::Record block; + block::gen::BlockExtra::Record extra; + if (!tlb::unpack_cell(virt_root, block) || !tlb::unpack_cell(block.extra, extra)) { + return td::Status::Error("cannot unpack block header"); + } + + return FullBlockId{std::move(block_id), std::move(mc_block_id), std::move(prev_block), std::move(extra.rand_seed)}; + } catch (vm::VmError& err) { + return err.as_status("error processing header"); + } catch (vm::VmVirtError& err) { + return err.as_status("error processing header"); + } + })); + } + + void get_mc_state_root(td::Promise>&& promise) { + TRY_RESULT_PROMISE(promise, lite_block, to_lite_api(*to_tonlib_api(block_id_.mc))); + auto block = ton::create_block_id(lite_block); + client_.send_query(ton::lite_api::liteServer_getConfigAll(0b11'11111111, std::move(lite_block)), promise.wrap([self = this, block](auto r_config) -> td::Result> { + + TRY_RESULT(state, block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), r_config->config_proof_.as_slice())); + + return std::move(state); + })); + } + + void get_account_state(td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "GetAccountState", client_.get_client(), request_.address, block_id_.prev, + actor_shared(this, actor_id), + promise.wrap([address = request_.address](auto&& state) { + return td::make_unique(std::move(address), std::move(state), 0); + })); + } + + td::Status get_transactions(std::int64_t lt) { + TRY_RESULT(lite_block, to_lite_api(*to_tonlib_api(block_id_.id))); + auto after = ton::lite_api::make_object(request_.address.addr, lt); + auto query = ton::lite_api::liteServer_listBlockTransactions(std::move(lite_block), 0b10100111, 256, std::move(after), false, false); + + client_.send_query(std::move(query), [self = this](lite_api_ptr&& bTxes) { + if (!bTxes) { + self->check(td::Status::Error("liteServer.blockTransactions is null")); + return; + } + + std::int64_t last_lt = 0; + for (auto& id : bTxes->ids_) { + last_lt = id->lt_; + if (id->account_ != self->request_.address.addr) { + continue; + } + + if (id->lt_ == self->request_.lt && id->hash_ == self->request_.hash) { + self->incomplete_ = false; + } + + self->transactions_.push_back({}); + self->get_transaction(id->lt_, id->hash_, [self, i = self->transactions_.size() - 1](auto transaction) { self->set_transaction(i, std::move(transaction)); }); + + if (!self->incomplete_) { + return; + } + } + + if (bTxes->incomplete_) { + self->check(self->get_transactions(last_lt)); + } + }); + return td::Status::OK(); + } + + void get_transaction(std::int64_t lt, td::Bits256 hash, td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "GetTransactionHistory", client_.get_client(), request_.address, lt, hash, 1, actor_shared(this, actor_id), + promise.wrap([](auto&& transactions) mutable { + return std::move(transactions.transactions.front().transaction); + })); + } + + void start_up() override { + if (stopped_) { + return; + } + get_block_id([self = this](td::Result&& block_id) { self->set_block_id(std::move(block_id)); }); + } + + void set_block_id(td::Result&& block_id) { + if (block_id.is_error()) { + check(block_id.move_as_error()); + } else { + block_id_ = block_id.move_as_ok(); + + get_mc_state_root([self = this](td::Result>&& mc_state_root) { self->set_mc_state_root(std::move(mc_state_root)); }); + get_account_state([self = this](td::Result>&& state) { self->set_account_state(std::move(state)); }); + check(get_transactions(0)); + + inc(); + } + } + + void set_mc_state_root(td::Result>&& mc_state_root) { + if (mc_state_root.is_error()) { + check(mc_state_root.move_as_error()); + } else { + mc_state_root_ = mc_state_root.move_as_ok(); + inc(); + } + } + + void set_account_state(td::Result>&& account_state) { + if (account_state.is_error()) { + check(account_state.move_as_error()); + } else { + account_state_ = account_state.move_as_ok(); + inc(); + } + } + + void set_transaction(size_t i, td::Result>&& transaction) { + if (transaction.is_error()) { + check(transaction.move_as_error()); + } else { + transactions_[i] = transaction.move_as_ok(); + inc_transactions(); + } + } + + void inc_transactions() { + if (stopped_ || ++count_transactions_ != transactions_.size() || incomplete_) { + return; + } + inc(); + } + + void inc() { + if (stopped_ || ++count_ != 4) { // 4 -- block_id + mc_state_root + account_state + transactions + return; + } + + auto r_config = block::Config::extract_from_state(mc_state_root_, 0b11'11111111); + if (r_config.is_error()) { + check(r_config.move_as_error()); + return; + } + std::unique_ptr config = r_config.move_as_ok(); + + block::gen::ShardStateUnsplit::Record shard_state; + if (!tlb::unpack_cell(mc_state_root_, shard_state)) { + check(td::Status::Error("Failed to unpack masterchain state")); + return; + } + vm::Dictionary libraries(shard_state.r1.libraries->prefetch_ref(), 256); + + auto r_shard_account = account_state_->to_shardAccountCellSlice(); + if (r_shard_account.is_error()) { + check(r_shard_account.move_as_error()); + return; + } + td::Ref shard_account = r_shard_account.move_as_ok(); + + const block::StdAddress& address = account_state_->get_address(); + ton::UnixTime now = account_state_->get_sync_time(); + bool is_special = address.workchain == ton::masterchainId && config->is_special_smartcontract(address.addr); + block::Account account(address.workchain, address.addr.bits()); + if (!account.unpack(std::move(shard_account), td::Ref(), now, is_special)) { + check(td::Status::Error("Can't unpack shard account")); + return; + } + + emulator::TransactionEmulator trans_emulator(std::move(*config)); + trans_emulator.set_libs(std::move(libraries)); + trans_emulator.set_rand_seed(block_id_.rand_seed); + td::Result emulation_result = trans_emulator.emulate_transactions_chain(std::move(account), std::move(transactions_)); + + if (emulation_result.is_error()) { + promise_.set_error(emulation_result.move_as_error()); + } else { + account = std::move(emulation_result.move_as_ok().account); + RawAccountState raw = std::move(account_state_->raw()); + raw.block_id = block_id_.id; + raw.balance = account.get_balance().grams->to_long(); + raw.storage_last_paid = std::move(account.last_paid); + raw.storage_stat = std::move(account.storage_stat); + raw.code = std::move(account.code); + raw.data = std::move(account.data); + raw.state = std::move(account.total_state); + raw.info.last_trans_lt = account.last_trans_lt_; + raw.info.last_trans_hash = account.last_trans_hash_; + raw.info.gen_utime = account.now_; + + if (account.status == block::Account::acc_frozen) { + raw.frozen_hash = (char*)account.state_hash.data(); + } + + promise_.set_value(td::make_unique(address, std::move(raw), 0)); + } + stopped_ = true; + try_stop(); + } + + void check(td::Status status) { + if (status.is_error()) { + promise_.set_error(std::move(status)); + stopped_ = true; + try_stop(); + } + } + + void try_stop() { + if (stopped_ && actors_.empty()) { + stop(); + } + } + + void hangup_shared() override { + actors_.erase(get_link_token()); + try_stop(); + } + + void hangup() override { + check(TonlibError::Cancelled()); + } +}; + TonlibClient::TonlibClient(td::unique_ptr callback) : callback_(std::move(callback)) { } TonlibClient::~TonlibClient() = default; @@ -2765,6 +3087,27 @@ td::Status TonlibClient::do_request(tonlib_api::raw_getAccountState& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(tonlib_api::raw_getAccountStateByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_raw_fullAccountState(); })); + return td::Status::OK(); +} + td::Result from_tonlib(tonlib_api::inputKeyRegular& input_key) { if (!input_key.key_) { return TonlibError::EmptyField("key"); @@ -2877,6 +3220,59 @@ td::Status TonlibClient::do_request(const tonlib_api::getAccountState& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::getAccountStateByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_fullAccountState(); })); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::getShardAccountCell& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + make_request(int_api::GetAccountState{std::move(account_address), query_context_.block_id.copy(), {}}, + promise.wrap([](auto&& res) { return res->to_shardAccountCell(); })); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::getShardAccountCellByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.wrap([](auto&& res) { return res->to_shardAccountCell(); })); + return td::Status::OK(); +} + td::Result to_dns_entry_data(tonlib_api::dns_EntryData& entry_data) { using R = td::Result; return downcast_call2( @@ -3502,7 +3898,7 @@ class GenericCreateSendGrams : public TonlibQueryActor { return with_wallet(*source_->get_wallet()); } -}; // namespace tonlib +}; td::int64 TonlibClient::register_query(td::unique_ptr query) { auto query_id = ++next_query_id_; @@ -3713,6 +4109,27 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_load& request, return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::smc_loadByTransaction& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + if (!request.transaction_id_) { + return TonlibError::EmptyField("transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + auto lt = request.transaction_id_->lt_; + auto hash_str = request.transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + make_request(int_api::GetAccountStateByTransaction{account_address, lt, hash}, + promise.send_closure(actor_id(this), &TonlibClient::finish_load_smc)); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(const tonlib_api::smc_forget& request, td::Promise>&& promise) { auto it = smcs_.find(request.id_); @@ -4555,6 +4972,17 @@ td::Status TonlibClient::do_request(int_api::GetAccountState request, return td::Status::OK(); } +td::Status TonlibClient::do_request(int_api::GetAccountStateByTransaction request, + td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "RunEmulator", client_.get_client(), request, actor_shared(this, actor_id), + promise.wrap([](auto&& state) { + return std::move(state); + })); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(int_api::RemoteRunSmcMethod request, td::Promise&& promise) { auto actor_id = actor_id_++; @@ -4641,29 +5069,72 @@ td::Result to_block_id(const tonlib_api::ton_blockIdExt& blk) { return ton::BlockIdExt(blk.workchain_, blk.shard_, blk.seqno_, root_hash, file_hash); } -td::Status TonlibClient::do_request(const tonlib_api::getConfigParam& request, - td::Promise>&& promise) { - TRY_RESULT(lite_block, to_lite_api(*request.id_)) - auto block = create_block_id(std::move(lite_block)); - auto param = request.param_; +void TonlibClient::get_config_param(int32_t param, int32_t mode, ton::BlockIdExt block, td::Promise>&& promise) { std::vector params = { param }; - - client_.send_query(ton::lite_api::liteServer_getConfigParams(0, std::move(lite_block), std::move(params)), - promise.wrap([block, param](auto r_config) { + client_.send_query(ton::lite_api::liteServer_getConfigParams(mode, ton::create_tl_lite_block_id(block), std::move(params)), + promise.wrap([param, block](auto r_config) -> td::Result> { auto state = block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), r_config->config_proof_.as_slice()); if (state.is_error()) { - LOG(ERROR) << "block::check_extract_state_proof failed: " << state.error(); + return state.move_as_error_prefix(TonlibError::ValidateConfig()); } auto config = block::Config::extract_from_state(std::move(state.move_as_ok()), 0); if (config.is_error()) { - LOG(ERROR) << "block::Config::extract_from_state failed: " << config.error(); + return config.move_as_error_prefix(TonlibError::ValidateConfig()); } tonlib_api::configInfo config_result; config_result.config_ = tonlib_api::make_object(to_bytes(config.move_as_ok()->get_config_param(param))); return tonlib_api::make_object(std::move(config_result)); })); +} +td::Status TonlibClient::do_request(const tonlib_api::getConfigParam& request, + td::Promise>&& promise) { + if (query_context_.block_id) { + get_config_param(request.param_, request.mode_, query_context_.block_id.value(), std::move(promise)); + } else { + client_.with_last_block([this, promise = std::move(promise), param = request.param_, mode = request.mode_](td::Result r_last_block) mutable { + if (r_last_block.is_error()) { + promise.set_error(r_last_block.move_as_error_prefix(TonlibError::Internal("get last block failed "))); + } else { + this->get_config_param(param, mode, r_last_block.move_as_ok().last_block_id, std::move(promise)); + } + }); + } + return td::Status::OK(); +} + +void TonlibClient::get_config_all(int32_t mode, ton::BlockIdExt block, td::Promise>&& promise) { + client_.send_query(ton::lite_api::liteServer_getConfigAll(mode, ton::create_tl_lite_block_id(block)), + promise.wrap([block](auto r_config) -> td::Result> { + auto state = block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), + r_config->config_proof_.as_slice()); + if (state.is_error()) { + return state.move_as_error_prefix(TonlibError::ValidateConfig()); + } + auto config = block::Config::extract_from_state(std::move(state.move_as_ok()), 0); + if (config.is_error()) { + return config.move_as_error_prefix(TonlibError::ValidateConfig()); + } + tonlib_api::configInfo config_result; + config_result.config_ = tonlib_api::make_object(to_bytes(config.move_as_ok()->get_root_cell())); + return tonlib_api::make_object(std::move(config_result)); + })); +} + +td::Status TonlibClient::do_request(const tonlib_api::getConfigAll& request, + td::Promise>&& promise) { + if (query_context_.block_id) { + get_config_all(request.mode_, query_context_.block_id.value(), std::move(promise)); + } else { + client_.with_last_block([this, promise = std::move(promise), mode = request.mode_](td::Result r_last_block) mutable { + if (r_last_block.is_error()) { + promise.set_error(r_last_block.move_as_error_prefix(TonlibError::Internal("get last block failed "))); + } else { + this->get_config_all(mode, r_last_block.move_as_ok().last_block_id, std::move(promise)); + } + }); + } return td::Status::OK(); } diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index 0bb7aadc..dbbb62a2 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -39,6 +39,7 @@ namespace tonlib { namespace int_api { struct GetAccountState; +struct GetAccountStateByTransaction; struct GetPrivateKey; struct GetDnsResolver; struct SendMessage; @@ -51,6 +52,7 @@ inline std::string to_string(const int_api::SendMessage&) { } // namespace int_api class AccountState; class Query; +class RunEmulator; td::Result> to_tonlib_api( const ton::ManualDns::EntryData& entry_data); @@ -234,6 +236,8 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(tonlib_api::raw_getAccountState& request, td::Promise>&& promise); + td::Status do_request(tonlib_api::raw_getAccountStateByTransaction& request, + td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactions& request, td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactionsV2& request, @@ -241,6 +245,12 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::getAccountState& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::getAccountStateByTransaction& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getShardAccountCell& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getShardAccountCellByTransaction& request, + td::Promise>&& promise); td::Status do_request(tonlib_api::guessAccountRevision& request, td::Promise>&& promise); td::Status do_request(tonlib_api::guessAccount& request, @@ -305,6 +315,7 @@ class TonlibClient : public td::actor::Actor { td::Result> get_smc_info(td::int64 id); void finish_load_smc(td::unique_ptr query, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_load& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_loadByTransaction& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_forget& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::smc_getCode& request, td::Promise>&& promise); @@ -344,6 +355,7 @@ class TonlibClient : public td::actor::Actor { td::Promise>&& promise); td::Status do_request(int_api::GetAccountState request, td::Promise>&&); + td::Status do_request(int_api::GetAccountStateByTransaction request, td::Promise>&&); td::Status do_request(int_api::GetPrivateKey request, td::Promise&&); td::Status do_request(int_api::GetDnsResolver request, td::Promise&&); td::Status do_request(int_api::RemoteRunSmcMethod request, @@ -370,8 +382,14 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::blocks_getShardBlockProof& request, td::Promise>&& promise); + void get_config_param(int32_t param, int32_t mode, ton::BlockIdExt block, + td::Promise>&& promise); td::Status do_request(const tonlib_api::getConfigParam& request, td::Promise>&& promise); + void get_config_all(int32_t mode, ton::BlockIdExt block, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::getConfigAll& request, + td::Promise>&& promise); void proxy_request(td::int64 query_id, std::string data); diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index db7497b0..ba476599 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -389,6 +389,7 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " " with specified parameters\n"; td::TerminalIO::out() << "getstate \tget state of wallet with requested key\n"; + td::TerminalIO::out() << "getstatebytransaction \tget state of wallet with requested key after transaction with local time and hash (base64url)\n"; td::TerminalIO::out() << "guessrevision \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "guessaccount \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "getaddress \tget address of wallet with requested key\n"; @@ -489,6 +490,8 @@ class TonlibCli : public td::actor::Actor { transfer(parser, cmd, std::move(cmd_promise)); } else if (cmd == "getstate") { get_state(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "getstatebytransaction") { + get_state_by_transaction(parser, std::move(cmd_promise)); } else if (cmd == "getaddress") { get_address(parser.read_word(), std::move(cmd_promise)); } else if (cmd == "importkeypem") { @@ -2067,6 +2070,30 @@ class TonlibCli : public td::actor::Actor { })); } + void get_state_by_transaction(td::ConstParser& parser, td::Promise promise) { + TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false)); + TRY_RESULT_PROMISE(promise, lt, td::to_integer_safe(parser.read_word())); + TRY_RESULT_PROMISE(promise, hash, td::base64url_decode(parser.read_word())); + + auto address_str = address.address->account_address_; + auto transaction_id = std::make_unique(lt, std::move(hash)); + send_query(make_object( + ton::move_tl_object_as(std::move(address.address)), + ton::move_tl_object_as(std::move(transaction_id))), + promise.wrap([address_str](auto&& state) { + td::TerminalIO::out() << "Address: " << address_str << "\n"; + td::TerminalIO::out() << "Balance: " + << Grams{td::narrow_cast(state->balance_ * (state->balance_ > 0))} + << "\n"; + td::TerminalIO::out() << "Sync utime: " << state->sync_utime_ << "\n"; + td::TerminalIO::out() << "transaction.LT: " << state->last_transaction_id_->lt_ << "\n"; + td::TerminalIO::out() << "transaction.Hash: " << td::base64_encode(state->last_transaction_id_->hash_) + << "\n"; + td::TerminalIO::out() << to_string(state->account_state_); + return td::Unit(); + })); + } + void get_address(td::Slice key, td::Promise promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); promise.set_value(td::Unit()); diff --git a/validator/impl/collator-impl.h b/validator/impl/collator-impl.h index 8fd8dc0c..dfe844a3 100644 --- a/validator/impl/collator-impl.h +++ b/validator/impl/collator-impl.h @@ -104,19 +104,7 @@ class Collator final : public td::actor::Actor { return 2; } - static td::Result> - impl_fetch_config_params(std::unique_ptr config, - Ref* old_mparams, - std::vector* storage_prices, - block::StoragePhaseConfig* storage_phase_cfg, - td::BitArray<256>* rand_seed, - block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, - td::RefInt256* masterchain_create_fee, - td::RefInt256* basechain_create_fee, - WorkchainId wc, UnixTime now); - - static td::Result> + static td::Result> impl_create_ordinary_transaction(Ref msg_root, block::Account* acc, UnixTime utime, LogicalTime lt, @@ -285,7 +273,7 @@ class Collator final : public td::actor::Actor { td::Result register_shard_signatures_cell(Ref shard_blk_signatures); td::Result register_shard_signatures(td::Slice shard_blk_signatures_boc); void register_new_msg(block::NewOutMsg msg); - void register_new_msgs(block::Transaction& trans); + void register_new_msgs(block::transaction::Transaction& trans); bool process_new_messages(bool enqueue_only = false); int process_one_new_message(block::NewOutMsg msg, bool enqueue_only = false, Ref* is_special = nullptr); bool process_inbound_internal_messages(); diff --git a/validator/impl/collator.cpp b/validator/impl/collator.cpp index fb18d8be..8eab9103 100644 --- a/validator/impl/collator.cpp +++ b/validator/impl/collator.cpp @@ -1561,90 +1561,19 @@ bool Collator::init_lt() { } bool Collator::fetch_config_params() { - auto res = impl_fetch_config_params(std::move(config_), &old_mparams_, &storage_prices_, &storage_phase_cfg_, - &rand_seed_, &compute_phase_cfg_, &action_phase_cfg_, &masterchain_create_fee_, - &basechain_create_fee_, workchain(), now_); + auto res = block::FetchConfigParams::fetch_config_params(*config_, + &old_mparams_, &storage_prices_, &storage_phase_cfg_, + &rand_seed_, &compute_phase_cfg_, &action_phase_cfg_, + &masterchain_create_fee_, &basechain_create_fee_, + workchain(), now_ + ); if (res.is_error()) { return fatal_error(res.move_as_error()); } - config_ = res.move_as_ok(); + compute_phase_cfg_.libraries = std::make_unique(config_->get_libraries_root(), 256); return true; } -td::Result> Collator::impl_fetch_config_params( - std::unique_ptr config, Ref* old_mparams, - std::vector* storage_prices, block::StoragePhaseConfig* storage_phase_cfg, - td::BitArray<256>* rand_seed, block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, td::RefInt256* masterchain_create_fee, - td::RefInt256* basechain_create_fee, WorkchainId wc, UnixTime now) { - *old_mparams = config->get_config_param(9); - { - auto res = config->get_storage_prices(); - if (res.is_error()) { - return res.move_as_error(); - } - *storage_prices = res.move_as_ok(); - } - { - // generate rand seed - prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); - LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); - } - TRY_RESULT(size_limits, config->get_size_limits_config()); - { - // compute compute_phase_cfg / storage_phase_cfg - auto cell = config->get_config_param(wc == ton::masterchainId ? 20 : 21); - if (cell.is_null()) { - return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); - } - if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, - storage_phase_cfg->delete_due_limit)) { - return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); - } - compute_phase_cfg->block_rand_seed = *rand_seed; - compute_phase_cfg->libraries = std::make_unique(config->get_libraries_root(), 256); - compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; - compute_phase_cfg->global_config = config->get_root_cell(); - compute_phase_cfg->suspended_addresses = config->get_suspended_addresses(now); - } - { - // compute action_phase_cfg - block::gen::MsgForwardPrices::Record rec; - auto cell = config->get_config_param(24); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_mc = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - cell = config->get_config_param(25); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_std = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - action_phase_cfg->workchains = &config->get_workchain_list(); - action_phase_cfg->bounce_msg_body = (config->has_capability(ton::capBounceMsgBody) ? 256 : 0); - action_phase_cfg->size_limits = size_limits; - } - { - // fetch block_grams_created - auto cell = config->get_config_param(14); - if (cell.is_null()) { - *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); - } else { - block::gen::BlockCreateFees::Record create_fees; - if (!(tlb::unpack_cell(cell, create_fees) && - block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && - block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { - return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); - } - } - } - return std::move(config); -} - bool Collator::compute_minted_amount(block::CurrencyCollection& to_mint) { if (!is_masterchain()) { return to_mint.set_zero(); @@ -2167,8 +2096,8 @@ bool Collator::create_ticktock_transaction(const ton::StdSmcAddress& smc_addr, t << "last transaction time in the state of account " << workchain() << ":" << smc_addr.to_hex() << " is too large")); } - std::unique_ptr trans = std::make_unique( - *acc, mask == 2 ? block::Transaction::tr_tick : block::Transaction::tr_tock, req_start_lt, now_); + std::unique_ptr trans = std::make_unique( + *acc, mask == 2 ? block::transaction::Transaction::tr_tick : block::transaction::Transaction::tr_tock, req_start_lt, now_); if (!trans->prepare_storage_phase(storage_phase_cfg_, true)) { return fatal_error(td::Status::Error( -666, std::string{"cannot create storage phase of a new transaction for smart contract "} + smc_addr.to_hex())); @@ -2258,7 +2187,7 @@ Ref Collator::create_ordinary_transaction(Ref msg_root) { fatal_error(std::move(error)); return {}; } - std::unique_ptr trans = res.move_as_ok(); + std::unique_ptr trans = res.move_as_ok(); if (!trans->update_limits(*block_limit_status_)) { fatal_error("cannot update block limit status to include the new transaction"); @@ -2277,10 +2206,13 @@ Ref Collator::create_ordinary_transaction(Ref msg_root) { // If td::status::error_code == 669 - Fatal Error block can not be produced // if td::status::error_code == 701 - Transaction can not be included into block, but it's ok (external or too early internal) -td::Result> Collator::impl_create_ordinary_transaction( - Ref msg_root, block::Account* acc, UnixTime utime, LogicalTime lt, - block::StoragePhaseConfig* storage_phase_cfg, block::ComputePhaseConfig* compute_phase_cfg, - block::ActionPhaseConfig* action_phase_cfg, bool external, LogicalTime after_lt) { +td::Result> Collator::impl_create_ordinary_transaction(Ref msg_root, + block::Account* acc, + UnixTime utime, LogicalTime lt, + block::StoragePhaseConfig* storage_phase_cfg, + block::ComputePhaseConfig* compute_phase_cfg, + block::ActionPhaseConfig* action_phase_cfg, + bool external, LogicalTime after_lt) { if (acc->last_trans_end_lt_ >= lt && acc->transactions.empty()) { return td::Status::Error(-669, PSTRING() << "last transaction time in the state of account " << acc->workchain << ":" << acc->addr.to_hex() << " is too large"); @@ -2291,8 +2223,8 @@ td::Result> Collator::impl_create_ordinary_t trans_min_lt = std::max(trans_min_lt, after_lt); } - std::unique_ptr trans = - std::make_unique(*acc, block::Transaction::tr_ord, trans_min_lt + 1, utime, msg_root); + std::unique_ptr trans = + std::make_unique(*acc, block::transaction::Transaction::tr_ord, trans_min_lt + 1, utime, msg_root); bool ihr_delivered = false; // FIXME if (!trans->unpack_input_msg(ihr_delivered, action_phase_cfg)) { if (external) { @@ -3041,7 +2973,7 @@ void Collator::register_new_msg(block::NewOutMsg new_msg) { new_msgs.push(std::move(new_msg)); } -void Collator::register_new_msgs(block::Transaction& trans) { +void Collator::register_new_msgs(block::transaction::Transaction& trans) { CHECK(trans.root.not_null()); for (unsigned i = 0; i < trans.out_msgs.size(); i++) { register_new_msg(trans.extract_out_msg_ext(i)); diff --git a/validator/impl/external-message.cpp b/validator/impl/external-message.cpp index 5d3028db..9383e734 100644 --- a/validator/impl/external-message.cpp +++ b/validator/impl/external-message.cpp @@ -143,16 +143,17 @@ td::Status ExtMessageQ::run_message_on_account(ton::WorkchainId wc, block::ActionPhaseConfig action_phase_cfg_; td::RefInt256 masterchain_create_fee, basechain_create_fee; - auto fetch_res = Collator::impl_fetch_config_params(std::move(config), &old_mparams, - &storage_prices_, &storage_phase_cfg_, - &rand_seed_, &compute_phase_cfg_, - &action_phase_cfg_, &masterchain_create_fee, - &basechain_create_fee, wc, utime); + auto fetch_res = block::FetchConfigParams::fetch_config_params(*config, &old_mparams, + &storage_prices_, &storage_phase_cfg_, + &rand_seed_, &compute_phase_cfg_, + &action_phase_cfg_, &masterchain_create_fee, + &basechain_create_fee, wc, utime); if(fetch_res.is_error()) { auto error = fetch_res.move_as_error(); LOG(DEBUG) << "Cannot fetch config params: " << error.message(); return error.move_as_error_prefix("Cannot fetch config params: "); } + compute_phase_cfg_.libraries = std::make_unique(config->get_libraries_root(), 256); compute_phase_cfg_.with_vm_log = true; auto res = Collator::impl_create_ordinary_transaction(msg_root, acc, utime, lt, @@ -164,7 +165,7 @@ td::Status ExtMessageQ::run_message_on_account(ton::WorkchainId wc, LOG(DEBUG) << "Cannot run message on account: " << error.message(); return error.move_as_error_prefix("Cannot run message on account: "); } - std::unique_ptr trans = res.move_as_ok(); + std::unique_ptr trans = res.move_as_ok(); auto trans_root = trans->commit(*acc); if (trans_root.is_null()) { diff --git a/validator/impl/validate-query.cpp b/validator/impl/validate-query.cpp index 4d751f33..50a3e591 100644 --- a/validator/impl/validate-query.cpp +++ b/validator/impl/validate-query.cpp @@ -4359,7 +4359,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT << account.total_state->get_hash().to_hex()); } // some type-specific checks - int trans_type = block::Transaction::tr_none; + int trans_type = block::transaction::Transaction::tr_none; switch (tag) { case block::gen::TransactionDescr::trans_ord: { if (!block_limit_status_->fits(block::ParamLimits::cl_medium)) { @@ -4369,7 +4369,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT << "lt_delta=" << block_limit_status_->cur_lt - block_limits_->start_lt << "(limit=" << block_limits_->lt_delta.hard() << ")"); } - trans_type = block::Transaction::tr_ord; + trans_type = block::transaction::Transaction::tr_ord; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "ordinary transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4378,7 +4378,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_storage: { - trans_type = block::Transaction::tr_storage; + trans_type = block::transaction::Transaction::tr_storage; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "storage transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4394,7 +4394,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT } case block::gen::TransactionDescr::trans_tick_tock: { bool is_tock = (td_cs.prefetch_ulong(4) & 1); - trans_type = is_tock ? block::Transaction::tr_tock : block::Transaction::tr_tick; + trans_type = is_tock ? block::transaction::Transaction::tr_tock : block::transaction::Transaction::tr_tick; if (in_msg_root.not_null()) { return reject_query(PSTRING() << (is_tock ? "tock" : "tick") << " transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4402,7 +4402,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_merge_prepare: { - trans_type = block::Transaction::tr_merge_prepare; + trans_type = block::transaction::Transaction::tr_merge_prepare; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "merge prepare transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4417,7 +4417,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_merge_install: { - trans_type = block::Transaction::tr_merge_install; + trans_type = block::transaction::Transaction::tr_merge_install; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "merge install transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4429,7 +4429,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_split_prepare: { - trans_type = block::Transaction::tr_split_prepare; + trans_type = block::transaction::Transaction::tr_split_prepare; if (in_msg_root.not_null()) { return reject_query(PSTRING() << "split prepare transaction " << lt << " of account " << addr.to_hex() << " has an inbound message"); @@ -4444,7 +4444,7 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT break; } case block::gen::TransactionDescr::trans_split_install: { - trans_type = block::Transaction::tr_split_install; + trans_type = block::transaction::Transaction::tr_split_install; if (in_msg_root.is_null()) { return reject_query(PSTRING() << "split install transaction " << lt << " of account " << addr.to_hex() << " has no inbound message"); @@ -4459,8 +4459,8 @@ bool ValidateQuery::check_one_transaction(block::Account& account, ton::LogicalT // check transaction computation by re-doing it // similar to Collator::create_ordinary_transaction() and Collator::create_ticktock_transaction() // .... - std::unique_ptr trs = - std::make_unique(account, trans_type, lt, now_, in_msg_root); + std::unique_ptr trs = + std::make_unique(account, trans_type, lt, now_, in_msg_root); if (in_msg_root.not_null()) { if (!trs->unpack_input_msg(ihr_delivered, &action_phase_cfg_)) { // inbound external message was not accepted