Ton
672 строки · 22.8 Кб
1/*
2This file is part of TON Blockchain source code.
3
4TON Blockchain is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9TON Blockchain is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License
15
16In addition, as a special exception, the copyright holders give permission
17to link the code of portions of this program with the OpenSSL library.
18You must obey the GNU General Public License in all respects for all
19of the code used other than OpenSSL. If you modify file(s) with this
20exception, you may extend this exception to your version of the file(s),
21but you are not obligated to do so. If you do not wish to do so, delete this
22exception statement from your version. If you delete this exception statement
23from all source files in the program, then also delete it here.
24along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
25
26Copyright 2017-2020 Telegram Systems LLP
27*/
28#include "adnl/adnl-ext-client.h"
29#include "adnl/utils.hpp"
30#include "auto/tl/ton_api_json.h"
31#include "td/utils/OptionParser.h"
32#include "td/utils/Time.h"
33#include "td/utils/filesystem.h"
34#include "td/utils/format.h"
35#include "td/utils/Random.h"
36#include "td/utils/crypto.h"
37#include "td/utils/port/signals.h"
38#include "td/utils/port/user.h"
39#include "td/utils/port/FileFd.h"
40#include "ton/ton-tl.hpp"
41#include "block/block-db.h"
42#include "block/block.h"
43#include "block/block-auto.h"
44#include "vm/boc.h"
45#include "vm/cellops.h"
46#include "vm/cells/MerkleProof.h"
47#include "block/mc-config.h"
48#include "blockchain-explorer.hpp"
49#include "blockchain-explorer-http.hpp"
50#include "blockchain-explorer-query.hpp"
51
52#include "vm/boc.h"
53#include "vm/cellops.h"
54#include "vm/cells/MerkleProof.h"
55#include "vm/vm.h"
56
57#include "auto/tl/lite_api.h"
58#include "ton/lite-tl.hpp"
59#include "tl-utils/lite-utils.hpp"
60
61#include <microhttpd.h>
62
63#if TD_DARWIN || TD_LINUX
64#include <unistd.h>
65#include <fcntl.h>
66#endif
67#include <iostream>
68#include <sstream>
69
70int verbosity;
71
72td::actor::Scheduler* scheduler_ptr;
73
74static std::string urldecode(td::Slice from, bool decode_plus_sign_as_space) {
75size_t to_i = 0;
76
77td::BufferSlice x{from.size()};
78auto to = x.as_slice();
79
80for (size_t from_i = 0, n = from.size(); from_i < n; from_i++) {
81if (from[from_i] == '%' && from_i + 2 < n) {
82int high = td::hex_to_int(from[from_i + 1]);
83int low = td::hex_to_int(from[from_i + 2]);
84if (high < 16 && low < 16) {
85to[to_i++] = static_cast<char>(high * 16 + low);
86from_i += 2;
87continue;
88}
89}
90to[to_i++] = decode_plus_sign_as_space && from[from_i] == '+' ? ' ' : from[from_i];
91}
92
93return to.truncate(to_i).str();
94}
95
96class HttpQueryRunner {
97public:
98HttpQueryRunner(std::function<void(td::Promise<MHD_Response*>)> func) {
99auto P = td::PromiseCreator::lambda([Self = this](td::Result<MHD_Response*> R) {
100if (R.is_ok()) {
101Self->finish(R.move_as_ok());
102} else {
103Self->finish(nullptr);
104}
105});
106scheduler_ptr->run_in_context_external([&]() { func(std::move(P)); });
107}
108void finish(MHD_Response* response) {
109std::unique_lock<std::mutex> lock(mutex_);
110response_ = response;
111cond.notify_all();
112}
113MHD_Response* wait() {
114std::unique_lock<std::mutex> lock(mutex_);
115cond.wait(lock, [&]() { return response_ != nullptr; });
116return response_;
117}
118
119private:
120std::function<void(td::Promise<MHD_Response*>)> func_;
121MHD_Response* response_ = nullptr;
122std::mutex mutex_;
123std::condition_variable cond;
124};
125
126class CoreActor : public CoreActorInterface {
127private:
128std::string global_config_ = "ton-global.config";
129
130std::vector<td::actor::ActorOwn<ton::adnl::AdnlExtClient>> clients_;
131
132td::uint32 http_port_ = 80;
133MHD_Daemon* daemon_ = nullptr;
134
135td::IPAddress remote_addr_;
136ton::PublicKey remote_public_key_;
137
138bool hide_ips_ = false;
139
140std::unique_ptr<ton::adnl::AdnlExtClient::Callback> make_callback(td::uint32 idx) {
141class Callback : public ton::adnl::AdnlExtClient::Callback {
142public:
143void on_ready() override {
144td::actor::send_closure(id_, &CoreActor::conn_ready, idx_);
145}
146void on_stop_ready() override {
147td::actor::send_closure(id_, &CoreActor::conn_closed, idx_);
148}
149Callback(td::actor::ActorId<CoreActor> id, td::uint32 idx) : id_(std::move(id)), idx_(idx) {
150}
151
152private:
153td::actor::ActorId<CoreActor> id_;
154td::uint32 idx_;
155};
156
157return std::make_unique<Callback>(actor_id(this), idx);
158}
159
160std::shared_ptr<RemoteNodeStatus> new_result_;
161td::int32 attempt_ = 0;
162td::int32 waiting_ = 0;
163
164std::vector<bool> ready_;
165
166void run_queries();
167void got_result(td::uint32 idx, td::int32 attempt, td::Result<td::BufferSlice> data);
168void send_query(td::uint32 idx);
169
170void add_result() {
171if (new_result_) {
172auto ts = static_cast<td::int32>(new_result_->ts_.at_unix());
173results_.emplace(ts, std::move(new_result_));
174}
175}
176
177void alarm() override {
178auto t = static_cast<td::int32>(td::Clocks::system() / 60);
179if (t <= attempt_) {
180alarm_timestamp() = td::Timestamp::at_unix((attempt_ + 1) * 60);
181return;
182}
183if (waiting_ > 0 && new_result_) {
184add_result();
185}
186attempt_ = t;
187run_queries();
188alarm_timestamp() = td::Timestamp::at_unix((attempt_ + 1) * 60);
189}
190
191public:
192std::mutex queue_mutex_;
193std::mutex res_mutex_;
194std::map<td::int32, std::shared_ptr<RemoteNodeStatus>> results_;
195std::vector<td::IPAddress> addrs_;
196static CoreActor* instance_;
197td::actor::ActorId<CoreActor> self_id_;
198
199void conn_ready(td::uint32 idx) {
200ready_.at(idx) = true;
201}
202void conn_closed(td::uint32 idx) {
203ready_.at(idx) = false;
204}
205void set_global_config(std::string str) {
206global_config_ = str;
207}
208void set_http_port(td::uint32 port) {
209http_port_ = port;
210}
211void set_remote_addr(td::IPAddress addr) {
212remote_addr_ = addr;
213}
214void set_remote_public_key(td::BufferSlice file_name) {
215auto R = [&]() -> td::Result<ton::PublicKey> {
216TRY_RESULT_PREFIX(conf_data, td::read_file(file_name.as_slice().str()), "failed to read: ");
217return ton::PublicKey::import(conf_data.as_slice());
218}();
219
220if (R.is_error()) {
221LOG(FATAL) << "bad server public key: " << R.move_as_error();
222}
223remote_public_key_ = R.move_as_ok();
224}
225void set_hide_ips(bool value) {
226hide_ips_ = value;
227}
228
229void send_lite_query(td::uint32 idx, td::BufferSlice query, td::Promise<td::BufferSlice> promise);
230void send_lite_query(td::BufferSlice data, td::Promise<td::BufferSlice> promise) override {
231return send_lite_query(0, std::move(data), std::move(promise));
232}
233void get_last_result(td::Promise<std::shared_ptr<RemoteNodeStatus>> promise) override {
234}
235void get_results(td::uint32 max, td::Promise<RemoteNodeStatusList> promise) override {
236RemoteNodeStatusList r;
237r.ips = hide_ips_ ? std::vector<td::IPAddress>{addrs_.size()} : addrs_;
238auto it = results_.rbegin();
239while (it != results_.rend() && r.results.size() < max) {
240r.results.push_back(it->second);
241it++;
242}
243promise.set_value(std::move(r));
244}
245
246void start_up() override {
247instance_ = this;
248auto t = td::Clocks::system();
249attempt_ = static_cast<td::int32>(t / 60);
250auto next_t = (attempt_ + 1) * 60;
251alarm_timestamp() = td::Timestamp::at_unix(next_t);
252self_id_ = actor_id(this);
253}
254void tear_down() override {
255if (daemon_) {
256MHD_stop_daemon(daemon_);
257daemon_ = nullptr;
258}
259}
260
261CoreActor() {
262}
263
264static MHD_RESULT get_arg_iterate(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) {
265auto X = static_cast<std::map<std::string, std::string>*>(cls);
266if (key && value && std::strlen(key) > 0 && std::strlen(value) > 0) {
267X->emplace(key, urldecode(td::Slice{value}, false));
268}
269return MHD_YES;
270}
271
272struct HttpRequestExtra {
273HttpRequestExtra(MHD_Connection* connection, bool is_post) {
274if (is_post) {
275postprocessor = MHD_create_post_processor(connection, 1 << 14, iterate_post, static_cast<void*>(this));
276}
277}
278~HttpRequestExtra() {
279MHD_destroy_post_processor(postprocessor);
280}
281static MHD_RESULT iterate_post(void* coninfo_cls, enum MHD_ValueKind kind, const char* key, const char* filename,
282const char* content_type, const char* transfer_encoding, const char* data, uint64_t off,
283size_t size) {
284auto ptr = static_cast<HttpRequestExtra*>(coninfo_cls);
285ptr->total_size += strlen(key) + size;
286if (ptr->total_size > MAX_POST_SIZE) {
287return MHD_NO;
288}
289std::string k = key;
290if (ptr->opts[k].size() < off + size) {
291ptr->opts[k].resize(off + size);
292}
293td::MutableSlice(ptr->opts[k]).remove_prefix(off).copy_from(td::Slice(data, size));
294return MHD_YES;
295}
296MHD_PostProcessor* postprocessor;
297std::map<std::string, std::string> opts;
298td::uint64 total_size = 0;
299};
300
301static void request_completed(void* cls, struct MHD_Connection* connection, void** ptr,
302enum MHD_RequestTerminationCode toe) {
303auto e = static_cast<HttpRequestExtra*>(*ptr);
304if (e) {
305delete e;
306}
307}
308
309static MHD_RESULT process_http_request(void* cls, struct MHD_Connection* connection, const char* url, const char* method,
310const char* version, const char* upload_data, size_t* upload_data_size, void** ptr) {
311struct MHD_Response* response = nullptr;
312MHD_RESULT ret;
313
314bool is_post = false;
315if (std::strcmp(method, "GET") == 0) {
316is_post = false;
317} else if (std::strcmp(method, "POST") == 0) {
318is_post = true;
319} else {
320return MHD_NO; /* unexpected method */
321}
322std::map<std::string, std::string> opts;
323if (!is_post) {
324if (!*ptr) {
325*ptr = static_cast<void*>(new HttpRequestExtra{connection, false});
326return MHD_YES;
327}
328if (0 != *upload_data_size)
329return MHD_NO; /* upload data in a GET!? */
330} else {
331if (!*ptr) {
332*ptr = static_cast<void*>(new HttpRequestExtra{connection, true});
333return MHD_YES;
334}
335auto e = static_cast<HttpRequestExtra*>(*ptr);
336if (0 != *upload_data_size) {
337CHECK(e->postprocessor);
338MHD_post_process(e->postprocessor, upload_data, *upload_data_size);
339*upload_data_size = 0;
340return MHD_YES;
341}
342for (auto& o : e->opts) {
343opts[o.first] = std::move(o.second);
344}
345}
346
347std::string url_s = url;
348
349*ptr = nullptr; /* clear context pointer */
350
351auto pos = url_s.rfind('/');
352std::string prefix;
353std::string command;
354if (pos == std::string::npos) {
355prefix = "";
356command = url_s;
357} else {
358prefix = url_s.substr(0, pos + 1);
359command = url_s.substr(pos + 1);
360}
361
362MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, get_arg_iterate, static_cast<void*>(&opts));
363
364if (command == "status") {
365HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
366td::actor::create_actor<HttpQueryStatus>("blockinfo", opts, prefix, std::move(promise)).release();
367}};
368response = g.wait();
369} else if (command == "block") {
370HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
371td::actor::create_actor<HttpQueryBlockInfo>("blockinfo", opts, prefix, std::move(promise)).release();
372}};
373response = g.wait();
374} else if (command == "search") {
375if (opts.count("roothash") + opts.count("filehash") > 0) {
376HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
377td::actor::create_actor<HttpQueryBlockInfo>("blockinfo", opts, prefix, std::move(promise)).release();
378}};
379response = g.wait();
380} else {
381HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
382td::actor::create_actor<HttpQueryBlockSearch>("blocksearch", opts, prefix, std::move(promise)).release();
383}};
384response = g.wait();
385}
386} else if (command == "last") {
387HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
388td::actor::create_actor<HttpQueryViewLastBlock>("", opts, prefix, std::move(promise)).release();
389}};
390response = g.wait();
391} else if (command == "download") {
392HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
393td::actor::create_actor<HttpQueryBlockData>("downloadblock", opts, prefix, std::move(promise)).release();
394}};
395response = g.wait();
396} else if (command == "viewblock") {
397HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
398td::actor::create_actor<HttpQueryBlockView>("viewblock", opts, prefix, std::move(promise)).release();
399}};
400response = g.wait();
401} else if (command == "account") {
402HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
403td::actor::create_actor<HttpQueryViewAccount>("viewaccount", opts, prefix, std::move(promise)).release();
404}};
405response = g.wait();
406} else if (command == "transaction") {
407HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
408td::actor::create_actor<HttpQueryViewTransaction>("viewtransaction", opts, prefix, std::move(promise))
409.release();
410}};
411response = g.wait();
412} else if (command == "transaction2") {
413HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
414td::actor::create_actor<HttpQueryViewTransaction2>("viewtransaction2", opts, prefix, std::move(promise))
415.release();
416}};
417response = g.wait();
418} else if (command == "config") {
419HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
420td::actor::create_actor<HttpQueryConfig>("getconfig", opts, prefix, std::move(promise)).release();
421}};
422response = g.wait();
423} else if (command == "send") {
424HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
425td::actor::create_actor<HttpQuerySend>("send", opts, prefix, std::move(promise)).release();
426}};
427response = g.wait();
428} else if (command == "sendform") {
429HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
430td::actor::create_actor<HttpQuerySendForm>("sendform", opts, prefix, std::move(promise)).release();
431}};
432response = g.wait();
433} else if (command == "runmethod") {
434HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
435td::actor::create_actor<HttpQueryRunMethod>("runmethod", opts, prefix, std::move(promise)).release();
436}};
437response = g.wait();
438} else {
439ret = MHD_NO;
440}
441if (response) {
442ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
443MHD_destroy_response(response);
444} else {
445ret = MHD_NO;
446}
447
448return ret;
449}
450
451void run() {
452if (remote_public_key_.empty()) {
453auto G = td::read_file(global_config_).move_as_ok();
454auto gc_j = td::json_decode(G.as_slice()).move_as_ok();
455ton::ton_api::liteclient_config_global gc;
456ton::ton_api::from_json(gc, gc_j.get_object()).ensure();
457
458CHECK(gc.liteservers_.size() > 0);
459td::uint32 size = static_cast<td::uint32>(gc.liteservers_.size());
460ready_.resize(size, false);
461
462for (td::uint32 i = 0; i < size; i++) {
463auto& cli = gc.liteservers_[i];
464td::IPAddress addr;
465addr.init_host_port(td::IPAddress::ipv4_to_str(cli->ip_), cli->port_).ensure();
466addrs_.push_back(addr);
467clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull::create(cli->id_).move_as_ok(),
468addr, make_callback(i)));
469}
470} else {
471if (!remote_addr_.is_valid()) {
472LOG(FATAL) << "remote addr not set";
473}
474ready_.resize(1, false);
475addrs_.push_back(remote_addr_);
476clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull{remote_public_key_},
477remote_addr_, make_callback(0)));
478}
479daemon_ = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, static_cast<td::uint16>(http_port_), nullptr, nullptr,
480&process_http_request, nullptr, MHD_OPTION_NOTIFY_COMPLETED, request_completed, nullptr,
481MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END);
482CHECK(daemon_ != nullptr);
483}
484};
485
486void CoreActor::got_result(td::uint32 idx, td::int32 attempt, td::Result<td::BufferSlice> R) {
487if (attempt != attempt_) {
488return;
489}
490if (R.is_error()) {
491waiting_--;
492if (waiting_ == 0) {
493add_result();
494}
495return;
496}
497auto data = R.move_as_ok();
498{
499auto F = ton::fetch_tl_object<ton::lite_api::liteServer_error>(data.clone(), true);
500if (F.is_ok()) {
501auto f = F.move_as_ok();
502auto err = td::Status::Error(f->code_, f->message_);
503waiting_--;
504if (waiting_ == 0) {
505add_result();
506}
507return;
508}
509}
510auto F = ton::fetch_tl_object<ton::lite_api::liteServer_masterchainInfo>(std::move(data), true);
511if (F.is_error()) {
512waiting_--;
513if (waiting_ == 0) {
514add_result();
515}
516return;
517}
518auto f = F.move_as_ok();
519new_result_->values_[idx] = ton::create_block_id(f->last_);
520waiting_--;
521CHECK(waiting_ >= 0);
522if (waiting_ == 0) {
523add_result();
524}
525}
526
527void CoreActor::send_query(td::uint32 idx) {
528if (!ready_[idx]) {
529return;
530}
531waiting_++;
532auto query = ton::create_tl_object<ton::lite_api::liteServer_getMasterchainInfo>();
533auto q = ton::create_tl_object<ton::lite_api::liteServer_query>(serialize_tl_object(query, true));
534
535auto P =
536td::PromiseCreator::lambda([SelfId = actor_id(this), idx, attempt = attempt_](td::Result<td::BufferSlice> R) {
537td::actor::send_closure(SelfId, &CoreActor::got_result, idx, attempt, std::move(R));
538});
539td::actor::send_closure(clients_[idx], &ton::adnl::AdnlExtClient::send_query, "query", serialize_tl_object(q, true),
540td::Timestamp::in(10.0), std::move(P));
541}
542
543void CoreActor::run_queries() {
544waiting_ = 0;
545new_result_ = std::make_shared<RemoteNodeStatus>(ready_.size(), td::Timestamp::at_unix(attempt_ * 60));
546for (td::uint32 i = 0; i < ready_.size(); i++) {
547send_query(i);
548}
549CHECK(waiting_ >= 0);
550if (waiting_ == 0) {
551add_result();
552}
553}
554
555void CoreActor::send_lite_query(td::uint32 idx, td::BufferSlice query, td::Promise<td::BufferSlice> promise) {
556if (!ready_[idx]) {
557promise.set_error(td::Status::Error(ton::ErrorCode::notready, "ext conn not ready"));
558return;
559}
560auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
561if (R.is_error()) {
562promise.set_error(R.move_as_error());
563return;
564}
565auto B = R.move_as_ok();
566{
567auto F = ton::fetch_tl_object<ton::lite_api::liteServer_error>(B.clone(), true);
568if (F.is_ok()) {
569auto f = F.move_as_ok();
570promise.set_error(td::Status::Error(f->code_, f->message_));
571return;
572}
573}
574promise.set_value(std::move(B));
575});
576auto q = ton::create_tl_object<ton::lite_api::liteServer_query>(std::move(query));
577td::actor::send_closure(clients_[idx], &ton::adnl::AdnlExtClient::send_query, "query", serialize_tl_object(q, true),
578td::Timestamp::in(10.0), std::move(P));
579}
580
581td::actor::ActorId<CoreActorInterface> CoreActorInterface::instance_actor_id() {
582auto instance = CoreActor::instance_;
583CHECK(instance);
584return instance->self_id_;
585}
586
587CoreActor* CoreActor::instance_ = nullptr;
588
589int main(int argc, char* argv[]) {
590SET_VERBOSITY_LEVEL(verbosity_INFO);
591td::set_default_failure_signal_handler().ensure();
592
593td::actor::ActorOwn<CoreActor> x;
594
595td::OptionParser p;
596p.set_description("TON Blockchain explorer");
597p.add_checked_option('h', "help", "prints_help", [&]() {
598char b[10240];
599td::StringBuilder sb(td::MutableSlice{b, 10000});
600sb << p;
601std::cout << sb.as_cslice().c_str();
602std::exit(2);
603return td::Status::OK();
604});
605p.add_checked_option('I', "hide-ips", "hides ips from status", [&]() {
606td::actor::send_closure(x, &CoreActor::set_hide_ips, true);
607return td::Status::OK();
608});
609p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); });
610p.add_checked_option('C', "global-config", "file to read global config", [&](td::Slice fname) {
611td::actor::send_closure(x, &CoreActor::set_global_config, fname.str());
612return td::Status::OK();
613});
614p.add_checked_option('a', "addr", "connect to ip:port", [&](td::Slice arg) {
615td::IPAddress addr;
616TRY_STATUS(addr.init_host_port(arg.str()));
617td::actor::send_closure(x, &CoreActor::set_remote_addr, addr);
618return td::Status::OK();
619});
620p.add_checked_option('p', "pub", "remote public key", [&](td::Slice arg) {
621td::actor::send_closure(x, &CoreActor::set_remote_public_key, td::BufferSlice{arg});
622return td::Status::OK();
623});
624p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
625verbosity = td::to_integer<int>(arg);
626SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
627return (verbosity >= 0 && verbosity <= 9) ? td::Status::OK() : td::Status::Error("verbosity must be 0..9");
628});
629p.add_checked_option('d', "daemonize", "set SIGHUP", [&]() {
630td::set_signal_handler(td::SignalType::HangUp, [](int sig) {
631#if TD_DARWIN || TD_LINUX
632close(0);
633setsid();
634#endif
635}).ensure();
636return td::Status::OK();
637});
638p.add_checked_option('H', "http-port", "listen on http port", [&](td::Slice arg) {
639td::actor::send_closure(x, &CoreActor::set_http_port, td::to_integer<td::uint32>(arg));
640return td::Status::OK();
641});
642p.add_checked_option('L', "local-scripts", "use local copy of ajax/bootstrap/... JS", [&]() {
643local_scripts = true;
644return td::Status::OK();
645});
646#if TD_DARWIN || TD_LINUX
647p.add_checked_option('l', "logname", "log to file", [&](td::Slice fname) {
648auto FileLog = td::FileFd::open(td::CSlice(fname.str().c_str()),
649td::FileFd::Flags::Create | td::FileFd::Flags::Append | td::FileFd::Flags::Write)
650.move_as_ok();
651
652dup2(FileLog.get_native_fd().fd(), 1);
653dup2(FileLog.get_native_fd().fd(), 2);
654return td::Status::OK();
655});
656#endif
657
658vm::init_vm().ensure();
659
660td::actor::Scheduler scheduler({2});
661scheduler_ptr = &scheduler;
662scheduler.run_in_context([&] { x = td::actor::create_actor<CoreActor>("testnode"); });
663
664scheduler.run_in_context([&] { p.run(argc, argv).ensure(); });
665scheduler.run_in_context([&] {
666td::actor::send_closure(x, &CoreActor::run);
667x.release();
668});
669scheduler.run();
670
671return 0;
672}
673