1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 11:12:16 +00:00

Add account state by transaction and emulator (extended) (#592)

* account_state_by_transaction

* Correct time calculation

* Bug fixes

* Refactor

* namespace block::transaction

* smc.forget

* RunEmulator: remove wallet_id

* Refactor & fixes

* AccountStateByTransaction: use shardchain block instead of masterchain block

* transaction emulator core

* refactor

* tx emulator major functionality

* small format changes

* readme

* clean

* return json, add support for init messages

* tx emulator readme

* refactor getConfigParam and getConfigAll

* add shardchain_libs_boc parameter

* option to change verbosity level of transaction emulator

* fix deserializing ShardAccount with account_none

* add mode needSpecialSmc when unpack config

* emulator: block::Transaction -> block::transaction::Transaction

* Refactor

* emulator: Fix bug

* emulator: Support for emulator-extern

* emulator: Refactor

* Return vm log and vm exit code.

* fix build on macos, emulator_static added

* adjust documentation

* ignore_chksig for external messages

* tvm emulator, run get method

* Added more params for transaction emulator

* Added setters for optional transaction emulator params, moved libs to a new setter

* Added actions cell output to transaction emulator

* fix tonlib build

* refactoring, rand seed as hex size 64, tvm emulator send message

* tvm send message, small refactoring

* fix config decoding, rename

* improve documentation

* macos export symbols

* Added run_get_method to transaction emulator emscipten wrapper

* Fixed empty action list serialization

* Changed actions list cell to serialize as json null instead of empty string in transaction emulator

* stack as boc

* log gas remaining

* Fix prev_block_id

* fix build errors

* Refactor fetch_config_params

* fix failing unwrap of optional rand_seed

* lookup correct shard, choose prev_block based on account shard

* fix tonlib android jni build

---------

Co-authored-by: legaii <jgates.ardux@gmail.com>
Co-authored-by: ms <dungeon666master@protonmail.com>
Co-authored-by: krigga <krigga7@gmail.com>
This commit is contained in:
EmelyanenkoK 2023-02-02 10:03:45 +03:00 committed by GitHub
parent adf67aa869
commit 3b3c25b654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 2095 additions and 158 deletions

View file

@ -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)

View file

@ -274,6 +274,10 @@ add_library(ton_crypto STATIC ${TON_CRYPTO_SOURCE})
target_include_directories(ton_crypto PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
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)

View file

