|
|
@ -10,6 +10,7 @@
|
|
|
|
#include "mstch/mstch.hpp"
|
|
|
|
#include "mstch/mstch.hpp"
|
|
|
|
#include "rapidjson/document.h"
|
|
|
|
#include "rapidjson/document.h"
|
|
|
|
#include "../ext/format.h"
|
|
|
|
#include "../ext/format.h"
|
|
|
|
|
|
|
|
#include "../ext/member_checker.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "monero_headers.h"
|
|
|
|
#include "monero_headers.h"
|
|
|
@ -33,10 +34,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
namespace xmreg {
|
|
|
|
namespace xmreg {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using namespace cryptonote;
|
|
|
|
using namespace cryptonote;
|
|
|
|
using namespace crypto;
|
|
|
|
using namespace crypto;
|
|
|
|
using namespace std;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// define a checker to test if a structure has "tx_blob"
|
|
|
|
|
|
|
|
// member variable. I use modified daemon with few extra
|
|
|
|
|
|
|
|
// bits and pieces here and there. One of them is
|
|
|
|
|
|
|
|
// tx_blob in cryptonote::tx_info structure
|
|
|
|
|
|
|
|
// thus I check if I run my version, or just
|
|
|
|
|
|
|
|
// generic one
|
|
|
|
|
|
|
|
DEFINE_MEMBER_CHECKER(tx_blob)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// define getter to get tx_blob, i.e., get_tx_blob function
|
|
|
|
|
|
|
|
// as string if exists. the getter return empty string if
|
|
|
|
|
|
|
|
// tx_blob does not exist
|
|
|
|
|
|
|
|
DEFINE_MEMBER_GETTER(tx_blob, string)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @brief The tx_details struct
|
|
|
|
* @brief The tx_details struct
|
|
|
|
*
|
|
|
|
*
|
|
|
@ -102,8 +118,9 @@ namespace xmreg {
|
|
|
|
|
|
|
|
|
|
|
|
for (const crypto::signature &sig: signatures.at(in_i))
|
|
|
|
for (const crypto::signature &sig: signatures.at(in_i))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
cout << print_sig(sig) << endl;
|
|
|
|
ring_sigs.push_back(mstch::map{
|
|
|
|
ring_sigs.push_back(mstch::map{{"ring_sig", print_signature(sig)}});
|
|
|
|
{"ring_sig", print_signature(sig)}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ring_sigs;
|
|
|
|
return ring_sigs;
|
|
|
@ -123,6 +140,11 @@ namespace xmreg {
|
|
|
|
|
|
|
|
|
|
|
|
class page {
|
|
|
|
class page {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if we have tx_blob member in tx_info structure
|
|
|
|
|
|
|
|
static const bool HAVE_TX_BLOB {
|
|
|
|
|
|
|
|
HAS_MEMBER(cryptonote::tx_info, tx_blob)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const bool FULL_AGE_FORMAT {true};
|
|
|
|
static const bool FULL_AGE_FORMAT {true};
|
|
|
|
|
|
|
|
|
|
|
|
MicroCore* mcore;
|
|
|
|
MicroCore* mcore;
|
|
|
@ -130,6 +152,7 @@ namespace xmreg {
|
|
|
|
rpccalls rpc;
|
|
|
|
rpccalls rpc;
|
|
|
|
time_t server_timestamp;
|
|
|
|
time_t server_timestamp;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
|
|
page(MicroCore* _mcore, Blockchain* _core_storage, string deamon_url)
|
|
|
|
page(MicroCore* _mcore, Blockchain* _core_storage, string deamon_url)
|
|
|
@ -138,6 +161,7 @@ namespace xmreg {
|
|
|
|
rpc {deamon_url},
|
|
|
|
rpc {deamon_url},
|
|
|
|
server_timestamp {std::time(nullptr)}
|
|
|
|
server_timestamp {std::time(nullptr)}
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string
|
|
|
|
string
|
|
|
@ -594,13 +618,43 @@ namespace xmreg {
|
|
|
|
return string("Cant parse tx hash: " + tx_hash_str);
|
|
|
|
return string("Cant parse tx hash: " + tx_hash_str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// tx age
|
|
|
|
|
|
|
|
pair<string, string> age;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string blk_timestamp {"N/A"};
|
|
|
|
|
|
|
|
|
|
|
|
// get transaction
|
|
|
|
// get transaction
|
|
|
|
transaction tx;
|
|
|
|
transaction tx;
|
|
|
|
|
|
|
|
|
|
|
|
if (!mcore->get_tx(tx_hash, tx))
|
|
|
|
if (!mcore->get_tx(tx_hash, tx))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
cerr << "Cant get tx: " << tx_hash << endl;
|
|
|
|
cerr << "Cant find tx ib blockchain: " << tx_hash
|
|
|
|
return string("Cant get tx: " + tx_hash_str);
|
|
|
|
<< ". Check mempool now" << endl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vector<pair<tx_info, transaction>> found_txs
|
|
|
|
|
|
|
|
= search_mempool(tx_hash);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!found_txs.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// there should be only one tx found
|
|
|
|
|
|
|
|
tx = found_txs.at(0).second;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// since its tx in mempool, it has no blk yet
|
|
|
|
|
|
|
|
// so use its recive_time as timestamp to show
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t tx_recieve_timestamp
|
|
|
|
|
|
|
|
= found_txs.at(0).first.receive_time;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
age = get_age(server_timestamp, tx_recieve_timestamp,
|
|
|
|
|
|
|
|
FULL_AGE_FORMAT);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// tx is nowhere to be found :-(
|
|
|
|
|
|
|
|
return string("Cant find tx: " + tx_hash_str);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tx_details txd = get_tx_details(tx);
|
|
|
|
tx_details txd = get_tx_details(tx);
|
|
|
@ -614,46 +668,54 @@ namespace xmreg {
|
|
|
|
tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash);
|
|
|
|
tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash);
|
|
|
|
tx_blk_found = true;
|
|
|
|
tx_blk_found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (BLOCK_DNE & e)
|
|
|
|
catch (exception& e)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
cerr << "Cant get block height: " << tx_hash << endl;
|
|
|
|
cerr << "Cant get block height: " << tx_hash
|
|
|
|
|
|
|
|
<< e.what() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get block cointaining this tx
|
|
|
|
// get block cointaining this tx
|
|
|
|
block blk;
|
|
|
|
block blk;
|
|
|
|
|
|
|
|
|
|
|
|
if (!mcore->get_block_by_height(tx_blk_height, blk))
|
|
|
|
if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
cerr << "Cant get block: " << tx_blk_height << endl;
|
|
|
|
cerr << "Cant get block: " << tx_blk_height << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string tx_blk_height_str {"N/A"};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (tx_blk_found)
|
|
|
|
|
|
|
|
{
|
|
|
|
// calculate difference between tx and server timestamps
|
|
|
|
// calculate difference between tx and server timestamps
|
|
|
|
pair<string, string> age = get_age(server_timestamp,
|
|
|
|
age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT);
|
|
|
|
blk.timestamp, FULL_AGE_FORMAT);
|
|
|
|
|
|
|
|
|
|
|
|
blk_timestamp = xmreg::timestamp_to_str(blk.timestamp);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tx_blk_height_str = std::to_string(tx_blk_height);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// initalise page tempate map with basic info about blockchain
|
|
|
|
// initalise page tempate map with basic info about blockchain
|
|
|
|
mstch::map context {
|
|
|
|
mstch::map context {
|
|
|
|
{"tx_hash" , tx_hash_str},
|
|
|
|
{"tx_hash" , tx_hash_str},
|
|
|
|
{"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))},
|
|
|
|
{"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))},
|
|
|
|
{"blk_height" , tx_blk_height},
|
|
|
|
{"blk_height" , tx_blk_height_str},
|
|
|
|
{"tx_size" , fmt::format("{:0.2f}",
|
|
|
|
{"tx_size" , fmt::format("{:0.4f}",
|
|
|
|
static_cast<double>(txd.size) / 1024.0)},
|
|
|
|
static_cast<double>(txd.size) / 1024.0)},
|
|
|
|
{"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))},
|
|
|
|
{"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))},
|
|
|
|
{"blk_timestamp" , xmreg::timestamp_to_str(blk.timestamp)},
|
|
|
|
{"blk_timestamp" , blk_timestamp},
|
|
|
|
{"delta_time" , age.first},
|
|
|
|
{"delta_time" , age.first},
|
|
|
|
{"inputs_no" , txd.input_key_imgs.size()},
|
|
|
|
{"inputs_no" , txd.input_key_imgs.size()},
|
|
|
|
{"outputs_no" , txd.output_pub_keys.size()}
|
|
|
|
{"outputs_no" , txd.output_pub_keys.size()}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
string server_time_str = xmreg::timestamp_to_str(server_timestamp,
|
|
|
|
string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F");
|
|
|
|
"%F");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mstch::array inputs = mstch::array{};
|
|
|
|
mstch::array inputs = mstch::array{};
|
|
|
|
|
|
|
|
|
|
|
|
mstch::array mixins_timescales;
|
|
|
|
mstch::array mixins_timescales;
|
|
|
|
|
|
|
|
|
|
|
|
double timescale_scale {0.0}; // size of one '_' in days
|
|
|
|
double timescale_scale {0.0}; // size of one '_' in days
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t input_idx {0};
|
|
|
|
uint64_t input_idx {0};
|
|
|
@ -683,11 +745,11 @@ namespace xmreg {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get reference to mixins array created above
|
|
|
|
// get reference to mixins array created above
|
|
|
|
mstch::array& mixins = boost::get<mstch::array>(
|
|
|
|
mstch::array& mixins = boost::get<mstch::array>(
|
|
|
|
boost::get<mstch::map>(inputs.back())["mixins"]);
|
|
|
|
boost::get<mstch::map>(inputs.back())["mixins"]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// mixin counter
|
|
|
|
size_t count = 0;
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// for each found output public key find its block to get timestamp
|
|
|
|
// for each found output public key find its block to get timestamp
|
|
|
@ -786,7 +848,6 @@ namespace xmreg {
|
|
|
|
|
|
|
|
|
|
|
|
context["outputs"] = outputs;
|
|
|
|
context["outputs"] = outputs;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// read tx.html
|
|
|
|
// read tx.html
|
|
|
|
string tx_html = xmreg::read(TMPL_TX);
|
|
|
|
string tx_html = xmreg::read(TMPL_TX);
|
|
|
|
|
|
|
|
|
|
|
@ -843,6 +904,66 @@ namespace xmreg {
|
|
|
|
return txd;
|
|
|
|
return txd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vector<pair<tx_info, transaction>>
|
|
|
|
|
|
|
|
search_mempool(crypto::hash tx_hash)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vector<pair<tx_info, transaction>> found_txs;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get txs in the mempool
|
|
|
|
|
|
|
|
std::vector<tx_info> mempool_txs;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!rpc.get_mempool(mempool_txs))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cerr << "Getting mempool failed " << endl;
|
|
|
|
|
|
|
|
return found_txs;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if we have tx blob disply more.
|
|
|
|
|
|
|
|
// this info can also be obtained from json that is
|
|
|
|
|
|
|
|
// normally returned by the RCP call (see below in detailed view)
|
|
|
|
|
|
|
|
if (HAVE_TX_BLOB)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// get tx_blob if exists
|
|
|
|
|
|
|
|
//string tx_blob = get_tx_blob(_tx_info);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < mempool_txs.size(); ++i)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// get transaction info of the tx in the mempool
|
|
|
|
|
|
|
|
tx_info _tx_info = mempool_txs.at(i);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get tx_blob if exists
|
|
|
|
|
|
|
|
string tx_blob = get_tx_blob(_tx_info);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (tx_blob.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cerr << "tx_blob is empty. Probably its not a custom deamon." << endl;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// pare tx_blob into tx class
|
|
|
|
|
|
|
|
transaction tx;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!parse_and_validate_tx_from_blob(
|
|
|
|
|
|
|
|
tx_blob, tx))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cerr << "Cant parse tx from blob" << endl;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if tx hash matches, and if yes, save it for return
|
|
|
|
|
|
|
|
if (tx_hash == get_transaction_hash(tx))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
found_txs.push_back(make_pair(_tx_info, tx));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return found_txs;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pair<string, string>
|
|
|
|
pair<string, string>
|
|
|
|
get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0)
|
|
|
|
get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|