1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-15 04:32:21 +00:00
ton/tonlib/tonlib/tonlib-cli.cpp

2407 lines
101 KiB
C++

/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2019-2020 Telegram Systems LLP
*/
#include "auto/tl/tonlib_api.h"
#include "block/block.h"
#include "common/bigint.hpp"
#include "common/refint.h"
#include "td/actor/actor.h"
#include "td/actor/common.h"
#include "td/utils/Time.h"
#include "td/utils/filesystem.h"
#include "td/utils/OptionParser.h"
#include "td/utils/overloaded.h"
#include "td/utils/Parser.h"
#include "td/utils/port/signals.h"
#include "td/utils/port/path.h"
#include "td/utils/Random.h"
#include "td/utils/as.h"
#include "terminal/terminal.h"
#include "tonlib/TonlibClient.h"
#include "tonlib/TonlibCallback.h"
#include "smc-envelope/ManualDns.h"
#include "smc-envelope/PaymentChannel.h"
#include "auto/tl/tonlib_api.hpp"
#include "crypto/util/Miner.h"
#include "vm/boc.h"
#include "vm/cells/CellBuilder.h"
#include "lite-client/ext-client.h"
#include <cinttypes>
#include <iostream>
#include <map>
#include "git.h"
using tonlib_api::make_object;
// GR$<amount>
struct Grams {
td::uint64 nano;
};
td::StringBuilder& operator<<(td::StringBuilder& sb, const Grams& grams) {
auto b = grams.nano % 1000000000;
auto a = grams.nano / 1000000000;
sb << "GR$" << a;
if (b != 0) {
size_t sz = 9;
while (b % 10 == 0) {
sz--;
b /= 10;
}
sb << '.';
[&](auto b_str) {
for (size_t i = b_str.size(); i < sz; i++) {
sb << '0';
}
sb << b_str;
}(PSLICE() << b);
}
return sb;
}
td::Result<Grams> parse_grams(td::Slice grams) {
td::ConstParser parser(grams);
if (parser.skip_start_with("GR$")) {
TRY_RESULT(a, td::to_integer_safe<td::uint32>(parser.read_till_nofail('.')));
td::uint64 res = a;
if (parser.try_skip('.')) {
for (int i = 0; i < 9; i++) {
res *= 10;
if (parser.peek_char() >= '0' && parser.peek_char() <= '9') {
res += parser.peek_char() - '0';
parser.advance(1);
}
}
} else {
res *= 1000000000;
}
if (!parser.empty()) {
return td::Status::Error(PSLICE() << "Failed to parse grams \"" << grams << "\", left \"" << parser.read_all()
<< "\"");
}
return Grams{res};
}
TRY_RESULT(value, td::to_integer_safe<td::uint64>(grams));
return Grams{value};
}
// Temporary hack
td::actor::Scheduler* global_scheduler_{nullptr};
class TonlibCli : public td::actor::Actor {
public:
struct Options {
bool enable_readline{true};
std::string config;
std::string name;
std::string key_dir{"."};
bool in_memory{false};
bool use_callbacks_for_network{false};
td::int32 wallet_version = 2;
td::int32 wallet_revision = 0;
td::optional<td::uint32> wallet_id;
td::optional<td::int32> workchain_id;
bool ignore_cache{false};
bool one_shot{false};
std::string cmd;
};
TonlibCli(Options options) : options_(std::move(options)) {
}
private:
Options options_;
td::actor::ActorOwn<td::TerminalIO> io_;
td::actor::ActorOwn<tonlib::TonlibClient> client_;
std::uint64_t next_query_id_{1};
td::Promise<td::Slice> cont_;
td::uint32 wallet_id_;
td::int32 workchain_id_;
ton::tonlib_api::object_ptr<tonlib_api::ton_blockIdExt> current_block_;
enum class BlockMode { Auto, Manual } block_mode_ = BlockMode::Auto;
struct KeyInfo {
std::string public_key;
td::SecureString secret;
};
std::vector<KeyInfo> keys_;
struct Address {
tonlib_api::object_ptr<tonlib_api::accountAddress> address;
std::string public_key;
td::SecureString secret;
auto input_key(td::Slice password = "") const {
return !secret.empty() ? make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(public_key, secret.copy()), td::SecureString(password))
: nullptr;
}
auto tonlib_api() const {
return make_object<tonlib_api::accountAddress>(address->account_address_);
}
};
std::map<std::uint64_t, td::Promise<tonlib_api::object_ptr<tonlib_api::Object>>> query_handlers_;
td::actor::ActorOwn<liteclient::ExtClient> raw_client_;
bool is_closing_{false};
td::uint32 ref_cnt_{1};
td::int64 snd_bytes_{0};
td::int64 rcv_bytes_{0};
void start_up() override {
class Cb : public td::TerminalIO::Callback {
public:
void line_cb(td::BufferSlice line) override {
td::actor::send_closure(id_, &TonlibCli::parse_line, std::move(line));
}
Cb(td::actor::ActorShared<TonlibCli> id) : id_(std::move(id)) {
}
private:
td::actor::ActorShared<TonlibCli> id_;
};
if (!options_.one_shot) {
ref_cnt_++;
io_ = td::TerminalIO::create("> ", options_.enable_readline, false, std::make_unique<Cb>(actor_shared(this)));
td::actor::send_closure(io_, &td::TerminalIO::set_log_interface);
}
class TonlibCb : public tonlib::TonlibCallback {
public:
TonlibCb(td::actor::ActorShared<TonlibCli> id) : id_(std::move(id)) {
}
void on_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) override {
send_closure(id_, &TonlibCli::on_tonlib_result, id, std::move(result));
}
void on_error(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::error> error) override {
send_closure(id_, &TonlibCli::on_tonlib_error, id, std::move(error));
}
private:
td::actor::ActorShared<TonlibCli> id_;
};
ref_cnt_++;
client_ = td::actor::create_actor<tonlib::TonlibClient>("Tonlib", td::make_unique<TonlibCb>(actor_shared(this, 1)));
td::mkdir(options_.key_dir).ignore();
load_keys();
if (options_.use_callbacks_for_network) {
auto config = tonlib::Config::parse(options_.config).move_as_ok();
class Callback : public liteclient::ExtClient::Callback {
public:
explicit Callback(td::actor::ActorShared<> parent) : parent_(std::move(parent)) {
}
private:
td::actor::ActorShared<> parent_;
};
ref_cnt_++;
raw_client_ = liteclient::ExtClient::create(config.lite_servers,
td::make_unique<Callback>(td::actor::actor_shared()));
}
auto config = !options_.config.empty()
? make_object<tonlib_api::config>(options_.config, options_.name,
options_.use_callbacks_for_network, options_.ignore_cache)
: nullptr;
tonlib_api::object_ptr<tonlib_api::KeyStoreType> ks_type;
if (options_.in_memory) {
ks_type = make_object<tonlib_api::keyStoreTypeInMemory>();
} else {
ks_type = make_object<tonlib_api::keyStoreTypeDirectory>(options_.key_dir);
}
send_query(make_object<tonlib_api::init>(make_object<tonlib_api::options>(std::move(config), std::move(ks_type))),
[&](auto r_ok) {
LOG_IF(ERROR, r_ok.is_error()) << r_ok.error();
if (r_ok.is_ok()) {
if (r_ok.ok()->config_info_) {
if (options_.wallet_id) {
wallet_id_ = options_.wallet_id.value();
} else {
wallet_id_ = static_cast<td::uint32>(r_ok.ok()->config_info_->default_wallet_id_);
}
if (options_.workchain_id) {
workchain_id_ = options_.workchain_id.value();
} else {
workchain_id_ = 0;
}
}
load_channnels();
td::TerminalIO::out() << "Tonlib is inited\n";
if (options_.one_shot) {
td::actor::send_closure(actor_id(this), &TonlibCli::parse_line, td::BufferSlice(options_.cmd));
}
}
});
}
void hangup_shared() override {
CHECK(ref_cnt_ > 0);
ref_cnt_--;
if (get_link_token() == 1) {
io_.reset();
}
try_stop();
}
void try_stop() {
if (is_closing_ && ref_cnt_ == 0) {
stop();
}
}
void tear_down() override {
td::actor::SchedulerContext::get()->stop();
}
void on_wait() {
if (options_.one_shot) {
LOG(ERROR) << "FAILED (not enough data)";
std::_Exit(2);
}
}
void dns_help() {
td::TerminalIO::out() << "dns help\n";
td::TerminalIO::out() << "dns resolve (<addr> | root) <name> <category>\n";
td::TerminalIO::out() << "dns cmd <key_id> <dns_cmd>\n";
//td::TerminalIO::out() << "dns cmdlist <key_id> {<dns_cmd>\\n} end\n";
td::TerminalIO::out() << "dns cmdfile <key_id> <file>\n";
td::TerminalIO::out() << "\t<dns_cmd> = set <name> <category> <data> | delete.name <name> | delete.all\n";
td::TerminalIO::out() << "\t<data> = DELETED | EMPTY | TEXT:<text> | NEXT:<smc-address> | SMC:<smc-address> | "
"ADNL:<adnl-address> | STORAGE:<bag-id>\n";
}
void pchan_help() {
td::TerminalIO::out() << "pchan help\n";
td::TerminalIO::out() << "pchan create <alice_public_key> <alice_address> <bob_public_key> <bob_address> "
"<init_timeout> <close_timeout> [<channel_id>]\n";
td::TerminalIO::out() << "pchan list\n";
td::TerminalIO::out() << "pchan delete <pchan_id>\n";
td::TerminalIO::out() << "pchan getstate <pchan_id>\n";
td::TerminalIO::out() << "pchan promise make <pchan_id> <key_id> (A|B) <promise_A> <promise_B>\n";
td::TerminalIO::out() << "pchan promise check <pchan id> (A|B) <promise_A> <promise_B> <signature>\n";
td::TerminalIO::out() << "pchan promise pack <channel_id> <promise_A> <promise_B> <signature>\n";
td::TerminalIO::out() << "pchan promise unpack <packed_promise>\n";
td::TerminalIO::out() << "pchan cmd <pchan_id> <pchan_cmd>\n";
td::TerminalIO::out() << "\tfor simplicity we assume that alice_key is same as wallet key alice uses\n";
td::TerminalIO::out() << "\t<pchan_cmd> = init <key_id> <A> <B> <min_A> <min_B>\n";
td::TerminalIO::out() << "\t | close <key_id> promise [<extra_A>]\n";
td::TerminalIO::out() << "\t | timeout <key_id>\n";
}
void rwallet_help() {
td::TerminalIO::out() << "rwallet help\n";
td::TerminalIO::out() << "rwallet address <key_id> <public_key>\n";
td::TerminalIO::out() << "rwallet init <key_id> <public_key> <start_at> [<seconds>:<value> ...]\n";
}
void pminer_help() {
td::TerminalIO::out() << "pminer help\n";
td::TerminalIO::out() << "pminer start <giver_addess> <my_address>\n";
td::TerminalIO::out() << "pminer stop\n";
}
void parse_line(td::BufferSlice line) {
if (is_closing_) {
return;
}
if (cont_) {
auto cont = std::move(cont_);
cont.set_value(line.as_slice());
return;
}
td::ConstParser parser(line.as_slice());
auto cmd = parser.read_word();
if (cmd.empty()) {
return;
}
auto to_bool = [](td::Slice word, bool def = false) {
if (word.empty()) {
return def;
}
if (word == "0" || word == "FALSE" || word == "false") {
return false;
}
return true;
};
td::Promise<td::Unit> cmd_promise = [line = line.clone(), one_shot = options_.one_shot](td::Result<td::Unit> res) {
if (res.is_ok()) {
if (one_shot) {
LOG(DEBUG) << "OK";
std::_Exit(0);
}
} else {
td::TerminalIO::out() << "Query {" << line.as_slice() << "} FAILED: \n\t" << res.error() << "\n";
if (one_shot) {
LOG(ERROR) << "FAILED";
std::_Exit(1);
}
}
};
if (cmd == "help") {
td::TerminalIO::out() << "help\tThis help\n";
td::TerminalIO::out() << "time\tGet server time\n";
td::TerminalIO::out() << "remote-version\tShows server time, version and capabilities\n";
td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n";
td::TerminalIO::out() << "setconfig|validateconfig <path> [<name>] [<use_callback>] [<force>] - set or validate "
"lite server config\n";
td::TerminalIO::out() << "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account "
"<addr> with specified parameters\n";
td::TerminalIO::out() << "getstate <key_id>\tget state of wallet with requested key\n";
td::TerminalIO::out() << "getstatebytransaction <key_id> <lt> <hash>\tget state of wallet with requested key after transaction with local time <lt> and hash <hash> (base64url)\n";
td::TerminalIO::out() << "guessrevision <key_id>\tsearch of existing accounts corresponding to the given key\n";
td::TerminalIO::out() << "guessaccount <key_id>\tsearch of existing accounts corresponding to the given key\n";
td::TerminalIO::out() << "getaddress <key_id>\tget address of wallet with requested key\n";
dns_help();
pchan_help();
rwallet_help();
pminer_help();
td::TerminalIO::out()
<< "blockmode auto|manual\tWith auto mode, all queries will be executed with respect to the latest block. "
"With manual mode, user must update current block explicitly: with last or setblock\n";
td::TerminalIO::out() << "last\tUpdate current block to the most recent one\n";
td::TerminalIO::out() << "setblock <block>\tSet current block\n";
td::TerminalIO::out() << "exit\tExit\n";
td::TerminalIO::out() << "quit\tExit\n";
td::TerminalIO::out()
<< "saveaccount[code|data] <filename> <addr>\tSaves into specified file the most recent state\n";
td::TerminalIO::out() << "genkey - generate new secret key\n";
td::TerminalIO::out() << "keys - show all stored keys\n";
td::TerminalIO::out() << "unpackaddress <address> - validate and parse address\n";
td::TerminalIO::out() << "setbounceble <address> [<bounceble>] - change bounceble flag in address\n";
td::TerminalIO::out() << "importkey - import key\n";
td::TerminalIO::out() << "importkeypem <filename> - import key\n";
td::TerminalIO::out() << "importkeyraw <filename> - import key\n";
td::TerminalIO::out() << "deletekeys - delete ALL PRIVATE KEYS\n";
td::TerminalIO::out() << "exportkey [<key_id>] - export key\n";
td::TerminalIO::out() << "exportkeypem [<key_id>] - export key\n";
td::TerminalIO::out()
<< "gethistory <key_id> - get history fo simple wallet with requested key (last 10 transactions)\n";
td::TerminalIO::out() << "init <key_id> - init simple wallet with requested key\n";
td::TerminalIO::out() << "transfer[f][F][e][k][c] <from_key_id> (<to_key_id> <value> <message>|<file_name>) - "
"make transfer from <from_key_id>\n"
<< "\t 'f' modifier - allow send to uninited account\n"
<< "\t 'F' modifier - read list of messages from <file_name> (in same format <to_key_id> "
"<value> <message>, one per line)\n"
<< "\t 'e' modifier - encrypt all messages\n"
<< "\t 'k' modifier - use fake key\n"
<< "\t 'c' modifier - just esmitate fees\n";
td::TerminalIO::out() << "getmasterchainsignatures <seqno> - get sigratures of masterchain block <seqno>\n";
} else if (cmd == "genkey") {
generate_key();
} else if (cmd == "exit" || cmd == "quit") {
is_closing_ = true;
client_.reset();
ref_cnt_--;
try_stop();
} else if (cmd == "keys") {
dump_keys();
} else if (cmd == "deletekey") {
//delete_key(parser.read_word());
} else if (cmd == "deletekeys") {
delete_all_keys();
} else if (cmd == "exportkey" || cmd == "exportkeypem") {
export_key(cmd.str(), parser.read_word());
} else if (cmd == "importkey") {
import_key(parser.read_all());
} else if (cmd == "hint") {
get_hints(parser.read_word());
} else if (cmd == "unpackaddress") {
unpack_address(parser.read_word());
} else if (cmd == "setbounceable") {
auto addr = parser.read_word();
auto bounceable = parser.read_word();
set_bounceable(addr, to_bool(bounceable, true));
} else if (cmd == "netstats") {
dump_netstats();
// reviewed from here
} else if (cmd == "blockmode") {
set_block_mode(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "sync" || cmd == "last") {
sync(std::move(cmd_promise), cmd == "last");
} else if (cmd == "time") {
remote_time(std::move(cmd_promise));
} else if (cmd == "remote-version") {
remote_version(std::move(cmd_promise));
} else if (cmd == "sendfile") {
send_file(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "saveaccount" || cmd == "saveaccountdata" || cmd == "saveaccountcode") {
auto path = parser.read_word();
auto address = parser.read_word();
save_account(cmd, path, address, std::move(cmd_promise));
} else if (cmd == "runmethod") {
run_method(parser, std::move(cmd_promise));
} else if (cmd == "setconfig" || cmd == "validateconfig") {
auto config = parser.read_word();
auto name = parser.read_word();
auto use_callback = parser.read_word();
auto force = parser.read_word();
set_validate_config(cmd, config, name, to_bool(use_callback), to_bool(force), std::move(cmd_promise));
} else if (td::begins_with(cmd, "transfer") || cmd == "init") {
// transfer[f][F]
// f - force
// F from file - SEND <address> <amount> <message>
// use @empty for empty message
transfer(parser, cmd, std::move(cmd_promise));
} else if (cmd == "getstate") {
get_state(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "getstatebytransaction") {
get_state_by_transaction(parser, std::move(cmd_promise));
} else if (cmd == "getaddress") {
get_address(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "importkeypem") {
import_key_pem(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "importkeyraw") {
import_key_raw(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "dns") {
run_dns_cmd(parser, std::move(cmd_promise));
} else if (cmd == "pchan") {
run_pchan_cmd(parser, std::move(cmd_promise));
} else if (cmd == "rwallet") {
run_rwallet_cmd(parser, std::move(cmd_promise));
} else if (cmd == "pminer") {
run_pminer_cmd(parser, std::move(cmd_promise));
} else if (cmd == "gethistory") {
get_history(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "guessrevision") {
guess_revision(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "guessaccount") {
auto key = parser.read_word();
auto init_key = parser.read_word();
guess_account(key, init_key, std::move(cmd_promise));
} else if (cmd == "getmasterchainsignatures") {
auto seqno = parser.read_word();
run_get_masterchain_block_signatures(seqno, std::move(cmd_promise));
} else {
cmd_promise.set_error(td::Status::Error(PSLICE() << "Unkwnown query `" << cmd << "`"));
}
if (cmd_promise) {
cmd_promise.set_value(td::Unit());
}
}
void rwallet_address(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false));
auto public_key = parser.read_word().str();
TRY_RESULT_PROMISE(
promise, addr,
sync_send_query(make_object<tonlib_api::getAccountAddress>(
make_object<tonlib_api::rwallet_initialAccountState>(address.public_key, public_key, wallet_id_ - 1), 1,
-1)));
td::TerminalIO::out() << addr->account_address_ << "\n";
promise.set_value(td::Unit());
}
void rwallet_init(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false));
auto public_key = parser.read_word().str();
auto initial_state =
make_object<tonlib_api::rwallet_initialAccountState>(address.public_key, public_key, wallet_id_ - 1);
TRY_RESULT_PROMISE(
promise, addr,
sync_send_query(make_object<tonlib_api::getAccountAddress>(
make_object<tonlib_api::rwallet_initialAccountState>(address.public_key, public_key, wallet_id_ - 1), 1,
-1)));
TRY_RESULT_PROMISE(promise, start_at, td::to_integer_safe<td::int32>(parser.read_word()));
std::vector<std::pair<td::int32, td::uint64>> limits;
while (true) {
auto word = parser.read_word();
if (word.empty()) {
break;
}
auto column_at = word.find(':');
TRY_RESULT_PROMISE(promise, value, parse_grams(word.substr(column_at + 1)));
TRY_RESULT_PROMISE(promise, seconds, td::to_integer_safe<td::int32>(word.substr(0, column_at)));
limits.emplace_back(seconds, value.nano);
}
auto config = make_object<tonlib_api::rwallet_config>();
config->start_at_ = start_at;
for (auto limit : limits) {
config->limits_.push_back(make_object<tonlib_api::rwallet_limit>(limit.first, limit.second));
}
auto action =
make_object<tonlib_api::actionRwallet>(make_object<tonlib_api::rwallet_actionInit>(std::move(config)));
send_query(make_object<tonlib_api::createQuery>(address.input_key(), std::move(addr), 60, std::move(action),
std::move(initial_state)),
promise.send_closure(actor_id(this), &TonlibCli::transfer2, false));
}
void run_rwallet_cmd(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto cmd = parser.read_word();
if (cmd == "help") {
rwallet_help();
return promise.set_value(td::Unit());
}
if (cmd == "address") {
return rwallet_address(parser, std::move(promise));
}
if (cmd == "init") {
return rwallet_init(parser, std::move(promise));
}
promise.set_error(td::Status::Error("Unknown command"));
}
class PowMiner : public td::actor::Actor {
public:
class Callback {
public:
};
struct Options {
Address giver_address;
Address my_address;
};
PowMiner(Options options, td::actor::ActorId<tonlib::TonlibClient> client)
: options_(std::move(options)), client_(std::move(client)) {
}
private:
Options options_;
td::actor::ActorId<tonlib::TonlibClient> client_;
td::optional<ton::Miner::Options> miner_options_;
static constexpr double QUERY_EACH = 5.0;
td::Timestamp next_options_query_at_;
bool need_run_miners_{false};
td::CancellationTokenSource source_;
ton::Miner::Options miner_options_copy_;
std::size_t threads_alive_{0};
std::vector<td::thread> threads_;
bool close_flag_{false};
template <class QueryT>
void send_query(QueryT query, td::Promise<typename QueryT::ReturnType> promise) {
td::actor::send_lambda(client_,
[self = client_, query = std::move(query), promise = std::move(promise)]() mutable {
self.get_actor_unsafe().make_request(std::move(query), std::move(promise));
});
}
void start_up() override {
next_options_query_at_ = td::Timestamp::now();
loop();
}
void hangup() override {
close_flag_ = true;
source_.cancel();
try_stop();
}
void try_stop() {
if (threads_alive_ == 0) {
td::TerminalIO::out() << "pminer: stopped\n";
stop();
}
}
void loop() override {
if (close_flag_) {
try_stop();
return;
}
if (next_options_query_at_ && next_options_query_at_.is_in_past()) {
send_query(tonlib_api::smc_load(options_.giver_address.tonlib_api()),
promise_send_closure(td::actor::actor_id(this), &PowMiner::with_giver_state));
next_options_query_at_ = {};
}
if (miner_options_ && threads_.empty() && need_run_miners_) {
td::TerminalIO::out() << "pminer: start workers\n";
need_run_miners_ = false;
miner_options_copy_ = miner_options_.value();
miner_options_copy_.token_ = source_.get_cancellation_token();
auto n = td::thread::hardware_concurrency();
threads_alive_ = n;
for (td::uint32 i = 0; i < n; i++) {
threads_.emplace_back([this, actor_id = actor_id(this)] {
auto res = ton::Miner::run(miner_options_copy_);
global_scheduler_->run_in_context_external(
[&] { send_closure(actor_id, &PowMiner::got_answer, std::move(res)); });
});
}
}
alarm_timestamp().relax(next_options_query_at_);
}
void got_answer(td::optional<std::string> answer) {
source_.cancel();
if (--threads_alive_ == 0) {
threads_.clear();
}
if (answer) {
td::TerminalIO::out() << "pminer: got some result - sending query to the giver\n";
vm::CellBuilder cb;
cb.store_bytes(answer.unwrap());
send_query(tonlib_api::raw_createAndSendMessage(
options_.giver_address.tonlib_api(), "",
vm::std_boc_serialize(cb.finalize_novm()).move_as_ok().as_slice().str()),
promise_send_closure(td::actor::actor_id(this), &PowMiner::on_query_sent));
}
loop();
}
void on_query_sent(td::Result<tonlib_api::object_ptr<tonlib_api::ok>> r_ok) {
LOG_IF(ERROR, r_ok.is_error()) << "pminer: " << r_ok.error();
}
void with_giver_state(td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> r_info) {
if (r_info.is_error()) {
return with_giver_info(r_info.move_as_error());
}
send_query(tonlib_api::smc_runGetMethod(r_info.ok()->id_,
make_object<tonlib_api::smc_methodIdName>("get_pow_params"), {}),
promise_send_closure(td::actor::actor_id(this), &PowMiner::with_giver_info));
}
void with_giver_info(td::Result<tonlib_api::object_ptr<tonlib_api::smc_runResult>> r_info) {
auto status = do_with_giver_info(std::move(r_info));
LOG_IF(ERROR, status.is_error()) << "pminer: " << status;
next_options_query_at_ = td::Timestamp::in(QUERY_EACH);
return loop();
}
td::Result<td::RefInt256> to_number(const tonlib_api::object_ptr<tonlib_api::tvm_StackEntry>& entry,
td::int32 bits) {
if (entry->get_id() != tonlib_api::tvm_stackEntryNumber::ID) {
return td::Status::Error("Expected stackEntryNumber");
}
auto& number_str = static_cast<const tonlib_api::tvm_stackEntryNumber&>(*entry.get()).number_->number_;
auto num = td::make_refint();
if (num.write().parse_dec(number_str.data(), (int)number_str.size()) < (int)number_str.size()) {
return td::Status::Error("Failed to parse a number");
}
if (!num->unsigned_fits_bits(bits)) {
return td::Status::Error(PSLICE() << "Number is too big " << num->to_dec_string() << " " << bits);
}
return num;
}
td::Status do_with_giver_info(td::Result<tonlib_api::object_ptr<tonlib_api::smc_runResult>> r_info) {
TRY_RESULT(info, std::move(r_info));
if (info->stack_.size() < 2) {
return td::Status::Error("Unexpected `get_pow_params` result format");
}
TRY_RESULT(seed, to_number(info->stack_[0], 128));
TRY_RESULT(complexity, to_number(info->stack_[1], 256));
ton::Miner::Options options;
seed->export_bytes(options.seed.data(), 16, false);
complexity->export_bytes(options.complexity.data(), 32, false);
TRY_RESULT(address, block::StdAddress::parse(options_.my_address.address->account_address_));
options.my_address = std::move(address);
options.token_ = source_.get_cancellation_token();
if (miner_options_ && miner_options_.value().seed == options.seed) {
return td::Status::OK();
}
td::TerminalIO::out() << "pminer: got new options\n";
td::BigInt256 bigpower, hrate;
bigpower.set_pow2(256).mod_div(*complexity, hrate);
long long hash_rate = hrate.to_long();
td::TerminalIO::out() << "[ expected required hashes for success: " << hash_rate << " ]\n";
miner_options_ = std::move(options);
need_run_miners_ = true;
source_.cancel();
return td::Status::OK();
}
};
td::uint64 pow_miner_id_{0};
std::map<td::uint64, td::actor::ActorOwn<PowMiner>> pow_miners_;
void pminer_start(td::ConstParser& parser, td::Promise<td::Unit> promise) {
if (!pow_miners_.empty()) {
promise.set_error(td::Status::Error("One pminer is already running"));
}
TRY_RESULT_PROMISE_PREFIX(promise, giver_address, to_account_address(parser.read_word(), false), "giver address");
TRY_RESULT_PROMISE_PREFIX(promise, my_address, to_account_address(parser.read_word(), false), "my address");
auto id = ++pow_miner_id_;
PowMiner::Options options;
options.giver_address = std::move(giver_address);
options.my_address = std::move(my_address);
pow_miners_.emplace(id, td::actor::create_actor<PowMiner>("PowMiner", std::move(options), client_.get()));
td::TerminalIO::out() << "Miner #" << id << " created";
promise.set_value({});
}
void pminer_stop(td::ConstParser& parser, td::Promise<td::Unit> promise) {
pow_miners_.clear();
promise.set_value({});
}
void run_pminer_cmd(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto cmd = parser.read_word();
if (cmd == "help") {
pminer_help();
return promise.set_value(td::Unit());
}
if (cmd == "start") {
return pminer_start(parser, std::move(promise));
}
if (cmd == "stop") {
return pminer_stop(parser, std::move(promise));
}
promise.set_error(td::Status::Error("Unknown command"));
}
void run_pchan_cmd(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto cmd = parser.read_word();
if (cmd == "help") {
pchan_help();
return promise.set_value(td::Unit());
}
if (cmd == "create") {
return pchan_create(parser, std::move(promise));
}
if (cmd == "list") {
return pchan_list(std::move(promise));
}
if (cmd == "delete") {
return pchan_delete(parser, std::move(promise));
}
if (cmd == "promise") {
return pchan_promise(parser, std::move(promise));
}
if (cmd == "getstate") {
return pchan_getstate(parser, std::move(promise));
}
if (cmd == "cmd") {
TRY_RESULT_PROMISE(promise, pchan_id, to_pchan_id(parser.read_word()));
auto subcmd = parser.read_word();
if (subcmd == "init") {
return pchan_init(pchan_id, parser, std::move(promise));
}
if (subcmd == "close") {
return pchan_close(pchan_id, parser, std::move(promise));
}
if (subcmd == "timeout") {
return pchan_timeout(pchan_id, parser, std::move(promise));
}
}
promise.set_error(td::Status::Error("Unknown command"));
}
struct Channel {
std::string alice_public_key;
std::string alice_address;
td::optional<int> alice_id;
std::string bob_public_key;
std::string bob_address;
td::optional<int> bob_id;
td::int32 init_timeout{0};
td::int32 close_timeout{0};
td::int64 channel_id;
std::string address;
td::Status parse(td::ConstParser& parser, bool gen_channel_id = false) {
alice_public_key = parser.read_word().str();
alice_address = parser.read_word().str();
bob_public_key = parser.read_word().str();
bob_address = parser.read_word().str();
TRY_RESULT_ASSIGN(init_timeout, td::to_integer_safe<td::int32>(parser.read_word()));
TRY_RESULT_ASSIGN(close_timeout, td::to_integer_safe<td::int32>(parser.read_word()));
if (parser.status().is_error()) {
return parser.status().clone();
}
auto channel_id_str = parser.read_word();
if (channel_id_str.empty()) {
if (gen_channel_id) {
channel_id = static_cast<td::int64>(td::Random::secure_uint64());
} else {
return td::Status::Error("Empty channel id");
}
} else {
TRY_RESULT_ASSIGN(channel_id, td::to_integer_safe<td::int64>(channel_id_str));
}
return td::Status::OK();
}
void store(td::StringBuilder& sb) {
sb << alice_public_key << " " << alice_address << " " << bob_public_key << " " << bob_address << " "
<< init_timeout << " " << close_timeout << " " << channel_id;
}
friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Channel& channel) {
sb << "\n\t" << td::tag("a_key", channel.alice_public_key) << td::tag("a_addr", channel.alice_address);
sb << "\n\t" << td::tag("b_key", channel.bob_public_key) << td::tag("b_addr", channel.bob_address);
if (channel.alice_id) {
sb << "\n\t" << td::tag("alice_id", channel.alice_id.value());
}
if (channel.bob_id) {
sb << "\n\t" << td::tag("b_id", channel.bob_id.value());
}
sb << "\n\t" << td::tag("init timeout", channel.init_timeout) << td::tag("close timeout", channel.close_timeout);
sb << "\n\t" << td::tag("channel id", channel.channel_id);
sb << "\n\t" << td::tag("addr", channel.address);
return sb;
}
auto to_address() {
return make_object<tonlib_api::accountAddress>(address);
}
auto to_init_state() {
return make_object<tonlib_api::pchan_initialAccountState>(make_object<tonlib_api::pchan_config>(
alice_public_key, make_object<tonlib_api::accountAddress>(alice_address), bob_public_key,
make_object<tonlib_api::accountAddress>(bob_address), init_timeout, close_timeout, channel_id));
}
};
void store_channels() {
td::SecureString buf(10000);
td::StringBuilder sb(buf.as_mutable_slice());
for (auto& it : channels_) {
it.second.store(sb);
sb << "\n";
}
LOG_IF(FATAL, sb.is_error()) << "StringBuilder overflow";
td::atomic_write_file(channel_db_path(), sb.as_cslice());
}
void load_channnels() {
auto r_db = td::read_file_secure(channel_db_path());
if (r_db.is_error()) {
return;
}
auto db = r_db.move_as_ok();
td::ConstParser parser(db.as_slice());
while (true) {
auto line = td::trim(parser.read_till_nofail('\n'));
parser.skip_nofail('\n');
if (line.empty()) {
break;
}
td::ConstParser line_parser(line);
do_pchan_create(line_parser, false).ensure();
}
}
std::map<td::int32, Channel> channels_;
td::int32 next_channel_id_{0};
td::Result<td::int32> to_pchan_id(td::Slice pchan_id_str) {
TRY_RESULT(pchan_id, td::to_integer_safe<td::int32>(pchan_id_str));
auto it = channels_.find(pchan_id);
if (it == channels_.end()) {
return td::Status::Error("Unknown channle id");
}
return pchan_id;
}
td::Status do_pchan_create(td::ConstParser& parser, bool gen_channel_id) {
Channel channel;
TRY_STATUS(channel.parse(parser, gen_channel_id));
TRY_RESULT(addr, sync_send_query(make_object<tonlib_api::getAccountAddress>(channel.to_init_state(), -1, 0)));
channel.address = addr->account_address_;
auto find_id = [&](td::Slice public_key, td::Slice address) -> td::optional<td::int32> {
auto r_addr = to_account_address(public_key);
if (r_addr.is_error()) {
return {};
}
auto addr = r_addr.move_as_ok().address->account_address_;
if (address != addr) {
return {};
}
for (td::int32 i = 0; i < static_cast<td::int32>(keys_.size()); i++) {
if (keys_[i].public_key == public_key) {
return i;
}
}
return {};
};
channel.alice_id = find_id(channel.alice_public_key, channel.alice_address);
channel.bob_id = find_id(channel.bob_public_key, channel.bob_address);
channels_[next_channel_id_++] = std::move(channel);
return td::Status::OK();
}
void pchan_getstate(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, pchan_id, to_pchan_id(parser.read_word()), "pchan_id");
auto& chan = channels_[pchan_id];
get_state(chan.address, std::move(promise));
}
void pchan_promise(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto cmd = parser.read_word();
if (cmd == "make") {
return pchan_promise_make(parser, std::move(promise));
}
if (cmd == "check") {
return pchan_promise_check(parser, std::move(promise));
}
if (cmd == "pack") {
return pchan_promise_pack(parser, std::move(promise));
}
if (cmd == "unpack") {
return pchan_promise_unpack(parser, std::move(promise));
}
promise.set_error(td::Status::Error("Unknown command"));
}
void pchan_promise_make2(tonlib_api::object_ptr<tonlib_api::pchan_promise> ans, td::Promise<td::Unit> promise) {
td::TerminalIO::out() << "Signature (base64url):" << td::base64url_encode(ans->signature_) << "\n";
send_query(make_object<tonlib_api::pchan_packPromise>(std::move(ans)), promise.wrap([](auto&& ans) {
td::TerminalIO::out() << "Promise (base64url): " << td::base64url_encode(ans->bytes_) << "\n";
return td::Unit();
}));
}
void pchan_promise_make(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, pchan_id, to_pchan_id(parser.read_word()), "pchan_id");
td::Slice a_or_b = parser.read_word();
bool is_a;
if (a_or_b == "A") {
is_a = true;
} else if (a_or_b == "B") {
is_a = false;
} else {
promise.set_error(td::Status::Error("(A|B) expected"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, promise_A, parse_grams(parser.read_word()), "A");
TRY_RESULT_PROMISE_PREFIX(promise, promise_B, parse_grams(parser.read_word()), "B");
auto& chan = channels_[pchan_id];
Address addr;
if (is_a) {
TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise, addr, to_account_address(chan.alice_public_key, true),
"Don't have Alice's key");
} else {
TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise, addr, to_account_address(chan.bob_public_key, true),
"Don't have Bob's key");
}
send_query(make_object<tonlib_api::pchan_signPromise>(
addr.input_key(),
make_object<tonlib_api::pchan_promise>("", promise_A.nano, promise_B.nano, chan.channel_id)),
promise.send_closure(actor_id(this), &TonlibCli::pchan_promise_make2));
}
void pchan_promise_check(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, pchan_id, to_pchan_id(parser.read_word()), "pchan_id");
td::Slice a_or_b = parser.read_word();
bool is_a;
if (a_or_b == "A") {
is_a = true;
} else if (a_or_b == "B") {
is_a = false;
} else {
promise.set_error(td::Status::Error("(A|B) expected"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, promise_A, parse_grams(parser.read_word()), "promise_A");
TRY_RESULT_PROMISE_PREFIX(promise, promise_B, parse_grams(parser.read_word()), "promise_B");
auto& chan = channels_[pchan_id];
std::string public_key = is_a ? chan.alice_public_key : chan.bob_public_key;
TRY_RESULT_PROMISE_PREFIX(promise, signature, td::base64url_decode(parser.read_word()), "signature");
send_query(make_object<tonlib_api::pchan_validatePromise>(
public_key, make_object<tonlib_api::pchan_promise>(std::move(signature), promise_A.nano,
promise_B.nano, chan.channel_id)),
promise.wrap([](auto&& ans) {
td::TerminalIO::out() << "signature is OK\n";
return td::Unit();
}));
}
void pchan_promise_pack(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, channel_id, td::to_integer_safe<td::int64>(parser.read_word()), "pchan_id");
TRY_RESULT_PROMISE_PREFIX(promise, promise_A, parse_grams(parser.read_word()), "promise_A");
TRY_RESULT_PROMISE_PREFIX(promise, promise_B, parse_grams(parser.read_word()), "promise_B");
TRY_RESULT_PROMISE_PREFIX(promise, signature, base64url_decode(parser.read_word()), "signature");
send_query(make_object<tonlib_api::pchan_packPromise>(make_object<tonlib_api::pchan_promise>(
std::move(signature), promise_A.nano, promise_B.nano, channel_id)),
promise.wrap([](auto packed) {
td::TerminalIO::out() << "packed promise: " << base64url_encode(packed->bytes_.as_slice()) << "\n";
return td::Unit();
}));
}
void pchan_promise_unpack(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, packed_promise, base64url_decode(parser.read_word()), "promise");
send_query(make_object<tonlib_api::pchan_unpackPromise>(td::SecureString(packed_promise)),
promise.wrap([](auto unpacked) {
td::TerminalIO::out() << "unpacked promise:\n"
<< "promise_A: " << Grams{static_cast<td::uint64>(unpacked->promise_A_)} << "\n"
<< "promise_B: " << Grams{static_cast<td::uint64>(unpacked->promise_B_)} << "\n"
<< "channel_id: " << unpacked->channel_id_ << "\n"
<< "signature: " << td::base64url_encode(unpacked->signature_) << "\n";
return td::Unit();
}));
}
void pchan_create(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_STATUS_PROMISE(promise, do_pchan_create(parser, true));
td::TerminalIO::out() << "Channel #" << next_channel_id_ - 1 << channels_[next_channel_id_ - 1] << "\n";
store_channels();
promise.set_value(td::Unit());
}
void pchan_list(td::Promise<td::Unit> promise) {
for (auto& it : channels_) {
td::TerminalIO::out() << "Channel #" << it.first << it.second << "\n";
}
promise.set_value(td::Unit());
}
void pchan_delete(td::ConstParser& parser, td::Promise<td::Unit> promise) {
promise.set_error(td::Status::Error("TODO"));
}
void pchan_init_2(Address addr, td::int32 pchan_id, td::int64 value,
tonlib_api::object_ptr<tonlib_api::query_info> query, td::Promise<td::Unit> promise) {
std::vector<tonlib_api::object_ptr<tonlib_api::msg_message>> messages;
messages.push_back(
make_object<tonlib_api::msg_message>(channels_[pchan_id].to_address(), "", value,
make_object<tonlib_api::msg_dataRaw>(query->body_, query->init_state_), -1));
auto action = make_object<tonlib_api::actionMsg>(std::move(messages), true);
send_query(
make_object<tonlib_api::createQuery>(addr.input_key(), std::move(addr.address), 60, std::move(action), nullptr),
promise.send_closure(actor_id(this), &TonlibCli::transfer2, false));
}
void pchan_init(td::int32 pchan_id, td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, addr, to_account_address(parser.read_word(), true), "key_id");
TRY_RESULT_PROMISE_PREFIX(promise, A, parse_grams(parser.read_word()), "A");
TRY_RESULT_PROMISE_PREFIX(promise, B, parse_grams(parser.read_word()), "B");
TRY_RESULT_PROMISE_PREFIX(promise, min_A, parse_grams(parser.read_word()), "min_A");
TRY_RESULT_PROMISE_PREFIX(promise, min_B, parse_grams(parser.read_word()), "min_B");
auto action = make_object<tonlib_api::actionPchan>(
make_object<tonlib_api::pchan_actionInit>(A.nano, B.nano, min_A.nano, min_B.nano));
auto value = A.nano + B.nano;
send_query(make_object<tonlib_api::createQuery>(addr.input_key(), channels_[pchan_id].to_address(), 60,
std::move(action), channels_[pchan_id].to_init_state()),
promise.send_closure(actor_id(this), &TonlibCli::pchan_init_2, std::move(addr), pchan_id, value));
return;
}
void pchan_close2(td::int32 pchan_id, Address addr, tonlib_api::object_ptr<tonlib_api::pchan_promise> pchan_promise,
td::Promise<td::Unit> promise) {
auto action = make_object<tonlib_api::actionPchan>(
make_object<tonlib_api::pchan_actionClose>(0, 0, std::move(pchan_promise)));
//send_query(make_object<tonlib_api::createQuery>(addr.input_key(), channels_[pchan_id].to_address(), 60,
//std::move(action), channels_[pchan_id].to_init_state()),
//promise.send_closure(actor_id(this), &TonlibCli::pchan_init_2, std::move(addr), pchan_id, 1000000000));
send_query(make_object<tonlib_api::createQuery>(addr.input_key(), channels_[pchan_id].to_address(), 60,
std::move(action), channels_[pchan_id].to_init_state()),
promise.send_closure(actor_id(this), &TonlibCli::transfer2, false));
}
void pchan_close(td::int32 pchan_id, td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, addr, to_account_address(parser.read_word(), true), "key_id");
TRY_RESULT_PROMISE_PREFIX(promise, packed_promise, base64url_decode(parser.read_word()), "promise");
send_query(make_object<tonlib_api::pchan_unpackPromise>(td::SecureString(packed_promise)),
promise.send_closure(actor_id(this), &TonlibCli::pchan_close2, pchan_id, std::move(addr)));
}
void pchan_timeout(td::int32 pchan_id, td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, addr, to_account_address(parser.read_word(), true), "key_id");
auto action = make_object<tonlib_api::actionPchan>(make_object<tonlib_api::pchan_actionTimeout>());
send_query(make_object<tonlib_api::createQuery>(addr.input_key(), channels_[pchan_id].to_address(), 60,
std::move(action), channels_[pchan_id].to_init_state()),
promise.send_closure(actor_id(this), &TonlibCli::pchan_init_2, std::move(addr), pchan_id, 1000000000));
}
void run_dns_cmd(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto cmd = parser.read_word();
if (cmd == "help") {
dns_help();
return promise.set_value(td::Unit());
}
if (cmd == "cmd" || cmd == "cmdlist" || cmd == "cmdfile") {
return dns_cmd(cmd, parser, std::move(promise));
}
if (cmd == "resolve") {
return dns_resolve(parser, std::move(promise));
}
promise.set_error(td::Status::Error("Unknown command"));
}
void do_dns_resolve(std::string name, td::Bits256 category, td::int32 ttl,
tonlib_api::object_ptr<tonlib_api::dns_resolved> resolved, td::Promise<td::Unit> promise) {
if (resolved->entries_.empty()) {
td::TerminalIO::out() << "No dns entries found\n";
promise.set_value(td::Unit());
return;
}
if (resolved->entries_[0]->entry_->get_id() == tonlib_api::dns_entryDataNextResolver::ID && ttl != 0) {
td::TerminalIO::out() << "Redirect resolver\n";
auto entry = tonlib_api::move_object_as<tonlib_api::dns_entryDataNextResolver>(resolved->entries_[0]->entry_);
send_query(make_object<tonlib_api::dns_resolve>(std::move(entry->resolver_), name, category, ttl),
promise.send_closure(actor_id(this), &TonlibCli::do_dns_resolve, name, category, 0));
return;
}
td::TerminalIO::out() << "Done\n";
for (auto& entry : resolved->entries_) {
td::TerminalIO::out() << " " << entry->name_ << " " << entry->category_ << " "
<< tonlib::to_dns_entry_data(*entry->entry_).move_as_ok() << "\n";
}
promise.set_value(td::Unit());
}
void dns_resolve(td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto key_id = parser.read_word();
if (key_id == "root") {
key_id = "none";
}
TRY_RESULT_PROMISE(promise, address, to_account_address(key_id, false));
auto name = parser.read_word();
auto category_str = parser.read_word();
td::Bits256 category = category_str.empty() ? td::Bits256::zero() : td::sha256_bits256(td::as_slice(category_str));
std::vector<tonlib_api::object_ptr<tonlib_api::dns_entry>> entries;
entries.push_back(make_object<tonlib_api::dns_entry>(
"", ton::DNS_NEXT_RESOLVER_CATEGORY,
make_object<tonlib_api::dns_entryDataNextResolver>(std::move(address.address))));
do_dns_resolve(name.str(), category, 10, make_object<tonlib_api::dns_resolved>(std::move(entries)),
std::move(promise));
}
void dns_cmd(td::Slice cmd, td::ConstParser& parser, td::Promise<td::Unit> promise) {
auto key_id = parser.read_word();
TRY_RESULT_PROMISE(promise, address, to_account_address(key_id, true));
std::vector<ton::ManualDns::ActionExt> actions_ext;
if (cmd == "cmd") {
TRY_RESULT_PROMISE_ASSIGN(promise, actions_ext, ton::ManualDns::parse(parser.read_all()));
} else if (cmd == "cmdfile") {
TRY_RESULT_PROMISE(promise, file_data, td::read_file(parser.read_word().str()));
TRY_RESULT_PROMISE_ASSIGN(promise, actions_ext, ton::ManualDns::parse(file_data));
}
std::vector<tonlib_api::object_ptr<tonlib_api::dns_Action>> actions;
for (auto& action : actions_ext) {
if (action.name.empty()) {
actions.push_back(make_object<tonlib_api::dns_actionDeleteAll>());
td::TerminalIO::out() << "Delete all dns entries\n";
} else if (action.category.is_zero()) {
actions.push_back(make_object<tonlib_api::dns_actionDelete>(action.name, td::Bits256::zero()));
td::TerminalIO::out() << "Delete all dns enties with name: " << action.name << "\n";
} else if (!action.data) {
actions.push_back(make_object<tonlib_api::dns_actionDelete>(action.name, action.category));
td::TerminalIO::out() << "Delete all dns enties with name and category: " << action.name << ":"
<< action.category << "\n";
} else {
td::StringBuilder sb;
td::Status error;
if (action.data.value().data.empty()) {
TRY_STATUS_PROMISE(promise, td::Status::Error("Empty entry data is not supported"));
}
TRY_RESULT_PROMISE(promise, data, tonlib::to_tonlib_api(action.data.value()));
sb << action.data.value();
TRY_STATUS_PROMISE(promise, std::move(error));
td::TerminalIO::out() << "Set dns entry: " << action.name << ":" << action.category << " "
<< sb.as_cslice() << "\n";
actions.push_back(make_object<tonlib_api::dns_actionSet>(
make_object<tonlib_api::dns_entry>(action.name, action.category, std::move(data))));
}
}
auto action = make_object<tonlib_api::actionDns>(std::move(actions));
td::Slice password; // empty by default
auto key = !address.secret.empty() ? make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(address.public_key, address.secret.copy()),
td::SecureString(password))
: nullptr;
send_query(make_object<tonlib_api::createQuery>(std::move(key), std::move(address.address), 60, std::move(action),
nullptr),
promise.send_closure(actor_id(this), &TonlibCli::transfer2, false));
}
void remote_time(td::Promise<td::Unit> promise) {
send_query(make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
return td::Unit();
}));
}
void remote_version(td::Promise<td::Unit> promise) {
send_query(make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
td::TerminalIO::out() << "Lite server version is: " << info->version_ << "\n";
td::TerminalIO::out() << "Lite server capabilities are: " << info->capabilities_ << "\n";
return td::Unit();
}));
}
void send_file(td::Slice name, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, data, td::read_file_str(name.str()));
send_query(make_object<tonlib_api::raw_sendMessage>(std::move(data)), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Query was sent\n";
return td::Unit();
}));
}
void save_account(td::Slice cmd, td::Slice path, td::Slice address, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, addr, to_account_address(address, false));
send_query(make_object<tonlib_api::smc_load>(std::move(addr.address)),
promise.send_closure(actor_id(this), &TonlibCli::save_account_2, cmd.str(), path.str(), address.str()));
}
void save_account_2(std::string cmd, std::string path, std::string address,
tonlib_api::object_ptr<tonlib_api::smc_info> info, td::Promise<td::Unit> promise) {
auto with_query = [&, self = this](auto query, auto log) {
send_query(std::move(query),
promise.send_closure(actor_id(self), &TonlibCli::save_account_3, std::move(path), std::move(log)));
};
if (cmd == "saveaccount") {
with_query(make_object<tonlib_api::smc_getState>(info->id_), PSTRING() << "StateInit of account " << address);
} else if (cmd == "saveaccountcode") {
with_query(make_object<tonlib_api::smc_getCode>(info->id_), PSTRING() << "Code of account " << address);
} else if (cmd == "saveaccountdata") {
with_query(make_object<tonlib_api::smc_getData>(info->id_), PSTRING() << "Data of account " << address);
} else {
promise.set_error(td::Status::Error("Unknown query"));
}
}
void save_account_3(std::string path, std::string log, tonlib_api::object_ptr<tonlib_api::tvm_cell> cell,
td::Promise<td::Unit> promise) {
TRY_STATUS_PROMISE(promise, td::write_file(path, cell->bytes_));
td::TerminalIO::out() << log << " was successfully written to the disk(" << td::format::as_size(cell->bytes_.size())
<< ")\n";
promise.set_value(td::Unit());
}
void sync(td::Promise<td::Unit> promise, bool update_last) {
send_query(make_object<tonlib_api::sync>(), promise.wrap([&, update_last](auto&& block) {
td::TerminalIO::out() << "synchronized\n";
td::TerminalIO::out() << to_string(block) << "\n";
if (update_last) {
current_block_ = std::move(block);
td::TerminalIO::out() << "Update current block\n";
}
return td::Unit();
}));
}
void set_block_mode(td::Slice mode, td::Promise<td::Unit> promise) {
if (mode == "auto") {
block_mode_ = BlockMode::Auto;
promise.set_value(td::Unit());
} else if (mode == "manual") {
block_mode_ = BlockMode::Manual;
promise.set_value(td::Unit());
} else {
promise.set_error(td::Status::Error("Invalid block mode"));
}
}
td::Result<tonlib_api::object_ptr<tonlib_api::tvm_StackEntry>> parse_stack_entry(td::Slice str) {
if (str.empty() || str.size() > 65535) {
return td::Status::Error("String is or empty or too big");
}
int l = (int)str.size();
if (str[0] == '"') {
vm::CellBuilder cb;
if (l == 1 || str.back() != '"' || l >= 127 + 2 || !cb.store_bytes_bool(str.data() + 1, l - 2)) {
return td::Status::Error("Failed to parse slice");
}
return make_object<tonlib_api::tvm_stackEntrySlice>(
make_object<tonlib_api::tvm_slice>(vm::std_boc_serialize(cb.finalize()).ok().as_slice().str()));
}
if (l >= 3 && (str[0] == 'x' || str[0] == 'b') && str[1] == '{' && str.back() == '}') {
unsigned char buff[128];
int bits =
(str[0] == 'x')
? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1)
: (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1);
if (bits < 0) {
return td::Status::Error("Failed to parse slice");
}
return make_object<tonlib_api::tvm_stackEntrySlice>(make_object<tonlib_api::tvm_slice>(
vm::std_boc_serialize(vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize())
.ok()
.as_slice()
.str()));
}
auto num = td::RefInt256{true};
auto& x = num.unique_write();
if (l >= 3 && str[0] == '0' && str[1] == 'x') {
if (x.parse_hex(str.data() + 2, l - 2) != l - 2) {
return td::Status::Error("Failed to parse a number");
}
} else if (l >= 4 && str[0] == '-' && str[1] == '0' && str[2] == 'x') {
if (x.parse_hex(str.data() + 3, l - 3) != l - 3) {
return td::Status::Error("Failed to parse a number");
}
x.negate().normalize();
} else if (!l || x.parse_dec(str.data(), l) != l) {
return td::Status::Error("Failed to parse a number");
}
return make_object<tonlib_api::tvm_stackEntryNumber>(make_object<tonlib_api::tvm_numberDecimal>(dec_string(num)));
}
td::Result<std::vector<tonlib_api::object_ptr<tonlib_api::tvm_StackEntry>>> parse_stack(td::ConstParser& parser,
td::Slice end_token) {
std::vector<tonlib_api::object_ptr<tonlib_api::tvm_StackEntry>> stack;
while (true) {
auto word = parser.read_word();
LOG(ERROR) << word << " vs " << end_token;
if (word == end_token) {
break;
}
if (word == "[") {
TRY_RESULT(elements, parse_stack(parser, "]"));
stack.push_back(
make_object<tonlib_api::tvm_stackEntryTuple>(make_object<tonlib_api::tvm_tuple>(std::move(elements))));
} else if (word == "(") {
TRY_RESULT(elements, parse_stack(parser, ")"));
stack.push_back(
make_object<tonlib_api::tvm_stackEntryList>(make_object<tonlib_api::tvm_list>(std::move(elements))));
} else {
TRY_RESULT(stack_entry, parse_stack_entry(word));
stack.push_back(std::move(stack_entry));
}
}
return std::move(stack);
}
static void store_entry(td::StringBuilder& sb, tonlib_api::tvm_StackEntry& entry) {
downcast_call(entry, td::overloaded(
[&](tonlib_api::tvm_stackEntryCell& cell) {
auto r_cell = vm::std_boc_deserialize(cell.cell_->bytes_);
if (r_cell.is_error()) {
sb << "<INVALID_CELL>";
}
auto cs = vm::load_cell_slice(r_cell.move_as_ok());
std::stringstream ss;
cs.print_rec(ss);
sb << ss.str();
},
[&](tonlib_api::tvm_stackEntrySlice& cell) {
auto r_cell = vm::std_boc_deserialize(cell.slice_->bytes_);
if (r_cell.is_error()) {
sb << "<INVALID_CELL>";
}
auto cs = vm::load_cell_slice(r_cell.move_as_ok());
std::stringstream ss;
cs.print_rec(ss);
sb << ss.str();
},
[&](tonlib_api::tvm_stackEntryNumber& cell) { sb << cell.number_->number_; },
[&](tonlib_api::tvm_stackEntryTuple& cell) {
sb << "(";
for (auto& element : cell.tuple_->elements_) {
sb << " ";
store_entry(sb, *element);
}
sb << " )";
},
[&](tonlib_api::tvm_stackEntryList& cell) {
sb << "[";
for (auto& element : cell.list_->elements_) {
sb << " ";
store_entry(sb, *element);
}
sb << " ]";
},
[&](tonlib_api::tvm_stackEntryUnsupported& cell) { sb << "<UNSUPPORTED>"; }));
}
void run_method(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, addr, to_account_address(parser.read_word(), false));
auto method_str = parser.read_word();
tonlib_api::object_ptr<tonlib_api::smc_MethodId> method;
if (std::all_of(method_str.begin(), method_str.end(), [](auto c) { return c >= '0' && c <= '9'; })) {
method = make_object<tonlib_api::smc_methodIdNumber>(td::to_integer<td::int32>(method_str.str()));
} else {
method = make_object<tonlib_api::smc_methodIdName>(method_str.str());
}
TRY_RESULT_PROMISE(promise, stack, parse_stack(parser, ""));
td::StringBuilder sb;
for (auto& entry : stack) {
store_entry(sb, *entry);
sb << "\n";
}
td::TerminalIO::out() << "Run " << to_string(method) << "With stack:\n" << sb.as_cslice();
auto to_run = make_object<tonlib_api::smc_runGetMethod>(0 /*fixme*/, std::move(method), std::move(stack));
send_query(make_object<tonlib_api::smc_load>(std::move(addr.address)),
promise.send_closure(actor_id(this), &TonlibCli::run_method_2, std::move(to_run)));
}
void run_method_2(tonlib_api::object_ptr<tonlib_api::smc_runGetMethod> to_run,
tonlib_api::object_ptr<tonlib_api::smc_info> info, td::Promise<td::Unit> promise) {
to_run->id_ = info->id_;
send_query(std::move(to_run), promise.send_closure(actor_id(this), &TonlibCli::run_method_3));
}
void run_method_3(tonlib_api::object_ptr<tonlib_api::smc_runResult> info, td::Promise<td::Unit> promise) {
td::StringBuilder sb;
for (auto& entry : info->stack_) {
store_entry(sb, *entry);
sb << "\n";
}
td::TerminalIO::out() << "Got smc result. exit code: " << info->exit_code_ << ", gas_used: " << info->gas_used_
<< "\n"
<< sb.as_cslice();
promise.set_value({});
}
void set_validate_config(td::Slice cmd, td::Slice path, td::Slice name, bool use_callback, bool ignore_cache,
td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, data, td::read_file_str(path.str()));
auto config = make_object<tonlib_api::config>(std::move(data), name.str(), use_callback, ignore_cache);
if (cmd == "setconfig") {
send_query(make_object<tonlib_api::options_setConfig>(std::move(config)), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Config is set\n";
return td::Unit();
}));
} else {
send_query(make_object<tonlib_api::options_validateConfig>(std::move(config)), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Config is valid: " << to_string(info) << "\n";
return td::Unit();
}));
}
}
void dump_netstats() {
td::TerminalIO::out() << td::tag("snd", td::format::as_size(snd_bytes_)) << "\n";
td::TerminalIO::out() << td::tag("rcv", td::format::as_size(rcv_bytes_)) << "\n";
}
void on_adnl_result(td::uint64 id, td::Result<td::BufferSlice> res) {
if (res.is_ok()) {
rcv_bytes_ += res.ok().size();
send_query(make_object<tonlib_api::onLiteServerQueryResult>(id, res.move_as_ok().as_slice().str()),
[](auto r_ok) { LOG_IF(ERROR, r_ok.is_error()) << r_ok.error(); });
} else {
send_query(make_object<tonlib_api::onLiteServerQueryError>(
id, make_object<tonlib_api::error>(res.error().code(), res.error().message().str())),
[](auto r_ok) { LOG_IF(ERROR, r_ok.is_error()) << r_ok.error(); });
}
}
td::Timestamp sync_started_;
void on_tonlib_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) {
if (id == 0) {
switch (result->get_id()) {
case tonlib_api::updateSendLiteServerQuery::ID: {
auto update = tonlib_api::move_object_as<tonlib_api::updateSendLiteServerQuery>(std::move(result));
CHECK(!raw_client_.empty());
snd_bytes_ += update->data_.size();
send_closure(raw_client_, &liteclient::ExtClient::send_query, "query", td::BufferSlice(update->data_),
ton::ShardIdFull(update->workchain_, update->shard_), td::Timestamp::in(5),
[actor_id = actor_id(this), id = update->id_](td::Result<td::BufferSlice> res) {
send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res));
});
return;
}
case tonlib_api::updateSyncState::ID: {
auto update = tonlib_api::move_object_as<tonlib_api::updateSyncState>(std::move(result));
switch (update->sync_state_->get_id()) {
case tonlib_api::syncStateDone::ID: {
td::TerminalIO::out() << "synchronization: DONE in "
<< td::format::as_time(td::Time::now() - sync_started_.at()) << "\n";
sync_started_ = {};
break;
}
case tonlib_api::syncStateInProgress::ID: {
if (!sync_started_) {
sync_started_ = td::Timestamp::now();
}
auto progress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(update->sync_state_);
auto from = progress->from_seqno_;
auto to = progress->to_seqno_;
auto at = progress->current_seqno_;
auto d = to - from;
if (d <= 0) {
td::TerminalIO::out() << "synchronization: ???\n";
} else {
td::TerminalIO::out() << "synchronization: " << 100 * (at - from) / d << "%\n";
}
break;
}
}
return;
}
}
}
auto it = query_handlers_.find(id);
if (it == query_handlers_.end()) {
return;
}
auto promise = std::move(it->second);
query_handlers_.erase(it);
promise.set_value(std::move(result));
}
void on_tonlib_error(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::error> error) {
auto it = query_handlers_.find(id);
if (it == query_handlers_.end()) {
return;
}
auto promise = std::move(it->second);
query_handlers_.erase(it);
promise.set_error(td::Status::Error(error->code_, error->message_));
}
template <class QueryT>
void send_query(tonlib_api::object_ptr<QueryT> query, td::Promise<typename QueryT::ReturnType> promise) {
if (is_closing_) {
return;
}
tonlib_api::object_ptr<tonlib_api::Function> func = std::move(query);
if (block_mode_ == BlockMode::Manual && func->get_id() != tonlib_api::sync::ID) {
if (!current_block_) {
promise.set_error(td::Status::Error("empty current block"));
return;
}
func = make_object<tonlib_api::withBlock>(
make_object<tonlib_api::ton_blockIdExt>(current_block_->workchain_, current_block_->shard_,
current_block_->seqno_, current_block_->root_hash_,
current_block_->file_hash_),
std::move(func));
}
auto query_id = next_query_id_++;
td::actor::send_closure(client_, &tonlib::TonlibClient::request, query_id, std::move(func));
query_handlers_[query_id] =
[promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::Object>> r_obj) mutable {
if (r_obj.is_error()) {
return promise.set_error(r_obj.move_as_error());
}
promise.set_value(ton::move_tl_object_as<typename QueryT::ReturnType::element_type>(r_obj.move_as_ok()));
};
}
template <class QueryT>
td::Result<typename QueryT::ReturnType> sync_send_query(tonlib_api::object_ptr<QueryT> query) {
if (is_closing_) {
return td::Status::Error("Closing");
}
auto r_obj = tonlib::TonlibClient::static_request(std::move(query));
if (r_obj->get_id() == tonlib_api::error::ID) {
auto err = ton::move_tl_object_as<tonlib_api::error>(std::move(r_obj));
return td::Status::Error(err->code_, err->message_);
}
return ton::move_tl_object_as<typename QueryT::ReturnType::element_type>(r_obj);
}
td::Status validate_address(td::Slice addr) {
TRY_STATUS(sync_send_query(make_object<tonlib_api::unpackAccountAddress>(addr.str())));
return td::Status::OK();
}
void unpack_address(td::Slice addr) {
send_query(make_object<tonlib_api::unpackAccountAddress>(addr.str()),
[addr = addr.str()](auto r_parsed_addr) mutable {
if (r_parsed_addr.is_error()) {
LOG(ERROR) << "Failed to parse address: " << r_parsed_addr.error();
return;
}
LOG(ERROR) << to_string(r_parsed_addr.ok());
});
}
void set_bounceable(td::Slice addr, bool bounceable) {
send_query(make_object<tonlib_api::unpackAccountAddress>(addr.str()), [addr = addr.str(), bounceable,
this](auto r_parsed_addr) mutable {
if (r_parsed_addr.is_error()) {
LOG(ERROR) << "Failed to parse address: " << r_parsed_addr.error();
return;
}
auto parsed_addr = r_parsed_addr.move_as_ok();
parsed_addr->bounceable_ = bounceable;
this->send_query(make_object<tonlib_api::packAccountAddress>(std::move(parsed_addr)), [](auto r_addr) mutable {
if (r_addr.is_error()) {
LOG(ERROR) << "Failed to pack address";
return;
}
td::TerminalIO::out() << r_addr.ok()->account_address_ << "\n";
});
});
}
void generate_key(td::SecureString entropy = {}) {
if (entropy.size() < 20) {
td::TerminalIO::out() << "Enter some entropy";
cont_ = [this, entropy = std::move(entropy)](td::Slice new_entropy) {
td::SecureString res(entropy.size() + new_entropy.size());
res.as_mutable_slice().copy_from(entropy.as_slice());
res.as_mutable_slice().substr(entropy.size()).copy_from(new_entropy);
generate_key(std::move(res));
};
return;
}
td::TerminalIO::out() << "Enter password (could be empty)";
cont_ = [this, entropy = std::move(entropy)](td::Slice password) mutable {
generate_key(std::move(entropy), td::SecureString(password));
};
}
void generate_key(td::SecureString entropy, td::SecureString password) {
auto password_copy = password.copy();
send_query(make_object<tonlib_api::createNewKey>(std::move(password_copy), td::SecureString() /*mnemonic password*/,
std::move(entropy)),
[this, password = std::move(password)](auto r_key) mutable {
if (r_key.is_error()) {
LOG(ERROR) << "Failed to create new key: " << r_key.error();
return;
}
auto key = r_key.move_as_ok();
LOG(ERROR) << to_string(key);
KeyInfo info;
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
store_keys();
});
}
void store_keys() {
td::SecureString buf(10000);
td::StringBuilder sb(buf.as_mutable_slice());
for (auto& info : keys_) {
sb << info.public_key << " " << td::base64_encode(info.secret) << "\n";
}
LOG_IF(FATAL, sb.is_error()) << "StringBuilder overflow";
td::atomic_write_file(key_db_path(), sb.as_cslice());
}
void load_keys() {
auto r_db = td::read_file_secure(key_db_path());
if (r_db.is_error()) {
return;
}
auto db = r_db.move_as_ok();
td::ConstParser parser(db.as_slice());
while (true) {
auto public_key = parser.read_word().str();
{
auto tmp = td::base64_decode(public_key);
if (tmp.is_ok()) {
public_key = td::base64url_encode(tmp.move_as_ok());
}
}
auto secret_b64 = parser.read_word();
if (secret_b64.empty()) {
break;
}
auto r_secret = td::base64_decode_secure(secret_b64);
if (r_secret.is_error()) {
LOG(ERROR) << "Invalid secret database at " << key_db_path();
return;
}
KeyInfo info;
info.public_key = public_key;
info.secret = r_secret.move_as_ok();
keys_.push_back(std::move(info));
}
}
void dump_key(size_t i) {
td::TerminalIO::out() << " #" << i << ": Public key: " << keys_[i].public_key << " "
<< " Address: "
<< to_account_address(PSLICE() << i, false).move_as_ok().address->account_address_ << "\n";
}
void dump_keys() {
td::TerminalIO::out() << "Got " << keys_.size() << " keys"
<< "\n";
for (size_t i = 0; i < keys_.size(); i++) {
dump_key(i);
}
}
void delete_all_keys() {
static td::Slice password = td::Slice("I have written down mnemonic words");
td::TerminalIO::out() << "You are going to delete ALL PRIVATE KEYS. To confirm enter `" << password << "`\n";
cont_ = [this](td::Slice entered) {
if (password == entered) {
this->do_delete_all_keys();
} else {
td::TerminalIO::out() << "Your keys left intact\n";
}
};
}
void do_delete_all_keys() {
send_query(make_object<tonlib_api::deleteAllKeys>(), [](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Something went wrong: " << r_res.error() << "\n";
return;
}
td::TerminalIO::out() << "All your keys have been deleted\n";
});
}
std::string key_db_path() {
return options_.key_dir + TD_DIR_SLASH + "key_db";
}
std::string channel_db_path() {
return options_.key_dir + TD_DIR_SLASH + "channel_db";
}
td::Result<size_t> to_key_i(td::Slice key) {
if (key.empty()) {
return td::Status::Error("Empty key id");
}
if (key[0] == '#') {
TRY_RESULT(res, td::to_integer_safe<size_t>(key.substr(1)));
if (res < keys_.size()) {
return res;
}
return td::Status::Error("Invalid key id");
}
auto r_res = td::to_integer_safe<size_t>(key);
if (r_res.is_ok() && r_res.ok() < keys_.size()) {
return r_res.ok();
}
if (key.size() < 3) {
return td::Status::Error("Too short key id");
}
auto prefix = td::to_lower(key);
size_t res = 0;
size_t cnt = 0;
for (size_t i = 0; i < keys_.size(); i++) {
auto full_key = td::to_lower(keys_[i].public_key);
if (td::begins_with(full_key, prefix)) {
res = i;
cnt++;
}
}
if (cnt == 0) {
return td::Status::Error("Unknown key prefix");
}
if (cnt > 1) {
return td::Status::Error("Non unique key prefix");
}
return res;
}
template <class F>
auto with_account_state(int version, std::string public_key, td::uint32 wallet_id, F&& f) {
if (version == 4) {
return f(make_object<tonlib_api::wallet_highload_v1_initialAccountState>(public_key, wallet_id));
}
if (version == 5) {
return f(make_object<tonlib_api::wallet_highload_v2_initialAccountState>(public_key, wallet_id));
}
if (version == 6) {
return f(make_object<tonlib_api::dns_initialAccountState>(public_key, wallet_id));
}
return f(make_object<tonlib_api::wallet_v3_initialAccountState>(public_key, wallet_id));
}
td::Result<Address> to_account_address(td::Slice public_key) {
auto r_addr = [&, self = this](td::int32 version, td::int32 revision) {
auto do_request = [revision, self](auto x) {
return self->sync_send_query(
make_object<tonlib_api::getAccountAddress>(std::move(x), revision, self->workchain_id_));
};
return with_account_state(version, public_key.str(), wallet_id_ + workchain_id_, do_request);
}(options_.wallet_version, options_.wallet_revision);
TRY_RESULT(addr, std::move(r_addr));
Address res;
res.address = std::move(addr);
res.public_key = public_key.str();
return std::move(res);
}
td::Result<Address> to_account_address(td::Slice key, bool need_private_key) {
if (key.empty()) {
return td::Status::Error("account address is empty");
}
if (key == "none" && !need_private_key) {
return Address{};
}
auto at_pos = key.find('@');
td::optional<std::string> address;
if (at_pos != td::Slice::npos) {
address = key.substr(at_pos + 1).str();
key.truncate(at_pos);
}
auto r_key_i = to_key_i(key);
if (r_key_i.is_ok()) {
auto& key = keys_[r_key_i.ok()];
if (address) {
Address res;
res.public_key = key.public_key;
res.secret = key.secret.copy();
res.address = make_object<tonlib_api::accountAddress>(address.unwrap());
return std::move(res);
}
auto r_addr = to_account_address(key.public_key);
if (r_addr.is_ok()) {
Address res = r_addr.move_as_ok();
res.secret = keys_[r_key_i.ok()].secret.copy();
return std::move(res);
}
}
if (!need_private_key) {
auto r_addr = to_account_address(key);
if (r_addr.is_ok()) {
return r_addr.move_as_ok();
}
}
if (need_private_key) {
return td::Status::Error("Don't have a private key for this address");
}
//TODO: validate address
Address res;
res.address = make_object<tonlib_api::accountAddress>(key.str());
return std::move(res);
}
void delete_key(td::Slice key) {
auto r_key_i = to_key_i(key);
if (r_key_i.is_error()) {
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
return;
}
auto key_i = r_key_i.move_as_ok();
send_query(make_object<tonlib_api::deleteKey>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy())),
[key = key.str()](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't delete key id: [" << key << "] " << r_res.error() << "\n";
return;
}
td::TerminalIO::out() << "Ok\n";
});
}
void export_key(std::string cmd, td::Slice key) {
if (key.empty()) {
dump_keys();
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
cont_ = [this, cmd](td::Slice key) { this->export_key(cmd, key); };
return;
}
auto r_key_i = to_key_i(key);
if (r_key_i.is_error()) {
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
return;
}
auto key_i = r_key_i.move_as_ok();
td::TerminalIO::out() << "Key #" << key_i << "\n"
<< "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n";
td::TerminalIO::out() << "Enter password (could be empty)";
cont_ = [this, cmd, key = key.str(), key_i](td::Slice password) { this->export_key(cmd, key, key_i, password); };
}
void import_key_pem(td::Slice filename, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, data, td::read_file_secure(filename.str()));
send_query(make_object<tonlib_api::importPemKey>(td::SecureString(), td::SecureString("cucumber"),
make_object<tonlib_api::exportedPemKey>(std::move(data))),
promise.wrap([&](auto&& key) {
LOG(ERROR) << to_string(key);
KeyInfo info;
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key("exportkey", key->public_key_, keys_.size() - 1, td::SecureString());
store_keys();
return td::Unit();
}));
}
void import_key_raw(td::Slice filename, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, data, td::read_file_secure(filename.str()));
send_query(make_object<tonlib_api::importUnencryptedKey>(
td::SecureString(), make_object<tonlib_api::exportedUnencryptedKey>(std::move(data))),
promise.wrap([&](auto&& key) {
LOG(ERROR) << to_string(key);
KeyInfo info;
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key("exportkey", key->public_key_, keys_.size() - 1, td::SecureString());
store_keys();
return td::Unit();
}));
}
void export_key(std::string cmd, std::string key, size_t key_i, td::Slice password) {
if (cmd == "exportkey") {
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password))),
[this, key = std::move(key), key_i](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
return;
}
dump_key(key_i);
for (auto& word : r_res.ok()->word_list_) {
td::TerminalIO::out() << " " << word.as_slice() << "\n";
}
});
} else {
send_query(make_object<tonlib_api::exportPemKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password)),
td::SecureString("cucumber")),
[this, key = std::move(key), key_i](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
return;
}
dump_key(key_i);
td::TerminalIO::out() << "\n" << r_res.ok()->pem_.as_slice() << "\n";
});
}
}
void import_key(td::Slice slice, std::vector<td::SecureString> words = {}) {
td::ConstParser parser(slice);
while (true) {
auto word = parser.read_word();
if (word.empty()) {
break;
}
words.push_back(td::SecureString(word));
}
if (words.size() < 24) {
td::TerminalIO::out() << "Enter mnemonic words (got " << words.size() << " out of 24)";
cont_ = [this, words = std::move(words)](td::Slice slice) mutable { this->import_key(slice, std::move(words)); };
return;
}
td::TerminalIO::out() << "Enter password (could be empty)";
cont_ = [this, words = std::move(words)](td::Slice password) mutable {
this->import_key(std::move(words), password);
};
}
void import_key(std::vector<td::SecureString> words, td::Slice password) {
send_query(make_object<tonlib_api::importKey>(td::SecureString(password), td::SecureString(""),
make_object<tonlib_api::exportedKey>(std::move(words))),
[this, password = td::SecureString(password)](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't import key " << r_res.error() << "\n";
return;
}
auto key = r_res.move_as_ok();
LOG(ERROR) << to_string(key);
KeyInfo info;
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
store_keys();
});
}
void get_state(td::Slice key, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(key, false));
auto address_str = address.address->account_address_;
send_query(make_object<tonlib_api::getAccountState>(
ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address))),
promise.wrap([address_str](auto&& state) {
td::TerminalIO::out() << "Address: " << address_str << "\n";
td::TerminalIO::out() << "Balance: "
<< Grams{td::narrow_cast<td::uint64>(state->balance_ * (state->balance_ > 0))}
<< "\n";
td::TerminalIO::out() << "Sync utime: " << state->sync_utime_ << "\n";
td::TerminalIO::out() << "transaction.LT: " << state->last_transaction_id_->lt_ << "\n";
td::TerminalIO::out() << "transaction.Hash: " << td::base64_encode(state->last_transaction_id_->hash_)
<< "\n";
td::TerminalIO::out() << to_string(state->account_state_);
return td::Unit();
}));
}
void get_state_by_transaction(td::ConstParser& parser, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false));
TRY_RESULT_PROMISE(promise, lt, td::to_integer_safe<std::int64_t>(parser.read_word()));
TRY_RESULT_PROMISE(promise, hash, td::base64url_decode(parser.read_word()));
auto address_str = address.address->account_address_;
auto transaction_id = std::make_unique<tonlib_api::internal_transactionId>(lt, std::move(hash));
send_query(make_object<tonlib_api::getAccountStateByTransaction>(
ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address)),
ton::move_tl_object_as<tonlib_api::internal_transactionId>(std::move(transaction_id))),
promise.wrap([address_str](auto&& state) {
td::TerminalIO::out() << "Address: " << address_str << "\n";
td::TerminalIO::out() << "Balance: "
<< Grams{td::narrow_cast<td::uint64>(state->balance_ * (state->balance_ > 0))}
<< "\n";
td::TerminalIO::out() << "Sync utime: " << state->sync_utime_ << "\n";
td::TerminalIO::out() << "transaction.LT: " << state->last_transaction_id_->lt_ << "\n";
td::TerminalIO::out() << "transaction.Hash: " << td::base64_encode(state->last_transaction_id_->hash_)
<< "\n";
td::TerminalIO::out() << to_string(state->account_state_);
return td::Unit();
}));
}
void get_address(td::Slice key, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(key, false));
promise.set_value(td::Unit());
td::TerminalIO::out() << address.address->account_address_ << "\n";
}
void get_history(td::Slice key, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(key, false));
send_query(make_object<tonlib_api::getAccountState>(
ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address))),
promise.send_closure(td::actor::actor_id(this), &TonlibCli::get_history2, key.str()));
}
void guess_revision(td::Slice key, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, key_i, to_key_i(key));
with_account_state(options_.wallet_version, keys_[key_i].public_key, wallet_id_, [&](auto state) {
send_query(make_object<tonlib_api::guessAccountRevision>(std::move(state), 0), promise.wrap([](auto revisions) {
td::TerminalIO::out() << to_string(revisions);
return td::Unit();
}));
});
}
void guess_account(td::Slice key, td::Slice init_public_key, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, address, to_account_address(key, false));
send_query(make_object<tonlib_api::guessAccount>(address.public_key, init_public_key.str()),
promise.wrap([](auto revisions) {
td::TerminalIO::out() << to_string(revisions);
return td::Unit();
}));
}
void run_get_masterchain_block_signatures(td::Slice seqno_s, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, seqno, td::to_integer_safe<td::int32>(seqno_s));
send_query(make_object<tonlib_api::blocks_getMasterchainBlockSignatures>(seqno), promise.wrap([](auto signatures) {
td::TerminalIO::out() << "Signatures: " << signatures->signatures_.size() << "\n";
for (const auto& s : signatures->signatures_) {
td::TerminalIO::out() << " " << s->node_id_short_ << " : " << td::base64_encode(td::Slice(s->signature_))
<< "\n";
}
return td::Unit();
}));
}
void get_history2(td::Slice key, td::Result<tonlib_api::object_ptr<tonlib_api::fullAccountState>> r_state,
td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, state, std::move(r_state));
auto r_address = to_account_address(key, true);
if (r_address.is_error()) {
r_address = to_account_address(key, false);
}
TRY_RESULT_PROMISE(promise, address, std::move(r_address));
td::Slice password;
auto input_key = address.input_key(password);
send_query(make_object<tonlib_api::raw_getTransactions>(
std::move(input_key), ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address)),
std::move(state->last_transaction_id_)),
promise.wrap([](auto res) {
td::StringBuilder sb;
for (tonlib_api::object_ptr<tonlib_api::raw_transaction>& t : res->transactions_) {
td::int64 balance = 0;
balance += t->in_msg_->value_;
for (auto& ot : t->out_msgs_) {
balance -= ot->value_;
}
if (balance >= 0) {
sb << Grams{td::uint64(balance)};
} else {
sb << "-" << Grams{td::uint64(-balance)};
}
sb << " Fee: " << Grams{td::uint64(t->fee_)};
if (t->in_msg_->source_->account_address_.empty()) {
sb << " External ";
} else {
sb << " From " << t->in_msg_->source_->account_address_;
}
auto print_msg_data = [](td::StringBuilder& sb,
tonlib_api::object_ptr<tonlib_api::msg_Data>& msg_data) {
if (!msg_data) {
return;
}
sb << " ";
downcast_call(*msg_data,
td::overloaded([&](tonlib_api::msg_dataRaw& raw) { sb << "<unknown message>"; },
[&](tonlib_api::msg_dataText& raw) { sb << "{" << raw.text_ << "}"; },
[&](tonlib_api::msg_dataEncryptedText& raw) { sb << "<encrypted>"; },
[&](tonlib_api::msg_dataDecryptedText& raw) {
sb << "decrypted{" << raw.text_ << "}";
}));
};
print_msg_data(sb, t->in_msg_->msg_data_);
for (auto& ot : t->out_msgs_) {
sb << "\n\t";
if (ot->destination_->account_address_.empty()) {
sb << " External ";
} else {
sb << " To " << ot->destination_->account_address_;
}
sb << " " << Grams{td::uint64(ot->value_)};
print_msg_data(sb, ot->msg_data_);
}
sb << "\n";
}
td::TerminalIO::out() << sb.as_cslice() << "\n";
return td::Unit();
}));
}
void transfer(td::ConstParser& parser, td::Slice cmd, td::Promise<td::Unit> cmd_promise) {
bool from_file = false;
bool force = false;
bool use_encryption = false;
bool use_fake_key = false;
bool estimate_fees = false;
if (cmd != "init") {
td::ConstParser cmd_parser(cmd);
cmd_parser.advance(td::Slice("transfer").size());
while (!cmd_parser.empty()) {
auto c = cmd_parser.peek_char();
cmd_parser.advance(1);
if (c == 'F') {
from_file = true;
} else if (c == 'f') {
force = true;
} else if (c == 'e') {
use_encryption = true;
} else if (c == 'k') {
use_fake_key = true;
} else if (c == 'c') {
estimate_fees = true;
} else {
cmd_promise.set_error(td::Status::Error(PSLICE() << "Unknown suffix '" << c << "'"));
return;
}
}
}
auto from = parser.read_word();
TRY_RESULT_PROMISE(cmd_promise, from_address, to_account_address(from, true));
struct Message {
Address to;
td::int64 amount;
std::string message;
};
std::vector<tonlib_api::object_ptr<tonlib_api::msg_message>> messages;
auto add_message = [&](td::ConstParser& parser) {
auto to = parser.read_word();
auto grams = parser.read_word();
parser.skip_whitespaces();
auto message = parser.read_all();
Message res;
TRY_RESULT(address, to_account_address(to, false));
TRY_RESULT(amount, parse_grams(grams));
tonlib_api::object_ptr<tonlib_api::msg_Data> data;
if (use_encryption) {
data = make_object<tonlib_api::msg_dataDecryptedText>(message.str());
} else {
data = make_object<tonlib_api::msg_dataText>(message.str());
}
messages.push_back(
make_object<tonlib_api::msg_message>(std::move(address.address), "", amount.nano, std::move(data), -1));
return td::Status::OK();
};
if (from_file) {
TRY_RESULT_PROMISE(cmd_promise, data, td::read_file(parser.read_word().str()));
auto lines = td::full_split(data.as_slice(), '\n');
for (auto& line : lines) {
td::ConstParser parser(line);
parser.skip_whitespaces();
if (parser.empty()) {
continue;
}
if (parser.read_word() != "SEND") {
TRY_STATUS_PROMISE(cmd_promise, td::Status::Error("Expected `SEND` in file"));
}
TRY_STATUS_PROMISE(cmd_promise, add_message(parser));
}
} else {
while (parser.skip_whitespaces(), !parser.empty()) {
TRY_STATUS_PROMISE(cmd_promise, add_message(parser));
}
}
td::Slice password; // empty by default
tonlib_api::object_ptr<tonlib_api::InputKey> key =
!from_address.secret.empty()
? make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(from_address.public_key, from_address.secret.copy()),
td::SecureString(password))
: nullptr;
if (use_fake_key) {
key = make_object<tonlib_api::inputKeyFake>();
}
bool allow_send_to_uninited = force;
send_query(make_object<tonlib_api::createQuery>(
std::move(key), std::move(from_address.address), 60,
make_object<tonlib_api::actionMsg>(std::move(messages), allow_send_to_uninited), nullptr),
cmd_promise.send_closure(actor_id(this), &TonlibCli::transfer2, estimate_fees));
}
void transfer2(bool estimate_fees, td::Result<tonlib_api::object_ptr<tonlib_api::query_info>> r_info,
td::Promise<td::Unit> cmd_promise) {
if (estimate_fees) {
send_query(make_object<tonlib_api::query_estimateFees>(r_info.ok()->id_, true), cmd_promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Extimated fees: " << to_string(info);
return td::Unit();
}));
} else {
send_query(make_object<tonlib_api::query_send>(r_info.ok()->id_), cmd_promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Transfer sent: " << to_string(info);
return td::Unit();
}));
}
}
void get_hints(td::Slice prefix) {
auto obj = tonlib::TonlibClient::static_request(make_object<tonlib_api::getBip39Hints>(prefix.str()));
if (obj->get_id() == tonlib_api::error::ID) {
return;
}
td::TerminalIO::out() << to_string(obj);
}
};
int main(int argc, char* argv[]) {
SET_VERBOSITY_LEVEL(verbosity_INFO);
td::set_default_failure_signal_handler();
td::OptionParser p;
TonlibCli::Options options;
p.set_description("cli wrapper around tonlib");
p.add_option('h', "help", "prints_help", [&]() {
std::cout << (PSLICE() << p).c_str();
std::exit(2);
});
p.add_option('r', "disable-readline", "disable readline", [&]() { options.enable_readline = false; });
p.add_option('R', "enable-readline", "enable readline", [&]() { options.enable_readline = true; });
p.add_option('D', "directory", "set keys directory", [&](td::Slice arg) { options.key_dir = arg.str(); });
p.add_option('M', "in-memory", "store keys only in-memory", [&]() { options.in_memory = true; });
p.add_option('E', "execute", "execute one command", [&](td::Slice arg) {
options.one_shot = true;
options.cmd = arg.str();
});
p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
auto verbosity = td::to_integer<int>(arg);
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
return (verbosity >= 0 && verbosity <= 20) ? td::Status::OK() : td::Status::Error("verbosity must be 0..20");
});
p.add_option('V', "version", "show tonlib-cli build information", [&]() {
std::cout << "tonlib-cli build information: [ Commit: " << GitMetadata::CommitSHA1() << ", Date: " << GitMetadata::CommitDate() << "]\n";
std::exit(0);
});
p.add_checked_option('C', "config-force", "set lite server config, drop config related blockchain cache",
[&](td::Slice arg) {
TRY_RESULT(data, td::read_file_str(arg.str()));
options.config = std::move(data);
options.ignore_cache = true;
return td::Status::OK();
});
p.add_checked_option('c', "config", "set lite server config", [&](td::Slice arg) {
TRY_RESULT(data, td::read_file_str(arg.str()));
options.config = std::move(data);
return td::Status::OK();
});
p.add_option('N', "config-name", "set lite server config name", [&](td::Slice arg) { options.name = arg.str(); });
p.add_option('n', "use-callbacks-for-network", "do not use this",
[&]() { options.use_callbacks_for_network = true; });
p.add_checked_option('w', "wallet-id", "do not use this", [&](td::Slice arg) {
TRY_RESULT(wallet_id, td::to_integer_safe<td::uint32>((arg)));
options.wallet_id = wallet_id;
return td::Status::OK();
});
p.add_checked_option('x', "workchain", "default workchain", [&](td::Slice arg) {
TRY_RESULT(workchain_id, td::to_integer_safe<td::int32>((arg)));
options.workchain_id = workchain_id;
LOG(INFO) << "Use workchain_id = " << workchain_id;
return td::Status::OK();
});
p.add_checked_option('W', "wallet-version", "do not use this (version[.revision])", [&](td::Slice arg) {
td::ConstParser parser(arg);
TRY_RESULT(version, td::to_integer_safe<td::int32>((parser.read_till_nofail('.'))));
options.wallet_version = version;
LOG(INFO) << "Use wallet version = " << version;
if (parser.peek_char() == '.') {
parser.skip('.');
TRY_RESULT(revision, td::to_integer_safe<td::int32>((parser.read_all())));
options.wallet_revision = revision;
LOG(INFO) << "Use wallet revision = " << revision;
}
return td::Status::OK();
});
auto S = p.run(argc, argv);
if (S.is_error()) {
std::cerr << S.move_as_error().message().str() << std::endl;
std::_Exit(2);
}
td::actor::Scheduler scheduler({2});
// temporaty hack
global_scheduler_ = &scheduler;
scheduler.run_in_context([&] { td::actor::create_actor<TonlibCli>("console", options).release(); });
scheduler.run();
return 0;
}