1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

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

* account_state_by_transaction

* Correct time calculation

* Bug fixes

* Refactor

* namespace block::transaction

* smc.forget

* RunEmulator: remove wallet_id

* Refactor & fixes

* AccountStateByTransaction: use shardchain block instead of masterchain block

* transaction emulator core

* refactor

* tx emulator major functionality

* small format changes

* readme

* clean

* return json, add support for init messages

* tx emulator readme

* refactor getConfigParam and getConfigAll

* add shardchain_libs_boc parameter

* option to change verbosity level of transaction emulator

* fix deserializing ShardAccount with account_none

* add mode needSpecialSmc when unpack config

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

* Refactor

* emulator: Fix bug

* emulator: Support for emulator-extern

* emulator: Refactor

* Return vm log and vm exit code.

* fix build on macos, emulator_static added

* adjust documentation

* ignore_chksig for external messages

* tvm emulator, run get method

* Added more params for transaction emulator

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

* Added actions cell output to transaction emulator

* fix tonlib build

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

* tvm send message, small refactoring

* fix config decoding, rename

* improve documentation

* macos export symbols

* Added run_get_method to transaction emulator emscipten wrapper

* Fixed empty action list serialization

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

* stack as boc

* log gas remaining

* Fix prev_block_id

* fix build errors

* Refactor fetch_config_params

* fix failing unwrap of optional rand_seed

* lookup correct shard, choose prev_block based on account shard

* fix tonlib android jni build

---------

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

View file

