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:
parent
adf67aa869
commit
3b3c25b654
32 changed files with 2095 additions and 158 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
55
emulator/CMakeLists.txt
Normal 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
32
emulator/README.md
Normal 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
27
emulator/StringLog.h
Normal 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
|
189
emulator/emulator-emscripten.cpp
Normal file
189
emulator/emulator-emscripten.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
435
emulator/emulator-extern.cpp
Normal file
435
emulator/emulator-extern.cpp
Normal 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
216
emulator/emulator-extern.h
Normal 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
|
17
emulator/emulator_export_list
Normal file
17
emulator/emulator_export_list
Normal 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
|
254
emulator/transaction-emulator.cpp
Normal file
254
emulator/transaction-emulator.cpp
Normal 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
|
83
emulator/transaction-emulator.h
Normal file
83
emulator/transaction-emulator.h
Normal 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
46
emulator/tvm-emulator.hpp
Normal 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));
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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.
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue