Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 93 additions & 71 deletions crypto/block/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,59 +27,6 @@
#include "vm/vm.h"
#include "td/utils/Timer.h"

namespace {
/**
* Logger that stores the tail of log messages.
*
* @param max_size The size of the buffer. Default is 256.
*/
class StringLoggerTail : public td::LogInterface {
public:
explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {}

/**
* Appends a slice of data to the buffer.
*
* @param slice The slice of data to be appended.
*/
void append(td::CSlice slice) override {
if (slice.size() > buf.size()) {
slice.remove_prefix(slice.size() - buf.size());
}
while (!slice.empty()) {
size_t s = std::min(buf.size() - pos, slice.size());
std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos);
pos += s;
if (pos == buf.size()) {
pos = 0;
truncated = true;
}
slice.remove_prefix(s);
}
}

/**
* Retrieves the tail of the log.
*
* @returns The log as std::string.
*/
std::string get_log() const {
if (truncated) {
std::string res = buf;
std::rotate(res.begin(), res.begin() + pos, res.end());
return res;
} else {
return buf.substr(0, pos);
}
}

private:
std::string buf;
size_t pos = 0;
bool truncated = false;
};
}

namespace block {
using td::Ref;

Expand Down Expand Up @@ -1775,13 +1722,60 @@ bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precom
}

/**
* Prepares the compute phase of a transaction, which includes running TVM.
* Prepares the compute phase of a transaction and run VM.
*
* @param cfg The configuration for the compute phase.
*
* @returns True if the compute phase was successfully prepared and executed, false otherwise.
*/
bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
bool Transaction::execute_compute_phase(const ComputePhaseConfig& cfg) {
auto maybe_res = prepare_compute_phase(cfg);
if (!maybe_res) {
return false;
}

auto res = std::move(*maybe_res);
if (res.skipped) {
return true;
}

if (res.precompiled_impl) {
return run_precompiled_contract(cfg, *res.precompiled_impl);
}

auto& cp = *compute_phase.get();
return run_compute_phase(cfg, cp, res.vm, res.logger, res.precompiled);
}

bool Transaction::prepare_debug_compute_phase(const ComputePhaseConfig& cfg, std::unique_ptr<vm::VmState>& vm,
std::unique_ptr<StringLoggerTail>& logger) {
auto maybe_res = prepare_compute_phase(cfg);
if (!maybe_res) {
return false;
}

auto res = std::move(*maybe_res);
if (res.skipped) {
return true;
}

if (res.precompiled_impl) {
return run_precompiled_contract(cfg, *res.precompiled_impl);
}

vm = std::make_unique<vm::VmState>(std::move(res.vm));
logger = std::move(res.logger);
return true;
}

