1
0
mirror of https://github.com/danog/ton.git synced 2024-11-30 04:29:19 +01:00
ton/blockchain-explorer/blockchain-explorer.cpp
2019-09-07 14:33:36 +04:00

583 lines
19 KiB
C++

/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
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 <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "adnl/adnl-ext-client.h"
#include "adnl/utils.hpp"
#include "auto/tl/ton_api_json.h"
#include "td/utils/OptionsParser.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 "auto/tl/lite_api.h"
#include "ton/lite-tl.hpp"
#include "tl-utils/lite-utils.hpp"
#include <microhttpd.h>
#if TD_DARWIN || TD_LINUX
#include <unistd.h>
#include <fcntl.h>
#endif
#include <iostream>
#include <sstream>
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<char>(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<void(td::Promise<MHD_Response*>)> func) {
auto P = td::PromiseCreator::lambda([Self = this](td::Result<MHD_Response*> R) {
if (R.is_ok()) {
Self->finish(R.move_as_ok());
} else {
Self->finish(nullptr);
}
});
mutex_.lock();
scheduler_ptr->run_in_context_external([&]() { func(std::move(P)); });
}
void finish(MHD_Response* response) {
response_ = response;
mutex_.unlock();
}
MHD_Response* wait() {
mutex_.lock();
mutex_.unlock();
return response_;
}
private:
std::function<void(td::Promise<MHD_Response*>)> func_;
MHD_Response* response_;
std::mutex mutex_;
};
class CoreActor : public CoreActorInterface {
private:
std::string global_config_ = "ton-global.config";
std::vector<td::actor::ActorOwn<ton::adnl::AdnlExtClient>> clients_;
td::uint32 http_port_ = 80;
MHD_Daemon* daemon_ = nullptr;
td::IPAddress remote_addr_;
ton::PublicKey remote_public_key_;
bool hide_ips_ = false;
std::unique_ptr<ton::adnl::AdnlExtClient::Callback> make_callback(td::uint32 idx) {
class Callback : public ton::adnl::AdnlExtClient::Callback {
public:
void on_ready() override {
td::actor::send_closure(id_, &CoreActor::conn_ready, idx_);
}
void on_stop_ready() override {
td::actor::send_closure(id_, &CoreActor::conn_closed, idx_);
}
Callback(td::actor::ActorId<CoreActor> id, td::uint32 idx) : id_(std::move(id)), idx_(idx) {
}
private:
td::actor::ActorId<CoreActor> id_;
td::uint32 idx_;
};
return std::make_unique<Callback>(actor_id(this), idx);
}
std::shared_ptr<RemoteNodeStatus> new_result_;
td::int32 attempt_ = 0;
td::int32 waiting_ = 0;
std::vector<bool> ready_;
void run_queries();
void got_result(td::uint32 idx, td::int32 attempt, td::Result<td::BufferSlice> data);
void send_query(td::uint32 idx);
void add_result() {
if (new_result_) {
auto ts = static_cast<td::int32>(new_result_->ts_.at_unix());
results_.emplace(ts, std::move(new_result_));
}
}
void alarm() override {
auto t = static_cast<td::int32>(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<td::int32, std::shared_ptr<RemoteNodeStatus>> results_;
std::vector<td::IPAddress> addrs_;
static CoreActor* instance_;
td::actor::ActorId<CoreActor> self_id_;
void conn_ready(td::uint32 idx) {
ready_.at(idx) = true;
}
void conn_closed(td::uint32 idx) {
ready_.at(idx) = false;
}
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<ton::PublicKey> {
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::uint32 idx, td::BufferSlice query, td::Promise<td::BufferSlice> promise);
void send_lite_query(td::BufferSlice data, td::Promise<td::BufferSlice> promise) override {
return send_lite_query(0, std::move(data), std::move(promise));
}
void get_last_result(td::Promise<std::shared_ptr<RemoteNodeStatus>> promise) override {
}
void get_results(td::uint32 max, td::Promise<RemoteNodeStatusList> promise) override {
RemoteNodeStatusList r;
r.ips = hide_ips_ ? std::vector<td::IPAddress>{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<td::int32>(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 int get_arg_iterate(void* cls, enum MHD_ValueKind kind, const char* key, const char* value) {
auto X = static_cast<std::map<std::string, std::string>*>(cls);
if (key && value && std::strlen(key) > 0 && std::strlen(value) > 0) {
X->emplace(key, urldecode(td::Slice{value}, false));
}
return MHD_YES;
}
static int 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) {
static int dummy;
struct MHD_Response* response = nullptr;
int ret;
if (0 != std::strcmp(method, "GET"))
return MHD_NO; /* unexpected method */
if (&dummy != *ptr) {
/* The first time only the headers are valid,
do not respond in the first round... */
*ptr = &dummy;
return MHD_YES;
}
if (0 != *upload_data_size)
return MHD_NO; /* upload data in a GET!? */
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);
}
std::map<std::string, std::string> opts;
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, get_arg_iterate, static_cast<void*>(&opts));
if (command == "status") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryStatus>("blockinfo", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "block") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryBlockInfo>("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<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryBlockInfo>("blockinfo", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryBlockSearch>("blocksearch", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
}
} else if (command == "last") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryViewLastBlock>("", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "download") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryBlockData>("downloadblock", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "viewblock") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryBlockView>("viewblock", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "account") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryViewAccount>("viewaccount", opts, prefix, std::move(promise)).release();
}};
response = g.wait();
} else if (command == "transaction") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryViewTransaction>("viewtransaction", opts, prefix, std::move(promise))
.release();
}};
response = g.wait();
} else if (command == "transaction2") {
HttpQueryRunner g{[&](td::Promise<MHD_Response*> promise) {
td::actor::create_actor<HttpQueryViewTransaction2>("viewtransaction2", 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() {
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();
CHECK(gc.liteservers_.size() > 0);
td::uint32 size = static_cast<td::uint32>(gc.liteservers_.size());
ready_.resize(size, false);
for (td::uint32 i = 0; i < size; i++) {
auto& cli = gc.liteservers_[i];
td::IPAddress addr;
addr.init_host_port(td::IPAddress::ipv4_to_str(cli->ip_), cli->port_).ensure();
addrs_.push_back(addr);
clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull::create(cli->id_).move_as_ok(),
addr, make_callback(i)));
}
} else {
if (!remote_addr_.is_valid()) {
LOG(FATAL) << "remote addr not set";
}
ready_.resize(1, false);
addrs_.push_back(remote_addr_);
clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull{remote_public_key_},
remote_addr_, make_callback(0)));
}
daemon_ = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, static_cast<td::uint16>(http_port_), nullptr, nullptr,
&process_http_request, nullptr, MHD_OPTION_END);
CHECK(daemon_ != nullptr);
}
};
void CoreActor::got_result(td::uint32 idx, td::int32 attempt, td::Result<td::BufferSlice> 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<ton::lite_api::liteServer_error>(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<ton::lite_api::liteServer_masterchainInfo>(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_query(td::uint32 idx) {
if (!ready_[idx]) {
return;
}
waiting_++;
auto query = ton::create_tl_object<ton::lite_api::liteServer_getMasterchainInfo>();
auto q = ton::create_tl_object<ton::lite_api::liteServer_query>(serialize_tl_object(query, true));
auto P =
td::PromiseCreator::lambda([SelfId = actor_id(this), idx, attempt = attempt_](td::Result<td::BufferSlice> R) {
td::actor::send_closure(SelfId, &CoreActor::got_result, idx, attempt, std::move(R));
});
td::actor::send_closure(clients_[idx], &ton::adnl::AdnlExtClient::send_query, "query", serialize_tl_object(q, true),
td::Timestamp::in(10.0), std::move(P));
}
void CoreActor::run_queries() {
waiting_ = 0;
new_result_ = std::make_shared<RemoteNodeStatus>(ready_.size(), td::Timestamp::at_unix(attempt_ * 60));
for (td::uint32 i = 0; i < ready_.size(); i++) {
send_query(i);
}
CHECK(waiting_ >= 0);
if (waiting_ == 0) {
add_result();
}
}
void CoreActor::send_lite_query(td::uint32 idx, td::BufferSlice query, td::Promise<td::BufferSlice> promise) {
if (!ready_[idx]) {
promise.set_error(td::Status::Error(ton::ErrorCode::notready, "ext conn not ready"));
return;
}
auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result<td::BufferSlice> 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<ton::lite_api::liteServer_error>(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<ton::lite_api::liteServer_query>(std::move(query));
td::actor::send_closure(clients_[idx], &ton::adnl::AdnlExtClient::send_query, "query", serialize_tl_object(q, true),
td::Timestamp::in(10.0), std::move(P));
}
td::actor::ActorId<CoreActorInterface> 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<CoreActor> x;
td::OptionsParser p;
p.set_description("TON Blockchain explorer");
p.add_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_option('I', "hide-ips", "hides ips from status", [&]() {
td::actor::send_closure(x, &CoreActor::set_hide_ips, true);
return td::Status::OK();
});
p.add_option('u', "user", "change user", [&](td::Slice user) { return td::change_user(user); });
p.add_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_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_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_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
verbosity = td::to_integer<int>(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_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_option('H', "http-port", "listen on http port", [&](td::Slice arg) {
td::actor::send_closure(x, &CoreActor::set_http_port, td::to_integer<td::uint32>(arg));
return td::Status::OK();
});
#if TD_DARWIN || TD_LINUX
p.add_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
td::actor::Scheduler scheduler({2});
scheduler_ptr = &scheduler;
scheduler.run_in_context([&] { x = td::actor::create_actor<CoreActor>("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;
}