/* 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 . 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 "tonlib/ExtClientLazy.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 #include #include #include "git.h" using tonlib_api::make_object; // GR$ 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 parse_grams(td::Slice grams) { td::ConstParser parser(grams); if (parser.skip_start_with("GR$")) { TRY_RESULT(a, td::to_integer_safe(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(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 wallet_id; td::optional 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 io_; td::actor::ActorOwn client_; std::uint64_t next_query_id_{1}; td::Promise cont_; td::uint32 wallet_id_; td::int32 workchain_id_; ton::tonlib_api::object_ptr current_block_; enum class BlockMode { Auto, Manual } block_mode_ = BlockMode::Auto; struct KeyInfo { std::string public_key; td::SecureString secret; }; std::vector keys_; struct Address { tonlib_api::object_ptr address; std::string public_key; td::SecureString secret; auto input_key(td::Slice password = "") const { return !secret.empty() ? make_object( make_object(public_key, secret.copy()), td::SecureString(password)) : nullptr; } auto tonlib_api() const { return make_object(address->account_address_); } }; std::map>> query_handlers_; td::actor::ActorOwn 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 id) : id_(std::move(id)) { } private: td::actor::ActorShared id_; }; if (!options_.one_shot) { ref_cnt_++; io_ = td::TerminalIO::create("> ", options_.enable_readline, false, std::make_unique(actor_shared(this))); td::actor::send_closure(io_, &td::TerminalIO::set_log_interface); } class TonlibCb : public tonlib::TonlibCallback { public: TonlibCb(td::actor::ActorShared id) : id_(std::move(id)) { } void on_result(std::uint64_t id, tonlib_api::object_ptr result) override { send_closure(id_, &TonlibCli::on_tonlib_result, id, std::move(result)); } void on_error(std::uint64_t id, tonlib_api::object_ptr error) override { send_closure(id_, &TonlibCli::on_tonlib_error, id, std::move(error)); } private: td::actor::ActorShared id_; }; ref_cnt_++; client_ = td::actor::create_actor("Tonlib", td::make_unique(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(); auto lite_clients_size = config.lite_clients.size(); CHECK(lite_clients_size != 0); auto lite_client_id = td::Random::fast(0, td::narrow_cast(lite_clients_size) - 1); auto& lite_client = config.lite_clients[lite_client_id]; class Callback : public tonlib::ExtClientLazy::Callback { public: explicit Callback(td::actor::ActorShared<> parent) : parent_(std::move(parent)) { } private: td::actor::ActorShared<> parent_; }; ref_cnt_++; raw_client_ = tonlib::ExtClientLazy::create(lite_client.adnl_id, lite_client.address, td::make_unique(td::actor::actor_shared())); } auto config = !options_.config.empty() ? make_object(options_.config, options_.name, options_.use_callbacks_for_network, options_.ignore_cache) : nullptr; tonlib_api::object_ptr ks_type; if (options_.in_memory) { ks_type = make_object(); } else { ks_type = make_object(options_.key_dir); } send_query(make_object(make_object(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(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 ( | root) \n"; td::TerminalIO::out() << "dns cmd \n"; //td::TerminalIO::out() << "dns cmdlist {\\n} end\n"; td::TerminalIO::out() << "dns cmdfile \n"; td::TerminalIO::out() << "\t = set | delete.name | delete.all\n"; td::TerminalIO::out() << "\t = DELETED | EMPTY | TEXT: | NEXT: | SMC: | " "ADNL: | STORAGE:\n"; } void pchan_help() { td::TerminalIO::out() << "pchan help\n"; td::TerminalIO::out() << "pchan create " " []\n"; td::TerminalIO::out() << "pchan list\n"; td::TerminalIO::out() << "pchan delete \n"; td::TerminalIO::out() << "pchan getstate \n"; td::TerminalIO::out() << "pchan promise make (A|B) \n"; td::TerminalIO::out() << "pchan promise check (A|B) \n"; td::TerminalIO::out() << "pchan promise pack \n"; td::TerminalIO::out() << "pchan promise unpack \n"; td::TerminalIO::out() << "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 = init \n"; td::TerminalIO::out() << "\t | close promise []\n"; td::TerminalIO::out() << "\t | timeout \n"; } void rwallet_help() { td::TerminalIO::out() << "rwallet help\n"; td::TerminalIO::out() << "rwallet address \n"; td::TerminalIO::out() << "rwallet init [: ...]\n"; } void pminer_help() { td::TerminalIO::out() << "pminer help\n"; td::TerminalIO::out() << "pminer start \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 cmd_promise = [line = line.clone(), one_shot = options_.one_shot](td::Result 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 \tLoad a serialized message from and send it to server\n"; td::TerminalIO::out() << "setconfig|validateconfig [] [] [] - set or validate " "lite server config\n"; td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " " with specified parameters\n"; td::TerminalIO::out() << "getstate \tget state of wallet with requested key\n"; td::TerminalIO::out() << "getstatebytransaction \tget state of wallet with requested key after transaction with local time and hash (base64url)\n"; td::TerminalIO::out() << "guessrevision \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "guessaccount \tsearch of existing accounts corresponding to the given key\n"; td::TerminalIO::out() << "getaddress \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 \tSet current block\n"; td::TerminalIO::out() << "exit\tExit\n"; td::TerminalIO::out() << "quit\tExit\n"; td::TerminalIO::out() << "saveaccount[code|data] \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
- validate and parse address\n"; td::TerminalIO::out() << "setbounceble
[] - change bounceble flag in address\n"; td::TerminalIO::out() << "importkey - import key\n"; td::TerminalIO::out() << "importkeypem - import key\n"; td::TerminalIO::out() << "importkeyraw - import key\n"; td::TerminalIO::out() << "deletekeys - delete ALL PRIVATE KEYS\n"; td::TerminalIO::out() << "exportkey [] - export key\n"; td::TerminalIO::out() << "exportkeypem [] - export key\n"; td::TerminalIO::out() << "gethistory - get history fo simple wallet with requested key (last 10 transactions)\n"; td::TerminalIO::out() << "init - init simple wallet with requested key\n"; td::TerminalIO::out() << "transfer[f][F][e][k][c] ( |) - " "make transfer from \n" << "\t 'f' modifier - allow send to uninited account\n" << "\t 'F' modifier - read list of messages from (in same format " " , 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 - get sigratures of masterchain block \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
// 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 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( make_object(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 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(address.public_key, public_key, wallet_id_ - 1); TRY_RESULT_PROMISE( promise, addr, sync_send_query(make_object( make_object(address.public_key, public_key, wallet_id_ - 1), 1, -1))); TRY_RESULT_PROMISE(promise, start_at, td::to_integer_safe(parser.read_word())); std::vector> 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(word.substr(0, column_at))); limits.emplace_back(seconds, value.nano); } auto config = make_object(); config->start_at_ = start_at; for (auto limit : limits) { config->limits_.push_back(make_object(limit.first, limit.second)); } auto action = make_object(make_object(std::move(config))); send_query(make_object(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 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 client) : options_(std::move(options)), client_(std::move(client)) { } private: Options options_; td::actor::ActorId client_; td::optional 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 threads_; bool close_flag_{false}; template void send_query(QueryT query, td::Promise 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 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> r_ok) { LOG_IF(ERROR, r_ok.is_error()) << "pminer: " << r_ok.error(); } void with_giver_state(td::Result> 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("get_pow_params"), {}), promise_send_closure(td::actor::actor_id(this), &PowMiner::with_giver_info)); } void with_giver_info(td::Result> 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 to_number(const tonlib_api::object_ptr& entry, td::int32 bits) { if (entry->get_id() != tonlib_api::tvm_stackEntryNumber::ID) { return td::Status::Error("Expected stackEntryNumber"); } auto& number_str = static_cast(*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> 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> pow_miners_; void pminer_start(td::ConstParser& parser, td::Promise 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", std::move(options), client_.get())); td::TerminalIO::out() << "Miner #" << id << " created"; promise.set_value({}); } void pminer_stop(td::ConstParser& parser, td::Promise promise) { pow_miners_.clear(); promise.set_value({}); } void run_pminer_cmd(td::ConstParser& parser, td::Promise 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 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 alice_id; std::string bob_public_key; std::string bob_address; td::optional 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(parser.read_word())); TRY_RESULT_ASSIGN(close_timeout, td::to_integer_safe(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::Random::secure_uint64()); } else { return td::Status::Error("Empty channel id"); } } else { TRY_RESULT_ASSIGN(channel_id, td::to_integer_safe(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(address); } auto to_init_state() { return make_object(make_object( alice_public_key, make_object(alice_address), bob_public_key, make_object(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 channels_; td::int32 next_channel_id_{0}; td::Result to_pchan_id(td::Slice pchan_id_str) { TRY_RESULT(pchan_id, td::to_integer_safe(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(channel.to_init_state(), -1, 0))); channel.address = addr->account_address_; auto find_id = [&](td::Slice public_key, td::Slice address) -> td::optional { 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(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 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 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 ans, td::Promise promise) { td::TerminalIO::out() << "Signature (base64url):" << td::base64url_encode(ans->signature_) << "\n"; send_query(make_object(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 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( addr.input_key(), make_object("", 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 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( public_key, make_object(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 promise) { TRY_RESULT_PROMISE_PREFIX(promise, channel_id, td::to_integer_safe(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(make_object( 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 promise) { TRY_RESULT_PROMISE_PREFIX(promise, packed_promise, base64url_decode(parser.read_word()), "promise"); send_query(make_object(td::SecureString(packed_promise)), promise.wrap([](auto unpacked) { td::TerminalIO::out() << "unpacked promise:\n" << "promise_A: " << Grams{static_cast(unpacked->promise_A_)} << "\n" << "promise_B: " << Grams{static_cast(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 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 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 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 query, td::Promise promise) { std::vector> messages; messages.push_back( make_object(channels_[pchan_id].to_address(), "", value, make_object(query->body_, query->init_state_), -1)); auto action = make_object(std::move(messages), true); send_query( make_object(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 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( make_object(A.nano, B.nano, min_A.nano, min_B.nano)); auto value = A.nano + B.nano; send_query(make_object(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 pchan_promise, td::Promise promise) { auto action = make_object( make_object(0, 0, std::move(pchan_promise))); //send_query(make_object(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(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 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(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 promise) { TRY_RESULT_PROMISE_PREFIX(promise, addr, to_account_address(parser.read_word(), true), "key_id"); auto action = make_object(make_object()); send_query(make_object(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 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 resolved, td::Promise 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(resolved->entries_[0]->entry_); send_query(make_object(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 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> entries; entries.push_back(make_object( "", ton::DNS_NEXT_RESOLVER_CATEGORY, make_object(std::move(address.address)))); do_dns_resolve(name.str(), category, 10, make_object(std::move(entries)), std::move(promise)); } void dns_cmd(td::Slice cmd, td::ConstParser& parser, td::Promise promise) { auto key_id = parser.read_word(); TRY_RESULT_PROMISE(promise, address, to_account_address(key_id, true)); std::vector 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> actions; for (auto& action : actions_ext) { if (action.name.empty()) { actions.push_back(make_object()); td::TerminalIO::out() << "Delete all dns entries\n"; } else if (action.category.is_zero()) { actions.push_back(make_object(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(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( make_object(action.name, action.category, std::move(data)))); } } auto action = make_object(std::move(actions)); td::Slice password; // empty by default auto key = !address.secret.empty() ? make_object( make_object(address.public_key, address.secret.copy()), td::SecureString(password)) : nullptr; send_query(make_object(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 promise) { send_query(make_object(), promise.wrap([](auto&& info) { td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n"; return td::Unit(); })); } void remote_version(td::Promise promise) { send_query(make_object(), 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 promise) { TRY_RESULT_PROMISE(promise, data, td::read_file_str(name.str())); send_query(make_object(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 promise) { TRY_RESULT_PROMISE(promise, addr, to_account_address(address, false)); send_query(make_object(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 info, td::Promise 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(info->id_), PSTRING() << "StateInit of account " << address); } else if (cmd == "saveaccountcode") { with_query(make_object(info->id_), PSTRING() << "Code of account " << address); } else if (cmd == "saveaccountdata") { with_query(make_object(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 cell, td::Promise 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 promise, bool update_last) { send_query(make_object(), 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 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> 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( make_object(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(make_object( 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(make_object(dec_string(num))); } td::Result>> parse_stack(td::ConstParser& parser, td::Slice end_token) { std::vector> 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(make_object(std::move(elements)))); } else if (word == "(") { TRY_RESULT(elements, parse_stack(parser, ")")); stack.push_back( make_object(make_object(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 << ""; } 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 << ""; } 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 << ""; })); } void run_method(td::ConstParser& parser, td::Promise promise) { TRY_RESULT_PROMISE(promise, addr, to_account_address(parser.read_word(), false)); auto method_str = parser.read_word(); tonlib_api::object_ptr method; if (std::all_of(method_str.begin(), method_str.end(), [](auto c) { return c >= '0' && c <= '9'; })) { method = make_object(td::to_integer(method_str.str())); } else { method = make_object(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(0 /*fixme*/, std::move(method), std::move(stack)); send_query(make_object(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 to_run, tonlib_api::object_ptr info, td::Promise 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 info, td::Promise 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 promise) { TRY_RESULT_PROMISE(promise, data, td::read_file_str(path.str())); auto config = make_object(std::move(data), name.str(), use_callback, ignore_cache); if (cmd == "setconfig") { send_query(make_object(std::move(config)), promise.wrap([](auto&& info) { td::TerminalIO::out() << "Config is set\n"; return td::Unit(); })); } else { send_query(make_object(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 res) { if (res.is_ok()) { rcv_bytes_ += res.ok().size(); send_query(make_object(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( id, make_object(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 result) { if (id == 0) { switch (result->get_id()) { case tonlib_api::updateSendLiteServerQuery::ID: { auto update = tonlib_api::move_object_as(std::move(result)); CHECK(!raw_client_.empty()); snd_bytes_ += update->data_.size(); send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_), td::Timestamp::in(5), [actor_id = actor_id(this), id = update->id_](td::Result 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(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(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 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 void send_query(tonlib_api::object_ptr query, td::Promise promise) { if (is_closing_) { return; } tonlib_api::object_ptr 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( make_object(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> 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(r_obj.move_as_ok())); }; } template td::Result sync_send_query(tonlib_api::object_ptr 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(std::move(r_obj)); return td::Status::Error(err->code_, err->message_); } return ton::move_tl_object_as(r_obj); } td::Status validate_address(td::Slice addr) { TRY_STATUS(sync_send_query(make_object(addr.str()))); return td::Status::OK(); } void unpack_address(td::Slice addr) { send_query(make_object(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(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(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(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(), [](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 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(key.substr(1))); if (res < keys_.size()) { return res; } return td::Status::Error("Invalid key id"); } auto r_res = td::to_integer_safe(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 auto with_account_state(int version, std::string public_key, td::uint32 wallet_id, F&& f) { if (version == 4) { return f(make_object(public_key, wallet_id)); } if (version == 5) { return f(make_object(public_key, wallet_id)); } if (version == 6) { return f(make_object(public_key, wallet_id)); } return f(make_object(public_key, wallet_id)); } td::Result
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(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
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 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(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(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( make_object(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 promise) { TRY_RESULT_PROMISE(promise, data, td::read_file_secure(filename.str())); send_query(make_object(td::SecureString(), td::SecureString("cucumber"), make_object(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 promise) { TRY_RESULT_PROMISE(promise, data, td::read_file_secure(filename.str())); send_query(make_object( td::SecureString(), make_object(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(make_object( make_object(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( make_object( make_object(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 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 words, td::Slice password) { send_query(make_object(td::SecureString(password), td::SecureString(""), make_object(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 promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); auto address_str = address.address->account_address_; send_query(make_object( ton::move_tl_object_as(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(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 promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(parser.read_word(), false)); TRY_RESULT_PROMISE(promise, lt, td::to_integer_safe(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(lt, std::move(hash)); send_query(make_object( ton::move_tl_object_as(std::move(address.address)), ton::move_tl_object_as(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(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 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 promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); send_query(make_object( ton::move_tl_object_as(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 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(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 promise) { TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); send_query(make_object(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 promise) { TRY_RESULT_PROMISE(promise, seqno, td::to_integer_safe(seqno_s)); send_query(make_object(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> r_state, td::Promise 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( std::move(input_key), ton::move_tl_object_as(std::move(address.address)), std::move(state->last_transaction_id_)), promise.wrap([](auto res) { td::StringBuilder sb; for (tonlib_api::object_ptr& 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& msg_data) { if (!msg_data) { return; } sb << " "; downcast_call(*msg_data, td::overloaded([&](tonlib_api::msg_dataRaw& raw) { sb << ""; }, [&](tonlib_api::msg_dataText& raw) { sb << "{" << raw.text_ << "}"; }, [&](tonlib_api::msg_dataEncryptedText& raw) { sb << ""; }, [&](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 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> 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 data; if (use_encryption) { data = make_object(message.str()); } else { data = make_object(message.str()); } messages.push_back( make_object(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 key = !from_address.secret.empty() ? make_object( make_object(from_address.public_key, from_address.secret.copy()), td::SecureString(password)) : nullptr; if (use_fake_key) { key = make_object(); } bool allow_send_to_uninited = force; send_query(make_object( std::move(key), std::move(from_address.address), 60, make_object(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> r_info, td::Promise cmd_promise) { if (estimate_fees) { send_query(make_object(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(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(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(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((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((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((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((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("console", options).release(); }); scheduler.run(); return 0; }