/**
* Prepares the compute phase of a transaction, without actually running it in VM.
*
* @param cfg The configuration for the compute phase.
*
* @returns structure with prepared data
*/
std::optional<Transaction::PrepareComputePhaseResult> Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
// TODO: add more skip verifications + sometimes use state from in_msg to re-activate
// ...
compute_phase = std::make_unique<ComputePhase>();
Expand All @@ -1797,17 +1791,17 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
if (td::sgn(balance.grams) <= 0) {
// no gas
cp.skip_reason = ComputePhase::sk_no_gas;
return true;
return PrepareComputePhaseResult::create_skipped();
}
// Compute gas limits
if (!compute_gas_limits(cp, cfg)) {
compute_phase.reset();
return false;
return std::nullopt;
}
if (!cp.gas_limit && !cp.gas_credit) {
// no gas
cp.skip_reason = ComputePhase::sk_no_gas;
return true;
return PrepareComputePhaseResult::create_skipped();
}
if (in_msg_state.not_null()) {
LOG(DEBUG) << "HASH(in_msg_state) = " << in_msg_state->get_hash().bits().to_hex(256)
Expand All @@ -1821,7 +1815,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
if (acc_status == Account::acc_uninit && cfg.is_address_suspended(account.workchain, account.addr)) {
LOG(DEBUG) << "address is suspended, skipping compute phase";
cp.skip_reason = ComputePhase::sk_suspended;
return true;
return PrepareComputePhaseResult::create_skipped();
}
use_msg_state = true;
bool forbid_public_libs =
Expand All @@ -1830,31 +1824,31 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
account.check_addr_rewrite_length(new_fixed_prefix_length))) {
LOG(DEBUG) << "cannot unpack in_msg_state, or it has bad fixed_prefix_length; cannot init account state";
cp.skip_reason = ComputePhase::sk_bad_state;
return true;
return PrepareComputePhaseResult::create_skipped();
}
if (acc_status == Account::acc_uninit && !check_in_msg_state_hash(cfg)) {
LOG(DEBUG) << "in_msg_state hash mismatch, cannot init account state";
cp.skip_reason = ComputePhase::sk_bad_state;
return true;
return PrepareComputePhaseResult::create_skipped();
}
if (cfg.disable_anycast && acc_status == Account::acc_uninit &&
new_fixed_prefix_length > cfg.size_limits.max_acc_fixed_prefix_length) {
LOG(DEBUG) << "cannot init account state: too big fixed prefix length (" << new_fixed_prefix_length << ", max "
<< cfg.size_limits.max_acc_fixed_prefix_length << ")";
cp.skip_reason = ComputePhase::sk_bad_state;
return true;
return PrepareComputePhaseResult::create_skipped();
}
} else if (acc_status != Account::acc_active) {
// no state, cannot perform transactions
cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state;
return true;
return PrepareComputePhaseResult::create_skipped();
} else if (in_msg_state.not_null()) {
if (cfg.allow_external_unfreeze) {
if (in_msg_extern && account.addr != in_msg_state->get_hash().bits()) {
// only for external messages with non-zero initstate in active accounts
LOG(DEBUG) << "in_msg_state hash mismatch in external message";
cp.skip_reason = ComputePhase::sk_bad_state;
return true;
return PrepareComputePhaseResult::create_skipped();
}
}
unpack_msg_state(cfg, true); // use only libraries
Expand All @@ -1863,7 +1857,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) {
LOG(DEBUG) << "in_msg_state hash mismatch in external message";
cp.skip_reason = ComputePhase::sk_bad_state;
return true;
return PrepareComputePhaseResult::create_skipped();
}
}
if (cfg.disable_anycast) {
Expand All @@ -1883,11 +1877,11 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
cp.precompiled_gas_usage = gas_usage;
if (gas_usage > cp.gas_limit) {
cp.skip_reason = ComputePhase::sk_no_gas;
return true;
return PrepareComputePhaseResult::create_skipped();
}
auto impl = precompiled::get_implementation(new_code->get_hash().bits());
if (impl != nullptr && !cfg.dont_run_precompiled_ && impl->required_version() <= cfg.global_version) {
return run_precompiled_contract(cfg, *impl);
return PrepareComputePhaseResult::create_precompiled(std::move(impl));
}

// Contract is marked as precompiled in global config, but implementation is not available
Expand All @@ -1902,7 +1896,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
Ref<vm::Stack> stack = prepare_vm_stack(cp);
if (stack.is_null()) {
compute_phase.reset();
return false;
return std::nullopt;
}
// OstreamLogger ostream_logger(error_stream);
// auto log = create_vm_log(error_stream ? &ostream_logger : nullptr);
Expand Down Expand Up @@ -1943,15 +1937,31 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {

LOG(DEBUG) << "starting VM";
cp.vm_init_state_hash = vm.get_state_hash();

return PrepareComputePhaseResult{false, std::move(vm), std::move(logger), nullptr, precompiled};
}

bool Transaction::run_compute_phase(const ComputePhaseConfig& cfg, ComputePhase& cp, vm::VmState& vm,
std::unique_ptr<StringLoggerTail>& logger,
td::optional<PrecompiledContractsConfig::Contract> precompiled) {
td::Timer timer;
cp.exit_code = ~vm.run();
double elapsed = timer.elapsed();
const bool compute_phase_result = get_compute_phase_result(cfg, cp, vm, logger, precompiled, elapsed);
cp.vm_loaded_cells = vm.extract_loaded_cells();
return compute_phase_result;
}

bool Transaction::get_compute_phase_result(const ComputePhaseConfig& cfg, ComputePhase& cp, const vm::VmState& vm,
std::unique_ptr<StringLoggerTail>& logger,
td::optional<PrecompiledContractsConfig::Contract> precompiled,
double elapsed) {
LOG(DEBUG) << "VM terminated with exit code " << cp.exit_code;
cp.out_of_gas = (cp.exit_code == ~(int)vm::Excno::out_of_gas);
cp.vm_final_state_hash = vm.get_final_state_hash(cp.exit_code);
stack = vm.get_stack_ref();
const auto stack = vm.get_stack_ref();
cp.vm_steps = (int)vm.get_steps_count();
gas = vm.get_gas_limits();
const auto gas = vm.get_gas_limits();
cp.gas_used = std::min<long long>(gas.gas_consumed(), gas.gas_limit);
cp.accepted = (gas.gas_credit == 0);
cp.success = (cp.accepted && vm.committed());
Expand Down Expand Up @@ -2010,10 +2020,22 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
<< cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str();
CHECK(td::sgn(balance.grams) >= 0);
}
cp.vm_loaded_cells = vm.extract_loaded_cells();
return true;
}

