/*
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
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.
along with TON Blockchain. If not, see .
Copyright 2017-2020 Telegram Systems LLP
*/
#include "adnl/adnl-ext-client.h"
#include "adnl/utils.hpp"
#include "auto/tl/ton_api_json.h"
#include "td/utils/OptionParser.h"
#include "td/utils/Time.h"
#include "td/utils/filesystem.h"
#include "td/utils/format.h"
#include "td/utils/Random.h"
#include "td/utils/crypto.h"
#include "td/utils/port/signals.h"
#include "td/utils/port/user.h"
#include "td/utils/port/FileFd.h"
#include "ton/ton-tl.hpp"
#include "block/block-db.h"
#include "block/block.h"
#include "block/block-auto.h"
#include "vm/boc.h"
#include "vm/cellops.h"
#include "vm/cells/MerkleProof.h"
#include "block/mc-config.h"
#include "blockchain-explorer.hpp"
#include "blockchain-explorer-http.hpp"
#include "blockchain-explorer-query.hpp"
#include "vm/boc.h"
#include "vm/cellops.h"
#include "vm/cells/MerkleProof.h"
#include "vm/vm.h"
#include "auto/tl/lite_api.h"
#include "ton/lite-tl.hpp"
#include "tl-utils/lite-utils.hpp"
#include "lite-client/ext-client.h"
#include
#if TD_DARWIN || TD_LINUX
#include
#include
#endif
#include
#include
int verbosity;
td::actor::Scheduler* scheduler_ptr;
static std::string urldecode(td::Slice from, bool decode_plus_sign_as_space) {
size_t to_i = 0;
td::BufferSlice x{from.size()};
auto to = x.as_slice();
for (size_t from_i = 0, n = from.size(); from_i < n; from_i++) {
if (from[from_i] == '%' && from_i + 2 < n) {
int high = td::hex_to_int(from[from_i + 1]);
int low = td::hex_to_int(from[from_i + 2]);
if (high < 16 && low < 16) {
to[to_i++] = static_cast(high * 16 + low);
from_i += 2;
continue;
}
}
to[to_i++] = decode_plus_sign_as_space && from[from_i] == '+' ? ' ' : from[from_i];
}
return to.truncate(to_i).str();
}
class HttpQueryRunner {
public:
HttpQueryRunner(std::function)> func) {
auto P = td::PromiseCreator::lambda([Self = this](td::Result R) {
if (R.is_ok()) {
Self->finish(R.move_as_ok());
} else {
Self->finish(nullptr);
}
});
scheduler_ptr->run_in_context_external([&]() { func(std::move(P)); });
}
void finish(MHD_Response* response) {
std::unique_lock lock(mutex_);
response_ = response;
cond.notify_all();
}
MHD_Response* wait() {
std::unique_lock lock(mutex_);
cond.wait(lock, [&]() { return response_ != nullptr; });
return response_;
}
private:
std::function)> func_;
MHD_Response* response_ = nullptr;
std::mutex mutex_;
std::condition_variable cond;
};
class CoreActor : public CoreActorInterface {
private:
std::string global_config_ = "ton-global.config";
td::actor::ActorOwn client_;
td::uint32 http_port_ = 80;
MHD_Daemon* daemon_ = nullptr;
td::IPAddress remote_addr_;
ton::PublicKey remote_public_key_;
bool hide_ips_ = false;
td::unique_ptr make_callback() {
class Callback : public liteclient::ExtClient::Callback {
public:
Callback(td::actor::ActorId id) : id_(std::move(id)) {
}
private:
td::actor::ActorId id_;
};
return td::make_unique(actor_id(this));
}
std::shared_ptr new_result_;
td::int32 attempt_ = 0;
td::int32 waiting_ = 0;
size_t n_servers_ = 0;
void run_queries();
void got_servers_ready(td::int32 attempt, std::vector ready);
void send_ping(td::uint32 idx);
void got_ping_result(td::uint32 idx, td::int32 attempt, td::Result data);
void add_result() {
if (new_result_) {
auto ts = static_cast(new_result_->ts_.at_unix());
results_.emplace(ts, std::move(new_result_));
}
}
void alarm() override {
auto t = static_cast(td::Clocks::system() / 60);
if (t <= attempt_) {
alarm_timestamp() = td::Timestamp::at_unix((attempt_ + 1) * 60);
return;
}
if (waiting_ > 0 && new_result_) {
add_result();
}
attempt_ = t;
run_queries();
alarm_timestamp() = td::Timestamp::at_unix((attempt_ + 1) * 60);
}
public:
std::mutex queue_mutex_;
std::mutex res_mutex_;
std::map> results_;
std::vector addrs_;
static CoreActor* instance_;
td::actor::ActorId self_id_;
void set_global_config(std::string str) {
global_config_ = str;
}
void set_http_port(td::uint32 port) {
http_port_ = port;
}
void set_remote_addr(td::IPAddress addr) {
remote_addr_ = addr;
}
void set_remote_public_key(td::BufferSlice file_name) {
auto R = [&]() -> td::Result {
TRY_RESULT_PREFIX(conf_data, td::read_file(file_name.as_slice().str()), "failed to read: ");
return ton::PublicKey::import(conf_data.as_slice());
}();
if (R.is_error()) {
LOG(FATAL) << "bad server public key: " << R.move_as_error();
}
remote_public_key_ = R.move_as_ok();
}
void set_hide_ips(bool value) {
hide_ips_ = value;
}
void send_lite_query(td::BufferSlice query, td::Promise promise) override;
void get_last_result(td::Promise> promise) override {
}
void get_results(td::uint32 max, td::Promise promise) override {
RemoteNodeStatusList r;
r.ips = hide_ips_ ? std::vector{addrs_.size()} : addrs_;
auto it = results_.rbegin();
while (it != results_.rend() && r.results.size() < max) {
r.results.push_back(it->second);
it++;
}
promise.set_value(std::move(r));
}
void start_up() override {
instance_ = this;
auto t = td::Clocks::system();
attempt_ = static_cast(t / 60);
auto next_t = (attempt_ + 1) * 60;
alarm_timestamp() = td::Timestamp::at_unix(next_t);
self_id_ = actor_id(this);
}
void tear_down() override {
if (daemon_) {
MHD_stop_daemon(daemon_);
daemon_ = nullptr;
}
}
CoreActor() {
}
static MHD_RESULT get_arg_iterate(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) {
auto X = static_cast*>(cls);
if (key && value && std::strlen(key) > 0 && std::strlen(value) > 0) {
X->emplace(key, urldecode(td::Slice{value}, false));
}
return MHD_YES;
}
struct HttpRequestExtra {
HttpRequestExtra(MHD_Connection* connection, bool is_post) {
if (is_post) {
postprocessor = MHD_create_post_processor(connection, 1 << 14, iterate_post, static_cast(this));
}
}
~HttpRequestExtra() {
MHD_destroy_post_processor(postprocessor);
}
static MHD_RESULT iterate_post(void* coninfo_cls, enum MHD_ValueKind kind, const char* key, const char* filename,
const char* content_type, const char* transfer_encoding, const char* data, uint64_t off,
size_t size) {
auto ptr = static_cast(coninfo_cls);
ptr->total_size += strlen(key) + size;
if (ptr->total_size > MAX_POST_SIZE) {
return MHD_NO;
}
std::string k = key;
if (ptr->opts[k].size() < off + size) {
ptr->opts[k].resize(off + size);
}
td::MutableSlice(ptr->opts[k]).remove_prefix(off).copy_from(td::Slice(data, size));
return MHD_YES;
}
MHD_PostProcessor* postprocessor;
std::map opts;
td::uint64 total_size = 0;
};
static void request_completed(void* cls, struct MHD_Connection* connection, void** ptr,
enum MHD_RequestTerminationCode toe) {
auto e = static_cast(*ptr);
if (e) {
delete e;
}
}
static MHD_RESULT process_http_request(void* cls, struct MHD_Connection* connection, const char* url, const char* method,
const char* version, const char* upload_data, size_t* upload_data_size, void** ptr) {
struct MHD_Response* response = nullptr;
MHD_RESULT ret;
bool is_post = false;
if (std::strcmp(method, "GET") == 0) {
is_post = false;
} else if (std::strcmp(method, "POST") == 0) {
is_post = true;
} else {
return MHD_NO; /* unexpected method */
}
std::map opts;
if (!is_post) {
if (!*ptr) {
*ptr = static_cast(new HttpRequestExtra{connection, false});
return MHD_YES;
}
if (0 != *upload_data_size)
return MHD_NO; /* upload data in a GET!? */
} else {
if (!*ptr) {
*ptr = static_cast(new HttpRequestExtra{connection, true});
return MHD_YES;
}
auto e = static_cast(*ptr);
if (0 != *upload_data_size) {
CHECK(e->postprocessor);
MHD_post_process(e->postprocessor, upload_data, *upload_data_size);
*upload_data_size = 0;
return MHD_YES;
}
for (auto& o : e->opts) {
opts[o.first] = std::move(o.second);
}
}
std::string url_s = url;
*ptr = nullptr; /* clear context pointer */
auto pos = url_s.rfind('/');
std::string prefix;
std::string command;
if (pos == std::string::npos) {
prefix = "";
command = url_s;
} else {
prefix = url_s.substr(0, pos + 1);
command = url_s.substr(pos + 1);
}
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, get_arg_iterate, static_cast(&opts));
if (command == "status") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("blockinfo", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "block") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("blockinfo", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "search") {
if (opts.count("roothash") + opts.count("filehash") > 0) {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("blockinfo", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("blocksearch", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
}
} else if (command == "last") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "download") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("downloadblock", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "viewblock") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("viewblock", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "account") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("viewaccount", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "transaction") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("viewtransaction", opts, prefix, std::move(promise))
.release();
}};
response = g.wait();
} else if (command == "transaction2") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("viewtransaction2", opts, prefix, std::move(promise))
.release();
}};
response = g.wait();
} else if (command == "config") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("getconfig", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "send") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("send", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "sendform") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("sendform", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "runmethod") {
HttpQueryRunner g{[&](td::Promise promise) {
td::actor::create_actor("runmethod", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else {
ret = MHD_NO;
}
if (response) {
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
} else {
ret = MHD_NO;
}
return ret;
}
void run() {
std::vector servers;
if (remote_public_key_.empty()) {
auto G = td::read_file(global_config_).move_as_ok();
auto gc_j = td::json_decode(G.as_slice()).move_as_ok();
ton::ton_api::liteclient_config_global gc;
ton::ton_api::from_json(gc, gc_j.get_object()).ensure();
auto r_servers = liteclient::LiteServerConfig::parse_global_config(gc);
r_servers.ensure();
servers = r_servers.move_as_ok();
for (const auto& serv : servers) {
addrs_.push_back(serv.addr);
}
} else {
if (!remote_addr_.is_valid()) {
LOG(FATAL) << "remote addr not set";
}
addrs_.push_back(remote_addr_);
servers.push_back(liteclient::LiteServerConfig{ton::adnl::AdnlNodeIdFull{remote_public_key_}, remote_addr_});
}
n_servers_ = servers.size();
client_ = liteclient::ExtClient::create(std::move(servers), make_callback(), true);
daemon_ = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, static_cast(http_port_), nullptr, nullptr,
&process_http_request, nullptr, MHD_OPTION_NOTIFY_COMPLETED, request_completed, nullptr,
MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END);
CHECK(daemon_ != nullptr);
}
};
void CoreActor::run_queries() {
waiting_ = 0;
new_result_ = std::make_shared(n_servers_, td::Timestamp::at_unix(attempt_ * 60));
td::actor::send_closure(client_, &liteclient::ExtClient::get_servers_status,
[SelfId = actor_id(this), attempt = attempt_](td::Result> R) {
R.ensure();
td::actor::send_closure(SelfId, &CoreActor::got_servers_ready, attempt, R.move_as_ok());
});
}
void CoreActor::got_servers_ready(td::int32 attempt, std::vector ready) {
if (attempt != attempt_) {
return;
}
CHECK(ready.size() == n_servers_);
for (td::uint32 i = 0; i < n_servers_; i++) {
if (ready[i]) {
send_ping(i);
}
}
CHECK(waiting_ >= 0);
if (waiting_ == 0) {
add_result();
}
}
void CoreActor::send_ping(td::uint32 idx) {
waiting_++;
auto query = ton::create_tl_object();
auto q = ton::create_tl_object(serialize_tl_object(query, true));
auto P =
td::PromiseCreator::lambda([SelfId = actor_id(this), idx, attempt = attempt_](td::Result R) {
td::actor::send_closure(SelfId, &CoreActor::got_ping_result, idx, attempt, std::move(R));
});
td::actor::send_closure(client_, &liteclient::ExtClient::send_query_to_server, "query", serialize_tl_object(q, true),
idx, td::Timestamp::in(10.0), std::move(P));
}
void CoreActor::got_ping_result(td::uint32 idx, td::int32 attempt, td::Result R) {
if (attempt != attempt_) {
return;
}
if (R.is_error()) {
waiting_--;
if (waiting_ == 0) {
add_result();
}
return;
}
auto data = R.move_as_ok();
{
auto F = ton::fetch_tl_object(data.clone(), true);
if (F.is_ok()) {
auto f = F.move_as_ok();
auto err = td::Status::Error(f->code_, f->message_);
waiting_--;
if (waiting_ == 0) {
add_result();
}
return;
}
}
auto F = ton::fetch_tl_object(std::move(data), true);
if (F.is_error()) {
waiting_--;
if (waiting_ == 0) {
add_result();
}
return;
}
auto f = F.move_as_ok();
new_result_->values_[idx] = ton::create_block_id(f->last_);
waiting_--;
CHECK(waiting_ >= 0);
if (waiting_ == 0) {
add_result();
}
}
void CoreActor::send_lite_query(td::BufferSlice query, td::Promise promise) {
auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
}
auto B = R.move_as_ok();
{
auto F = ton::fetch_tl_object(B.clone(), true);
if (F.is_ok()) {
auto f = F.move_as_ok();
promise.set_error(td::Status::Error(f->code_, f->message_));
return;
}
}
promise.set_value(std::move(B));
});
auto q = ton::create_tl_object(std::move(query));
td::actor::send_closure(client_, &liteclient::ExtClient::send_query, "query", serialize_tl_object(q, true),
td::Timestamp::in(10.0), std::move(P));
}
td::actor::ActorId CoreActorInterface::instance_actor_id() {
auto instance = CoreActor::instance_;
CHECK(instance);
return instance->self_id_;
}
CoreActor* CoreActor::instance_ = nullptr;
int main(int argc, char* argv[]) {
SET_VERBOSITY_LEVEL(verbosity_INFO);
td::set_default_failure_signal_handler().ensure();
td::actor::ActorOwn x;
td::OptionParser p;
p.set_description("TON Blockchain explorer");
p.add_checked_option('h', "help", "prints_help", [&]() {
char b[10240];
td::StringBuilder sb(td::MutableSlice{b, 10000});
sb << p;
std::cout << sb.as_cslice().c_str();
std::exit(2);
return td::Status::OK();
});
p.add_checked_option('I', "hide-ips", "hides ips from status", [&]() {
td::actor::send_closure(x, &CoreActor::set_hide_ips, true);
return td::Status::OK();
});
p.add_checked_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user.str()); });
p.add_checked_option('C', "global-config", "file to read global config", [&](td::Slice fname) {
td::actor::send_closure(x, &CoreActor::set_global_config, fname.str());
return td::Status::OK();
});
p.add_checked_option('a', "addr", "connect to ip:port", [&](td::Slice arg) {
td::IPAddress addr;
TRY_STATUS(addr.init_host_port(arg.str()));
td::actor::send_closure(x, &CoreActor::set_remote_addr, addr);
return td::Status::OK();
});
p.add_checked_option('p', "pub", "remote public key", [&](td::Slice arg) {
td::actor::send_closure(x, &CoreActor::set_remote_public_key, td::BufferSlice{arg});
return td::Status::OK();
});
p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
verbosity = td::to_integer(arg);
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
return (verbosity >= 0 && verbosity <= 9) ? td::Status::OK() : td::Status::Error("verbosity must be 0..9");
});
p.add_checked_option('d', "daemonize", "set SIGHUP", [&]() {
td::set_signal_handler(td::SignalType::HangUp, [](int sig) {
#if TD_DARWIN || TD_LINUX
close(0);
setsid();
#endif
}).ensure();
return td::Status::OK();
});
p.add_checked_option('H', "http-port", "listen on http port", [&](td::Slice arg) {
td::actor::send_closure(x, &CoreActor::set_http_port, td::to_integer(arg));
return td::Status::OK();
});
p.add_checked_option('L', "local-scripts", "use local copy of ajax/bootstrap/... JS", [&]() {
local_scripts = true;
return td::Status::OK();
});
#if TD_DARWIN || TD_LINUX
p.add_checked_option('l', "logname", "log to file", [&](td::Slice fname) {
auto FileLog = td::FileFd::open(td::CSlice(fname.str().c_str()),
td::FileFd::Flags::Create | td::FileFd::Flags::Append | td::FileFd::Flags::Write)
.move_as_ok();
dup2(FileLog.get_native_fd().fd(), 1);
dup2(FileLog.get_native_fd().fd(), 2);
return td::Status::OK();
});
#endif
vm::init_vm().ensure();
td::actor::Scheduler scheduler({2});
scheduler_ptr = &scheduler;
scheduler.run_in_context([&] { x = td::actor::create_actor("testnode"); });
scheduler.run_in_context([&] { p.run(argc, argv).ensure(); });
scheduler.run_in_context([&] {
td::actor::send_closure(x, &CoreActor::run);
x.release();
});
scheduler.run();
return 0;
}