mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
initial commit
This commit is contained in:
commit
c2da007f40
1610 changed files with 398047 additions and 0 deletions
22
blockchain-explorer/CMakeLists.txt
Normal file
22
blockchain-explorer/CMakeLists.txt
Normal 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()
|
695
blockchain-explorer/blockchain-explorer-http.cpp
Normal file
695
blockchain-explorer/blockchain-explorer-http.cpp
Normal 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\"> </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\"> </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\"> </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) << "<=" << 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) << "<=" << 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();
|
||||
}
|
||||
}
|
222
blockchain-explorer/blockchain-explorer-http.hpp
Normal file
222
blockchain-explorer/blockchain-explorer-http.hpp
Normal 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_;
|
||||
};
|
1029
blockchain-explorer/blockchain-explorer-query.cpp
Normal file
1029
blockchain-explorer/blockchain-explorer-query.cpp
Normal file
File diff suppressed because it is too large
Load diff
274
blockchain-explorer/blockchain-explorer-query.hpp
Normal file
274
blockchain-explorer/blockchain-explorer-query.hpp
Normal 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_;
|
||||
};
|
||||
|
582
blockchain-explorer/blockchain-explorer.cpp
Normal file
582
blockchain-explorer/blockchain-explorer.cpp
Normal 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;
|
||||
}
|
56
blockchain-explorer/blockchain-explorer.hpp
Normal file
56
blockchain-explorer/blockchain-explorer.hpp
Normal 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();
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue