Merge pull request #63 from moneroexamples/add_network_info_front_page

Add network info front page
master
moneroexamples 8 years ago committed by GitHub
commit cd140fd4ba

@ -112,6 +112,7 @@ set(LIBRARIES
unbound
curl
crypto
atomic
ssl)
if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT WIN32)

@ -33,9 +33,10 @@ Clearnet versions:
- [https://xmrchain.net/](https://xmrchain.net/) - https enabled, most popular and very stable.
- [https://monerohash.com/explorer/](https://monerohash.com/explorer/) - nice looking one, https enabled.
- [http://explore.MoneroWorld.com](http://explore.moneroworld.com) - same as the second one.
- [https://explorer.xmr.my/](https://explorer.xmr.my/) - https enabled.
- [https://explorer.xmr.my/](https://explorer.xmr.my/) - nice looking one, https enabled.
- [https://explorer.monero-otc.com/](https://explorer.monero-otc.com/) - https enabled.
- [http://monerochain.com/](http://monerochain.com/) - JSON API based, multiple nodes.
- [http://66.85.74.134:8081/](http://66.85.74.134:8081/) - fluffynet subnet explorer.
Clearnet testnet Monero version:
@ -47,6 +48,11 @@ i2p users (main Monero network) - down for now:
- [http://monerotools.i2p](http://monerotools.i2p)
Alternative block explorers:
- [http://moneroblocks.info](http://moneroblocks.info/)
- [https://monerobase.com](https://monerobase.com/)
- [http://chainradar.com](http://chainradar.com/xmr/blocks)
## Onion Monero Blockchain Explorer features
@ -66,7 +72,8 @@ The key features of the Onion Monero Blockchain Explorer are:
- the only explorer showing number of amount output indices,
- the only explorer supporting Monero testnet network,
- the only explorer providing tx checker and pusher for online pushing of transactions,
- the only explorer able to estimate possible spendings based on address and viewkey.
- the only explorer able to estimate possible spendings based on address and viewkey,
- the only explorer that can provide total amount of all miner fees.
## Compilation on Ubuntu 16.04
@ -101,6 +108,7 @@ as follows:
```bash
# go to home folder if still in ~/monero
cd ~
# download the source code
git clone https://github.com/moneroexamples/onion-monero-blockchain-explorer.git
@ -113,6 +121,9 @@ mkdir build && cd build
# create the makefile
cmake ..
# altearnatively can use: cmake -DMONERO_DIR=/path/to/monero_folder ..
# if monero is not in ~/monero
# compile
make
```
@ -143,10 +154,10 @@ Go to your browser: http://127.0.0.1:8081
```
./xmrblocks -h
xmrblocks, start Onion Monero Blockchain Explorer:
xmrblocks, Onion Monero Blockchain Explorer:
-h [ --help ] [=arg(=1)] (=0) produce help message
-t [ --testnet ] [=arg(=1)] (=0) use testnet blockchain
--enable-pusher [=arg(=1)] (=0) enable pushing signed tx
--enable-pusher [=arg(=1)] (=0) enable signed transaction pusher
--enable-mixin-details [=arg(=1)] (=0)
enable mixin details for key images,
e.g., timescale, mixin of mixins, in tx
@ -155,44 +166,92 @@ xmrblocks, start Onion Monero Blockchain Explorer:
enable key images file checker
--enable-output-key-checker [=arg(=1)] (=0)
enable outputs key file checker
--enable-mempool-cache arg (=1) enable caching txs in the mempool
--enable-mempool-cache arg (=1) enable caching of transactions from the
mempool
--enable-json-api arg (=1) enable JSON REST api
--enable-tx-cache [=arg(=1)] (=0) enable caching of tx details
--enable-tx-cache [=arg(=1)] (=0) enable caching of transaction details
--show-cache-times [=arg(=1)] (=0) show times of getting data from cache
vs no cache
--enable-block-cache [=arg(=1)] (=0) enable caching of block details
--enable-autorefresh-option [=arg(=1)] (=0)
enable users to have the index page on
autorefresh
-p [ --port ] arg (=8081) default port
--testnet-url arg you can specifiy testnet url, if you
run it on mainet. link will show on
front page to testnet explorer
--mainnet-url arg you can specifiy mainnet url, if you
run it on testnet. link will show on
front page to mainnet explorer
--enable-emission-monitor [=arg(=1)] (=0)
enable Monero total emission monitoring
thread
-p [ --port ] arg (=8081) default explorer port
--testnet-url arg you can specify testnet url, if you run
it on mainnet. link will show on front
page to testnet explorer
--mainnet-url arg you can specify mainnet url, if you run
it on testnet. link will show on front
page to mainnet explorer
--no-blocks-on-index arg (=10) number of last blocks to be shown on
index page
-b [ --bc-path ] arg path to lmdb blockchain
--ssl-crt-file arg A path to crt file for ssl (https)
--network-info-timeout arg (=1000) maximum time, in milliseconds, to wait
for network info availability
--mempool-info-timeout arg (=5000) maximum time, in milliseconds, to wait
for mempool data for the front page
-b [ --bc-path ] arg path to lmdb folder of the blockchain,
e.g., ~/.bitmonero/lmdb
--ssl-crt-file arg path to crt file for ssl (https)
functionality
--ssl-key-file arg A path to key file for ssl (https)
--ssl-key-file arg path to key file for ssl (https)
functionality
-d [ --deamon-url ] arg (=http:://127.0.0.1:18081)
monero address string
Monero deamon url
```
Example usage, defined as bash aliases.
```bash
# for mainnet explorer
alias xmrblocksmainnet='~/onion-monero-blockchain-explorer/build/xmrblocks --port 8081 --no-blocks-on-index 49 --testnet-url "http://139.162.32.245:8082" --enable-block-cache=1 --enable-tx-cache=1 --enable-mempool-cache=1 --show-cache-times=1 --enable-pusher'
alias xmrblocksmainnet='~/onion-monero-blockchain-explorer/build/xmrblocks --port 8081 --no-blocks-on-index 24 --testnet-url "http://139.162.32.245:8082" --enable-pusher --enable-emission-monitor'
# for testnet explorer
alias xmrblockstestnet='~/onion-monero-blockchain-explorer/build/xmrblocks -t --port 8082 --no-blocks-on-index 24 --mainnet-url "http://139.162.32.245:8081" --enable-block-cache=1 --enable-tx-cache=1 --enable-mempool-cache=1 --show-cache-times=1 --enable-pusher'
alias xmrblockstestnet='~/onion-monero-blockchain-explorer/build/xmrblocks -t --port 8082 --no-blocks-on-index 24 --mainnet-url "http://139.162.32.245:8081" --enable-pusher --enable-emission-monitor'
```
These are aliases similar to those used for http://139.162.32.245:8081/ and http://139.162.32.245:8082/, respectively.
## Enable Monero emission
Obtaining current Monero emission amount is not straight forward. Thus, by default it is
disabled. To enable it use `--enable-emission-monitor` flag, e.g.,
```bash
xmrblocks --enable-emission-monitor
```
This flag will enable emission monitoring thread. When started, the thread
will initially scan the entire blockchain, and calculate the cumulative emission based on each block.
Since it is a separate thread, the explorer will work as usual during this time.
Every 10000 blocks, the thread will save current emission in a file, by default,
in `~/.bitmonero/lmdb/emission_amount.txt`. For testnet network,
it is `~/.bitmonero/testnet/lmdb/emission_amount.txt`. This file is used so that we don't
need to rescan entire blockchain whenever the explorer is restarted. When the
explorer restarts, the thread will first check if `~/.bitmonero/lmdb/emission_amount.txt`
is present, read its values, and continue from there if possible. Subsequently, only the initial
use of the tread is time consuming. Once the thread scans the entire blockchain, it updates
the emission amount using new blocks as they come. Since the explorer writes this file, there can
be only one instance of it running for mainnet and testnet. Thus, for example, you cant have
two explorers for mainnet
running at the same time, as they will be trying to write and read the same file at the same time,
leading to unexpected results. Off course having one instance for mainnet and one instance for testnet
is fine, as they write to different files.
When the emission monitor is enabled, information about current emission of coinbase and fees is
displayed on the front page, e.g., :
```
Monero emission (fees) is 14485540.430 (52545.373) as of 1313448 block
```
These are explorer commands used for http://139.162.32.245:8081/ and http://139.162.32.245:8082/, respectively.
The values given, can be checked using Monero daemon's `print_coinbase_tx_sum` command.
For example, for the above example: `print_coinbase_tx_sum 0 1313449`.
To disable the monitor, simply restart the explorer without `--enable-emission-monitor` flag.
## Enable SSL (https)
@ -564,27 +623,47 @@ curl -w "\n" -X GET "http://139.162.32.245:8081/api/networkinfo"
"data": {
"alt_blocks_count": 0,
"block_size_limit": 600000,
"cumulative_difficulty": 2067724366624367,
"difficulty": 7530486740,
"grey_peerlist_size": 4987,
"hash_rate": 62754056,
"height": 1307537,
"cumulative_difficulty": 2091549555696348,
"difficulty": 7941560081,
"fee_per_kb": 303970000,
"grey_peerlist_size": 4991,
"hash_rate": 66179667,
"height": 1310423,
"incoming_connections_count": 0,
"outgoing_connections_count": 8,
"start_time": 1494473774,
"outgoing_connections_count": 5,
"start_time": 1494822692,
"status": "OK",
"target": 120,
"target_height": 1307518,
"target_height": 0,
"testnet": false,
"top_block_hash": "0726de5b86f431547fc64fc2c8e1c11d76843ada0561993ee540e4eee29d83c3",
"tx_count": 1210222,
"tx_pool_size": 5,
"top_block_hash": "76f9e85d62415312758bc09e0b9b48fd2b005231ad1eee435a8081e551203f82",
"tx_count": 1219048,
"tx_pool_size": 2,
"white_peerlist_size": 1000
},
"status": "success"
}
```
#### api/emission
```bash
curl -w "\n" -X GET "http://139.162.32.245:8081/api/emission"
```
```json
{
"data": {
"blk_no": 1313969,
"coinbase": 14489473877253413000,
"fee": 52601974988641130
},
"status": "success"
}
```
Emission only works when the emission monitoring thread is enabled.
#### api/rawblock/<block_number|block_hash>

@ -42,6 +42,16 @@ macro(create_git_version)
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Get current branch name
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE
)
configure_file(
${CMAKE_SOURCE_DIR}/src/version.h.in
${CMAKE_BINARY_DIR}/gen/version.h

@ -17,7 +17,8 @@ namespace myxmr
{
struct jsonresponse: crow::response
{
jsonresponse(const nlohmann::json& _body) : crow::response {_body.dump()}
jsonresponse(const nlohmann::json& _body)
: crow::response {_body.dump()}
{
add_header("Access-Control-Allow-Origin", "*");
add_header("Access-Control-Allow-Headers", "Content-Type");
@ -26,10 +27,9 @@ struct jsonresponse: crow::response
};
}
int main(int ac, const char* av[]) {
int
main(int ac, const char* av[])
{
// get command line options
xmreg::CmdLineOptions opts {ac, av};
@ -49,6 +49,8 @@ int main(int ac, const char* av[]) {
auto no_blocks_on_index_opt = opts.get_option<string>("no-blocks-on-index");
auto testnet_url = opts.get_option<string>("testnet-url");
auto mainnet_url = opts.get_option<string>("mainnet-url");
auto network_info_timeout_opt = opts.get_option<string>("network-info-timeout");
auto mempool_info_timeout_opt = opts.get_option<string>("mempool-info-timeout");
auto testnet_opt = opts.get_option<bool>("testnet");
auto enable_key_image_checker_opt = opts.get_option<bool>("enable-key-image-checker");
auto enable_output_key_checker_opt = opts.get_option<bool>("enable-output-key-checker");
@ -60,6 +62,9 @@ int main(int ac, const char* av[]) {
auto enable_tx_cache_opt = opts.get_option<bool>("enable-tx-cache");
auto enable_block_cache_opt = opts.get_option<bool>("enable-block-cache");
auto show_cache_times_opt = opts.get_option<bool>("show-cache-times");
auto enable_emission_monitor_opt = opts.get_option<bool>("enable-emission-monitor");
bool testnet {*testnet_opt};
bool enable_pusher {*enable_pusher_opt};
@ -71,6 +76,7 @@ int main(int ac, const char* av[]) {
bool enable_json_api {*enable_json_api_opt};
bool enable_tx_cache {*enable_tx_cache_opt};
bool enable_block_cache {*enable_block_cache_opt};
bool enable_emission_monitor {*enable_emission_monitor_opt};
bool show_cache_times {*show_cache_times_opt};
@ -128,6 +134,7 @@ int main(int ac, const char* av[]) {
cout << blockchain_path << endl;
// create instance of our MicroCore
// and make pointer to the Blockchain
xmreg::MicroCore mcore;
@ -144,7 +151,58 @@ int main(int ac, const char* av[]) {
string deamon_url {*deamon_url_opt};
if (testnet && deamon_url == "http:://127.0.0.1:18081")
{
deamon_url = "http:://127.0.0.1:28081";
}
uint64_t network_info_timeout {1000};
uint64_t mempool_info_timeout {5000};
try
{
network_info_timeout = boost::lexical_cast<uint64_t>(*network_info_timeout_opt);
mempool_info_timeout = boost::lexical_cast<uint64_t>(*mempool_info_timeout_opt);
}
catch (boost::bad_lexical_cast &e)
{
cout << "Cant cast " << (*network_info_timeout_opt)
<< " or/and " << (*mempool_info_timeout_opt) <<" into numbers. Using default values."
<< endl;
}
if (enable_emission_monitor == true)
{
// This starts new thread, which aim is
// to calculate, store and monitor
// current total Monero emission amount.
// This thread stores the current emission
// which it has caluclated in
// <blockchain_path>/emission_amount.txt file,
// e.g., ~/.bitmonero/lmdb/emission_amount.txt.
// So instead of calcualting the emission
// from scrach whenever the explorer is started,
// the thread is initalized with the values
// found in emission_amount.txt file.
xmreg::CurrentBlockchainStatus::blockchain_path
= blockchain_path;
xmreg::CurrentBlockchainStatus::testnet
= testnet;
xmreg::CurrentBlockchainStatus::deamon_url
= deamon_url;
xmreg::CurrentBlockchainStatus::set_blockchain_variables(
&mcore, core_storage);
// launch the status monitoring thread so that it keeps track of blockchain
// info, e.g., current height. Information from this thread is used
// by tx searching threads that are launched for each user independently,
// when they log back or create new account.
xmreg::CurrentBlockchainStatus::start_monitor_blockchain_thread();
}
// create instance of page class which
// contains logic for the website
@ -162,6 +220,8 @@ int main(int ac, const char* av[]) {
enable_block_cache,
show_cache_times,
no_blocks_on_index,
network_info_timeout,
mempool_info_timeout,
*testnet_url,
*mainnet_url);
@ -434,6 +494,14 @@ int main(int ac, const char* av[]) {
return r;
});
CROW_ROUTE(app, "/api/emission")
([&](const crow::request &req) {
myxmr::jsonresponse r{xmrblocks.json_emission()};
return r;
});
CROW_ROUTE(app, "/api/outputs").methods("GET"_method)
([&](const crow::request &req) {
@ -490,5 +558,16 @@ int main(int ac, const char* av[]) {
}
if (enable_emission_monitor == true)
{
// finish Emission monitoring thread in a cotrolled manner.
xmreg::CurrentBlockchainStatus::m_thread.interrupt();
xmreg::CurrentBlockchainStatus::m_thread.join();
cout << "Emission monitoring thread joined." << endl;
}
cout << "The explorer is terminating." << endl;
return EXIT_SUCCESS;
}

@ -6,7 +6,7 @@ set(SOURCE_HEADERS
MicroCore.h
tools.h
monero_headers.h
)
CurrentBlockchainStatus.h)
set(SOURCE_FILES
MicroCore.cpp
@ -14,7 +14,7 @@ set(SOURCE_FILES
CmdLineOptions.cpp
page.h
rpccalls.cpp rpccalls.h
version.h.in)
version.h.in CurrentBlockchainStatus.cpp)
# make static library called libmyxrm
# that we are going to link to

@ -18,7 +18,7 @@ namespace xmreg
p.add("txhash", -1);
options_description desc(
"xmrblocks, start Onion Monero Blockchain Explorer");
"xmrblocks, Onion Monero Blockchain Explorer");
desc.add_options()
("help,h", value<bool>()->default_value(false)->implicit_value(true),
@ -26,7 +26,7 @@ namespace xmreg
("testnet,t", value<bool>()->default_value(false)->implicit_value(true),
"use testnet blockchain")
("enable-pusher", value<bool>()->default_value(false)->implicit_value(true),
"enable pushing signed tx")
"enable signed transaction pusher")
("enable-mixin-details", value<bool>()->default_value(false)->implicit_value(true),
"enable mixin details for key images, e.g., timescale, mixin of mixins, in tx context")
("enable-key-image-checker", value<bool>()->default_value(false)->implicit_value(true),
@ -34,33 +34,39 @@ namespace xmreg
("enable-output-key-checker", value<bool>()->default_value(false)->implicit_value(true),
"enable outputs key file checker")
("enable-mempool-cache", value<bool>()->default_value(true),
"enable caching txs in the mempool")
"enable caching of transactions from the mempool")
("enable-json-api", value<bool>()->default_value(true),
"enable JSON REST api")
("enable-tx-cache", value<bool>()->default_value(false)->implicit_value(true),
"enable caching of tx details")
"enable caching of transaction details")
("show-cache-times", value<bool>()->default_value(false)->implicit_value(true),
"show times of getting data from cache vs no cache")
("enable-block-cache", value<bool>()->default_value(false)->implicit_value(true),
"enable caching of block details")
("enable-autorefresh-option", value<bool>()->default_value(false)->implicit_value(true),
"enable users to have the index page on autorefresh")
("enable-emission-monitor", value<bool>()->default_value(false)->implicit_value(true),
"enable Monero total emission monitoring thread")
("port,p", value<string>()->default_value("8081"),
"default port")
"default explorer port")
("testnet-url", value<string>()->default_value(""),
"you can specifiy testnet url, if you run it on mainet. link will show on front page to testnet explorer")
"you can specify testnet url, if you run it on mainnet. link will show on front page to testnet explorer")
("mainnet-url", value<string>()->default_value(""),
"you can specifiy mainnet url, if you run it on testnet. link will show on front page to mainnet explorer")
"you can specify mainnet url, if you run it on testnet. link will show on front page to mainnet explorer")
("no-blocks-on-index", value<string>()->default_value("10"),
"number of last blocks to be shown on index page")
("network-info-timeout", value<string>()->default_value("1000"),
"maximum time, in milliseconds, to wait for network info availability")
("mempool-info-timeout", value<string>()->default_value("5000"),
"maximum time, in milliseconds, to wait for mempool data for the front page")
("bc-path,b", value<string>(),
"path to lmdb blockchain")
"path to lmdb folder of the blockchain, e.g., ~/.bitmonero/lmdb")
("ssl-crt-file", value<string>(),
"A path to crt file for ssl (https) functionality")
"path to crt file for ssl (https) functionality")
("ssl-key-file", value<string>(),
"A path to key file for ssl (https) functionality")
"path to key file for ssl (https) functionality")
("deamon-url,d", value<string>()->default_value("http:://127.0.0.1:18081"),
"monero address string");
"Monero deamon url");
store(command_line_parser(acc, avv)

@ -0,0 +1,322 @@
//
// Created by mwo on 16/05/17.
//
#include "CurrentBlockchainStatus.h"
namespace xmreg
{
using namespace std;
void
CurrentBlockchainStatus::set_blockchain_variables(MicroCore* _mcore,
Blockchain* _core_storage)
{
mcore = _mcore;
core_storage =_core_storage;
}
void
CurrentBlockchainStatus::start_monitor_blockchain_thread()
{
total_emission_atomic = Emission {0, 0, 0};
string emmision_saved_file = get_output_file_path().string();
// read stored emission data if possible
if (boost::filesystem::exists(emmision_saved_file))
{
if (!load_current_emission_amount())
{
cerr << "Emission file cant be read, got corrupted or has incorrect format:\n " << emmision_saved_file
<< "\nEmission monitoring thread is not started.\nDelete the file and"
<< " restart the explorer or disable emission monitoring."
<< endl;
cerr << "Press ENTER to continue without emission monitoring or Ctr+C to exit" << endl;
cin.get();
return;
}
}
if (!is_running)
{
m_thread = boost::thread{[]()
{
try
{
while (true)
{
Emission current_emission = total_emission_atomic;
current_height = core_storage->get_current_blockchain_height();
// scan 10000 blocks for emissiom or if we are at the top of
// the blockchain, only few top blocks
update_current_emission_amount();
cout << "current emission: " << string(current_emission) << endl;
save_current_emission_amount();
if (current_emission.blk_no < current_height - blockchain_chunk_size)
{
// while we scan the blockchain from scrach, every 10000
// blocks take 1 second break
boost::this_thread::sleep_for(boost::chrono::seconds(1));
}
else
{
// when we reach top of the blockchain, update
// the emission amount every minute.
boost::this_thread::sleep_for(boost::chrono::seconds(60));
}
} // while (true)
}
catch (boost::thread_interrupted&)
{
cout << "Emission monitoring thread interrupted." << endl;
return;
}
}}; // m_thread = boost::thread{[]()
is_running = true;
} // if (!is_running)
}
void
CurrentBlockchainStatus::update_current_emission_amount()
{
Emission current_emission = total_emission_atomic;
uint64_t blk_no = current_emission.blk_no;
uint64_t end_block = blk_no + blockchain_chunk_size;
uint64_t current_blockchain_height = current_height;
// blockchain_chunk_gap is used so that we
// never read and store few top blocks
// the emission in the top few blocks will be calcalted
// later
end_block = end_block > current_blockchain_height
? current_blockchain_height - blockchain_chunk_gap
: end_block;
Emission emission_calculated = calculate_emission_in_blocks(blk_no, end_block);
current_emission.coinbase += emission_calculated.coinbase;
current_emission.fee += emission_calculated.fee;
current_emission.blk_no = emission_calculated.blk_no;
total_emission_atomic = current_emission;
}
CurrentBlockchainStatus::Emission
CurrentBlockchainStatus::calculate_emission_in_blocks(
uint64_t start_blk, uint64_t end_blk)
{
Emission emission_calculated {0, 0, 0};
while (start_blk < end_blk)
{
block blk;
mcore->get_block_by_height(start_blk, blk);
uint64_t coinbase_amount = get_outs_money_amount(blk.miner_tx);
std::list<transaction> txs;
std::list<crypto::hash> missed_txs;
uint64_t tx_fee_amount = 0;
core_storage->get_transactions(blk.tx_hashes, txs, missed_txs);
for(const auto& tx: txs)
{
tx_fee_amount += get_tx_fee(tx);
}
(void) missed_txs;
emission_calculated.coinbase += coinbase_amount - tx_fee_amount;
emission_calculated.fee += tx_fee_amount;
++start_blk;
}
emission_calculated.blk_no = start_blk;
return emission_calculated;
}
bool
CurrentBlockchainStatus::save_current_emission_amount()
{
string emmision_saved_file = get_output_file_path().string();
ofstream out(emmision_saved_file);
if( !out )
{
cerr << "Couldn't open file." << endl;
return false;
}
Emission current_emission = total_emission_atomic;
out << string(current_emission) << flush;
return true;
}
bool
CurrentBlockchainStatus::load_current_emission_amount()
{
string emmision_saved_file = get_output_file_path().string();
string last_saved_emmision = xmreg::read(emmision_saved_file);
if (last_saved_emmision.empty())
{
cerr << "Couldn't open file." << endl;
return false;
}
last_saved_emmision.erase(last_saved_emmision.find_last_not_of(" \n\r\t")+1);
vector<string> strs;
boost::split(strs, last_saved_emmision, boost::is_any_of(","));
if (strs.empty())
{
cerr << "Problem spliting string values form emission_amount." << endl;
return false;
}
Emission emission_loaded {0, 0, 0};
uint64_t read_check_sum {0};
try
{
emission_loaded.blk_no = boost::lexical_cast<uint64_t>(strs.at(0));
emission_loaded.coinbase = boost::lexical_cast<uint64_t>(strs.at(1));
emission_loaded.fee = boost::lexical_cast<uint64_t>(strs.at(2));
read_check_sum = boost::lexical_cast<uint64_t>(strs.at(3));
}
catch (boost::bad_lexical_cast &e)
{
cerr << "Cant parse to number date from string: " << last_saved_emmision << endl;
return false;
}
if (read_check_sum != emission_loaded.checksum())
{
cerr << "read_check_sum != check_sum: "
<< read_check_sum << " != " << emission_loaded.checksum()
<< endl;
return false;
}
total_emission_atomic = emission_loaded;
return true;
}
bf::path
CurrentBlockchainStatus::get_output_file_path()
{
return blockchain_path / output_file;
}
CurrentBlockchainStatus::Emission
CurrentBlockchainStatus::get_emission()
{
// get current emission
Emission current_emission = total_emission_atomic;
// this emission will be few blocks behind current blockchain
// height. By default 3 blocks. So we need to calcualate here
// the emission from the top missing blocks, to have complete
// emission data.
uint64_t current_blockchain_height = current_height;
uint64_t start_blk = current_emission.blk_no;
// this should be at current hight or above
// as we calculate missing blocks only for top blockchain
// height
uint64_t end_block = start_blk + blockchain_chunk_gap;
if (end_block >= current_blockchain_height
&& start_blk < current_blockchain_height)
{
// make sure we are not over the blockchain height
end_block = end_block > current_blockchain_height
? current_blockchain_height : end_block;
// calculated emission for missing blocks
Emission gap_emission_calculated
= calculate_emission_in_blocks(start_blk, end_block);
//cout << "gap_emission_calculated: " << std::string(gap_emission_calculated) << endl;
current_emission.coinbase += gap_emission_calculated.coinbase;
current_emission.fee += gap_emission_calculated.fee;
current_emission.blk_no = gap_emission_calculated.blk_no > 0
? gap_emission_calculated.blk_no
: current_emission.blk_no;
}
return current_emission;
}
bool
CurrentBlockchainStatus::is_thread_running()
{
return is_running;
}
bf::path CurrentBlockchainStatus::blockchain_path {"/home/mwo/.bitmonero/lmdb"};
bool CurrentBlockchainStatus::testnet {false};
string CurrentBlockchainStatus::output_file {"emission_amount.txt"};
string CurrentBlockchainStatus::deamon_url {"http:://127.0.0.1:18081"};
uint64_t CurrentBlockchainStatus::blockchain_chunk_size {10000};
uint64_t CurrentBlockchainStatus::blockchain_chunk_gap {3};
atomic<uint64_t> CurrentBlockchainStatus::current_height {0};
atomic<CurrentBlockchainStatus::Emission> CurrentBlockchainStatus::total_emission_atomic;
boost::thread CurrentBlockchainStatus::m_thread;
atomic<bool> CurrentBlockchainStatus::is_running {false};
Blockchain* CurrentBlockchainStatus::core_storage {nullptr};
xmreg::MicroCore* CurrentBlockchainStatus::mcore {nullptr};
}

@ -0,0 +1,114 @@
//
// Created by mwo on 16/05/17.
//
#ifndef XMRBLOCKS_CURRENTBLOCKCHAINSTATUS_H
#define XMRBLOCKS_CURRENTBLOCKCHAINSTATUS_H
#include "MicroCore.h"
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
#include <atomic>
namespace xmreg
{
using namespace std;
namespace bf = boost::filesystem;
struct CurrentBlockchainStatus
{
struct Emission
{
uint64_t coinbase;
uint64_t fee;
uint64_t blk_no;
inline uint64_t
checksum() const
{
return coinbase + fee + blk_no;
}
operator
std::string() const
{
return to_string(blk_no) + "," + to_string(coinbase)
+ "," + to_string(fee) + "," + to_string(checksum());
}
};
static bf::path blockchain_path;
static bool testnet;
static string output_file;
static string deamon_url;
// how many blocks to read before thread goes to sleep
static uint64_t blockchain_chunk_size;
// gap from what we store total_emission_atomic and
// current blockchain height. We dont want to store
// what is on, e.g., top block, as this can get messy
// if the block gets orphaned or blockchain reorganization
// occurs. So the top 3 blocks (default number) will always
// be calculated in flight and added to what we have so far.
static uint64_t blockchain_chunk_gap;
// current blockchain height and
// hash of top block
static atomic<uint64_t> current_height;
static atomic<Emission> total_emission_atomic;
static boost::thread m_thread;
static atomic<bool> is_running;
// make object for accessing the blockchain here
static MicroCore* mcore;
static Blockchain* core_storage;
static void
start_monitor_blockchain_thread();
static void
set_blockchain_variables(MicroCore* _mcore,
Blockchain* _core_storage);
static void
update_current_emission_amount();
static Emission
calculate_emission_in_blocks(uint64_t start_blk, uint64_t end_blk);
static bool
save_current_emission_amount();
static bool
load_current_emission_amount();
static Emission
get_emission();
static bf::path
get_output_file_path();
static bool
is_thread_running();
};
}
#endif //XMRBLOCKS_CURRENTBLOCKCHAINSTATUS_H

@ -327,6 +327,7 @@ MicroCore::get_blk_timestamp(uint64_t blk_height)
*/
MicroCore::~MicroCore()
{
//m_blockchain_storage.get_db().close();
delete &m_blockchain_storage.get_db();
}

@ -14,6 +14,8 @@
#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002"
#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
#define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks
#include "release/version/version.h"
#include "net/http_client.h"

@ -17,6 +17,8 @@
#include "tools.h"
#include "rpccalls.h"
#include "CurrentBlockchainStatus.h"
#include "../ext/crow/http_request.h"
#include "../ext/vpetrigocaches/cache.hpp"
@ -33,6 +35,7 @@
#define TMPL_INDEX TMPL_DIR "/index.html"
#define TMPL_INDEX2 TMPL_DIR "/index2.html"
#define TMPL_MEMPOOL TMPL_DIR "/mempool.html"
#define TMPL_MEMPOOL_ERROR TMPL_DIR "/mempool_error.html"
#define TMPL_HEADER TMPL_DIR "/header.html"
#define TMPL_FOOTER TMPL_DIR "/footer.html"
#define TMPL_BLOCK TMPL_DIR "/block.html"
@ -149,7 +152,7 @@ namespace xmreg
if (!input_key_imgs.empty())
{
mixin_str = std::to_string(mixin_no - 1);
mixin_str = std::to_string(mixin_no);
fee_str = fmt::format("{:0.6f}", xmr_amount);
fee_short_str = fmt::format("{:0.3f}", xmr_amount);
}
@ -259,6 +262,8 @@ namespace xmreg
uint64_t no_of_mempool_tx_of_frontpage;
uint64_t no_blocks_on_index;
uint64_t network_info_timeout;
uint64_t mempool_info_timeout;
string testnet_url;
string mainnet_url;
@ -309,6 +314,21 @@ namespace xmreg
// parse their json for each request
fifo_cache_t<string, mempool_tx_info> mempool_tx_json_cache;
// to keep network_info in cache
// and to show previous info in case current querry for
// the current info timesout.
struct network_info
{
uint64_t difficulty;
uint64_t hash_rate;
uint64_t fee_per_kb;
uint64_t alt_blocks_no;
uint64_t tx_pool_size;
uint64_t info_timestamp;
};
atomic<network_info> previous_network_info;
// cache of txs_map of txs in blocks. this is useful for
// index2 page, so that we dont parse txs in each block
// for each request.
@ -333,6 +353,8 @@ namespace xmreg
bool _enable_block_cache,
bool _show_cache_times,
uint64_t _no_blocks_on_index,
uint64_t _network_info_timeout,
uint64_t _mempool_info_timeout,
string _testnet_url,
string _mainnet_url)
: mcore {_mcore},
@ -350,6 +372,8 @@ namespace xmreg
enable_block_cache {_enable_block_cache},
show_cache_times {_show_cache_times},
no_blocks_on_index {_no_blocks_on_index},
network_info_timeout {_network_info_timeout},
mempool_info_timeout {_mempool_info_timeout},
testnet_url {_testnet_url},
mainnet_url {_mainnet_url},
mempool_tx_json_cache(1000),
@ -359,6 +383,9 @@ namespace xmreg
no_of_mempool_tx_of_frontpage = 25;
// initialized stored network info atomic
previous_network_info = network_info {0, 0, 0, 0, 0, 0};
// read template files for all the pages
// into template_file map
@ -367,6 +394,7 @@ namespace xmreg
template_file["footer"] = get_footer();
template_file["index2"] = get_full_page(xmreg::read(TMPL_INDEX2));
template_file["mempool"] = xmreg::read(TMPL_MEMPOOL);
template_file["mempool_error"] = xmreg::read(TMPL_MEMPOOL_ERROR);
template_file["mempool_full"] = get_full_page(template_file["mempool"]);
template_file["block"] = get_full_page(xmreg::read(TMPL_BLOCK));
template_file["tx"] = get_full_page(xmreg::read(TMPL_TX));
@ -394,6 +422,41 @@ namespace xmreg
string
index2(uint64_t page_no = 0, bool refresh_page = false)
{
// we get network info, such as current hash rate
// but since this makes a rpc call to deamon, we make it as an async
// call. this way we dont have to wait with execution of the rest of the
// index2 method, until deamon gives as the required result.
std::future<json> network_info_ftr = std::async(std::launch::async, [&]
{
json j_info;
if (!get_monero_network_info(j_info))
{
return json{};
}
uint64_t fee_estimated {0};
// get dynamic fee estimate from last 10 blocks
if (!get_dynamic_per_kb_fee_estimate(fee_estimated))
{
return json{};
}
j_info["fee_per_kb"] = fee_estimated;
return j_info;
});
// get mempool for the front page also using async future
std::future<string> mempool_ftr = std::async(std::launch::async, [&]
{
// get memory pool rendered template
return mempool(false, no_of_mempool_tx_of_frontpage);
});
//get current server timestamp
server_timestamp = std::time(nullptr);
@ -427,6 +490,16 @@ namespace xmreg
{"show_cache_times" , show_cache_times}
};
// std::list<block> atl_blks;
//
// if (core_storage->get_alternative_blocks(atl_blks))
// {
// for (const block& alt_blk: atl_blks)
// {
// //cout << "alt_blk: " << get_block_height(alt_blk) << endl;
// }
// }
context.emplace("txs", mstch::array()); // will keep tx to show
// get reference to txs mstch map to be field below
@ -694,8 +767,116 @@ namespace xmreg
context["cache_hits"] = cache_hits;
context["cache_misses"] = cache_misses;
// now time to check if we have our networkinfo from network_info future
// wait a bit (300 millisecond max) if not, just in case, but we dont wait more.
// if its not ready by now, forget about it.
std::future_status ftr_status = network_info_ftr.wait_for(
std::chrono::milliseconds(network_info_timeout));
network_info current_network_info {0, 0, 0, 0, 0, 0};
bool is_network_info_current {false};
if (ftr_status == std::future_status::ready)
{
json j_network_info = network_info_ftr.get();
if (!j_network_info.empty())
{
current_network_info.difficulty = j_network_info["difficulty"];
current_network_info.hash_rate = j_network_info["hash_rate"];
current_network_info.fee_per_kb = j_network_info["fee_per_kb"];
current_network_info.tx_pool_size = j_network_info["tx_pool_size"];
current_network_info.alt_blocks_no = j_network_info["alt_blocks_count"];
current_network_info.info_timestamp = local_copy_server_timestamp;
previous_network_info = current_network_info;
is_network_info_current = true;
}
}
else
{
current_network_info = previous_network_info;
cerr << "network_info future not ready yet, use the previous_network_info." << endl;
}
// perapre network info mstch::map for the front page
string hash_rate;
if (testnet)
{
hash_rate = std::to_string(current_network_info.hash_rate) + " H/s";
}
else
{
hash_rate = fmt::format("{:0.3f} MH/s", current_network_info.hash_rate/1.0e6);
}
pair<string, string> network_info_age = get_age(local_copy_server_timestamp,
current_network_info.info_timestamp);
// if network info is younger than 2 minute, assume its current. No sense
// showing that it is not current if its less then block time.
if (local_copy_server_timestamp - current_network_info.info_timestamp < 120)
{
is_network_info_current = true;
}
context["network_info"] = mstch::map {
{"difficulty" , current_network_info.difficulty},
{"hash_rate" , hash_rate},
{"fee_per_kb" , print_money(current_network_info.fee_per_kb)},
{"alt_blocks_no" , current_network_info.alt_blocks_no},
{"tx_pool_size" , current_network_info.tx_pool_size},
{"is_current_info" , is_network_info_current},
{"is_pool_size_zero" , (current_network_info.tx_pool_size == 0)},
{"age" , network_info_age.first},
{"age_format" , network_info_age.second},
};
string mempool_html {"Cant get mempool_pool"};
// get mempool data for the front page, if ready. If not, then just skip.
std::future_status mempool_ftr_status = mempool_ftr.wait_for(
std::chrono::milliseconds(mempool_info_timeout));
if (mempool_ftr_status == std::future_status::ready)
{
mempool_html = mempool_ftr.get();
}
else
{
cerr << "mempool future not ready yet, skipping." << endl;
mempool_html = mstch::render(template_file["mempool_error"], context);
}
if (CurrentBlockchainStatus::is_thread_running())
{
CurrentBlockchainStatus::Emission current_values
= CurrentBlockchainStatus::get_emission();
string emission_blk_no = std::to_string(current_values.blk_no - 1);
string emission_coinbase = xmr_amount_to_str(current_values.coinbase, "{:0.3f}");
string emission_fee = xmr_amount_to_str(current_values.fee, "{:0.3f}");
context["emission"] = mstch::map {
{"blk_no" , emission_blk_no},
{"amount" , emission_coinbase},
{"fee_amount", emission_fee}
};
}
else
{
cerr << "emission thread not running, skipping." << endl;
}
// get memory pool rendered template
string mempool_html = mempool(false, no_of_mempool_tx_of_frontpage);
//string mempool_html = mempool(false, no_of_mempool_tx_of_frontpage);
// append mempool_html to the index context map
context["mempool_info"] = mempool_html;
@ -915,7 +1096,7 @@ namespace xmreg
{"no_inputs" , no_inputs},
{"no_outputs" , no_outputs},
{"no_nonrct_inputs", num_nonrct_inputs},
{"mixin" , mixin_no},
{"mixin" , mixin_no+1},
{"txsize" , txsize}
});
@ -4677,6 +4858,7 @@ namespace xmreg
json j_info;
// get basic network info
if (!get_monero_network_info(j_info))
{
j_response["status"] = "error";
@ -4684,6 +4866,38 @@ namespace xmreg
return j_response;
}
uint64_t fee_estimated {0};
// get dynamic fee estimate from last 10 blocks
if (!get_dynamic_per_kb_fee_estimate(fee_estimated))
{
j_response["status"] = "error";
j_response["message"] = "Cant get dynamic fee esimate";
return j_response;
}
j_info["fee_per_kb"] = fee_estimated;
// // get mempool size in kB.
// std::vector<tx_info> mempool_txs;
//
// if (!rpc.get_mempool(mempool_txs))
// {
// j_response["status"] = "error";
// j_response["message"] = "Cant get mempool transactions";
// return j_response;
// }
//
// uint64_t tx_pool_size_kbytes {0};
//
// for (const tx_info& tx_i: mempool_txs)
// {
// tx_pool_size_kbytes += tx_i.blob_size;
// }
//
// j_info["tx_pool_size"] = mempool_txs.size();
// j_info["tx_pool_size_kbytes"] = tx_pool_size_kbytes;
j_data = j_info;
j_response["status"] = "success";
@ -4692,6 +4906,50 @@ namespace xmreg
}
/*
* Lets use this json api convention for success and error
* https://labs.omniti.com/labs/jsend
*/
json
json_emission()
{
json j_response {
{"status", "fail"},
{"data", json {}}
};
json& j_data = j_response["data"];
json j_info;
// get basic network info
if (!CurrentBlockchainStatus::is_thread_running())
{
j_data["title"] = "Emission monitoring thread not enabled.";
return j_response;
}
else
{
CurrentBlockchainStatus::Emission current_values
= CurrentBlockchainStatus::get_emission();
string emission_blk_no = std::to_string(current_values.blk_no - 1);
string emission_coinbase = xmr_amount_to_str(current_values.coinbase, "{:0.3f}");
string emission_fee = xmr_amount_to_str(current_values.fee, "{:0.3f}");
j_data = json {
{"blk_no" , current_values.blk_no - 1},
{"coinbase", current_values.coinbase},
{"fee" , current_values.fee},
};
}
j_response["status"] = "success";
return j_response;
}
private:
json
@ -4717,6 +4975,7 @@ namespace xmreg
return j_tx;
}
bool
find_tx(const crypto::hash& tx_hash,
transaction& tx,
@ -5498,6 +5757,25 @@ namespace xmreg
return true;
}
bool
get_dynamic_per_kb_fee_estimate(uint64_t& fee_estimated)
{
string error_msg;
if (!rpc.get_dynamic_per_kb_fee_estimate(
FEE_ESTIMATE_GRACE_BLOCKS,
fee_estimated, error_msg))
{
cerr << "rpc.get_dynamic_per_kb_fee_estimate failed" << endl;
return false;
}
(void) error_msg;
return true;
}
string
get_footer()
{
@ -5506,7 +5784,8 @@ namespace xmreg
static const mstch::map footer_context {
{"last_git_commit_hash", string {GIT_COMMIT_HASH}},
{"last_git_commit_date", string {GIT_COMMIT_DATETIME}},
{"monero_version_full" , string {MONERO_VERSION_FULL}},
{"git_branch_name" , string {GIT_BRANCH_NAME}},
{"monero_version_full" , string {MONERO_VERSION_FULL}}
};
string footer_html = mstch::render(xmreg::read(TMPL_FOOTER), footer_context);
@ -5527,3 +5806,4 @@ namespace xmreg
#endif //CROWXMR_PAGE_H

@ -207,4 +207,70 @@ rpccalls::get_network_info(COMMAND_RPC_GET_INFO::response& response)
}
bool
rpccalls::get_dynamic_per_kb_fee_estimate(
uint64_t grace_blocks,
uint64_t& fee,
string& error_msg)
{
epee::json_rpc::request<COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request>
req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response, std::string>
resp_t = AUTO_VAL_INIT(resp_t);
req_t.jsonrpc = "2.0";
req_t.id = epee::serialization::storage_entry(0);
req_t.method = "get_fee_estimate";
req_t.params.grace_blocks = grace_blocks;
bool r {false};
std::lock_guard<std::mutex> guard(m_daemon_rpc_mutex);
{
if (!connect_to_monero_deamon())
{
cerr << "get_current_height: not connected to deamon" << endl;
return false;
}
r = epee::net_utils::invoke_http_json("/json_rpc",
req_t, resp_t,
m_http_client);
}
string err;
if (r)
{
if (resp_t.result.status == CORE_RPC_STATUS_BUSY)
{
err = "daemon is busy. Please try again later.";
}
else if (resp_t.result.status != CORE_RPC_STATUS_OK)
{
err = resp_t.result.status;
}
if (!err.empty())
{
cerr << "Error connecting to Monero deamon due to "
<< err << endl;
return false;
}
}
else
{
cerr << "Error connecting to Monero deamon at "
<< deamon_url << endl;
return false;
}
fee = resp_t.result.fee;
return true;
}
}

@ -52,6 +52,12 @@ public:
bool
get_network_info(COMMAND_RPC_GET_INFO::response& info);
bool
get_dynamic_per_kb_fee_estimate(
uint64_t grace_blocks,
uint64_t& fee,
string& error_msg);
};

@ -60,7 +60,7 @@
<td>hash</td>
<td>outputs</td>
<td>fee</td>
<td>mixin</td>
<td>ring size</td>
<td>in/out</td>
<td>size [kB]</td>
<td>version</td>

@ -36,8 +36,8 @@
</h5>
<h3>
Inputs' mixins time scale (from {{min_mix_time}} till {{max_mix_time}};
resolution: {{timescales_scale}} days{{#have_raw_tx}}; R - real mixin {{/have_raw_tx}})
Inputs' ring size time scale (from {{min_mix_time}} till {{max_mix_time}};
resolution: {{timescales_scale}} days{{#have_raw_tx}}; R - real ring member {{/have_raw_tx}})
</h3>
<div class="center">
<ul class="center">

@ -16,7 +16,7 @@ h1, h2, h3, h4, h5, h6 {
padding: 10px;*/
}
tr, li, #pages {
tr, li, #pages, .info {
font-family: "Lucida Console", Monaco, monospace;
font-size : 12px;
height: 22px;

@ -1,7 +1,7 @@
<div class="center">
<h6 style="margin-top:10px">
<a href="https://github.com/moneroexamples/onion-monero-blockchain-explorer">source code</a>
| explorer version: {{last_git_commit_date}}-{{last_git_commit_hash}}
| explorer version: {{git_branch_name}}-{{last_git_commit_date}}-{{last_git_commit_hash}}
| monero version: {{monero_version_full}}
</h6>
</div>

@ -32,7 +32,7 @@
<td>txs</td>
<td>fees</td>
<td>outputs</td>
<td>mixins</td>
<td>ring size</td>
<td>size [kB]</td>
</tr>
{{#blocks}}

@ -32,6 +32,27 @@
{{/testnet}}
</h3>
{{#network_info}}
<h3 style="font-size: 12px; margin-top: 5px; margin-bottom: 3">
Network difficulty: {{difficulty}}
| Hash rate: {{hash_rate}}
| Fee per kb: {{fee_per_kb}}
| Alternative blocks no: {{alt_blocks_no}}
{{^is_current_info}}
| Data from {{age}} {{age_format}} ago
{{/is_current_info}}
</h3>
{{/network_info}}
{{#emission}}
<h3 style="font-size: 12px; margin-top: 2px">
Monero emission (fees) is {{amount}} ({{fee_amount}}) as of {{blk_no}} block
</h3>
{{/emission}}
</div>
{{{mempool_info}}}
@ -58,7 +79,7 @@
<td>fees</td>
<td>outputs</td>
<td>in(nonrct)/out</td>
<td>mixin</td>
<td>ring size</td>
<td>tx size [kB]</td>
</tr>
{{#txs}}
@ -87,6 +108,7 @@
</div>
{{#show_cache_times}}
<div class="center">
<h6 style="margin-top: 1px;color:#949490">

@ -11,7 +11,7 @@
<td>fee</td>
<td>outputs</td>
<td>in(nonrct)/out</td>
<td>mixin</td>
<td>ring size</td>
<td>tx size [kB]</td>
</tr>
{{#mempooltxs}}

@ -0,0 +1,17 @@
<h2 style="margin-bottom: 0px">
Memory pool
</h2>
<h4 style="font-size: 14px; margin-top: 0px"></h4>
<div class="center info" style="text-align: center;width:80%;color:#949490">
<p>Mempool data preparation for the front page failed.
Its processing
{{#network_info}}{{^is_pool_size_zero}}({{tx_pool_size}} txs){{/is_pool_size_zero}}{{/network_info}}
took longer than expected and it timed out.
To view the mempool without time constrain,
go to dedicated mempool page: <a href="/mempool">memory pool</a>
</p>
</div>

@ -91,7 +91,7 @@
<table class="center">
<tr>
<td style="text-align: center;">
Mixin {{mixin_pub_key}} might use your outputs
Ring member {{mixin_pub_key}} might use your outputs
<br/>
from tx of hash: <a href="/tx/{{mix_tx_hash}}">{{mix_tx_hash}}</a>
<br/>(tx public key: {{mix_tx_pub_key}})
@ -133,7 +133,7 @@
</div>
<h3>
Sum XMR from matched and marked by * mixin's outputs: {{sum_mixin_xmr}}
Sum XMR from matched and marked by * ring member's outputs: {{sum_mixin_xmr}}
<br/>
<span style="font-size: 16px"> Possible spending is:
{{possible_spending}} (tx fee included)

@ -125,8 +125,8 @@
{{#has_inputs}}
{{#enable_mixins_details}}
<h3>Inputs' mixins time scale (from {{min_mix_time}} till {{max_mix_time}};
resolution: {{timescales_scale}} days{{#have_raw_tx}}; R - real mixin {{/have_raw_tx}})
<h3>Inputs' ring size time scale (from {{min_mix_time}} till {{max_mix_time}};
resolution: {{timescales_scale}} days{{#have_raw_tx}}; R - real ring member {{/have_raw_tx}})
</h3>
<div class="center">
<ul class="center">
@ -180,12 +180,12 @@
{{#enable_mixins_details}}
<table style="width:100%; margin-bottom:20px">
<tr>
<td>Mixin stealth address</td>
<td>ring members</td>
{{#have_raw_tx}}
<td>Is it real?</td>
{{/have_raw_tx}}
<td>blk</td>
<td>mixin</td>
<td>ring size</td>
<td>in/out</td>
<td>timestamp</td>
<td>age [y:d:h:m:s]</td>
@ -214,7 +214,7 @@
{{^enable_mixins_details}}
<table style="width:100%; margin-bottom:20px">
<tr>
<td>Mixin stealth address</td>
<td>ring members</td>
{{#have_raw_tx}}
<td>Is it real?</td>
{{/have_raw_tx}}

@ -3,7 +3,7 @@
<td>tx hash</td>
<td>outputs</td>
<td>fee</td>
<td>mixin</td>
<td>ring size</td>
<td>in/out</td>
<td>size [kB]</td>
</tr>

@ -8,6 +8,7 @@
#define GIT_BRANCH "@GIT_BRANCH@"
#define GIT_COMMIT_HASH "@GIT_COMMIT_HASH@"
#define GIT_COMMIT_DATETIME "@GIT_COMMIT_DATETIME@"
#define GIT_BRANCH_NAME "@GIT_BRANCH_NAME@"
#endif //XMRBLOCKS_VERSION_H_IN_H

Loading…
Cancel
Save