/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see .
Copyright 2017-2020 Telegram Systems LLP
*/
#include "TonlibClient.h"
#include "tonlib/ExtClientLazy.h"
#include "tonlib/ExtClientOutbound.h"
#include "tonlib/LastBlock.h"
#include "tonlib/LastConfig.h"
#include "tonlib/Logging.h"
#include "tonlib/utils.h"
#include "tonlib/keys/Mnemonic.h"
#include "tonlib/keys/SimpleEncryption.h"
#include "tonlib/TonlibError.h"
#include "smc-envelope/GenericAccount.h"
#include "smc-envelope/ManualDns.h"
#include "smc-envelope/WalletV3.h"
#include "smc-envelope/HighloadWallet.h"
#include "smc-envelope/HighloadWalletV2.h"
#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"
#include "ton/lite-tl.hpp"
#include "ton/ton-shard.h"
#include "lite-client/lite-client-common.h"
#include "vm/boc.h"
#include "vm/cellops.h"
#include "vm/cells/MerkleProof.h"
#include "vm/vm.h"
#include "vm/cp0.h"
#include "vm/memo.h"
#include "td/utils/as.h"
#include "td/utils/Random.h"
#include "td/utils/optional.h"
#include "td/utils/overloaded.h"
#include "td/utils/tests.h"
#include "td/utils/port/path.h"
#include "common/util.h"
template
using lite_api_ptr = ton::lite_api::object_ptr;
template
using tonlib_api_ptr = ton::tonlib_api::object_ptr;
namespace tonlib {
namespace int_api {
struct GetAccountState {
block::StdAddress address;
td::optional block_id;
td::optional public_key;
using ReturnType = td::unique_ptr;
};
struct GetAccountStateByTransaction {
block::StdAddress address;
std::int64_t lt;
td::Bits256 hash;
//td::optional public_key;
using ReturnType = td::unique_ptr;
};
struct RemoteRunSmcMethod {
block::StdAddress address;
td::optional block_id;
ton::SmartContract::Args args;
bool need_result{false};
using ReturnType = RemoteRunSmcMethodReturnType;
};
struct RemoteRunSmcMethodReturnType {
ton::SmartContract::State smc_state;
ton::BlockIdExt block_id;
// result
// c7
// libs
};
struct GetPrivateKey {
KeyStorage::InputKey input_key;
using ReturnType = KeyStorage::PrivateKey;
};
struct GetDnsResolver {
using ReturnType = block::StdAddress;
};
struct SendMessage {
td::Ref message;
using ReturnType = td::Unit;
};
} // namespace int_api
template
R downcast_call2(O&& o, F&& f, R res = {}) {
downcast_call(o, [&](auto& x) { res = f(x); });
return res;
}
auto to_tonlib_api(const ton::BlockIdExt& blk) {
return tonlib_api::make_object(
blk.id.workchain, blk.id.shard, blk.id.seqno, blk.root_hash.as_slice().str(), blk.file_hash.as_slice().str());
}
tonlib_api::object_ptr to_tonlib_api(const TonlibClient::FullConfig& full_config) {
return tonlib_api::make_object(full_config.wallet_id,
full_config.rwallet_init_public_key);
}
class TonlibQueryActor : public td::actor::Actor {
public:
TonlibQueryActor(td::actor::ActorShared client) : client_(std::move(client)) {
}
template
void send_query(QueryT query, td::Promise promise) {
td::actor::send_lambda(client_,
[self = client_.get(), query = std::move(query), promise = std::move(promise)]() mutable {
self.get_actor_unsafe().make_request(std::move(query), std::move(promise));
});
}
private:
td::actor::ActorShared client_;
};
tonlib_api::object_ptr status_to_tonlib_api(const td::Status& status) {
return tonlib_api::make_object(status.code(), status.message().str());
}
static block::AccountState create_account_state(ton::tl_object_ptr from) {
block::AccountState res;
res.blk = ton::create_block_id(from->id_);
res.shard_blk = ton::create_block_id(from->shardblk_);
res.shard_proof = std::move(from->shard_proof_);
res.proof = std::move(from->proof_);
res.state = std::move(from->state_);
return res;
}
static block::AccountState create_account_state(ton::tl_object_ptr& from) {
block::AccountState res;
res.blk = ton::create_block_id(from->id_);
res.shard_blk = ton::create_block_id(from->shardblk_);
res.shard_proof = std::move(from->shard_proof_);
res.proof = std::move(from->proof_);
res.state = std::move(from->state_proof_);
res.is_virtualized = from->mode_ > 0;
return res;
}
struct RawAccountState {
td::int64 balance = -1;
ton::UnixTime storage_last_paid{0};
vm::CellStorageStat storage_stat;
td::Ref code;
td::Ref data;
td::Ref state;
std::string frozen_hash;
block::AccountState::Info info;
ton::BlockIdExt block_id;
};
tonlib_api::object_ptr empty_transaction_id() {
return tonlib_api::make_object(0, std::string(32, 0));
}
tonlib_api::object_ptr to_transaction_id(const block::AccountState::Info& info) {
return tonlib_api::make_object(info.last_trans_lt,
info.last_trans_hash.as_slice().str());
}
std::string to_bytes(td::Ref cell) {
if (cell.is_null()) {
return "";
}
return vm::std_boc_serialize(cell, vm::BagOfCells::Mode::WithCRC32C).move_as_ok().as_slice().str();
}
td::Result get_public_key(td::Slice public_key) {
TRY_RESULT_PREFIX(address, block::PublicKey::parse(public_key), TonlibError::InvalidPublicKey());
return address;
}
td::Result get_account_address(td::Slice account_address) {
TRY_RESULT_PREFIX(address, block::StdAddress::parse(account_address), TonlibError::InvalidAccountAddress());
return address;
}
td::Result public_key_from_bytes(td::Slice bytes) {
TRY_RESULT_PREFIX(key_bytes, block::PublicKey::from_bytes(bytes), TonlibError::Internal());
return key_bytes;
}
td::Result to_init_data(const tonlib_api::wallet_v3_initialAccountState& wallet_state) {
TRY_RESULT(key_bytes, get_public_key(wallet_state.public_key_));
ton::WalletV3::InitData init_data;
init_data.public_key = td::SecureString(key_bytes.key);
init_data.wallet_id = static_cast(wallet_state.wallet_id_);
return std::move(init_data);
}
td::Result to_init_data(const tonlib_api::rwallet_initialAccountState& rwallet_state) {
TRY_RESULT(init_key_bytes, get_public_key(rwallet_state.init_public_key_));
TRY_RESULT(key_bytes, get_public_key(rwallet_state.public_key_));
ton::RestrictedWallet::InitData init_data;
init_data.init_key = td::SecureString(init_key_bytes.key);
init_data.main_key = td::SecureString(key_bytes.key);
init_data.wallet_id = static_cast(rwallet_state.wallet_id_);
return std::move(init_data);
}
td::Result to_pchan_config(const tonlib_api::pchan_initialAccountState& pchan_state) {
ton::pchan::Config config;
if (!pchan_state.config_) {
return TonlibError::EmptyField("config");
}
TRY_RESULT_PREFIX(a_key, get_public_key(pchan_state.config_->alice_public_key_),
TonlibError::InvalidField("alice_public_key", ""));
config.a_key = td::SecureString(a_key.key);
TRY_RESULT_PREFIX(b_key, get_public_key(pchan_state.config_->bob_public_key_),
TonlibError::InvalidField("bob_public_key", ""));
config.b_key = td::SecureString(b_key.key);
if (!pchan_state.config_->alice_address_) {
return TonlibError::EmptyField("config.alice_address");
}
TRY_RESULT_PREFIX(a_addr, get_account_address(pchan_state.config_->alice_address_->account_address_),
TonlibError::InvalidField("alice_address", ""));
config.a_addr = std::move(a_addr);
if (!pchan_state.config_->bob_address_) {
return TonlibError::EmptyField("config.bob_address");
}
TRY_RESULT_PREFIX(b_addr, get_account_address(pchan_state.config_->bob_address_->account_address_),
TonlibError::InvalidField("bob_address", ""));
config.b_addr = std::move(b_addr);
config.channel_id = pchan_state.config_->channel_id_;
config.init_timeout = pchan_state.config_->init_timeout_;
config.close_timeout = pchan_state.config_->close_timeout_;
return std::move(config);
}
class AccountState {
public:
AccountState(block::StdAddress address, RawAccountState&& raw, td::uint32 wallet_id)
: address_(std::move(address)), raw_(std::move(raw)), wallet_id_(wallet_id) {
guess_type();
}
auto to_uninited_accountState() const {
return tonlib_api::make_object(raw().frozen_hash);
}
td::Result> to_raw_accountState() const {
auto state = get_smc_state();
std::string code;
if (state.code.not_null()) {
code = to_bytes(state.code);
}
std::string data;
if (state.data.not_null()) {
data = to_bytes(state.data);
}
return tonlib_api::make_object(std::move(code), std::move(data), raw().frozen_hash);
}
td::Result> to_raw_fullAccountState() const {
auto state = get_smc_state();
std::string code;
if (state.code.not_null()) {
code = to_bytes(state.code);
}
std::string data;
if (state.data.not_null()) {
data = to_bytes(state.data);
}
return tonlib_api::make_object(
get_balance(), std::move(code), std::move(data), to_transaction_id(raw().info), to_tonlib_api(raw().block_id),
raw().frozen_hash, get_sync_time());
}
td::Result> to_wallet_v3_accountState() const {
if (wallet_type_ != WalletV3) {
return TonlibError::AccountTypeUnexpected("WalletV3");
}
auto wallet = ton::WalletV3(get_smc_state());
TRY_RESULT(seqno, wallet.get_seqno());
TRY_RESULT(wallet_id, wallet.get_wallet_id());
return tonlib_api::make_object(static_cast(wallet_id),
static_cast(seqno));
}
td::Result> to_wallet_highload_v1_accountState()
const {
if (wallet_type_ != HighloadWalletV1) {
return TonlibError::AccountTypeUnexpected("HighloadWalletV1");
}
auto wallet = ton::HighloadWallet(get_smc_state());
TRY_RESULT(seqno, wallet.get_seqno());
TRY_RESULT(wallet_id, wallet.get_wallet_id());
return tonlib_api::make_object(static_cast(wallet_id),
static_cast(seqno));
}
td::Result> to_wallet_highload_v2_accountState()
const {
if (wallet_type_ != HighloadWalletV2) {
return TonlibError::AccountTypeUnexpected("HighloadWalletV2");
}
auto wallet = ton::HighloadWalletV2(get_smc_state());
TRY_RESULT(wallet_id, wallet.get_wallet_id());
return tonlib_api::make_object(static_cast(wallet_id));
}
td::Result> to_rwallet_accountState() const {
if (wallet_type_ != RestrictedWallet) {
return TonlibError::AccountTypeUnexpected("RestrictedWallet");
}
auto wallet = ton::RestrictedWallet::create(get_smc_state());
TRY_RESULT(seqno, wallet->get_seqno());
TRY_RESULT(wallet_id, wallet->get_wallet_id());
TRY_RESULT(balance, wallet->get_balance(raw_.balance, raw_.info.gen_utime));
TRY_RESULT(config, wallet->get_config());
auto api_config = tonlib_api::make_object();
api_config->start_at_ = config.start_at;
for (auto& limit : config.limits) {
api_config->limits_.push_back(tonlib_api::make_object(limit.first, limit.second));
}
return tonlib_api::make_object(wallet_id, seqno, balance, std::move(api_config));
}
td::Result> to_payment_channel_accountState() const {
if (wallet_type_ != PaymentChannel) {
return TonlibError::AccountTypeUnexpected("PaymentChannel");
}
auto pchan = ton::PaymentChannel::create(get_smc_state());
TRY_RESULT(info, pchan->get_info());
TRY_RESULT(a_key, public_key_from_bytes(info.config.a_key));
TRY_RESULT(b_key, public_key_from_bytes(info.config.b_key));
tonlib_api::object_ptr tl_state;
info.state.visit(td::overloaded(
[&](const ton::pchan::StateInit& state) {
tl_state = tonlib_api::make_object(
state.signed_A, state.signed_B, state.min_A, state.min_B, state.A, state.B, state.expire_at);
},
[&](const ton::pchan::StateClose& state) {
tl_state = tonlib_api::make_object(
state.signed_A, state.signed_B, state.promise_A, state.promise_B, state.A, state.B, state.expire_at);
},
[&](const ton::pchan::StatePayout& state) {
tl_state = tonlib_api::make_object(state.A, state.B);
}));
using tonlib_api::make_object;
return tonlib_api::make_object(
tonlib_api::make_object(
a_key.serialize(true), make_object(info.config.a_addr.rserialize(true)),
b_key.serialize(true), make_object(info.config.b_addr.rserialize(true)),
info.config.init_timeout, info.config.close_timeout, info.config.channel_id),
std::move(tl_state), info.description);
}
td::Result> to_dns_accountState() const {
if (wallet_type_ != ManualDns) {
return TonlibError::AccountTypeUnexpected("ManualDns");
}
TRY_RESULT(wallet_id, ton::ManualDns(get_smc_state()).get_wallet_id());
return tonlib_api::make_object(static_cast(wallet_id));
}
td::Result> to_accountState() const {
auto f = [](auto&& r_x) -> td::Result> {
TRY_RESULT(x, std::move(r_x));
return std::move(x);
};
switch (wallet_type_) {
case Empty:
return to_uninited_accountState();
case Unknown:
return f(to_raw_accountState());
case WalletV3:
return f(to_wallet_v3_accountState());
case HighloadWalletV1:
return f(to_wallet_highload_v1_accountState());
case HighloadWalletV2:
return f(to_wallet_highload_v2_accountState());
case RestrictedWallet:
return f(to_rwallet_accountState());
case ManualDns:
return f(to_dns_accountState());
case PaymentChannel:
return f(to_payment_channel_accountState());
}
UNREACHABLE();
}
td::Result> to_fullAccountState() const {
TRY_RESULT(account_state, to_accountState());
return tonlib_api::make_object(
tonlib_api::make_object(get_address().rserialize(true)), get_balance(),
to_transaction_id(raw().info), to_tonlib_api(raw().block_id), get_sync_time(), std::move(account_state),
get_wallet_revision());
}
td::Result> to_shardAccountCell() const {
auto account_root = raw_.info.root;
if (account_root.is_null()) {
block::gen::Account().cell_pack_account_none(account_root);
}
auto cell = vm::CellBuilder().store_ref(account_root).store_bits(raw_.info.last_trans_hash.as_bitslice()).store_long(raw_.info.last_trans_lt).finalize();
return tonlib_api::make_object(to_bytes(cell));
}
td::Result> to_shardAccountCellSlice() const {
auto account_root = raw_.info.root;
if (account_root.is_null()) {
block::gen::Account().cell_pack_account_none(account_root);
}
return vm::CellBuilder().store_ref(account_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,
Unknown,
WalletV3,
HighloadWalletV1,
HighloadWalletV2,
ManualDns,
PaymentChannel,
RestrictedWallet
};
WalletType get_wallet_type() const {
return wallet_type_;
}
td::int32 get_wallet_revision() const {
return wallet_revision_;
}
bool is_wallet() const {
switch (get_wallet_type()) {
case AccountState::Empty:
case AccountState::Unknown:
case AccountState::ManualDns:
case AccountState::PaymentChannel:
return false;
case AccountState::WalletV3:
case AccountState::HighloadWalletV1:
case AccountState::HighloadWalletV2:
case AccountState::RestrictedWallet:
return true;
}
UNREACHABLE();
return false;
}
td::unique_ptr get_wallet() const {
switch (get_wallet_type()) {
case AccountState::Empty:
case AccountState::Unknown:
case AccountState::ManualDns:
case AccountState::PaymentChannel:
return {};
case AccountState::WalletV3:
return td::make_unique(get_smc_state());
case AccountState::HighloadWalletV1:
return td::make_unique(get_smc_state());
case AccountState::HighloadWalletV2:
return td::make_unique(get_smc_state());
case AccountState::RestrictedWallet:
return td::make_unique(get_smc_state());
}
UNREACHABLE();
return {};
}
bool is_frozen() const {
return !raw_.frozen_hash.empty();
}
const block::StdAddress& get_address() const {
return address_;
}
void make_non_bounceable() {
address_.bounceable = false;
}
td::uint32 get_sync_time() const {
return raw_.info.gen_utime;
}
ton::BlockIdExt get_block_id() const {
return raw_.block_id;
}
td::int64 get_balance() const {
return raw_.balance;
}
const RawAccountState& raw() const {
return raw_;
}
WalletType guess_type_by_init_state(tonlib_api::InitialAccountState& initial_account_state) {
if (wallet_type_ != WalletType::Empty) {
return wallet_type_;
}
downcast_call(
initial_account_state,
td::overloaded(
[](auto& x) {},
[&](tonlib_api::wallet_v3_initialAccountState& v3wallet) {
for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::WalletV3)) {
auto init_data = to_init_data(v3wallet);
if (init_data.is_error()) {
continue;
}
auto wallet = ton::WalletV3::create(init_data.move_as_ok(), revision);
if (!(wallet->get_address(ton::masterchainId) == address_ ||
wallet->get_address(ton::basechainId) == address_)) {
continue;
}
wallet_type_ = WalletType::WalletV3;
wallet_revision_ = revision;
set_new_state(wallet->get_state());
break;
}
},
[&](tonlib_api::rwallet_initialAccountState& rwallet) {
for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::RestrictedWallet)) {
auto r_init_data = to_init_data(rwallet);
if (r_init_data.is_error()) {
continue;
}
auto wallet = ton::RestrictedWallet::create(r_init_data.move_as_ok(), revision);
if (!(wallet->get_address(ton::masterchainId) == address_ ||
wallet->get_address(ton::basechainId) == address_)) {
continue;
}
wallet_type_ = WalletType::RestrictedWallet;
wallet_revision_ = revision;
set_new_state(wallet->get_state());
break;
}
},
[&](tonlib_api::pchan_initialAccountState& pchan) {
for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::PaymentChannel)) {
auto r_conf = to_pchan_config(pchan);
if (r_conf.is_error()) {
continue;
}
auto conf = r_conf.move_as_ok();
auto wallet = ton::PaymentChannel::create(conf, revision);
if (!(wallet->get_address(ton::masterchainId) == address_ ||
wallet->get_address(ton::basechainId) == address_)) {
continue;
}
wallet_type_ = WalletType::PaymentChannel;
wallet_revision_ = revision;
set_new_state(wallet->get_state());
break;
}
}));
return wallet_type_;
}
WalletType guess_type_by_public_key(td::Ed25519::PublicKey& key) {
if (wallet_type_ != WalletType::Empty) {
return wallet_type_;
}
auto wallet_id = static_cast(address_.workchain + wallet_id_);
ton::WalletV3::InitData init_data{key.as_octet_string(), wallet_id};
auto o_revision = ton::WalletV3::guess_revision(address_, init_data);
if (o_revision) {
wallet_type_ = WalletType::WalletV3;
wallet_revision_ = o_revision.value();
set_new_state(ton::WalletV3::get_init_state(wallet_revision_, init_data));
return wallet_type_;
}
o_revision = ton::HighloadWalletV2::guess_revision(address_, init_data);
if (o_revision) {
wallet_type_ = WalletType::HighloadWalletV2;
wallet_revision_ = o_revision.value();
set_new_state(ton::HighloadWallet::get_init_state(wallet_revision_, init_data));
return wallet_type_;
}
o_revision = ton::HighloadWallet::guess_revision(address_, init_data);
if (o_revision) {
wallet_type_ = WalletType::HighloadWalletV1;
wallet_revision_ = o_revision.value();
set_new_state(ton::HighloadWallet::get_init_state(wallet_revision_, init_data));
return wallet_type_;
}
o_revision = ton::ManualDns::guess_revision(address_, key, wallet_id);
if (o_revision) {
wallet_type_ = WalletType::ManualDns;
wallet_revision_ = o_revision.value();
auto dns = ton::ManualDns::create(key, wallet_id, wallet_revision_);
set_new_state(dns->get_state());
return wallet_type_;
}
return wallet_type_;
}
WalletType guess_type_default(td::Ed25519::PublicKey& key) {
if (wallet_type_ != WalletType::Empty) {
return wallet_type_;
}
ton::WalletV3::InitData init_data(key.as_octet_string(), wallet_id_ + address_.workchain);
set_new_state(ton::WalletV3::get_init_state(0, init_data));
wallet_type_ = WalletType::WalletV3;
return wallet_type_;
}
ton::SmartContract::State get_smc_state() const {
return {raw_.code, raw_.data};
}
td::Ref get_raw_state() {
return raw_.state;
}
void set_new_state(ton::SmartContract::State state) {
raw_.code = std::move(state.code);
raw_.data = std::move(state.data);
raw_.state = ton::GenericAccount::get_init_state(raw_.code, raw_.data);
has_new_state_ = true;
}
td::Ref get_new_state() const {
if (!has_new_state_) {
return {};
}
return raw_.state;
}
private:
block::StdAddress address_;
RawAccountState raw_;
WalletType wallet_type_{Unknown};
td::int32 wallet_revision_{0};
td::uint32 wallet_id_{0};
bool has_new_state_{false};
WalletType guess_type() {
if (raw_.code.is_null()) {
wallet_type_ = WalletType::Empty;
return wallet_type_;
}
auto code_hash = raw_.code->get_hash();
auto o_revision = ton::WalletV3::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::WalletV3;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
o_revision = ton::HighloadWalletV2::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::HighloadWalletV2;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
o_revision = ton::HighloadWallet::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::HighloadWalletV1;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
o_revision = ton::ManualDns::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::ManualDns;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
o_revision = ton::PaymentChannel::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::PaymentChannel;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
o_revision = ton::RestrictedWallet::guess_revision(code_hash);
if (o_revision) {
wallet_type_ = WalletType::RestrictedWallet;
wallet_revision_ = o_revision.value();
return wallet_type_;
}
LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice());
wallet_type_ = WalletType::Unknown;
return wallet_type_;
}
};
class Query {
public:
struct Raw {
td::unique_ptr source;
std::vector> destinations;
td::uint32 valid_until{std::numeric_limits::max()};
td::Ref message;
td::Ref new_state;
td::Ref message_body;
};
Query(Raw&& raw) : raw_(std::move(raw)) {
}
td::Ref get_message() const {
return raw_.message;
}
td::Ref get_message_body() const {
return raw_.message_body;
}
td::Ref get_init_state() const {
return raw_.new_state;
}
vm::CellHash get_body_hash() const {
return raw_.message_body->get_hash();
}
td::uint32 get_valid_until() const {
return raw_.valid_until;
}
// ported from block/transaction.cpp
// TODO: reuse code
static td::RefInt256 compute_threshold(const block::GasLimitsPrices& cfg) {
auto gas_price256 = td::RefInt256{true, cfg.gas_price};
if (cfg.gas_limit > cfg.flat_gas_limit) {
return td::rshift(gas_price256 * (cfg.gas_limit - cfg.flat_gas_limit), 16, 1) +
td::make_refint(cfg.flat_gas_price);
} else {
return td::make_refint(cfg.flat_gas_price);
}
}
static td::uint64 gas_bought_for(td::RefInt256 nanograms, td::RefInt256 max_gas_threshold,
const block::GasLimitsPrices& cfg) {
if (nanograms.is_null() || sgn(nanograms) < 0) {
return 0;
}
if (nanograms >= max_gas_threshold) {
return cfg.gas_limit;
}
if (nanograms < cfg.flat_gas_price) {
return 0;
}
auto gas_price256 = td::RefInt256{true, cfg.gas_price};
auto res = td::div((std::move(nanograms) - cfg.flat_gas_price) << 16, gas_price256);
return res->to_long() + cfg.flat_gas_limit;
}
static td::RefInt256 compute_gas_price(td::uint64 gas_used, const block::GasLimitsPrices& cfg) {
auto gas_price256 = td::RefInt256{true, cfg.gas_price};
return gas_used <= cfg.flat_gas_limit
? td::make_refint(cfg.flat_gas_price)
: td::rshift(gas_price256 * (gas_used - cfg.flat_gas_limit), 16, 1) + cfg.flat_gas_price;
}
static vm::GasLimits compute_gas_limits(td::RefInt256 balance, const block::GasLimitsPrices& cfg) {
vm::GasLimits res;
// Compute gas limits
if (false /*account.is_special*/) {
res.gas_max = cfg.special_gas_limit;
} else {
res.gas_max = gas_bought_for(balance, compute_threshold(cfg), cfg);
}
res.gas_credit = 0;
if (false /*trans_type != tr_ord*/) {
// may use all gas that can be bought using remaining balance
res.gas_limit = res.gas_max;
} else {
// originally use only gas bought using remaining message balance
// if the message is "accepted" by the smart contract, the gas limit will be set to gas_max
res.gas_limit = gas_bought_for(td::make_refint(0) /*msg balance remaining*/, compute_threshold(cfg), cfg);
if (true /*!block::tlb::t_Message.is_internal(in_msg)*/) {
// external messages carry no balance, give them some credit to check whether they are accepted
res.gas_credit = std::min(static_cast(cfg.gas_credit), static_cast(res.gas_max));
}
}
LOG(DEBUG) << "gas limits: max=" << res.gas_max << ", limit=" << res.gas_limit << ", credit=" << res.gas_credit;
return res;
}
struct Fee {
td::int64 in_fwd_fee{0};
td::int64 storage_fee{0};
td::int64 gas_fee{0};
td::int64 fwd_fee{0};
auto to_tonlib_api() const {
return tonlib_api::make_object(in_fwd_fee, storage_fee, gas_fee, fwd_fee);
}
};
td::Result calc_fwd_fees(td::Ref list, block::MsgPrices** msg_prices, bool is_masterchain) {
td::int64 res = 0;
std::vector> actions;
int n{0};
int max_actions = 20;
while (true) {
actions.push_back(list);
auto cs = load_cell_slice(std::move(list));
if (!cs.size_ext()) {
break;
}
if (!cs.have_refs()) {
return td::Status::Error("action list invalid: entry found with data but no next reference");
}
list = cs.prefetch_ref();
n++;
if (n > max_actions) {
return td::Status::Error(PSLICE() << "action list too long: more than " << max_actions << " actions");
}
}
for (int i = n - 1; i >= 0; --i) {
vm::CellSlice cs = load_cell_slice(actions[i]);
CHECK(cs.fetch_ref().not_null());
int tag = block::gen::t_OutAction.get_tag(cs);
CHECK(tag >= 0);
switch (tag) {
case block::gen::OutAction::action_set_code:
return td::Status::Error("estimate_fee: action_set_code unsupported");
case block::gen::OutAction::action_send_msg: {
block::gen::OutAction::Record_action_send_msg act_rec;
// mode: +128 = attach all remaining balance, +64 = attach all remaining balance of the inbound message, +1 = pay message fees, +2 = skip if message cannot be sent
if (!tlb::unpack_exact(cs, act_rec) || (act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) {
return td::Status::Error("estimate_fee: can't parse send_msg");
}
block::gen::MessageRelaxed::Record msg;
if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) {
return td::Status::Error("estimate_fee: can't parse send_msg");
}
bool dest_is_masterchain = false;
if (block::gen::t_CommonMsgInfoRelaxed.get_tag(*msg.info) == block::gen::CommonMsgInfoRelaxed::int_msg_info) {
block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info;
if (!tlb::csr_unpack(msg.info, info)) {
return td::Status::Error("estimate_fee: can't parse send_msg");
}
auto dest_addr = info.dest;
if (!dest_addr->prefetch_ulong(1)) {
return td::Status::Error("estimate_fee: messages with external addresses are unsupported");
}
int tag = block::gen::t_MsgAddressInt.get_tag(*dest_addr);
if (tag == block::gen::MsgAddressInt::addr_std) {
block::gen::MsgAddressInt::Record_addr_std recs;
if (!tlb::csr_unpack(dest_addr, recs)) {
return td::Status::Error("estimate_fee: can't parse send_msg");
}
dest_is_masterchain = recs.workchain_id == ton::masterchainId;
}
}
vm::CellStorageStat sstat; // for message size
sstat.add_used_storage(msg.init, true, 3); // message init
sstat.add_used_storage(msg.body, true, 3); // message body (the root cell itself is not counted)
res += msg_prices[is_masterchain || dest_is_masterchain]->compute_fwd_fees(sstat.cells, sstat.bits);
break;
}
case block::gen::OutAction::action_reserve_currency:
LOG(INFO) << "skip action_reserve_currency";
continue;
}
}
return res;
}
td::Result>> estimate_fees(bool ignore_chksig, std::shared_ptr& cfg, vm::Dictionary& libraries) {
// gas fees
bool is_masterchain = raw_.source->get_address().workchain == ton::masterchainId;
TRY_RESULT(gas_limits_prices, cfg->get_gas_limits_prices(is_masterchain));
TRY_RESULT(storage_prices, cfg->get_storage_prices());
TRY_RESULT(masterchain_msg_prices, cfg->get_msg_prices(true));
TRY_RESULT(basechain_msg_prices, cfg->get_msg_prices(false));
block::MsgPrices* msg_prices[2] = {&basechain_msg_prices, &masterchain_msg_prices};
auto storage_fee_256 = block::StoragePrices::compute_storage_fees(
raw_.source->get_sync_time(), storage_prices, raw_.source->raw().storage_stat,
raw_.source->raw().storage_last_paid, false, is_masterchain);
auto storage_fee = storage_fee_256.is_null() ? 0 : storage_fee_256->to_long();
auto smc = ton::SmartContract::create(raw_.source->get_smc_state());
td::int64 in_fwd_fee = 0;
{
vm::CellStorageStat sstat; // for message size
sstat.add_used_storage(raw_.message, true, 3); // message init
in_fwd_fee += msg_prices[is_masterchain]->compute_fwd_fees(sstat.cells, sstat.bits);
}
vm::GasLimits gas_limits = compute_gas_limits(td::make_refint(raw_.source->get_balance()), gas_limits_prices);
auto res = smc.write().send_external_message(raw_.message_body, ton::SmartContract::Args()
.set_limits(gas_limits)
.set_balance(raw_.source->get_balance())
.set_now(raw_.source->get_sync_time())
.set_ignore_chksig(ignore_chksig)
.set_address(raw_.source->get_address())
.set_config(cfg).set_libraries(libraries));
td::int64 fwd_fee = 0;
if (res.success) {
LOG(DEBUG) << "output actions:\n"
<< block::gen::OutList{output_actions_count(res.actions)}.as_string_ref(res.actions);
TRY_RESULT_ASSIGN(fwd_fee, calc_fwd_fees(res.actions, msg_prices, is_masterchain));
}
auto gas_fee = res.accepted ? compute_gas_price(res.gas_used, gas_limits_prices)->to_long() : 0;
LOG(INFO) << storage_fee << " " << in_fwd_fee << " " << gas_fee << " " << fwd_fee << " " << res.gas_used;
Fee fee;
fee.in_fwd_fee = in_fwd_fee;
fee.storage_fee = storage_fee;
fee.gas_fee = gas_fee;
fee.fwd_fee = fwd_fee;
std::vector dst_fees;
for (auto& destination : raw_.destinations) {
bool dest_is_masterchain = destination && destination->get_address().workchain == ton::masterchainId;
TRY_RESULT(dest_gas_limits_prices, cfg->get_gas_limits_prices(dest_is_masterchain));
auto dest_storage_fee_256 =
destination ? block::StoragePrices::compute_storage_fees(
destination->get_sync_time(), storage_prices, destination->raw().storage_stat,
destination->raw().storage_last_paid, false, is_masterchain)
: td::make_refint(0);
Fee dst_fee;
auto dest_storage_fee = dest_storage_fee_256.is_null() ? 0 : dest_storage_fee_256->to_long();
if (destination && destination->get_wallet_type() != AccountState::WalletType::Empty) {
dst_fee.gas_fee = dest_gas_limits_prices.flat_gas_price;
dst_fee.storage_fee = dest_storage_fee;
}
dst_fees.push_back(dst_fee);
}
return std::make_pair(fee, dst_fees);
}
private:
Raw raw_;
static int output_actions_count(td::Ref list) {
int i = -1;
do {
++i;
list = load_cell_slice(std::move(list)).prefetch_ref();
} while (list.not_null());
return i;
}
};
td::Result to_balance_or_throw(td::Ref balance_ref) {
vm::CellSlice balance_slice = *balance_ref;
auto balance = block::tlb::t_Grams.as_integer_skip(balance_slice);
if (balance.is_null()) {
return td::Status::Error("Failed to unpack balance");
}
auto res = balance->to_long();
if (res == td::int64(~0ULL << 63)) {
return td::Status::Error("Failed to unpack balance (2)");
}
return res;
}
td::Result to_balance(td::Ref balance_ref) {
return TRY_VM(to_balance_or_throw(std::move(balance_ref)));
}
class GetTransactionHistory : public td::actor::Actor {
public:
GetTransactionHistory(ExtClientRef ext_client_ref, block::StdAddress address, ton::LogicalTime lt, ton::Bits256 hash, td::int32 count,
td::actor::ActorShared<> parent, td::Promise promise)
: address_(std::move(address))
, lt_(std::move(lt))
, hash_(std::move(hash))
, count_(count)
, parent_(std::move(parent))
, promise_(std::move(promise)) {
client_.set_client(ext_client_ref);
}
private:
block::StdAddress address_;
ton::LogicalTime lt_;
ton::Bits256 hash_;
ExtClient client_;
td::int32 count_;
td::actor::ActorShared<> parent_;
td::Promise promise_;
void check(td::Status status) {
if (status.is_error()) {
promise_.set_error(std::move(status));
stop();
}
}
void with_transactions(
td::Result> r_transactions) {
check(do_with_transactions(std::move(r_transactions)));
stop();
}
td::Status do_with_transactions(
td::Result> r_transactions) {
TRY_RESULT(transactions, std::move(r_transactions));
TRY_RESULT_PREFIX(info, TRY_VM(do_with_transactions(std::move(transactions))), TonlibError::ValidateTransactions());
promise_.set_value(std::move(info));
return td::Status::OK();
}
td::Result do_with_transactions(
ton::lite_api::object_ptr transactions) {
std::vector blkids;
for (auto& id : transactions->ids_) {
blkids.push_back(ton::create_block_id(std::move(id)));
}
return do_with_transactions(std::move(blkids), std::move(transactions->transactions_));
}
td::Result do_with_transactions(std::vector blkids,
td::BufferSlice transactions) {
//LOG(INFO) << "got up to " << count_ << " transactions for " << address_ << " from last transaction " << lt_ << ":"
//<< hash_.to_hex();
block::TransactionList list;
list.blkids = std::move(blkids);
list.hash = hash_;
list.lt = lt_;
list.transactions_boc = std::move(transactions);
TRY_RESULT(info, list.validate());
if (info.transactions.size() > static_cast(count_)) {
LOG(WARNING) << "obtained " << info.transactions.size() << " transaction, but only " << count_
<< " have been requested";
}
return info;
}
void start_up() override {
if (lt_ == 0) {
promise_.set_value(block::TransactionList::Info());
stop();
return;
}
client_.send_query(
ton::lite_api::liteServer_getTransactions(
count_, ton::create_tl_object(address_.workchain, address_.addr), lt_,
hash_),
[self = this](auto r_transactions) { self->with_transactions(std::move(r_transactions)); });
}
void hangup() override {
check(TonlibError::Cancelled());
}
};
class RemoteRunSmcMethod : public td::actor::Actor {
public:
RemoteRunSmcMethod(ExtClientRef ext_client_ref, int_api::RemoteRunSmcMethod query, td::actor::ActorShared<> parent,
td::Promise&& promise)
: query_(std::move(query)), promise_(std::move(promise)), parent_(std::move(parent)) {
client_.set_client(ext_client_ref);
}
private:
int_api::RemoteRunSmcMethod query_;
td::Promise promise_;
td::actor::ActorShared<> parent_;
ExtClient client_;
void with_run_method_result(
td::Result> r_run_method_result) {
check(do_with_run_method_result(std::move(r_run_method_result)));
}
td::Status do_with_run_method_result(
td::Result> r_run_method_result) {
TRY_RESULT(run_method_result, std::move(r_run_method_result));
TRY_RESULT_PREFIX(state, TRY_VM(do_with_run_method_result(std::move(run_method_result))),
TonlibError::ValidateAccountState());
promise_.set_value(std::move(state));
stop();
return td::Status::OK();
}
td::Result do_with_run_method_result(
ton::tl_object_ptr run_method_result) {
auto account_state = create_account_state(run_method_result);
TRY_RESULT(info, account_state.validate(query_.block_id.value(), query_.address));
auto serialized_state = account_state.state.clone();
int_api::RemoteRunSmcMethod::ReturnType res;
res.block_id = query_.block_id.value();
auto cell = info.root;
if (cell.is_null()) {
return res;
}
block::gen::Account::Record_account account;
if (!tlb::unpack_cell(cell, account)) {
return td::Status::Error("Failed to unpack Account");
}
block::gen::AccountStorage::Record storage;
if (!tlb::csr_unpack(account.storage, storage)) {
return td::Status::Error("Failed to unpack AccountStorage");
}
auto state_tag = block::gen::t_AccountState.get_tag(*storage.state);
if (state_tag < 0) {
return td::Status::Error("Failed to parse AccountState tag");
}
if (state_tag != block::gen::AccountState::account_active) {
return td::Status::Error("Account is not active");
}
block::gen::AccountState::Record_account_active state;
if (!tlb::csr_unpack(storage.state, state)) {
return td::Status::Error("Failed to parse AccountState");
}
block::gen::StateInit::Record state_init;
if (!tlb::csr_unpack(state.x, state_init)) {
return td::Status::Error("Failed to parse StateInit");
}
state_init.code->prefetch_maybe_ref(res.smc_state.code);
state_init.data->prefetch_maybe_ref(res.smc_state.data);
return res;
}
void with_last_block(td::Result r_last_block) {
check(do_with_last_block(std::move(r_last_block)));
}
td::Status with_block_id() {
TRY_RESULT(method_id, query_.args.get_method_id());
TRY_RESULT(serialized_stack, query_.args.get_serialized_stack());
client_.send_query(
//liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult;
ton::lite_api::liteServer_runSmcMethod(
0x1f, ton::create_tl_lite_block_id(query_.block_id.value()),
ton::create_tl_object(query_.address.workchain, query_.address.addr),
method_id, std::move(serialized_stack)),
[self = this](auto r_state) { self->with_run_method_result(std::move(r_state)); },
query_.block_id.value().id.seqno);
return td::Status::OK();
}
td::Status do_with_last_block(td::Result r_last_block) {
TRY_RESULT(last_block, std::move(r_last_block));
query_.block_id = std::move(last_block.last_block_id);
with_block_id();
return td::Status::OK();
}
void start_up() override {
if (query_.block_id) {
check(with_block_id());
} else {
client_.with_last_block(
[self = this](td::Result r_last_block) { self->with_last_block(std::move(r_last_block)); });
}
}
void check(td::Status status) {
if (status.is_error()) {
promise_.set_error(std::move(status));
stop();
}
}
void hangup() override {
check(TonlibError::Cancelled());
}
};
class GetRawAccountState : public td::actor::Actor {
public:
GetRawAccountState(ExtClientRef ext_client_ref, block::StdAddress address, td::optional block_id,
td::actor::ActorShared<> parent, td::Promise&& promise)
: address_(std::move(address))
, block_id_(std::move(block_id))
, promise_(std::move(promise))
, parent_(std::move(parent)) {
client_.set_client(ext_client_ref);
}
private:
block::StdAddress address_;
td::optional block_id_;
td::Promise promise_;
td::actor::ActorShared<> parent_;
ExtClient client_;
void with_account_state(td::Result> r_account_state) {
check(do_with_account_state(std::move(r_account_state)));
}
td::Status do_with_account_state(
td::Result> r_raw_account_state) {
TRY_RESULT(raw_account_state, std::move(r_raw_account_state));
TRY_RESULT_PREFIX(state, TRY_VM(do_with_account_state(std::move(raw_account_state))),
TonlibError::ValidateAccountState());
promise_.set_value(std::move(state));
stop();
return td::Status::OK();
}
td::Result do_with_account_state(
ton::tl_object_ptr raw_account_state) {
auto account_state = create_account_state(std::move(raw_account_state));
TRY_RESULT(info, account_state.validate(block_id_.value(), address_));
auto serialized_state = account_state.state.clone();
RawAccountState res;
res.block_id = block_id_.value();
res.info = std::move(info);
auto cell = res.info.root;
//std::ostringstream outp;
//block::gen::t_Account.print_ref(outp, cell);
//LOG(INFO) << outp.str();
if (cell.is_null()) {
return res;
}
block::gen::Account::Record_account account;
if (!tlb::unpack_cell(cell, account)) {
return td::Status::Error("Failed to unpack Account");
}
{
block::gen::StorageInfo::Record storage_info;
if (!tlb::csr_unpack(account.storage_stat, storage_info)) {
return td::Status::Error("Failed to unpack StorageInfo");
}
res.storage_last_paid = storage_info.last_paid;
td::RefInt256 due_payment;
if (storage_info.due_payment->prefetch_ulong(1) == 1) {
vm::CellSlice& cs2 = storage_info.due_payment.write();
cs2.advance(1);
due_payment = block::tlb::t_Grams.as_integer_skip(cs2);
if (due_payment.is_null() || !cs2.empty_ext()) {
return td::Status::Error("Failed to upack due_payment");
}
} else {
due_payment = td::RefInt256{true, 0};
}
block::gen::StorageUsed::Record storage_used;
if (!tlb::csr_unpack(storage_info.used, storage_used)) {
return td::Status::Error("Failed to unpack StorageInfo");
}
unsigned long long u = 0;
vm::CellStorageStat storage_stat;
u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.cells);
u |= storage_stat.bits = block::tlb::t_VarUInteger_7.as_uint(*storage_used.bits);
u |= storage_stat.public_cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.public_cells);
//LOG(DEBUG) << "last_paid=" << res.storage_last_paid << "; cells=" << storage_stat.cells
//<< " bits=" << storage_stat.bits << " public_cells=" << storage_stat.public_cells;
if (u == std::numeric_limits::max()) {
return td::Status::Error("Failed to unpack StorageStat");
}
res.storage_stat = storage_stat;
}
block::gen::AccountStorage::Record storage;
if (!tlb::csr_unpack(account.storage, storage)) {
return td::Status::Error("Failed to unpack AccountStorage");
}
TRY_RESULT(balance, to_balance(storage.balance));
res.balance = balance;
auto state_tag = block::gen::t_AccountState.get_tag(*storage.state);
if (state_tag < 0) {
return td::Status::Error("Failed to parse AccountState tag");
}
if (state_tag == block::gen::AccountState::account_frozen) {
block::gen::AccountState::Record_account_frozen state;
if (!tlb::csr_unpack(storage.state, state)) {
return td::Status::Error("Failed to parse AccountState");
}
res.frozen_hash = state.state_hash.as_slice().str();
return res;
}
if (state_tag != block::gen::AccountState::account_active) {
return res;
}
block::gen::AccountState::Record_account_active state;
if (!tlb::csr_unpack(storage.state, state)) {
return td::Status::Error("Failed to parse AccountState");
}
block::gen::StateInit::Record state_init;
res.state = vm::CellBuilder().append_cellslice(state.x).finalize();
if (!tlb::csr_unpack(state.x, state_init)) {
return td::Status::Error("Failed to parse StateInit");
}
state_init.code->prefetch_maybe_ref(res.code);
state_init.data->prefetch_maybe_ref(res.data);
return res;
}
void with_last_block(td::Result r_last_block) {
check(do_with_last_block(std::move(r_last_block)));
}
void with_block_id() {
client_.send_query(
ton::lite_api::liteServer_getAccountState(
ton::create_tl_lite_block_id(block_id_.value()),
ton::create_tl_object(address_.workchain, address_.addr)),
[self = this](auto r_state) { self->with_account_state(std::move(r_state)); });
}
td::Status do_with_last_block(td::Result r_last_block) {
TRY_RESULT(last_block, std::move(r_last_block));
block_id_ = std::move(last_block.last_block_id);
with_block_id();
return td::Status::OK();
}
void start_up() override {
if (block_id_) {
with_block_id();
} else {
client_.with_last_block(
[self = this](td::Result r_last_block) { self->with_last_block(std::move(r_last_block)); });
}
}
void check(td::Status status) {
if (status.is_error()) {
promise_.set_error(std::move(status));
stop();
}
}
void hangup() override {
check(TonlibError::Cancelled());
}
};
class GetMasterchainBlockSignatures : public td::actor::Actor {
public:
GetMasterchainBlockSignatures(ExtClientRef ext_client_ref, ton::BlockSeqno seqno, td::actor::ActorShared<> parent,
td::Promise>&& promise)
: block_id_short_(ton::masterchainId, ton::shardIdAll, seqno)
, parent_(std::move(parent))
, promise_(std::move(promise)) {
client_.set_client(ext_client_ref);
}
void start_up() override {
if (block_id_short_.seqno == 0) {
abort(td::Status::Error("can't get signatures of block #0"));
return;
}
client_.with_last_block([SelfId = actor_id(this)](td::Result R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::got_last_block, R.ok().last_block_id);
}
});
}
void got_last_block(ton::BlockIdExt id) {
last_block_ = id;
prev_block_id_short_ = block_id_short_;
prev_block_id_short_.seqno--;
client_.send_query(
ton::lite_api::liteServer_lookupBlock(1, ton::create_tl_lite_block_id_simple(prev_block_id_short_), 0, 0),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::got_prev_block_id,
ton::create_block_id(R.ok()->id_));
}
});
}
void got_prev_block_id(ton::BlockIdExt id) {
prev_block_id_ = id;
if (prev_block_id_.id != prev_block_id_short_) {
abort(td::Status::Error("got incorrect block header from liteserver"));
return;
}
client_.send_query(
ton::lite_api::liteServer_getBlockProof(0x1001, ton::create_tl_lite_block_id(last_block_),
ton::create_tl_lite_block_id(prev_block_id_)),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::got_prev_proof, R.move_as_ok());
}
});
}
void got_prev_proof(lite_api_ptr proof) {
auto R = liteclient::deserialize_proof_chain(std::move(proof));
if (R.is_error()) {
abort(R.move_as_error());
return;
}
auto chain = R.move_as_ok();
if (chain->from != last_block_ || chain->to != prev_block_id_ || !chain->complete) {
abort(td::Status::Error("got invalid proof chain"));
return;
}
auto S = chain->validate();
if (S.is_error()) {
abort(std::move(S));
return;
}
client_.send_query(
ton::lite_api::liteServer_lookupBlock(1, ton::create_tl_lite_block_id_simple(block_id_short_), 0, 0),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::got_block_id,
ton::create_block_id(R.ok()->id_));
}
});
}
void got_block_id(ton::BlockIdExt id) {
block_id_ = id;
client_.send_query(
ton::lite_api::liteServer_getBlockProof(0x1001, ton::create_tl_lite_block_id(prev_block_id_),
ton::create_tl_lite_block_id(block_id_)),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetMasterchainBlockSignatures::got_proof, R.move_as_ok());
}
});
}
void got_proof(lite_api_ptr proof) {
auto R = liteclient::deserialize_proof_chain(std::move(proof));
if (R.is_error()) {
abort(R.move_as_error());
return;
}
auto chain = R.move_as_ok();
if (chain->from != prev_block_id_ || chain->to != block_id_ || !chain->complete || chain->links.empty() ||
chain->last_link().signatures.empty()) {
abort(td::Status::Error("got invalid proof chain"));
return;
}
auto S = chain->validate();
if (S.is_error()) {
abort(std::move(S));
return;
}
std::vector> signatures;
for (const auto& s : chain->last_link().signatures) {
signatures.push_back(ton::create_tl_object(s.node, s.signature.as_slice().str()));
}
promise_.set_result(
ton::create_tl_object(to_tonlib_api(block_id_), std::move(signatures)));
stop();
}
void abort(td::Status error) {
promise_.set_error(std::move(error));
stop();
}
private:
ton::BlockId block_id_short_;
td::actor::ActorShared<> parent_;
td::Promise> promise_;
ExtClient client_;
ton::BlockIdExt block_id_;
ton::BlockId prev_block_id_short_;
ton::BlockIdExt prev_block_id_;
ton::BlockIdExt last_block_;
};
class GetShardBlockProof : public td::actor::Actor {
public:
GetShardBlockProof(ExtClientRef ext_client_ref, ton::BlockIdExt id, ton::BlockIdExt from,
td::actor::ActorShared<> parent,
td::Promise>&& promise)
: id_(id), from_(from), parent_(std::move(parent)), promise_(std::move(promise)) {
client_.set_client(ext_client_ref);
}
void start_up() override {
if (from_.is_masterchain_ext()) {
got_from_block(from_);
} else {
client_.with_last_block([SelfId = actor_id(this)](td::Result R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetShardBlockProof::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetShardBlockProof::got_from_block, R.move_as_ok().last_block_id);
}
});
}
}
void got_from_block(ton::BlockIdExt from) {
from_ = from;
CHECK(from_.is_masterchain_ext());
client_.send_query(
ton::lite_api::liteServer_getShardBlockProof(ton::create_tl_lite_block_id(id_)),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetShardBlockProof::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetShardBlockProof::got_shard_block_proof, R.move_as_ok());
}
});
}
void got_shard_block_proof(lite_api_ptr result) {
mc_id_ = create_block_id(std::move(result->masterchain_id_));
if (!mc_id_.is_masterchain_ext()) {
abort(td::Status::Error("got invalid masterchain block id"));
return;
}
if (result->links_.size() > 8) {
abort(td::Status::Error("chain is too long"));
return;
}
ton::BlockIdExt cur_id = mc_id_;
try {
for (auto& link : result->links_) {
ton::BlockIdExt prev_id = create_block_id(link->id_);
td::BufferSlice proof = std::move(link->proof_);
auto R = vm::std_boc_deserialize(proof);
if (R.is_error()) {
abort(TonlibError::InvalidBagOfCells("proof"));
return;
}
auto block_root = vm::MerkleProof::virtualize(R.move_as_ok(), 1);
if (cur_id.root_hash != block_root->get_hash().bits()) {
abort(td::Status::Error("invalid block hash in proof"));
return;
}
if (cur_id.is_masterchain()) {
block::gen::Block::Record blk;
block::gen::BlockExtra::Record extra;
block::gen::McBlockExtra::Record mc_extra;
if (!tlb::unpack_cell(block_root, blk) || !tlb::unpack_cell(blk.extra, extra) || !extra.custom->have_refs() ||
!tlb::unpack_cell(extra.custom->prefetch_ref(), mc_extra)) {
abort(td::Status::Error("cannot unpack block header"));
return;
}
block::ShardConfig shards(mc_extra.shard_hashes->prefetch_ref());
td::Ref shard_hash = shards.get_shard_hash(prev_id.shard_full(), true);
if (shard_hash.is_null() || shard_hash->top_block_id() != prev_id) {
abort(td::Status::Error("invalid proof chain: prev block is not in mc shard list"));
return;
}
} else {
std::vector prev;
ton::BlockIdExt mc_blkid;
bool after_split;
td::Status S = block::unpack_block_prev_blk_try(block_root, cur_id, prev, mc_blkid, after_split);
if (S.is_error()) {
abort(std::move(S));
return;
}
CHECK(prev.size() == 1 || prev.size() == 2);
bool found = prev_id == prev[0] || (prev.size() == 2 && prev_id == prev[1]);
if (!found) {
abort(td::Status::Error("invalid proof chain: prev block is not in prev blocks list"));
return;
}
}
links_.emplace_back(prev_id, std::move(proof));
cur_id = prev_id;
}
} catch (vm::VmVirtError& err) {
abort(err.as_status());
return;
}
if (cur_id != id_) {
abort(td::Status::Error("got invalid proof chain"));
return;
}
if (mc_id_.seqno() > from_.seqno()) {
abort(td::Status::Error("from mc block is too old"));
return;
}
client_.send_query(
ton::lite_api::liteServer_getBlockProof(0x1001, ton::create_tl_lite_block_id(from_),
ton::create_tl_lite_block_id(mc_id_)),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &GetShardBlockProof::abort, R.move_as_error());
} else {
td::actor::send_closure(SelfId, &GetShardBlockProof::got_mc_proof, R.move_as_ok());
}
});
}
void got_mc_proof(lite_api_ptr result) {
auto R = liteclient::deserialize_proof_chain(std::move(result));
if (R.is_error()) {
abort(R.move_as_error());
return;
}
auto chain = R.move_as_ok();
if (chain->from != from_ || chain->to != mc_id_ || !chain->complete || chain->link_count() > 1) {
abort(td::Status::Error("got invalid proof chain"));
return;
}
auto S = chain->validate();
if (S.is_error()) {
abort(std::move(S));
return;
}
std::vector> links;
std::vector> mc_proof;
for (const auto& p : links_) {
links.push_back(
ton::create_tl_object(to_tonlib_api(p.first), p.second.as_slice().str()));
}
if (chain->link_count() == 1) {
auto& link = chain->last_link();
td::BufferSlice dest_proof = vm::std_boc_serialize(link.dest_proof).move_as_ok();
td::BufferSlice proof = vm::std_boc_serialize(link.proof).move_as_ok();
td::BufferSlice state_proof = vm::std_boc_serialize(link.state_proof).move_as_ok();
mc_proof.push_back(ton::create_tl_object(
link.is_key, to_tonlib_api(link.from), to_tonlib_api(link.to), dest_proof.as_slice().str(),
proof.as_slice().str(), state_proof.as_slice().str()));
}
promise_.set_result(ton::create_tl_object(
to_tonlib_api(from_), to_tonlib_api(mc_id_), std::move(links), std::move(mc_proof)));
stop();
}
void abort(td::Status error) {
promise_.set_error(std::move(error));
stop();
}
private:
ton::BlockIdExt id_, from_, mc_id_;
td::actor::ActorShared<> parent_;
td::Promise> promise_;
ExtClient client_;
std::vector> links_;
};
auto to_lite_api(const tonlib_api::ton_blockIdExt& blk) -> td::Result>;
auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) -> tonlib_api_ptr;
class RunEmulator : public td::actor::Actor {
public:
RunEmulator(ExtClientRef ext_client_ref, int_api::GetAccountStateByTransaction request,
td::actor::ActorShared<> parent, td::Promise>&& promise)
: request_(std::move(request)), parent_(std::move(parent)), promise_(std::move(promise)) {
client_.set_client(ext_client_ref);
}
private:
struct FullBlockId {
ton::BlockIdExt id;
ton::BlockIdExt mc;
ton::BlockIdExt prev;
ton::Bits256 rand_seed;
};
ExtClient client_;
int_api::GetAccountStateByTransaction request_;
td::actor::ActorShared<> parent_;
td::Promise> promise_;
std::map> actors_;
td::int64 actor_id_{1};
FullBlockId block_id_;
td::Ref mc_state_root_; // ^ShardStateUnsplit
td::unique_ptr account_state_;
std::vector> transactions_; // std::vector<^Transaction>
size_t count_{0};
size_t count_transactions_{0};
bool incomplete_{true};
bool stopped_{false};
void get_block_id(td::Promise&& promise) {
auto shard_id = ton::shard_prefix(request_.address.addr, 60);
auto query = ton::lite_api::liteServer_lookupBlock(0b111111010, ton::create_tl_lite_block_id_simple({request_.address.workchain, shard_id, 0}), request_.lt, 0);
client_.send_query(std::move(query), promise.wrap([self = this, shard_id](td::Result> header_r) -> td::Result {
TRY_RESULT(header, std::move(header_r));
ton::BlockIdExt block_id = ton::create_block_id(header->id_);
TRY_RESULT(root, vm::std_boc_deserialize(std::move(header->header_proof_)));
try {
auto virt_root = vm::MerkleProof::virtualize(root, 1);
if (virt_root.is_null()) {
return td::Status::Error("block header proof is not a valid Merkle proof");
}
ton::RootHash vhash{virt_root->get_hash().bits()};
if (ton::RootHash{virt_root->get_hash().bits()} != block_id.root_hash) {
return td::Status::Error("block header has incorrect root hash");
}
std::vector prev_blocks;
ton::BlockIdExt mc_block_id;
bool after_split;
td::Status status = block::unpack_block_prev_blk_ext(virt_root, block_id, prev_blocks, mc_block_id, after_split);
if (status.is_error()) {
return status.move_as_error();
}
ton::BlockIdExt prev_block;
if (prev_blocks.size() == 1 || ton::shard_is_ancestor(prev_blocks[0].id.shard, shard_id)) {
prev_block = std::move(prev_blocks[0]);
} else {
prev_block = std::move(prev_blocks[1]);
}
block::gen::Block::Record block;
block::gen::BlockExtra::Record extra;
if (!tlb::unpack_cell(virt_root, block) || !tlb::unpack_cell(block.extra, extra)) {
return td::Status::Error("cannot unpack block header");
}
return FullBlockId{std::move(block_id), std::move(mc_block_id), std::move(prev_block), std::move(extra.rand_seed)};
} catch (vm::VmError& err) {
return err.as_status("error processing header");
} catch (vm::VmVirtError& err) {
return err.as_status("error processing header");
}
}));
}
void get_mc_state_root(td::Promise>&& promise) {
TRY_RESULT_PROMISE(promise, lite_block, to_lite_api(*to_tonlib_api(block_id_.mc)));
auto block = ton::create_block_id(lite_block);
client_.send_query(ton::lite_api::liteServer_getConfigAll(0b11'11111111, std::move(lite_block)), promise.wrap([self = this, block](auto r_config) -> td::Result> {
TRY_RESULT(state, block::check_extract_state_proof(block, r_config->state_proof_.as_slice(), r_config->config_proof_.as_slice()));
return std::move(state);
}));
}
void get_account_state(td::Promise>&& promise) {
auto actor_id = actor_id_++;
actors_[actor_id] = td::actor::create_actor(
"GetAccountState", client_.get_client(), request_.address, block_id_.prev,
actor_shared(this, actor_id),
promise.wrap([address = request_.address](auto&& state) {
return td::make_unique(std::move(address), std::move(state), 0);
}));
}
td::Status get_transactions(std::int64_t lt) {
TRY_RESULT(lite_block, to_lite_api(*to_tonlib_api(block_id_.id)));
auto after = ton::lite_api::make_object(request_.address.addr, lt);
auto query = ton::lite_api::liteServer_listBlockTransactions(std::move(lite_block), 0b10100111, 256, std::move(after), false, false);
client_.send_query(std::move(query), [self = this](lite_api_ptr&& bTxes) {
if (!bTxes) {
self->check(td::Status::Error("liteServer.blockTransactions is null"));
return;
}
std::int64_t last_lt = 0;
for (auto& id : bTxes->ids_) {
last_lt = id->lt_;
if (id->account_ != self->request_.address.addr) {
continue;
}
if (id->lt_ == self->request_.lt && id->hash_ == self->request_.hash) {
self->incomplete_ = false;
}
self->transactions_.push_back({});
self->get_transaction(id->lt_, id->hash_, [self, i = self->transactions_.size() - 1](auto transaction) { self->set_transaction(i, std::move(transaction)); });
if (!self->incomplete_) {
return;
}
}
if (bTxes->incomplete_) {
self->check(self->get_transactions(last_lt));
}
});
return td::Status::OK();
}
void get_transaction(std::int64_t lt, td::Bits256 hash, td::Promise>&& promise) {
auto actor_id = actor_id_++;
actors_[actor_id] = td::actor::create_actor(
"GetTransactionHistory", client_.get_client(), request_.address, lt, hash, 1, actor_shared(this, actor_id),
promise.wrap([](auto&& transactions) mutable {
return std::move(transactions.transactions.front().transaction);
}));
}
void start_up() override {
if (stopped_) {
return;
}
get_block_id([self = this](td::Result&& block_id) { self->set_block_id(std::move(block_id)); });
}
void set_block_id(td::Result&& block_id) {
if (block_id.is_error()) {
check(block_id.move_as_error());
} else {
block_id_ = block_id.move_as_ok();
get_mc_state_root([self = this](td::Result>&& mc_state_root) { self->set_mc_state_root(std::move(mc_state_root)); });
get_account_state([self = this](td::Result>&& state) { self->set_account_state(std::move(state)); });
check(get_transactions(0));
inc();
}
}
void set_mc_state_root(td::Result>&& mc_state_root) {
if (mc_state_root.is_error()) {
check(mc_state_root.move_as_error());
} else {
mc_state_root_ = mc_state_root.move_as_ok();
inc();
}
}
void set_account_state(td::Result>&& account_state) {
if (account_state.is_error()) {
check(account_state.move_as_error());
} else {
account_state_ = account_state.move_as_ok();
inc();
}
}
void set_transaction(size_t i, td::Result>&& transaction) {
if (transaction.is_error()) {
check(transaction.move_as_error());
} else {
transactions_[i] = transaction.move_as_ok();
inc_transactions();
}
}
void inc_transactions() {
if (stopped_ || ++count_transactions_ != transactions_.size() || incomplete_) {
return;
}
inc();
}
void inc() {
if (stopped_ || ++count_ != 4) { // 4 -- block_id + mc_state_root + account_state + transactions
return;
}
auto r_config = block::Config::extract_from_state(mc_state_root_, 0b11'11111111);
if (r_config.is_error()) {
check(r_config.move_as_error());
return;
}
std::unique_ptr config = r_config.move_as_ok();
block::gen::ShardStateUnsplit::Record shard_state;
if (!tlb::unpack_cell(mc_state_root_, shard_state)) {
check(td::Status::Error("Failed to unpack masterchain state"));
return;
}
vm::Dictionary libraries(shard_state.r1.libraries->prefetch_ref(), 256);
auto r_shard_account = account_state_->to_shardAccountCellSlice();
if (r_shard_account.is_error()) {
check(r_shard_account.move_as_error());
return;
}
td::Ref shard_account = r_shard_account.move_as_ok();
const block::StdAddress& address = account_state_->get_address();
ton::UnixTime now = account_state_->get_sync_time();
bool is_special = address.workchain == ton::masterchainId && config->is_special_smartcontract(address.addr);
block::Account account(address.workchain, address.addr.bits());
if (!account.unpack(std::move(shard_account), td::Ref(), now, is_special)) {
check(td::Status::Error("Can't unpack shard account"));
return;
}
emulator::TransactionEmulator trans_emulator(std::move(*config));
trans_emulator.set_libs(std::move(libraries));
trans_emulator.set_rand_seed(block_id_.rand_seed);
td::Result emulation_result = trans_emulator.emulate_transactions_chain(std::move(account), std::move(transactions_));
if (emulation_result.is_error()) {
promise_.set_error(emulation_result.move_as_error());
} else {
account = std::move(emulation_result.move_as_ok().account);
RawAccountState raw = std::move(account_state_->raw());
raw.block_id = block_id_.id;
raw.balance = account.get_balance().grams->to_long();
raw.storage_last_paid = std::move(account.last_paid);
raw.storage_stat = std::move(account.storage_stat);
raw.code = std::move(account.code);
raw.data = std::move(account.data);
raw.state = std::move(account.total_state);
raw.info.last_trans_lt = account.last_trans_lt_;
raw.info.last_trans_hash = account.last_trans_hash_;
raw.info.gen_utime = account.now_;
if (account.status == block::Account::acc_frozen) {
raw.frozen_hash = (char*)account.state_hash.data();
}
promise_.set_value(td::make_unique(address, std::move(raw), 0));
}
stopped_ = true;
try_stop();
}
void check(td::Status status) {
if (status.is_error()) {
promise_.set_error(std::move(status));
stopped_ = true;
try_stop();
}
}
void try_stop() {
if (stopped_ && actors_.empty()) {
stop();
}
}
void hangup_shared() override {
actors_.erase(get_link_token());
try_stop();
}
void hangup() override {
check(TonlibError::Cancelled());
}
};
TonlibClient::TonlibClient(td::unique_ptr callback) : callback_(std::move(callback)) {
}
TonlibClient::~TonlibClient() = default;
void TonlibClient::hangup() {
source_.cancel();
is_closing_ = true;
ref_cnt_--;
raw_client_ = {};
raw_last_block_ = {};
raw_last_config_ = {};
try_stop();
}
ExtClientRef TonlibClient::get_client_ref() {
ExtClientRef ref;
ref.adnl_ext_client_ = raw_client_.get();
ref.last_block_actor_ = raw_last_block_.get();
ref.last_config_actor_ = raw_last_config_.get();
return ref;
}
void TonlibClient::proxy_request(td::int64 query_id, std::string data) {
on_update(tonlib_api::make_object(query_id, data));
}
void TonlibClient::init_ext_client() {
if (use_callbacks_for_network_) {
class Callback : public ExtClientOutbound::Callback {
public:
explicit Callback(td::actor::ActorShared parent, td::uint32 config_generation)
: parent_(std::move(parent)), config_generation_(config_generation) {
}
void request(td::int64 id, std::string data) override {
send_closure(parent_, &TonlibClient::proxy_request, (id << 16) | (config_generation_ & 0xffff),
std::move(data));
}
private:
td::actor::ActorShared parent_;
td::uint32 config_generation_;
};
ref_cnt_++;
auto client =
ExtClientOutbound::create(td::make_unique(td::actor::actor_shared(this), config_generation_));
ext_client_outbound_ = client.get();
raw_client_ = std::move(client);
} else {
std::vector> servers;
for (const auto& s : config_.lite_clients) {
servers.emplace_back(s.adnl_id, s.address);
}
class Callback : public ExtClientLazy::Callback {
public:
explicit Callback(td::actor::ActorShared<> parent) : parent_(std::move(parent)) {
}
private:
td::actor::ActorShared<> parent_;
};
ext_client_outbound_ = {};
ref_cnt_++;
raw_client_ = ExtClientLazy::create(std::move(servers), td::make_unique(td::actor::actor_shared()));
}
}
void TonlibClient::update_last_block_state(LastBlockState state, td::uint32 config_generation) {
if (config_generation != config_generation_) {
return;
}
last_block_storage_.save_state(last_state_key_, state);
}
void TonlibClient::update_sync_state(LastBlockSyncState state, td::uint32 config_generation) {
if (config_generation != config_generation_) {
return;
}
switch (state.type) {
case LastBlockSyncState::Done:
on_update(
tonlib_api::make_object(tonlib_api::make_object()));
break;
case LastBlockSyncState::InProgress:
on_update(
tonlib_api::make_object(tonlib_api::make_object(
state.from_seqno, state.to_seqno, state.current_seqno)));
break;
default:
LOG(ERROR) << "Unknown LastBlockSyncState type " << state.type;
}
}
void TonlibClient::init_last_block(LastBlockState state) {
ref_cnt_++;
class Callback : public LastBlock::Callback {
public:
Callback(td::actor::ActorShared client, td::uint32 config_generation)
: client_(std::move(client)), config_generation_(config_generation) {
}
void on_state_changed(LastBlockState state) override {
send_closure(client_, &TonlibClient::update_last_block_state, std::move(state), config_generation_);
}
void on_sync_state_changed(LastBlockSyncState sync_state) override {
send_closure(client_, &TonlibClient::update_sync_state, std::move(sync_state), config_generation_);
}
private:
td::actor::ActorShared client_;
td::uint32 config_generation_;
};
last_block_storage_.save_state(last_state_key_, state);
raw_last_block_ = td::actor::create_actor(
td::actor::ActorOptions().with_name("LastBlock").with_poll(false), get_client_ref(), std::move(state), config_,
source_.get_cancellation_token(), td::make_unique(td::actor::actor_shared(this), config_generation_));
}
void TonlibClient::init_last_config() {
ref_cnt_++;
class Callback : public LastConfig::Callback {
public:
Callback(td::actor::ActorShared client) : client_(std::move(client)) {
}
private:
td::actor::ActorShared client_;
};
raw_last_config_ =
td::actor::create_actor(td::actor::ActorOptions().with_name("LastConfig").with_poll(false),
get_client_ref(), td::make_unique(td::actor::actor_shared(this)));
}
void TonlibClient::on_result(td::uint64 id, tonlib_api::object_ptr response) {
VLOG_IF(tonlib_query, id != 0) << "Tonlib answer query " << td::tag("id", id) << " " << to_string(response);
VLOG_IF(tonlib_query, id == 0) << "Tonlib update " << to_string(response);
if (response->get_id() == tonlib_api::error::ID) {
callback_->on_error(id, tonlib_api::move_object_as(response));
return;
}
callback_->on_result(id, std::move(response));
}
void TonlibClient::on_update(object_ptr response) {
on_result(0, std::move(response));
}
void TonlibClient::make_any_request(tonlib_api::Function& function, QueryContext query_context,
td::Promise>&& promise) {
auto old_context = std::move(query_context_);
SCOPE_EXIT {
query_context_ = std::move(old_context);
};
query_context_ = std::move(query_context);
downcast_call(function, [&](auto& request) { this->make_request(request, promise.wrap([](auto x) { return x; })); });
}
void TonlibClient::request(td::uint64 id, tonlib_api::object_ptr function) {
VLOG(tonlib_query) << "Tonlib got query " << td::tag("id", id) << " " << to_string(function);
if (function == nullptr) {
LOG(ERROR) << "Receive empty static request";
return on_result(id, tonlib_api::make_object(400, "Request is empty"));
}
if (is_static_request(function->get_id())) {
return on_result(id, static_request(std::move(function)));
}
if (state_ == State::Closed) {
return on_result(id, tonlib_api::make_object(400, "tonlib is closed"));
}
if (state_ == State::Uninited) {
if (!is_uninited_request(function->get_id())) {
return on_result(id, tonlib_api::make_object(400, "library is not inited"));
}
}
ref_cnt_++;
using Object = tonlib_api::object_ptr;
td::Promise