@ -274,6 +274,10 @@ add_library(ton_crypto STATIC ${TON_CRYPTO_SOURCE})
target_include_directories(ton_crypto PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
target_link_libraries(ton_crypto PUBLIC ${OPENSSL_CRYPTO_LIBRARY} tdutils tddb_utils)
if (USE_EMSCRIPTEN)
target_link_options(ton_crypto PRIVATE -fexceptions)
target_compile_options(ton_crypto PRIVATE -fexceptions)
endif()
if (NOT WIN32)
find_library(DL dl)
if (DL)

View file

@ -1000,7 +1000,7 @@ bool Account::skip_copy_depth_balance(vm::CellBuilder& cb, vm::CellSlice& cs) co
}
const Account t_Account, t_AccountE{true};
const RefTo<Account> t_Ref_Account;
const RefTo<Account> t_Ref_AccountE{true};
bool ShardAccount::extract_account_state(Ref<vm::CellSlice> cs_ref, Ref<vm::Cell>& acc_state) {
if (cs_ref.is_null()) {

View file

@ -536,7 +536,7 @@ struct Account final : TLB_Complex {
};
extern const Account t_Account, t_AccountE;
extern const RefTo<Account> t_Ref_Account;
extern const RefTo<Account> t_Ref_AccountE;
struct AccountStatus final : TLB {
enum { acc_state_uninit, acc_state_frozen, acc_state_active, acc_state_nonexist };
@ -572,7 +572,7 @@ struct ShardAccount final : TLB_Complex {
return cs.advance_ext(0x140, 1);
}
bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override {
return cs.advance(0x140) && t_Ref_Account.validate_skip(ops, cs, weak);
return cs.advance(0x140) && t_Ref_AccountE.validate_skip(ops, cs, weak);
}
static bool unpack(vm::CellSlice& cs, Record& info) {
return info.unpack(cs);

View file

@ -366,7 +366,7 @@ trans_merge_install$0111 split_info:SplitMergeInfo
smc_info#076ef1ea actions:uint16 msgs_sent:uint16
unixtime:uint32 block_lt:uint64 trans_lt:uint64
rand_seed:bits256 balance_remaining:CurrencyCollection
myself:MsgAddressInt = SmartContractInfo;
myself:MsgAddressInt global_config:(Maybe Cell) = SmartContractInfo;
//
//
out_list_empty$_ = OutList 0;

View file

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

View file

@ -20,6 +20,7 @@
#include "block/block.h"
#include "block/block-parse.h"
#include "block/block-auto.h"
#include "crypto/openssl/rand.hpp"
#include "td/utils/bits.h"
#include "td/utils/uint128.h"
#include "ton/ton-shard.h"
@ -513,6 +514,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector
return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain());
}
namespace transaction {
Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now,
Ref<vm::Cell> _inmsg)
: trans_type(ttype)
@ -745,6 +747,7 @@ bool Transaction::prepare_credit_phase() {
total_fees += std::move(collected);
return true;
}
} // namespace transaction
bool ComputePhaseConfig::parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit,
td::RefInt256& delete_due_limit) {
@ -837,6 +840,7 @@ td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const {
: td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price;
}
namespace transaction {
bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) {
// Compute gas limits
if (account.is_special) {
@ -1057,13 +1061,21 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
std::unique_ptr<StringLoggerTail> logger;
auto vm_log = vm::VmLog();
if (cfg.with_vm_log) {
logger = std::make_unique<StringLoggerTail>();
size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256;
logger = std::make_unique<StringLoggerTail>(log_max_size);
vm_log.log_interface = logger.get();
vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false);
if (cfg.vm_log_verbosity > 1) {
vm_log.log_mask |= vm::VmLog::ExecLocation;
if (cfg.vm_log_verbosity > 2) {
vm_log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining;
}
}
}
vm::VmState vm{new_code, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)};
vm.set_max_data_depth(cfg.max_vm_data_depth);
vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo
vm.set_chksig_always_succeed(cfg.ignore_chksig);
// vm.incr_stack_trace(1); // enable stack dump after each step
LOG(DEBUG) << "starting VM";
@ -1338,6 +1350,7 @@ int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, c
ap.spec_actions++;
return 0;
}
} // namespace transaction
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
@ -1372,6 +1385,7 @@ td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const {
return (std::move(total) * next_frac) >> 16;
}
namespace transaction {
bool Transaction::check_replace_src_addr(Ref<vm::CellSlice>& src_addr) const {
int t = (int)src_addr->prefetch_ulong(2);
if (!t && src_addr->size_ext() == 2) {
@ -1978,6 +1992,7 @@ bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) {
bp.ok = true;
return true;
}
} // namespace transaction
/*
*
@ -2033,6 +2048,7 @@ static td::optional<vm::CellStorageStat> try_update_storage_stat(const vm::CellS
return new_stat;
}
namespace transaction {
bool Transaction::compute_state() {
if (new_total_state.not_null()) {
return true;
@ -2460,6 +2476,7 @@ void Transaction::extract_out_msgs(std::vector<LtCellRef>& list) {
list.emplace_back(start_lt + i + 1, std::move(out_msgs[i]));
}
}
} // namespace transaction
void Account::push_transaction(Ref<vm::Cell> trans_root, ton::LogicalTime trans_lt) {
transactions.emplace_back(trans_lt, std::move(trans_root));
@ -2503,4 +2520,82 @@ bool Account::libraries_changed() const {
}
}
td::Status FetchConfigParams::fetch_config_params(const block::Config& config,
Ref<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* storage_prices,
block::StoragePhaseConfig* storage_phase_cfg,
td::BitArray<256>* rand_seed,
block::ComputePhaseConfig* compute_phase_cfg,
block::ActionPhaseConfig* action_phase_cfg,
td::RefInt256* masterchain_create_fee,
td::RefInt256* basechain_create_fee,
ton::WorkchainId wc,
ton::UnixTime now) {
*old_mparams = config.get_config_param(9);
{
auto res = config.get_storage_prices();
if (res.is_error()) {
return res.move_as_error();
}
*storage_prices = res.move_as_ok();
}
if (rand_seed->is_zero()) {
// generate rand seed
prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32);
LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex();
}
TRY_RESULT(size_limits, config.get_size_limits_config());
{
// compute compute_phase_cfg / storage_phase_cfg
auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21);
if (cell.is_null()) {
return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration");
}
if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit,
storage_phase_cfg->delete_due_limit)) {
return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration");
}
compute_phase_cfg->block_rand_seed = *rand_seed;
compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth;
compute_phase_cfg->global_config = config.get_root_cell();
compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now);
}
{
// compute action_phase_cfg
block::gen::MsgForwardPrices::Record rec;
auto cell = config.get_config_param(24);
if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) {
return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration");
}
action_phase_cfg->fwd_mc =
block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor,
(unsigned)rec.first_frac, (unsigned)rec.next_frac};
cell = config.get_config_param(25);
if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) {
return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration");
}
action_phase_cfg->fwd_std =
block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor,
(unsigned)rec.first_frac, (unsigned)rec.next_frac};
action_phase_cfg->workchains = &config.get_workchain_list();
action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0);
action_phase_cfg->size_limits = size_limits;
}
{
// fetch block_grams_created
auto cell = config.get_config_param(14);
if (cell.is_null()) {
*basechain_create_fee = *masterchain_create_fee = td::zero_refint();
} else {
block::gen::BlockCreateFees::Record create_fees;
if (!(tlb::unpack_cell(cell, create_fees) &&
block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) &&
block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) {
return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14");
}
}
}
return td::Status::OK();
}
} // namespace block

View file

@ -35,7 +35,10 @@ using td::Ref;
using LtCellRef = std::pair<ton::LogicalTime, Ref<vm::Cell>>;
struct Account;
namespace transaction {
struct Transaction;
} // namespace transaction
struct CollatorError {
std::string msg;
@ -106,9 +109,11 @@ struct ComputePhaseConfig {
std::unique_ptr<vm::Dictionary> libraries;
Ref<vm::Cell> global_config;
td::BitArray<256> block_rand_seed;
bool ignore_chksig{false};
bool with_vm_log{false};
td::uint16 max_vm_data_depth = 512;
std::unique_ptr<vm::Dictionary> suspended_addresses;
int vm_log_verbosity = 0;
ComputePhaseConfig(td::uint64 _gas_price = 0, td::uint64 _gas_limit = 0, td::uint64 _gas_credit = 0)
: gas_price(_gas_price), gas_limit(_gas_limit), special_gas_limit(_gas_limit), gas_credit(_gas_credit) {
compute_threshold();
@ -273,7 +278,7 @@ struct Account {
bool create_account_block(vm::CellBuilder& cb); // stores an AccountBlock with all transactions
protected:
friend struct Transaction;
friend struct transaction::Transaction;
bool set_split_depth(int split_depth);
bool check_split_depth(int split_depth) const;
bool forget_split_depth();
@ -288,6 +293,7 @@ struct Account {
bool compute_my_addr(bool force = false);
};
namespace transaction {
struct Transaction {
enum {
tr_none,
@ -390,5 +396,20 @@ struct Transaction {
bool serialize_bounce_phase(vm::CellBuilder& cb);
bool unpack_msg_state(bool lib_only = false);
};
} // namespace transaction
struct FetchConfigParams {
static td::Status fetch_config_params(const block::Config& config,
Ref<vm::Cell>* old_mparams,
std::vector<block::StoragePrices>* storage_prices,
StoragePhaseConfig* storage_phase_cfg,
td::BitArray<256>* rand_seed,
ComputePhaseConfig* compute_phase_cfg,
ActionPhaseConfig* action_phase_cfg,
td::RefInt256* masterchain_create_fee,
td::RefInt256* basechain_create_fee,
ton::WorkchainId wc,
ton::UnixTime now);
};
} // namespace block

View file

@ -54,7 +54,11 @@ td::Ref<vm::Stack> prepare_vm_stack(td::RefInt256 amount, td::Ref<vm::CellSlice>
td::Ref<vm::Tuple> prepare_vm_c7(SmartContract::Args args) {
td::BitArray<256> rand_seed;
rand_seed.as_slice().fill(0);
if (args.rand_seed) {
rand_seed = args.rand_seed.unwrap();
} else {
rand_seed.as_slice().fill(0);
}
td::RefInt256 rand_seed_int{true};
rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false);
@ -96,7 +100,7 @@ td::Ref<vm::Tuple> prepare_vm_c7(SmartContract::Args args) {
}
SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stack> stack, td::Ref<vm::Tuple> c7,
vm::GasLimits gas, bool ignore_chksig, td::Ref<vm::Cell> libraries) {
vm::GasLimits gas, bool ignore_chksig, td::Ref<vm::Cell> libraries, int vm_log_verbosity) {
auto gas_credit = gas.gas_credit;
vm::init_op_cp0();
vm::DictionaryBase::get_empty_dictionary();
@ -109,15 +113,12 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stac
std::string res;
};
Logger logger;
vm::VmLog log{&logger, td::LogOptions::plain()};
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
log.log_options.level = 4;
log.log_options.fix_newlines = true;
log.log_mask |= vm::VmLog::DumpStack;
} else {
log.log_options.level = 0;
log.log_mask = 0;
vm::VmLog log{&logger, td::LogOptions(VERBOSITY_NAME(DEBUG), true, false)};
if (vm_log_verbosity > 1) {
log.log_mask |= vm::VmLog::ExecLocation;
if (vm_log_verbosity > 2) {
log.log_mask |= vm::VmLog::DumpStack | vm::VmLog::GasRemaining;
}
}
SmartContract::Answer res;
@ -137,13 +138,13 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stac
} catch (...) {
LOG(FATAL) << "catch unhandled exception";
}
td::ConstBitPtr mlib = vm.get_missing_library();
res.new_state = std::move(state);
res.stack = vm.get_stack_ref();
gas = vm.get_gas_limits();
res.gas_used = gas.gas_consumed();
res.accepted = gas.gas_credit == 0;
res.success = (res.accepted && (unsigned)res.code <= 1);
res.vm_log = logger.res;
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
LOG(DEBUG) << "VM log\n" << logger.res;
std::ostringstream os;
@ -153,6 +154,7 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stac
LOG(DEBUG) << "VM accepted: " << res.accepted;
LOG(DEBUG) << "VM success: " << res.success;
}
td::ConstBitPtr mlib = vm.get_missing_library();
if (!mlib.is_null()) {
LOG(DEBUG) << "Missing library: " << mlib.to_hex(256);
res.missing_library = mlib;
@ -219,7 +221,7 @@ SmartContract::Answer SmartContract::run_method(Args args) {
args.stack.value().write().push_smallint(args.method_id.unwrap());
auto res =
run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig,
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{});
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{}, args.vm_log_verbosity_level);
state_ = res.new_state;
return res;
}
@ -237,7 +239,7 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const {
CHECK(args.method_id);
args.stack.value().write().push_smallint(args.method_id.unwrap());
return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig,
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{});
args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref<vm::Cell>{}, args.vm_log_verbosity_level);
}
SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const {

View file

@ -50,6 +50,7 @@ class SmartContract : public td::CntObject {
td::int32 code;
td::int64 gas_used;
td::ConstBitPtr missing_library{0};
std::string vm_log;
static int output_actions_count(td::Ref<vm::Cell> list);
};
@ -59,9 +60,11 @@ class SmartContract : public td::CntObject {
td::optional<td::Ref<vm::Tuple>> c7;
td::optional<td::Ref<vm::Stack>> stack;
td::optional<td::int32> now;
td::optional<td::BitArray<256>> rand_seed;
bool ignore_chksig{false};
td::uint64 amount{0};
td::uint64 balance{0};
int vm_log_verbosity_level{0};
td::optional<block::StdAddress> address;
td::optional<std::shared_ptr<const block::Config>> config;
@ -100,6 +103,10 @@ class SmartContract : public td::CntObject {
this->stack = std::move(stack);
return std::move(*this);
}
Args&& set_rand_seed(td::BitArray<256> rand_seed) {
this->rand_seed = std::move(rand_seed);
return std::move(*this);
}
Args&& set_ignore_chksig(bool ignore_chksig) {
this->ignore_chksig = ignore_chksig;
return std::move(*this);
@ -124,6 +131,10 @@ class SmartContract : public td::CntObject {
this->libraries = libraries;
return std::move(*this);
}
Args&& set_vm_verbosity_level(int vm_log_verbosity_level) {
this->vm_log_verbosity_level = vm_log_verbosity_level;
return std::move(*this);
}
td::Result<td::int32> get_method_id() const {
if (!method_id) {

View file

@ -31,7 +31,7 @@ namespace vm {
struct VmLog {
td::LogInterface *log_interface{td::log_interface};
td::LogOptions log_options{td::log_options};
enum { DumpStack = 2 };
enum { DumpStack = 2, ExecLocation = 4, GasRemaining = 8 };
int log_mask{1};
static VmLog Null() {
VmLog res;

View file

@ -433,18 +433,24 @@ void VmState::change_gas_limit(long long new_limit) {
int VmState::step() {
CHECK(code.not_null() && stack.not_null());
//VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st));
//VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl;
if (log.log_mask & vm::VmLog::DumpStack) {
std::stringstream ss;
stack->dump(ss, 3);
VM_LOG(this) << "stack:" << ss.str();
}
if (stack_trace) {
stack->dump(std::cerr, 3);
}
++steps;
if (code->size()) {
VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << code->get_base_cell()->get_hash().to_hex() << " offset: " << code->cur_pos();
return dispatch->dispatch(this, code.write());
} else if (code->size_refs()) {
VM_LOG(this) << "execute implicit JMPREF";
auto ref_cell = code->prefetch_ref();
VM_LOG_MASK(this, vm::VmLog::ExecLocation) << "code cell hash: " << ref_cell->get_hash().to_hex() << " offset: 0";
gas.consume_chk(implicit_jmpref_gas_price);
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(std::move(ref_cell)), get_cp()};
return jump(std::move(cont));
} else {
VM_LOG(this) << "execute implicit RET";
@ -465,6 +471,7 @@ int VmState::run() {
try {
try {
res = step();
VM_LOG_MASK(this, vm::VmLog::GasRemaining) << "gas remaining: " << gas.gas_remaining;
gas.check();
} catch (vm::CellBuilder::CellWriteError) {
throw VmError{Excno::cell_ov};