bool Transaction::compute_phase_step_debug(const ComputePhaseConfig& cfg, std::unique_ptr<vm::VmState>& vm,
std::unique_ptr<StringLoggerTail>& logger) {
td::optional<int> res = vm->debug_step();
if (!res) {
return false;
}

ComputePhase& cp = *(compute_phase.get());
cp.exit_code = ~(*res);

return get_compute_phase_result(cfg, cp, *vm, logger, {}, 0);
}

/**
* Prepares the action phase of a transaction.
*
Expand Down
84 changes: 83 additions & 1 deletion crypto/block/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
#include "vm/cellslice.h"
#include "vm/dict.h"
#include "vm/boc.h"
#include "vm/vm.h"
#include <ostream>
#include <optional>
#include "tl/tlblib.hpp"
#include "td/utils/bits.h"
#include "ton/ton-types.h"
Expand All @@ -43,6 +45,57 @@ namespace transaction {
struct Transaction;
} // namespace transaction

/**
* Logger that stores the tail of log messages.
*
* @param max_size The size of the buffer. Default is 256.
*/
class StringLoggerTail : public td::LogInterface {
public:
explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {}

/**
* Appends a slice of data to the buffer.
*
* @param slice The slice of data to be appended.
*/
void append(td::CSlice slice) override {
if (slice.size() > buf.size()) {
slice.remove_prefix(slice.size() - buf.size());
}
while (!slice.empty()) {
size_t s = std::min(buf.size() - pos, slice.size());
std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos);
pos += s;
if (pos == buf.size()) {
pos = 0;
truncated = true;
}
slice.remove_prefix(s);
}
}

/**
* Retrieves the tail of the log.
*
* @returns The log as std::string.
*/
std::string get_log() const {
if (truncated) {
std::string res = buf;
std::rotate(res.begin(), res.begin() + pos, res.end());
return res;
} else {
return buf.substr(0, pos);
}
}

private:
std::string buf;
size_t pos = 0;
bool truncated = false;
};

struct CollatorError {
std::string msg;
CollatorError(std::string _msg) : msg(_msg) {
Expand Down Expand Up @@ -410,7 +463,36 @@ struct Transaction {
Ref<vm::Stack> prepare_vm_stack(ComputePhase& cp);
std::vector<Ref<vm::Cell>> compute_vm_libraries(const ComputePhaseConfig& cfg);
bool run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& precompiled);
bool prepare_compute_phase(const ComputePhaseConfig& cfg);

bool execute_compute_phase(const ComputePhaseConfig& cfg);
bool prepare_debug_compute_phase(const ComputePhaseConfig& cfg, std::unique_ptr<vm::VmState>& vm,
std::unique_ptr<StringLoggerTail>& logger);
bool get_compute_phase_result(const ComputePhaseConfig& cfg, ComputePhase& cp, const vm::VmState& vm,
std::unique_ptr<StringLoggerTail>& logger,
td::optional<PrecompiledContractsConfig::Contract> precompiled, double elapsed);

struct PrepareComputePhaseResult {
bool skipped;
vm::VmState vm{};
std::unique_ptr<StringLoggerTail> logger{};
std::unique_ptr<precompiled::PrecompiledSmartContract> precompiled_impl{};
td::optional<PrecompiledContractsConfig::Contract> precompiled{};

static PrepareComputePhaseResult create_skipped() {
return {true};
}

static PrepareComputePhaseResult create_precompiled(std::unique_ptr<precompiled::PrecompiledSmartContract> precompiled_impl) {
return {false, {}, nullptr, std::move(precompiled_impl)};
}
};

std::optional<PrepareComputePhaseResult> prepare_compute_phase(const ComputePhaseConfig& cfg);
bool run_compute_phase(const ComputePhaseConfig& cfg, ComputePhase& cp, vm::VmState& vm,
std::unique_ptr<StringLoggerTail>& logger,
td::optional<PrecompiledContractsConfig::Contract> precompiled);
bool compute_phase_step_debug(const ComputePhaseConfig& cfg, std::unique_ptr<vm::VmState>& vm,
std::unique_ptr<StringLoggerTail>& logger);
bool prepare_action_phase(const ActionPhaseConfig& cfg);
td::Status check_state_limits(const SizeLimitsConfig& size_limits, bool is_account_stat = true);
bool prepare_bounce_phase(const ActionPhaseConfig& cfg);
Expand Down
Loading