1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

View file

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
find_package(MHD)
if (MHD_FOUND)
set(BLOCHAIN_EXPLORER_SOURCE
blockchain-explorer.cpp
blockchain-explorer.hpp
blockchain-explorer-http.cpp
blockchain-explorer-http.hpp
blockchain-explorer-query.cpp
blockchain-explorer-query.hpp
)
add_executable(blockchain-explorer ${BLOCHAIN_EXPLORER_SOURCE})
target_include_directories(blockchain-explorer PUBLIC ${MHD_INCLUDE_DIRS})
target_link_libraries(blockchain-explorer tdutils tdactor adnllite tl_lite_api tl-lite-utils
ton_crypto ton_block ${MHD_LIBRARY})
endif()

View file

@ -0,0 +1,695 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "blockchain-explorer-http.hpp"
#include "block/block-db.h"
#include "block/block.h"
#include "block/block-parse.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 "ton/ton-shard.h"
HttpAnswer& HttpAnswer::operator<<(AddressCell addr_c) {
ton::WorkchainId wc;
ton::StdSmcAddress addr;
if (!block::tlb::t_MsgAddressInt.extract_std_address(addr_c.root, wc, addr)) {
abort("<cannot unpack addr>");
return *this;
}
block::StdAddress caddr{wc, addr};
*this << "<a href=\"" << AccountLink{caddr, ton::BlockIdExt{}} << "\">" << caddr.rserialize(true) << "</a>";
return *this;
}
HttpAnswer& HttpAnswer::operator<<(MessageCell msg) {
if (msg.root.is_null()) {
abort("<message not found");
return *this;
}
vm::CellSlice cs{vm::NoVmOrd(), msg.root};
block::gen::CommonMsgInfo info;
td::Ref<vm::CellSlice> src, dest;
*this << "<div id=\"msg" << msg.root->get_hash() << "\">";
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table-sm table-striped\">\n"
<< "<tr><th>hash</th><td>" << msg.root->get_hash().to_hex() << "</td></tr>\n";
switch (block::gen::t_CommonMsgInfo.get_tag(cs)) {
case block::gen::CommonMsgInfo::ext_in_msg_info: {
block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
if (!tlb::unpack(cs, info)) {
abort("<cannot unpack inbound external message>");
return *this;
}
*this << "<tr><th>type</th><td>external</td></tr>\n"
<< "<tr><th>source</th><td>NONE</td></tr>\n"
<< "<tr><th>destination</th><td>" << AddressCell{info.dest} << "</td></tr>\n";
break;
}
case block::gen::CommonMsgInfo::ext_out_msg_info: {
block::gen::CommonMsgInfo::Record_ext_out_msg_info info;
if (!tlb::unpack(cs, info)) {
abort("<cannot unpack outbound external message>");
return *this;
}
*this << "<tr><th>type</th><td>external OUT</td></tr>\n"
<< "<tr><th>source</th><td>" << AddressCell{info.src} << "</td></tr>\n"
<< "<tr><th>destination</th><td>NONE</td></tr>\n"
<< "<tr><th>lt</th><td>" << info.created_lt << "</td></tr>\n"
<< "<tr><th>time</th><td>" << info.created_at << "</td></tr>\n";
break;
}
case block::gen::CommonMsgInfo::int_msg_info: {
block::gen::CommonMsgInfo::Record_int_msg_info info;
if (!tlb::unpack(cs, info)) {
abort("cannot unpack internal message");
return *this;
}
td::RefInt256 value;
td::Ref<vm::Cell> extra;
if (!block::unpack_CurrencyCollection(info.value, value, extra)) {
abort("cannot unpack message value");
return *this;
}
*this << "<tr><th>type</th><td>internal</td></tr>\n"
<< "<tr><th>source</th><td>" << AddressCell{info.src} << "</td></tr>\n"
<< "<tr><th>destination</th><td>" << AddressCell{info.dest} << "</td></tr>\n"
<< "<tr><th>lt</th><td>" << info.created_lt << "</td></tr>\n"
<< "<tr><th>time</th><td>" << info.created_at << "</td></tr>\n"
<< "<tr><th>value</th><td>" << value << "</td></tr>\n";
break;
}
default:
abort("cannot unpack message");
return *this;
}
*this << "</table></div>\n";
*this << RawData<block::gen::Message>{msg.root, block::gen::t_Anything} << "</div>";
return *this;
}
HttpAnswer& HttpAnswer::operator<<(ton::BlockIdExt block_id) {
return *this << "<a href=\"" << BlockLink{block_id} << "\">" << block_id.id.to_str() << "</a>";
}
HttpAnswer& HttpAnswer::operator<<(ton::BlockId block_id) {
return *this << "<a href=\"" << prefix_ << "search?workchain=" << block_id.workchain
<< "&shard=" << ton::shard_to_str(block_id.shard) << "&seqno=" << block_id.seqno << "\">"
<< block_id.to_str() << "</a>";
}
HttpAnswer& HttpAnswer::operator<<(BlockSearch bs) {
*this << "<form class=\"container\" action=\"" << prefix_ << "search\" method=\"get\">"
<< "<div class=\"row\">"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>workchain</label>"
<< "<input type=\"text\" class=\"form-control mr-2\" name=\"workchain\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.workchain) : "") << "\">"
<< "</div>\n"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>shard/account</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"shard\" value=\""
<< (bs.block_id.is_valid() ? ton::shard_to_str(bs.block_id.id.shard) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>seqno</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"seqno\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.seqno) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label class=\"d-none d-lg-block\">&nbsp;</label>"
<< "<div><button type=\"submit\" class=\"btn btn-primary mr-2\">Submit</button></div>"
<< "</div></div><div class=\"row\">"
<< "<div class=\"form-group col-md-6\">"
<< "<label>logical time</label>"
<< "<input type=\"text\" class=\"form-control mr-2\" name=\"lt\">"
<< "</div>\n"
<< "<div class=\"form-group col-md-6\">"
<< "<label>unix time</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"utime\"></div>"
<< "</div><div class=\"row\">"
<< "<div class=\"form-group col-md-6\">"
<< "<label>root hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"roothash\" value=\""
//<< (!bs.block_id.id.is_valid() || bs.block_id.root_hash.is_zero() ? "" : bs.block_id.root_hash.to_hex())
<< "\"></div>"
<< "<div class=\"col-md-6\">"
<< "<label>file hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"filehash\" value=\""
//<< (!bs.block_id.id.is_valid() || bs.block_id.file_hash.is_zero() ? "" : bs.block_id.file_hash.to_hex())
<< "\"></div>"
<< "</div></form>\n";
return *this;
}
HttpAnswer& HttpAnswer::operator<<(AccountSearch bs) {
*this << "<form class=\"container\" action=\"" << prefix_ << "account\" method=\"get\">"
<< "<div class=\"row\">"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>workchain</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"workchain\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.workchain) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>shard</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"shard\" value=\""
<< (bs.block_id.is_valid() ? ton::shard_to_str(bs.block_id.id.shard) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>seqno</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"seqno\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.seqno) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label class=\"d-none d-lg-block\">&nbsp;</label>"
<< "<div><button type=\"submit\" class=\"btn btn-primary mr-2\">Submit</button>"
<< "<button class=\"btn btn-outline-primary\" type=\"reset\">Reset</button></div>"
<< "</div></div><div class=\"row\">"
<< "<div class=\"form-group col-md-6\">"
<< "<label>root hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"roothash\" value=\""
<< (!bs.block_id.id.is_valid() || bs.block_id.root_hash.is_zero() ? "" : bs.block_id.root_hash.to_hex())
<< "\"></div>"
<< "<div class=\"form-group col-md-6\">"
<< "<label>file hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"filehash\" value=\""
<< (!bs.block_id.id.is_valid() || bs.block_id.file_hash.is_zero() ? "" : bs.block_id.file_hash.to_hex())
<< "\"></div>"
<< "</div><div class=\"row\">"
<< "<div class=\"form-group col-md-12\">"
<< "<label>account id</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"account\" value=\""
<< (bs.addr.addr.is_zero() ? "" : bs.addr.rserialize(true)) << "\"></div>"
<< "</div>\n"
<< "</form>\n";
return *this;
}
HttpAnswer& HttpAnswer::operator<<(TransactionSearch bs) {
*this << "<form class=\"container\" action=\"" << prefix_ << "transaction\" method=\"get\">"
<< "<div class=\"row\">"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>workchain</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"workchain\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.workchain) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>shard</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"shard\" value=\""
<< (bs.block_id.is_valid() ? ton::shard_to_str(bs.block_id.id.shard) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label>seqno</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"seqno\" value=\""
<< (bs.block_id.is_valid() ? std::to_string(bs.block_id.id.seqno) : "") << "\"></div>"
<< "<div class=\"form-group col-lg-3 col-md-4\">"
<< "<label class=\"d-none d-lg-block\">&nbsp;</label>"
<< "<div><button type=\"submit\" class=\"btn btn-primary mr-2\">Submit</button>"
<< "<button class=\"btn btn-outline-primary\" type=\"reset\">Reset</button></div>"
<< "</div></div><div class=\"row\">"
<< "<div class=\"form-group col-md-6\">"
<< "<label>root hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"roothash\" value=\""
<< (!bs.block_id.id.is_valid() || bs.block_id.root_hash.is_zero() ? "" : bs.block_id.root_hash.to_hex())
<< "\"></div>"
<< "<div class=\"form-group col-md-6\">"
<< "<label>file hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"filehash\" value=\""
<< (!bs.block_id.id.is_valid() || bs.block_id.file_hash.is_zero() ? "" : bs.block_id.file_hash.to_hex())
<< "\"></div>"
<< "</div><div class=\"row\">"
<< "<div class=\"form-group col-md-12\">"
<< "<label>account id</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"account\" value=\""
<< (bs.addr.addr.is_zero() ? "" : bs.addr.rserialize(true)) << "\"></div>"
<< "</div><div class=\"row\">"
<< "<div class=\"form-group col-md-3\">"
<< "<label>transaction lt</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"lt\" value=\""
<< (bs.lt ? std::to_string(bs.lt) : "") << "\"></div>"
<< "<div class=\"form-group col-md-9\">"
<< "<label>transaction hash</label>"
<< "<input type =\"text\" class=\"form-control mr-2\" name=\"hash\" value=\""
<< (bs.hash.is_zero() ? "" : bs.hash.to_hex()) << "\"></div>"
<< "</div>\n"
<< "</form>\n";
return *this;
}
HttpAnswer& HttpAnswer::operator<<(TransactionCell trans_c) {
if (trans_c.root.is_null()) {
abort("transaction not found");
return *this;
}
block::gen::Transaction::Record trans;
if (!tlb::unpack_cell(trans_c.root, trans)) {
abort("cannot unpack");
return *this;
}
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table-sm table-striped\">\n"
<< "<tr><th>block</th><td><a href=\"" << BlockLink{trans_c.block_id} << "\">"
<< trans_c.block_id.id.to_str() << "</a></td></tr>"
<< "<tr><th>workchain</th><td>" << trans_c.addr.workchain << "</td></tr>"
<< "<tr><th>account hex</th><td>" << trans_c.addr.addr.to_hex() << "</td></tr>"
<< "<tr><th>account</th><td>" << trans_c.addr.rserialize(true) << "</td></tr>"
<< "<tr><th>hash</th><td>" << trans_c.root->get_hash().to_hex() << "</td></tr>\n"
<< "<tr><th>lt</th><td>" << trans.lt << "</td></tr>\n"
<< "<tr><th>time</th><td>" << trans.now << "</td></tr>\n"
<< "<tr><th>out messages</th><td>";
vm::Dictionary dict{trans.r1.out_msgs, 15};
for (td::int32 i = 0; i < trans.outmsg_cnt; i++) {
auto out_msg = dict.lookup_ref(td::BitArray<15>{i});
*this << " <a href=\"" << MessageLink{out_msg} << "\">" << i << "</a>";
}
*this << "</td></tr>\n"
<< "<tr><th>in message</th><td>";
auto in_msg = trans.r1.in_msg->prefetch_ref();
if (in_msg.is_null()) {
*this << "NONE";
} else {
*this << "<a href=\"" << MessageLink{in_msg} << "\">" << in_msg->get_hash() << "</a>";
}
*this << "</td></tr>\n"
<< "<tr><th>prev transaction</th><td>";
auto prev_lt = trans.prev_trans_lt;
auto prev_hash = trans.prev_trans_hash;
if (prev_lt > 0) {
*this << "<a href=\"" << TransactionLink{trans_c.addr, prev_lt, prev_hash} << "\">lt=" << prev_lt
<< " hash=" << prev_hash.to_hex() << "</a>";
} else {
*this << "NONE";
}
*this << "</td></tr></table></div>\n";
if (in_msg.not_null()) {
*this << "<hr />" << MessageCell{in_msg};
}
for (int x = 0; x < trans.outmsg_cnt && x < 100; x++) {
auto out_msg = dict.lookup_ref(td::BitArray<15>{x});
*this << "<hr />" << MessageCell{out_msg};
}
*this << "<hr />";
return *this << RawData<block::gen::Transaction>{trans_c.root} << "</div>";
}
HttpAnswer& HttpAnswer::operator<<(AccountCell acc_c) {
*this << "<div>";
auto block_id = acc_c.block_id;
if (!block_id.is_valid_full()) {
abort(PSTRING() << "shard block id " << block_id.to_str() << " in answer is invalid");
return *this;
}
if (!ton::shard_contains(block_id.shard_full(), ton::extract_addr_prefix(acc_c.addr.workchain, acc_c.addr.addr))) {
abort(PSTRING() << "received data from shard block " << block_id.to_str()
<< " that cannot contain requested account " << acc_c.addr.workchain << ":"
<< acc_c.addr.addr.to_hex());
return *this;
}
if (acc_c.q_roots.size() != 2) {
abort(PSTRING() << "account state proof must have exactly two roots");
return *this;
}
ton::LogicalTime last_trans_lt = 0;
ton::Bits256 last_trans_hash;
last_trans_hash.set_zero();
try {
auto state_root = vm::MerkleProof::virtualize(acc_c.q_roots[1], 1);
if (state_root.is_null()) {
abort("account state proof is invalid");
return *this;
}
block::gen::ShardStateUnsplit::Record sstate;
if (!(tlb::unpack_cell(std::move(state_root), sstate))) {
abort("cannot unpack state header");
return *this;
}
vm::AugmentedDictionary accounts_dict{vm::load_cell_slice_ref(sstate.accounts), 256, block::tlb::aug_ShardAccounts};
auto acc_csr = accounts_dict.lookup(acc_c.addr.addr);
if (acc_csr.not_null()) {
if (acc_c.root.is_null()) {
abort(PSTRING() << "account state proof shows that account state for " << acc_c.addr.workchain << ":"
<< acc_c.addr.addr.to_hex() << " must be non-empty, but it actually is empty");
return *this;
}
block::gen::ShardAccount::Record acc_info;
if (!tlb::csr_unpack(std::move(acc_csr), acc_info)) {
abort("cannot unpack ShardAccount from proof");
return *this;
}
if (acc_info.account->get_hash().bits().compare(acc_c.root->get_hash().bits(), 256)) {
abort(PSTRING() << "account state hash mismatch: Merkle proof expects "
<< acc_info.account->get_hash().bits().to_hex(256) << " but received data has "
<< acc_c.root->get_hash().bits().to_hex(256));
return *this;
}
last_trans_hash = acc_info.last_trans_hash;
last_trans_lt = acc_info.last_trans_lt;
} else if (acc_c.root.not_null()) {
abort(PSTRING() << "account state proof shows that account state for " << acc_c.addr.workchain << ":"
<< acc_c.addr.addr.to_hex() << " must be empty, but it is not");
return *this;
}
} catch (vm::VmError err) {
abort(PSTRING() << "error while traversing account proof : " << err.get_msg());
return *this;
} catch (vm::VmVirtError err) {
abort(PSTRING() << "virtualization error while traversing account proof : " << err.get_msg());
return *this;
}
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table-sm table-striped\">\n";
*this << "<tr><th>block</th><td><a href=\"" << BlockLink{acc_c.block_id} << "\">"
<< block_id.id.to_str() << "</a></td></tr>";
*this << "<tr><th>workchain</th><td>" << acc_c.addr.workchain << "</td></tr>";
*this << "<tr><th>account hex</th><td>" << acc_c.addr.addr.to_hex() << "</td></tr>";
*this << "<tr><th>account</th><td>" << acc_c.addr.rserialize(true) << "</td></tr>";
if (last_trans_lt > 0) {
*this << "<tr><th>last transaction</th><td>"
<< "<a href=\"" << TransactionLink{acc_c.addr, last_trans_lt, last_trans_hash} << "\">lt=" << last_trans_lt
<< " hash=" << last_trans_hash.to_hex() << "</a></td></tr>\n";
} else {
*this << "<tr><th>last transaction</th><td>no transactions</td></tr>";
}
*this << "</table></div>\n";
*this << "<p><a class=\"btn btn-primary\" href=\"" << prefix_ << "account?account=" << acc_c.addr.rserialize(true)
<< "\">go to current state</a></p>\n";
if (acc_c.root.not_null()) {
*this << RawData<block::gen::Account>{acc_c.root};
} else {
*this << "<div class=\"alert alert-info\">account state is empty</div>";
}
return *this << "</div>";
}
HttpAnswer& HttpAnswer::operator<<(BlockHeaderCell head_c) {
*this << "<div>";
vm::CellSlice cs{vm::NoVm{}, head_c.root};
auto block_id = head_c.block_id;
try {
auto virt_root = vm::MerkleProof::virtualize(head_c.root, 1);
if (virt_root.is_null()) {
abort("invalid merkle proof");
return *this;
}
ton::RootHash vhash{virt_root->get_hash().bits()};
std::vector<ton::BlockIdExt> prev;
ton::BlockIdExt mc_blkid;
bool after_split;
auto res = block::unpack_block_prev_blk_ext(virt_root, block_id, prev, mc_blkid, after_split);
if (res.is_error()) {
abort(PSTRING() << "cannot unpack header for block " << block_id.to_str() << ": " << res);
return *this;
}
block::gen::Block::Record blk;
block::gen::BlockInfo::Record info;
if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(blk.info, info))) {
abort(PSTRING() << "cannot unpack header for block " << block_id.to_str());
return *this;
}
bool before_split = info.before_split;
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table-sm table-striped\">\n"
<< "<tr><th>block</th><td>" << block_id.id.to_str() << "</td></tr>\n"
<< "<tr><th>roothash</th><td>" << block_id.root_hash.to_hex() << "</td></tr>\n"
<< "<tr><th>filehash</th><td>" << block_id.file_hash.to_hex() << "</td></tr>\n"
<< "<tr><th>time</th><td>" << info.gen_utime << "</td></tr>\n"
<< "<tr><th>lt</th><td>" << info.start_lt << " .. " << info.end_lt
<< "</td></tr>\n"
<< "<tr><th>global_id</th><td>" << blk.global_id << "</td></tr>\n"
<< "<tr><th>version</th><td>" << info.version << "</td></tr>\n"
<< "<tr><th>not_master</th><td>" << info.not_master << "</td></tr>\n"
<< "<tr><th>after_merge</th><td>" << info.after_merge << "</td></tr>\n"
<< "<tr><th>after_split</th><td>" << info.after_split << "</td></tr>\n"
<< "<tr><th>before_split</th><td>" << info.before_split << "</td></tr>\n"
<< "<tr><th>want_merge</th><td>" << info.want_merge << "</td></tr>\n"
<< "<tr><th>want_split</th><td>" << info.want_split << "</td></tr>\n"
<< "<tr><th>validator_list_hash_short</th><td>"
<< info.gen_validator_list_hash_short << "</td></tr>\n"
<< "<tr><th>catchain_seqno</th><td>" << info.gen_catchain_seqno << "</td></tr>\n"
<< "<tr><th>min_ref_mc_seqno</th><td>" << info.min_ref_mc_seqno
<< "</td></tr>\n";
for (auto id : prev) {
*this << "<tr><th>prev block</th><td>" << id << "</td></tr>\n";
}
if (!before_split) {
*this << "<tr><th>next block</th><td>"
<< ton::BlockId{block_id.id.workchain, block_id.id.shard, block_id.id.seqno + 1} << "</td></tr>\n";
} else {
*this << "<tr><th>next block</th><td>"
<< ton::BlockId{block_id.id.workchain, ton::shard_child(block_id.id.shard, true), block_id.id.seqno + 1}
<< "</td></tr>\n";
*this << "<tr><th>next block</th><td>"
<< ton::BlockId{block_id.id.workchain, ton::shard_child(block_id.id.shard, false), block_id.id.seqno + 1}
<< "</td></tr>\n";
}
*this << "<tr><th>masterchain block</th><td>" << mc_blkid << "</td></tr>\n"
<< "</table></div>";
} catch (vm::VmError err) {
abort(PSTRING() << "error processing header : " << err.get_msg());
return *this;
} catch (vm::VmVirtError err) {
abort(PSTRING() << "error processing header : " << err.get_msg());
return *this;
}
return *this << "<p><a class=\"btn btn-primary mr-2\" href=\"" << BlockDownloadLink{block_id} << "\" download=\""
<< block_id.file_hash << ".boc\">download block</a>"
<< "<a class=\"btn btn-primary\" href=\"" << BlockViewLink{block_id} << "\">view block</a>\n"
<< "</p></div>";
}
HttpAnswer& HttpAnswer::operator<<(BlockShardsCell shards_c) {
block::ShardConfig sh_conf;
if (!sh_conf.unpack(vm::load_cell_slice_ref(shards_c.root))) {
abort("cannot extract shard block list from shard configuration");
return *this;
} else {
auto ids = sh_conf.get_shard_hash_ids(true);
auto workchain = ton::masterchainId;
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table\">\n<tbody>\n"
<< "<thead>\n"
<< "<tr>\n"
<< "<th scope=\"col\">shard</th>"
<< "<th scope=\"col\">seqno</th>"
<< "<th scope=\"col\">created</th>"
<< "<th scope=\"col\">wantsplit</th>"
<< "<th scope=\"col\">wantmerge</th>"
<< "<th scope=\"col\">beforesplit</th>"
<< "<th scope=\"col\">beforemerge</th>"
<< "</tr>\n"
<< "</thead>\n";
for (auto id : ids) {
auto ref = sh_conf.get_shard_hash(ton::ShardIdFull(id));
if (id.workchain != workchain) {
if (workchain != ton::masterchainId) {
*this << "<tr></tr>\n";
}
workchain = id.workchain;
}
*this << "<tr>";
ton::ShardIdFull shard{id.workchain, id.shard};
if (ref.not_null()) {
*this << "<td>" << shard.to_str() << "</td><td><a href=\"" << HttpAnswer::BlockLink{ref->top_block_id()}
<< "\">" << ref->top_block_id().id.seqno << "</a></td><td>" << ref->created_at() << "</td>"
<< "<td>" << ref->want_split_ << "</td>"
<< "<td>" << ref->want_merge_ << "</td>"
<< "<td>" << ref->before_split_ << "</td>"
<< "<td>" << ref->before_merge_ << "</td>";
} else {
*this << "<td>" << shard.to_str() << "</td>";
}
*this << "</tr>";
}
return *this << "</tbody></table></div>";
}
}
HttpAnswer& HttpAnswer::operator<<(AccountLink account) {
*this << prefix_ << "account?";
if (account.block_id.is_valid()) {
block_id_link(account.block_id);
*this << "&";
}
return *this << "account=" << account.account_id.rserialize(true);
}
HttpAnswer& HttpAnswer::operator<<(MessageLink msg) {
return *this << "#msg" << msg.root->get_hash();
}
HttpAnswer& HttpAnswer::operator<<(TransactionLink trans) {
return *this << prefix_ << "transaction?"
<< "account=" << trans.account_id.rserialize(true) << "&lt=" << trans.lt << "&hash=" << trans.hash;
}
HttpAnswer& HttpAnswer::operator<<(TransactionLinkShort trans) {
*this << prefix_ << "transaction2?";
block_id_link(trans.block_id);
return *this << "&account=" << trans.account_id.rserialize(true) << "&lt=" << trans.lt;
}
HttpAnswer& HttpAnswer::operator<<(BlockLink block) {
*this << prefix_ << "block?";
block_id_link(block.block_id);
return *this;
}
HttpAnswer& HttpAnswer::operator<<(BlockViewLink block) {
*this << prefix_ << "viewblock?";
block_id_link(block.block_id);
return *this;
}
HttpAnswer& HttpAnswer::operator<<(BlockDownloadLink block) {
*this << prefix_ << "download?";
block_id_link(block.block_id);
return *this;
}
HttpAnswer& HttpAnswer::operator<<(TransactionList trans) {
*this << "<div class=\"table-responsive my-3\">\n"
<< "<table class=\"table\">\n<tbody>\n"
<< "<thead>\n"
<< "<tr>\n"
<< "<th scope=\"col\">seq</th>"
<< "<th scope=\"col\">account</th>"
<< "<th scope=\"col\">lt</th>"
<< "<th scope=\"col\">hash</th>"
<< "<th scope=\"col\">link</th>"
<< "</tr>\n"
<< "</thead>\n";
td::uint32 idx = 0;
for (auto& x : trans.vec) {
*this << "<tr><td><a href=\"" << TransactionLink{x.addr, x.lt, x.hash} << "\">" << ++idx << "</a></td>"
<< "<td><a href=\"" << AccountLink{x.addr, trans.block_id} << "\">" << x.addr.rserialize(true) << "</a></td>"
<< "<td>" << x.lt << "</td>"
<< "<td>" << x.hash.to_hex() << "</td>"
<< "<td><a href=\"" << TransactionLink{x.addr, x.lt, x.hash} << "\">view</a></td></tr>";
}
if (trans.vec.size() == trans.req_count_) {
*this << "<tr><td>" << ++idx << "</td>"
<< "<td>more</td>"
<< "<td>more</td>"
<< "<td>more</td></tr>";
}
return *this << "</tbody></table></div>";
}
HttpAnswer& HttpAnswer::operator<<(Error error) {
return *this << "<div class=\"alert alert-danger\">" << error.error.to_string() << "</div>";
}
void HttpAnswer::block_id_link(ton::BlockIdExt block_id) {
*this << "workchain=" << block_id.id.workchain << "&shard=" << ton::shard_to_str(block_id.id.shard)
<< "&seqno=" << block_id.id.seqno << "&roothash=" << block_id.root_hash << "&filehash=" << block_id.file_hash;
}
std::string HttpAnswer::abort(td::Status error) {
if (error_.is_ok()) {
error_ = std::move(error);
}
return header() + "<div class=\"alert alert-danger\">" + error_.to_string() + "</div>" + footer();
}
std::string HttpAnswer::abort(std::string error) {
return abort(td::Status::Error(404, error));
}
std::string HttpAnswer::header() {
sb_->clear();
*this << "<!DOCTYPE html>\n"
<< "<html lang=\"en\"><head><meta charset=\"utf-8\"><title>" << title_ << "</title>\n"
<< "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n"
<< "<meta name=\"format-detection\" content=\"telephone=no\" />\n"
<< "<!-- Latest compiled and minified CSS -->\n"
<< "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\">\n"
<< "<!-- jQuery library -->"
<< "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js\"></script>\n"
<< "<!-- Popper JS -->\n"
<< "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js\"></script>\n"
<< "<!-- Latest compiled JavaScript -->\n"
<< "<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js\"></script>\n"
<< "</head><body>\n"
<< "<div class=\"container-fluid\">\n"
<< "<nav class=\"navbar navbar-expand px-0 mt-1 flex-wrap\">\n"
<< "<ul class=\"navbar-nav ml-1 mr-5 my-1\">\n"
<< "<li class=\"nav-item\"><a class=\"nav-link\" href=\"" << prefix_ << "status\">status</a></li>\n"
<< "<li class=\"nav-item\"><a class=\"nav-link\" href=\"" << prefix_ << "last\">last</a></li>\n"
<< "</ul>";
*this << "<form class=\"my-1 my-lg-0 flex-grow-1\" action=\"" << prefix_ << "account\" method=\"get\">"
<< "<div class=\"input-group ml-auto\" style=\"max-width:540px;\">"
<< "<input class=\"form-control mr-2 rounded\" type=\"search\" placeholder=\"account\" aria-label=\"account\" "
<< "name=\"account\">";
*this << "<div class=\"input-group-append\"><button class=\"btn btn-outline-primary rounded\" type=\"submit\">view</button></div>"
<< "</div></form>"
<< "</nav>\n";
*this << "<p>\n"
<< "<a class=\"btn btn-primary mt-1\" data-toggle=\"collapse\" href=\"#blocksearch\" role=\"button\" "
"aria-expanded=\"false\" aria-controls=\"blocksearch\">\n"
<< "Search block\n"
<< "</a>\n"
<< "<a class=\"btn btn-primary mt-1\" data-toggle=\"collapse\" href=\"#accountsearch\" role=\"button\" "
"aria-expanded=\"false\" aria-controls=\"accountsearch\">\n"
<< "Search account\n"
<< "</a>\n"
<< "<a class=\"btn btn-primary mt-1\" data-toggle=\"collapse\" href=\"#transactionsearch\" role=\"button\" "
"aria-expanded=\"false\" aria-controls=\"transactionsearch\">\n"
<< "Search transaction\n"
<< "</a>\n"
<< "</p>\n";
*this << "<div id=\"searchgroup\">\n"
<< "<div class=\"collapse\" data-parent=\"#searchgroup\" id=\"blocksearch\">\n"
<< "<div class=\"card card-body\">\n"
<< BlockSearch{block_id_} << "</div></div>\n";
*this << "<div class=\"collapse\" data-parent=\"#searchgroup\" id=\"accountsearch\">\n"
<< "<div class=\"card card-body\">\n"
<< AccountSearch{block_id_, account_id_} << "</div></div>\n";
*this << "<div class=\"collapse\" data-parent=\"#searchgroup\" id=\"transactionsearch\">\n"
<< "<div class=\"card card-body\">\n"
<< TransactionSearch{block_id_, account_id_, 0, ton::Bits256::zero()} << "</div></div></div>\n";
return sb_->as_cslice().c_str();
}
std::string HttpAnswer::footer() {
return PSTRING() << "</div></body></html>";
}
std::string HttpAnswer::finish() {
if (error_.is_ok()) {
std::string data = sb_->as_cslice().c_str();
return header() + data + footer();
} else {
return header() + "<div class=\"alert alert-danger\">" + error_.to_string() + "</div>" + footer();
}
}

View file

@ -0,0 +1,222 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "ton/ton-types.h"
#include "vm/boc.h"
#include "vm/cellops.h"
#include "td/utils/Random.h"
#include "block/block.h"
class HttpAnswer {
public:
struct MessageCell {
td::Ref<vm::Cell> root;
};
struct AddressCell {
td::Ref<vm::CellSlice> root;
};
struct TransactionCell {
block::StdAddress addr;
ton::BlockIdExt block_id;
td::Ref<vm::Cell> root;
};
struct AccountCell {
block::StdAddress addr;
ton::BlockIdExt block_id;
td::Ref<vm::Cell> root;
std::vector<td::Ref<vm::Cell>> q_roots;
};
struct BlockHeaderCell {
ton::BlockIdExt block_id;
td::Ref<vm::Cell> root;
};
struct BlockShardsCell {
ton::BlockIdExt block_id;
td::Ref<vm::Cell> root;
};
struct AccountLink {
block::StdAddress account_id;
ton::BlockIdExt block_id;
};
struct MessageLink {
td::Ref<vm::Cell> root;
};
struct TransactionLink {
block::StdAddress account_id;
ton::LogicalTime lt;
ton::Bits256 hash;
};
struct TransactionLinkShort {
ton::BlockIdExt block_id;
block::StdAddress account_id;
ton::LogicalTime lt;
};
struct BlockLink {
ton::BlockIdExt block_id;
};
struct BlockViewLink {
ton::BlockIdExt block_id;
};
struct BlockDownloadLink {
ton::BlockIdExt block_id;
};
struct BlockSearch {
ton::BlockIdExt block_id;
};
struct AccountSearch {
ton::BlockIdExt block_id;
block::StdAddress addr;
};
struct TransactionSearch {
ton::BlockIdExt block_id;
block::StdAddress addr;
ton::LogicalTime lt;
ton::Bits256 hash;
};
struct TransactionList {
struct TransactionDescr {
TransactionDescr(block::StdAddress addr, ton::LogicalTime lt, ton::Bits256 hash)
: addr(addr), lt(lt), hash(hash) {
}
block::StdAddress addr;
ton::LogicalTime lt;
ton::Bits256 hash;
};
ton::BlockIdExt block_id;
std::vector<TransactionDescr> vec;
td::uint32 req_count_;
};
struct CodeBlock {
std::string data;
};
struct Error {
td::Status error;
};
template <class T>
struct RawData {
td::Ref<vm::Cell> root;
T x;
template <typename... Args>
RawData(td::Ref<vm::Cell> root, Args &&... args) : root(std::move(root)), x(std::forward<Args>(args)...) {
}
};
public:
HttpAnswer(std::string title, std::string prefix) : title_(title), prefix_(prefix) {
buf_ = td::BufferSlice{1 << 28};
sb_ = std::make_unique<td::StringBuilder>(buf_.as_slice());
}
void set_title(std::string title) {
title_ = title;
}
void set_block_id(ton::BlockIdExt block_id) {
block_id_ = block_id;
workchain_id_ = block_id_.id.workchain;
}
void set_account_id(block::StdAddress addr) {
account_id_ = addr;
}
void set_workchain(ton::WorkchainId workchain_id) {
workchain_id_ = workchain_id;
}
std::string abort(td::Status error);
std::string abort(std::string error);
std::string finish();
std::string header();
std::string footer();
template <typename T>
HttpAnswer &operator<<(T x) {
sb() << x;
return *this;
}
td::StringBuilder &sb() {
return *sb_;
}
HttpAnswer &operator<<(td::Bits256 x) {
sb() << x.to_hex();
return *this;
}
HttpAnswer &operator<<(td::BitString x) {
sb() << x.to_hex();
return *this;
}
HttpAnswer &operator<<(AddressCell addr);
HttpAnswer &operator<<(MessageCell msg);
HttpAnswer &operator<<(ton::BlockIdExt block_id);
HttpAnswer &operator<<(ton::BlockId block_id);
HttpAnswer &operator<<(TransactionCell trans);
HttpAnswer &operator<<(AccountCell trans);
HttpAnswer &operator<<(BlockHeaderCell head);
HttpAnswer &operator<<(BlockShardsCell shards);
HttpAnswer &operator<<(BlockSearch head);
HttpAnswer &operator<<(AccountSearch head);
HttpAnswer &operator<<(TransactionSearch head);
HttpAnswer &operator<<(AccountLink account);
HttpAnswer &operator<<(MessageLink msg);
HttpAnswer &operator<<(TransactionLink trans);
HttpAnswer &operator<<(TransactionLinkShort trans);
HttpAnswer &operator<<(BlockLink block);
HttpAnswer &operator<<(BlockViewLink block);
HttpAnswer &operator<<(BlockDownloadLink block);
HttpAnswer &operator<<(Error error);
HttpAnswer &operator<<(TransactionList trans);
HttpAnswer &operator<<(CodeBlock block) {
return *this << "<pre><code>" << block.data << "</code></pre>";
}
template <class T>
HttpAnswer &operator<<(RawData<T> data) {
std::ostringstream outp;
data.x.print_ref(outp, data.root);
vm::load_cell_slice(data.root).print_rec(outp);
return *this << CodeBlock{outp.str()};
}
private:
void block_id_link(ton::BlockIdExt block_id);
std::string title_;
ton::BlockIdExt block_id_;
ton::WorkchainId workchain_id_ = ton::workchainInvalid;
block::StdAddress account_id_;
std::string prefix_;
td::Status error_;
std::unique_ptr<td::StringBuilder> sb_;
td::BufferSlice buf_;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,274 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "ton/ton-types.h"
#include "block/block.h"
#include "blockchain-explorer.hpp"
#include <map>
#include <microhttpd.h>
td::Result<ton::BlockIdExt> parse_block_id(std::map<std::string, std::string> &opts, bool allow_empty = false);
td::Result<block::StdAddress> parse_account_addr(std::map<std::string, std::string> &opts);
class HttpAnswer;
class HttpQueryCommon : public td::actor::Actor {
public:
HttpQueryCommon(std::string prefix, td::Promise<MHD_Response *> promise)
: prefix_(std::move(prefix)), promise_(std::move(promise)) {
}
void start_up() override {
if (error_.is_error()) {
abort_query(std::move(error_));
return;
}
start_up_query();
}
virtual void start_up_query() {
UNREACHABLE();
}
virtual void abort_query(td::Status error);
void create_header(HttpAnswer &ans) {
}
protected:
td::Status error_;
std::string prefix_;
td::Promise<MHD_Response *> promise_;
};
class HttpQueryBlockData : public HttpQueryCommon {
public:
HttpQueryBlockData(ton::BlockIdExt block_id, std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockData(std::map<std::string, std::string> opts, std::string prefix, td::Promise<MHD_Response *> promise);
void abort_query(td::Status error) override;
void finish_query();
void start_up() override;
void got_block_data(td::BufferSlice result);
private:
ton::BlockIdExt block_id_;
td::BufferSlice data_;
};
class HttpQueryBlockView : public HttpQueryCommon {
public:
HttpQueryBlockView(ton::BlockIdExt block_id, std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockView(std::map<std::string, std::string> opts, std::string prefix, td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_block_data(td::BufferSlice result);
private:
ton::BlockIdExt block_id_;
td::BufferSlice data_;
};
class HttpQueryBlockInfo : public HttpQueryCommon {
public:
HttpQueryBlockInfo(ton::BlockIdExt block_id, std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockInfo(std::map<std::string, std::string> opts, std::string prefix, td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_block_header(td::BufferSlice result);
void got_shard_info(td::BufferSlice result);
void got_transactions(td::BufferSlice result);
void failed_to_get_shard_info(td::Status error);
private:
ton::BlockIdExt block_id_;
td::int32 pending_queries_ = 0;
td::BufferSlice data_;
td::BufferSlice shard_data_;
td::Status shard_data_error_;
struct TransactionDescr {
TransactionDescr(block::StdAddress addr, ton::LogicalTime lt, ton::Bits256 hash) : addr(addr), lt(lt), hash(hash) {
}
block::StdAddress addr;
ton::LogicalTime lt;
ton::Bits256 hash;
};
std::vector<TransactionDescr> transactions_;
td::uint32 trans_req_count_;
};
class HttpQueryBlockSearch : public HttpQueryCommon {
public:
HttpQueryBlockSearch(ton::WorkchainId workchain, ton::AccountIdPrefix account, ton::BlockSeqno seqno,
std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockSearch(ton::WorkchainId workchain, ton::AccountIdPrefix account, ton::LogicalTime lt,
std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockSearch(ton::WorkchainId workchain, ton::AccountIdPrefix account, bool dummy, ton::UnixTime utime,
std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryBlockSearch(std::map<std::string, std::string> opts, std::string prefix,
td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_block_header(td::BufferSlice result);
void got_shard_info(td::BufferSlice result);
void got_transactions(td::BufferSlice result);
void failed_to_get_shard_info(td::Status error);
private:
ton::AccountIdPrefixFull account_prefix_;
td::uint32 mode_ = 0;
ton::BlockSeqno seqno_ = 0;
ton::LogicalTime lt_ = 0;
ton::UnixTime utime_ = 0;
ton::BlockIdExt block_id_;
td::BufferSlice data_;
td::BufferSlice shard_data_;
td::Status shard_data_error_;
td::uint32 pending_queries_ = 0;
struct TransactionDescr {
TransactionDescr(block::StdAddress addr, ton::LogicalTime lt, ton::Bits256 hash) : addr(addr), lt(lt), hash(hash) {
}
block::StdAddress addr;
ton::LogicalTime lt;
ton::Bits256 hash;
};
std::vector<TransactionDescr> transactions_;
td::uint32 trans_req_count_;
};
class HttpQueryViewAccount : public HttpQueryCommon {
public:
HttpQueryViewAccount(ton::BlockIdExt block_id, block::StdAddress addr, std::string prefix,
td::Promise<MHD_Response *> promise);
HttpQueryViewAccount(std::map<std::string, std::string> opts, std::string prefix,
td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_account(td::BufferSlice result);
private:
ton::BlockIdExt block_id_;
block::StdAddress addr_;
td::BufferSlice data_;
td::BufferSlice proof_;
ton::BlockIdExt res_block_id_;
};
class HttpQueryViewTransaction : public HttpQueryCommon {
public:
HttpQueryViewTransaction(block::StdAddress addr, ton::LogicalTime lt, ton::Bits256 hash, std::string prefix,
td::Promise<MHD_Response *> promise);
HttpQueryViewTransaction(std::map<std::string, std::string> opts, std::string prefix,
td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_transaction(td::BufferSlice result);
private:
block::StdAddress addr_;
ton::LogicalTime lt_;
ton::Bits256 hash_;
td::BufferSlice data_;
ton::BlockIdExt res_block_id_;
};
class HttpQueryViewTransaction2 : public HttpQueryCommon {
public:
HttpQueryViewTransaction2(ton::BlockIdExt block_id, block::StdAddress addr, ton::LogicalTime lt, std::string prefix,
td::Promise<MHD_Response *> promise);
HttpQueryViewTransaction2(std::map<std::string, std::string> opts, std::string prefix,
td::Promise<MHD_Response *> promise);
void finish_query();
void start_up_query() override;
void got_transaction(td::BufferSlice result);
private:
ton::BlockIdExt block_id_;
block::StdAddress addr_;
ton::LogicalTime lt_;
ton::Bits256 hash_;
td::BufferSlice data_;
};
class HttpQueryViewLastBlock : public HttpQueryCommon {
public:
HttpQueryViewLastBlock(std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryViewLastBlock(std::map<std::string, std::string> opts, std::string prefix,
td::Promise<MHD_Response *> promise);
void finish_query();
void start_up() override;
void got_result(td::BufferSlice result);
private:
ton::BlockIdExt res_block_id_;
};
class HttpQueryStatus : public HttpQueryCommon {
public:
HttpQueryStatus(std::string prefix, td::Promise<MHD_Response *> promise);
HttpQueryStatus(std::map<std::string, std::string> opts, std::string prefix, td::Promise<MHD_Response *> promise);
void finish_query();
void start_up() override;
void got_results(CoreActorInterface::RemoteNodeStatusList results);
private:
CoreActorInterface::RemoteNodeStatusList results_;
};

View file

@ -0,0 +1,582 @@
/*
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;
}

View file

@ -0,0 +1,56 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "ton/ton-types.h"
#include "td/utils/port/IPAddress.h"
class CoreActorInterface : public td::actor::Actor {
public:
struct RemoteNodeStatus {
std::vector<ton::BlockIdExt> values_;
td::Timestamp ts_;
RemoteNodeStatus(size_t size, td::Timestamp ts) : ts_(ts) {
values_.resize(size);
}
};
struct RemoteNodeStatusList {
std::vector<td::IPAddress> ips;
std::vector<std::shared_ptr<RemoteNodeStatus>> results;
};
virtual ~CoreActorInterface() = default;
virtual void send_lite_query(td::BufferSlice data, td::Promise<td::BufferSlice> promise) = 0;
virtual void get_last_result(td::Promise<std::shared_ptr<RemoteNodeStatus>> promise) = 0;
virtual void get_results(td::uint32 max, td::Promise<RemoteNodeStatusList> promise) = 0;
static td::actor::ActorId<CoreActorInterface> instance_actor_id();
};