@ -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<Account> t_Ref_Account;
const RefTo<Account> t_Ref_AccountE{true};
bool ShardAccount::extract_account_state(Ref<vm::CellSlice> cs_ref, Ref<vm::Cell>& acc_state) {
if (cs_ref.is_null()) {

View file

@ -536,7 +536,7 @@ struct Account final : TLB_Complex {
};
extern const Account t_Account, t_AccountE;
extern const RefTo<Account> t_Ref_Account;
extern const RefTo<Account> 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);

View file

@ -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;

View file

@ -632,7 +632,6 @@ class Config {
static td::Result<std::vector<int>> unpack_param_dict(vm::Dictionary& dict);
static td::Result<std::vector<int>> unpack_param_dict(Ref<vm::Cell> dict_root);
protected:
Config(int _mode) : mode(_mode) {
config_addr.set_zero();
}

View file

@ -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<vm::Cell> _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<vm::Cell> 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<StringLoggerTail> logger;
auto vm_log = vm::VmLog();
if (cfg.with_vm_log) {
logger = std::make_unique<StringLoggerTail>();
size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256;
logger = std::make_unique<StringLoggerTail>(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<vm::CellSlice>& 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<vm::CellStorageStat> 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<LtCellRef>& list) {
list.emplace_back(start_lt + i + 1, std::move(out_msgs[i]));
}
}
} // namespace transaction
void Account::push_transaction(Ref<vm::Cell> 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<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* 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

View file

@ -35,7 +35,10 @@ using td::Ref;
using LtCellRef = std::pair<ton::LogicalTime, Ref<vm::Cell>>;
struct Account;
namespace transaction {
struct Transaction;
} // namespace transaction
struct CollatorError {
std::string msg;
@ -106,9 +109,11 @@ struct ComputePhaseConfig {
std::unique_ptr<vm::Dictionary> libraries;
Ref<vm::Cell> 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<vm::Dictionary> 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<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* 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

View file

@ -54,7 +54,11 @@ td::Ref<vm::Stack> prepare_vm_stack(td::RefInt256 amount, td::Ref<vm::CellSlice>
td::Ref<vm::Tuple> 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<vm::Tuple> prepare_vm_c7(SmartContract::Args args) {
}
SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stack> stack, td::Ref<vm::Tuple> c7,
vm::GasLimits gas, bool ignore_chksig, td::Ref<vm::Cell> libraries) {
vm::GasLimits gas, bool ignore_chksig, td::Ref<vm::Cell> 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<vm::Stac
std::string res;
};
Logger logger;
vm::VmLog log{&logger, td::LogOptions::plain()};
if (GET_VERBOSITY_LEVEL() >= 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<vm::Stac
} catch (...) {
LOG(FATAL) << "catch unhandled exception";
}
td::ConstBitPtr mlib = vm.get_missing_library();
res.new_state = std::move(state);
res.stack = vm.get_stack_ref();
gas = vm.get_gas_limits();
res.gas_used = gas.gas_consumed();
res.accepted = gas.gas_credit == 0;
res.success = (res.accepted && (unsigned)res.code <= 1);
res.vm_log = logger.res;
if (GET_VERBOSITY_LEVEL() >= 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<vm::Stac
LOG(DEBUG) << "VM accepted: " << res.accepted;
LOG(DEBUG) << "VM success: " << res.success;
}
td::ConstBitPtr mlib = vm.get_missing_library();
if (!mlib.is_null()) {
LOG(DEBUG) << "Missing library: " << mlib.to_hex(256);
res.missing_library = mlib;
@ -219,7 +221,7 @@ SmartContract::Answer SmartContract::run_method(Args args) {
args.stack.value().write().push_smallint(args.method_id.unwrap());
auto res =
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<vm::Cell>{});
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{}, 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<vm::Cell>{});
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{}, args.vm_log_verbosity_level);
}
SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const {

View file

@ -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<vm::Cell> list);
};
@ -59,9 +60,11 @@ class SmartContract : public td::CntObject {
td::optional<td::Ref<vm::Tuple>> c7;
td::optional<td::Ref<vm::Stack>> stack;
td::optional<td::int32> now;
td::optional<td::BitArray<256>> rand_seed;
bool ignore_chksig{false};
td::uint64 amount{0};
td::uint64 balance{0};
int vm_log_verbosity_level{0};
td::optional<block::StdAddress> address;
td::optional<std::shared_ptr<const block::Config>> 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<td::int32> get_method_id() const {
if (!method_id) {

View file

@ -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;

View file

@ -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<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
Ref<Continuation> cont = Ref<OrdCont>{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};

55
emulator/CMakeLists.txt Normal file
View file

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
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()

32
emulator/README.md Normal file
View file

@ -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.

27
emulator/StringLog.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef TON_STRINGLOG_H
#define TON_STRINGLOG_H
#include "td/utils/logging.h"
#include <thread>
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

View file

@ -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 <iostream>
#include "crypto/common/bitstring.h"
struct TransactionEmulationParams {
uint32_t utime;
uint64_t lt;
td::optional<std::string> rand_seed_hex;
bool ignore_chksig;
};
td::Result<TransactionEmulationParams> 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<td::uint32>(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<td::uint64>(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<std::string> libs;
std::string address;
uint32_t unixtime;
uint64_t balance;
std::string rand_seed_hex;
int64_t gas_limit;
int method_id;
};
td::Result<GetMethodParams> 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<td::uint32>(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<td::uint64>(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<td::uint64>(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;
}
}

View file

@ -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<td::Ref<vm::Cell>> 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<std::string> cell_to_boc_b64(td::Ref<vm::Cell> 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<std::string>&& 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<block::Config> 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<emulator::TransactionEmulator *>(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<vm::CellSlice> 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<vm::CellSlice>(), 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<emulator::TransactionEmulator::EmulationExternalNotAccepted *>(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<emulator::TransactionEmulator::EmulationSuccess&>(*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<td::string> 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<emulator::TransactionEmulator *>(transaction_emulator);
emulator->set_unixtime(unixtime);
return true;
}
bool transaction_emulator_set_lt(void *transaction_emulator, uint64_t lt) {
auto emulator = static_cast<emulator::TransactionEmulator *>(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<emulator::TransactionEmulator *>(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<emulator::TransactionEmulator *>(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<emulator::TransactionEmulator *>(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<emulator::TransactionEmulator *>(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<emulator::TransactionEmulator *>(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<emulator::TvmEmulator *>(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<emulator::TvmEmulator *>(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<block::Config>(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<const block::Config>(global_config));
return true;
}
bool tvm_emulator_set_gas_limit(void *tvm_emulator, int64_t gas_limit) {
auto emulator = static_cast<emulator::TvmEmulator *>(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<vm::Stack> stack;
if (!vm::Stack::deserialize_to(stack_cs, stack)) {
ERROR_RESPONSE(PSTRING() << "Couldn't deserialize stack");
}
auto emulator = static_cast<emulator::TvmEmulator *>(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<emulator::TvmEmulator *>(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<emulator::TvmEmulator *>(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<emulator::TvmEmulator *>(tvm_emulator);
}

216
emulator/emulator-extern.h Normal file
View file

@ -0,0 +1,216 @@
#pragma once
#include <stdint.h>
#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

View file

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

View file

@ -0,0 +1,254 @@
#include <string>
#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<std::unique_ptr<TransactionEmulator::EmulationResult>> TransactionEmulator::emulate_transaction(
block::Account&& account, td::Ref<vm::Cell> msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type) {
td::Ref<vm::Cell> old_mparams;
std::vector<block::StoragePrices> 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<vm::Dictionary>(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<block::transaction::Transaction> 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<TransactionEmulator::EmulationExternalNotAccepted>(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<TransactionEmulator::EmulationSuccess>(std::move(trans_root), std::move(account), std::move(trans->compute_phase->vm_log), std::move(trans->compute_phase->actions));
}
td::Result<TransactionEmulator::EmulationSuccess> TransactionEmulator::emulate_transaction(block::Account&& account, td::Ref<vm::Cell> 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<vm::Cell> 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<EmulationSuccess&>(*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::EmulationChain> TransactionEmulator::emulate_transactions_chain(block::Account&& account, std::vector<td::Ref<vm::Cell>>&& original_transactions) {
std::vector<td::Ref<vm::Cell>> 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<std::unique_ptr<block::transaction::Transaction>> TransactionEmulator::create_transaction(
td::Ref<vm::Cell> 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<block::transaction::Transaction> trans =
std::make_unique<block::transaction::Transaction>(*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<block::Config>(config);
}
void TransactionEmulator::set_libs(vm::Dictionary &&libs) {
libraries_ = std::forward<vm::Dictionary>(libs);
}
} // namespace emulator

View file

@ -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<vm::Cell> transaction;
block::Account account;
td::Ref<vm::Cell> actions;
EmulationSuccess(td::Ref<vm::Cell> transaction_, block::Account account_, std::string vm_log_, td::Ref<vm::Cell> 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<td::Ref<vm::Cell>> transactions;
block::Account account;
};
const block::Config& get_config() {
return config_;
}
td::Result<std::unique_ptr<EmulationResult>> emulate_transaction(
block::Account&& account, td::Ref<vm::Cell> msg_root, ton::UnixTime utime, ton::LogicalTime lt, int trans_type);
td::Result<EmulationSuccess> emulate_transaction(block::Account&& account, td::Ref<vm::Cell> original_trans);
td::Result<EmulationChain> emulate_transactions_chain(block::Account&& account, std::vector<td::Ref<vm::Cell>>&& 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<std::unique_ptr<block::transaction::Transaction>> create_transaction(
td::Ref<vm::Cell> 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

46
emulator/tvm-emulator.hpp Normal file
View file

@ -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<vm::Cell> code, td::Ref<vm::Cell> 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<const block::Config> 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<vm::Stack> stack) {
return smc_.run_get_method(args_.set_stack(stack).set_method_id(method_id));
}
Answer send_external_message(td::Ref<vm::Cell> message_body) {
return smc_.send_external_message(message_body, args_);
}
Answer send_internal_message(td::Ref<vm::Cell> message_body, uint64_t amount) {
return smc_.send_internal_message(message_body, args_.set_amount(amount));
}
};
}

View file

@ -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;

Binary file not shown.

View file

@ -60,7 +60,7 @@ target_include_directories(tonlib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
$<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>
)
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

View file

@ -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<AccountState>;
};
struct GetAccountStateByTransaction {
block::StdAddress address;
std::int64_t lt;
td::Bits256 hash;
//td::optional<td::Ed25519::PublicKey> public_key;
using ReturnType = td::unique_ptr<AccountState>;
};
struct RemoteRunSmcMethod {
block::StdAddress address;
td::optional<ton::BlockIdExt> block_id;
@ -416,6 +426,19 @@ class AccountState {
get_wallet_revision());
}
td::Result<tonlib_api::object_ptr<tonlib_api::tvm_cell>> 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<tonlib_api::tvm_cell>(to_bytes(cell));
}
td::Result<td::Ref<vm::CellSlice>> 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<td::int64> to_balance_or_throw(td::Ref<vm::CellSlice> balance_ref) {
vm::CellSlice balance_slice = *balance_ref;
@ -1633,6 +1656,305 @@ class GetShardBlockProof : public td::actor::Actor {
std::vector<std::pair<ton::BlockIdExt, td::BufferSlice>> links_;
};
auto to_lite_api(const tonlib_api::ton_blockIdExt& blk) -> td::Result<lite_api_ptr<ton::lite_api::tonNode_blockIdExt>>;
auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) -> tonlib_api_ptr<tonlib_api::blocks_shortTxId>;
class RunEmulator : public td::actor::Actor {
public:
RunEmulator(ExtClientRef ext_client_ref, int_api::GetAccountStateByTransaction request,
td::actor::ActorShared<> parent, td::Promise<td::unique_ptr<AccountState>>&& 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<td::unique_ptr<AccountState>> promise_;
std::map<td::int64, td::actor::ActorOwn<>> actors_;
td::int64 actor_id_{1};
FullBlockId block_id_;
td::Ref<vm::Cell> mc_state_root_; // ^ShardStateUnsplit
td::unique_ptr<AccountState> account_state_;
std::vector<td::Ref<vm::Cell>> 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<FullBlockId>&& 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<tonlib_api::object_ptr<ton::lite_api::liteServer_blockHeader>> header_r) -> td::Result<FullBlockId> {
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<ton::BlockIdExt> 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<td::Ref<vm::Cell>>&& 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<td::Ref<vm::Cell>> {
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<td::unique_ptr<AccountState>>&& promise) {
auto actor_id = actor_id_++;
actors_[actor_id] = td::actor::create_actor<GetRawAccountState>(
"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<AccountState>(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<ton::lite_api::liteServer_transactionId3>(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<ton::lite_api::liteServer_blockTransactions>&& 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<td::Ref<vm::Cell>>&& promise) {
auto actor_id = actor_id_++;
actors_[actor_id] = td::actor::create_actor<GetTransactionHistory>(
"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<FullBlockId>&& block_id) { self->set_block_id(std::move(block_id)); });
}
void set_block_id(td::Result<FullBlockId>&& 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<td::Ref<vm::Cell>>&& mc_state_root) { self->set_mc_state_root(std::move(mc_state_root)); });
get_account_state([self = this](td::Result<td::unique_ptr<AccountState>>&& state) { self->set_account_state(std::move(state)); });
check(get_transactions(0));
inc();
}
}
void set_mc_state_root(td::Result<td::Ref<vm::Cell>>&& 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<td::unique_ptr<AccountState>>&& 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<td::Ref<vm::Cell>>&& 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<block::Config> 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<vm::CellSlice> 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<vm::CellSlice>(), 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<emulator::TransactionEmulator::EmulationChain> 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<AccountState>(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<TonlibCallback> 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<object_ptr<tonlib_api::raw_fullAccountState>>&& 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<KeyStorage::InputKey> 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<object_ptr<tonlib_api::fullAccountState>>&& 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<object_ptr<tonlib_api::tvm_cell>>&& 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<object_ptr<tonlib_api::tvm_cell>>&& 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<ton::ManualDns::EntryData> to_dns_entry_data(tonlib_api::dns_EntryData& entry_data) {
using R = td::Result<ton::ManualDns::EntryData>;
return downcast_call2<R>(
@ -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> 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<object_ptr<tonlib_api::smc_info>>&& 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<object_ptr<tonlib_api::ok>>&& 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<td::unique_ptr<AccountState>>&& promise) {
auto actor_id = actor_id_++;
actors_[actor_id] = td::actor::create_actor<RunEmulator>(
"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<int_api::RemoteRunSmcMethod::ReturnType>&& promise) {
auto actor_id = actor_id_++;
@ -4641,29 +5069,72 @@ td::Result<ton::BlockIdExt> 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<object_ptr<tonlib_api::configInfo>>&& 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<object_ptr<tonlib_api::configInfo>>&& promise) {
std::vector<int32_t> 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<object_ptr<tonlib_api::configInfo>> {
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<tonlib_api::tvm_cell>(to_bytes(config.move_as_ok()->get_config_param(param)));
return tonlib_api::make_object<tonlib_api::configInfo>(std::move(config_result));
}));
}
td::Status TonlibClient::do_request(const tonlib_api::getConfigParam& request,
td::Promise<object_ptr<tonlib_api::configInfo>>&& 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<LastBlockState> 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<object_ptr<tonlib_api::configInfo>>&& 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<object_ptr<tonlib_api::configInfo>> {
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<tonlib_api::tvm_cell>(to_bytes(config.move_as_ok()->get_root_cell()));
return tonlib_api::make_object<tonlib_api::configInfo>(std::move(config_result));
}));
}
td::Status TonlibClient::do_request(const tonlib_api::getConfigAll& request,
td::Promise<object_ptr<tonlib_api::configInfo>>&& 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<LastBlockState> 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();
}

View file

@ -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<tonlib_api::object_ptr<tonlib_api::dns_EntryData>> 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<object_ptr<tonlib_api::raw_fullAccountState>>&& promise);
td::Status do_request(tonlib_api::raw_getAccountStateByTransaction& request,
td::Promise<object_ptr<tonlib_api::raw_fullAccountState>>&& promise);
td::Status do_request(tonlib_api::raw_getTransactions& request,
td::Promise<object_ptr<tonlib_api::raw_transactions>>&& 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<object_ptr<tonlib_api::fullAccountState>>&& promise);
td::Status do_request(const tonlib_api::getAccountStateByTransaction& request,
td::Promise<object_ptr<tonlib_api::fullAccountState>>&& promise);
td::Status do_request(const tonlib_api::getShardAccountCell& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
td::Status do_request(const tonlib_api::getShardAccountCellByTransaction& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
td::Status do_request(tonlib_api::guessAccountRevision& request,
td::Promise<object_ptr<tonlib_api::accountRevisionList>>&& promise);
td::Status do_request(tonlib_api::guessAccount& request,
@ -305,6 +315,7 @@ class TonlibClient : public td::actor::Actor {
td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> get_smc_info(td::int64 id);
void finish_load_smc(td::unique_ptr<AccountState> query, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
td::Status do_request(const tonlib_api::smc_load& request, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
td::Status do_request(const tonlib_api::smc_loadByTransaction& request, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
td::Status do_request(const tonlib_api::smc_forget& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(const tonlib_api::smc_getCode& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
@ -344,6 +355,7 @@ class TonlibClient : public td::actor::Actor {
td::Promise<object_ptr<tonlib_api::dns_resolved>>&& promise);
td::Status do_request(int_api::GetAccountState request, td::Promise<td::unique_ptr<AccountState>>&&);
td::Status do_request(int_api::GetAccountStateByTransaction request, td::Promise<td::unique_ptr<AccountState>>&&);
td::Status do_request(int_api::GetPrivateKey request, td::Promise<KeyStorage::PrivateKey>&&);
td::Status do_request(int_api::GetDnsResolver request, td::Promise<block::StdAddress>&&);
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<object_ptr<tonlib_api::blocks_shardBlockProof>>&& promise);
void get_config_param(int32_t param, int32_t mode, ton::BlockIdExt block,
td::Promise<object_ptr<tonlib_api::configInfo>>&& promise);
td::Status do_request(const tonlib_api::getConfigParam& request,
td::Promise<object_ptr<tonlib_api::configInfo>>&& promise);
void get_config_all(int32_t mode, ton::BlockIdExt block,
td::Promise<object_ptr<tonlib_api::configInfo>>&& promise);
td::Status do_request(const tonlib_api::getConfigAll& request,
td::Promise<object_ptr<tonlib_api::configInfo>>&& promise);
void proxy_request(td::int64 query_id, std::string data);

View file

@ -389,6 +389,7 @@ class TonlibCli : public td::actor::Actor {
td::TerminalIO::out() << "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account "
"<addr> with specified parameters\n";
td::TerminalIO::out() << "getstate <key_id>\tget state of wallet with requested key\n";
td::TerminalIO::out() << "getstatebytransaction <key_id> <lt> <hash>\tget state of wallet with requested key after transaction with local time <lt> and hash <hash> (base64url)\n";
td::TerminalIO::out() << "guessrevision <key_id>\tsearch of existing accounts corresponding to the given key\n";
td::TerminalIO::out() << "guessaccount <key_id>\tsearch of existing accounts corresponding to the given key\n";
td::TerminalIO::out() << "getaddress <key_id>\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<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false));
TRY_RESULT_PROMISE(promise, lt, td::to_integer_safe<std::int64_t>(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<tonlib_api::internal_transactionId>(lt, std::move(hash));
send_query(make_object<tonlib_api::getAccountStateByTransaction>(
ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address)),
ton::move_tl_object_as<tonlib_api::internal_transactionId>(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<td::uint64>(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<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(key, false));
promise.set_value(td::Unit());

View file

@ -104,19 +104,7 @@ class Collator final : public td::actor::Actor {
return 2;
}
static td::Result<std::unique_ptr<block::ConfigInfo>>
impl_fetch_config_params(std::unique_ptr<block::ConfigInfo> config,
Ref<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* 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<std::unique_ptr<block::Transaction>>
static td::Result<std::unique_ptr<block::transaction::Transaction>>
impl_create_ordinary_transaction(Ref<vm::Cell> msg_root,
block::Account* acc,
UnixTime utime, LogicalTime lt,
@ -285,7 +273,7 @@ class Collator final : public td::actor::Actor {
td::Result<bool> register_shard_signatures_cell(Ref<vm::Cell> shard_blk_signatures);
td::Result<bool> 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<vm::Cell>* is_special = nullptr);
bool process_inbound_internal_messages();

View file

@ -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<vm::Dictionary>(config_->get_libraries_root(), 256);
return true;
}
td::Result<std::unique_ptr<block::ConfigInfo>> Collator::impl_fetch_config_params(
std::unique_ptr<block::ConfigInfo> config, Ref<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* 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<vm::Dictionary>(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<block::Transaction> trans = std::make_unique<block::Transaction>(
*acc, mask == 2 ? block::Transaction::tr_tick : block::Transaction::tr_tock, req_start_lt, now_);
std::unique_ptr<block::transaction::Transaction> trans = std::make_unique<block::transaction::Transaction>(
*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<vm::Cell> Collator::create_ordinary_transaction(Ref<vm::Cell> msg_root) {
fatal_error(std::move(error));
return {};
}
std::unique_ptr<block::Transaction> trans = res.move_as_ok();
std::unique_ptr<block::transaction::Transaction> 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<vm::Cell> Collator::create_ordinary_transaction(Ref<vm::Cell> 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<std::unique_ptr<block::Transaction>> Collator::impl_create_ordinary_transaction(
Ref<vm::Cell> 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<std::unique_ptr<block::transaction::Transaction>> Collator::impl_create_ordinary_transaction(Ref<vm::Cell> 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<std::unique_ptr<block::Transaction>> Collator::impl_create_ordinary_t
trans_min_lt = std::max(trans_min_lt, after_lt);
}
std::unique_ptr<block::Transaction> trans =
std::make_unique<block::Transaction>(*acc, block::Transaction::tr_ord, trans_min_lt + 1, utime, msg_root);
std::unique_ptr<block::transaction::Transaction> trans =
std::make_unique<block::transaction::Transaction>(*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));

View file

@ -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<vm::Dictionary>(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<block::Transaction> trans = res.move_as_ok();
std::unique_ptr<block::transaction::Transaction> trans = res.move_as_ok();
auto trans_root = trans->commit(*acc);
if (trans_root.is_null()) {

View file

@ -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<block::Transaction> trs =
std::make_unique<block::Transaction>(account, trans_type, lt, now_, in_msg_root);
std::unique_ptr<block::transaction::Transaction> trs =
std::make_unique<block::transaction::Transaction>(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