diff --git a/main.cpp b/main.cpp index 0a8bdf0..e6b7020 100644 --- a/main.cpp +++ b/main.cpp @@ -321,6 +321,11 @@ main(int ac, const char* av[]) return crow::response(xmrblocks.show_tx_hex(remove_bad_chars(tx_hash))); }); + CROW_ROUTE(app, "/ringmembershex/") + ([&](string tx_hash) { + return crow::response(xmrblocks.show_ringmembers_hex(remove_bad_chars(tx_hash))); + }); + CROW_ROUTE(app, "/blockhex/") ([&](size_t block_height) { return crow::response(xmrblocks.show_block_hex(block_height, false)); diff --git a/src/page.h b/src/page.h index 55032b5..6259b39 100644 --- a/src/page.h +++ b/src/page.h @@ -342,1948 +342,2039 @@ struct tx_details class page { - static const bool FULL_AGE_FORMAT {true}; +static const bool FULL_AGE_FORMAT {true}; - MicroCore* mcore; - Blockchain* core_storage; - rpccalls rpc; +MicroCore* mcore; +Blockchain* core_storage; +rpccalls rpc; - atomic server_timestamp; +atomic server_timestamp; - cryptonote::network_type nettype; - bool mainnet; - bool testnet; - bool stagenet; +cryptonote::network_type nettype; +bool mainnet; +bool testnet; +bool stagenet; - bool enable_js; +bool enable_js; - bool enable_pusher; +bool enable_pusher; - bool enable_key_image_checker; - bool enable_output_key_checker; - bool enable_mixins_details; - bool enable_tx_cache; - bool enable_block_cache; - bool enable_as_hex; - bool show_cache_times; +bool enable_key_image_checker; +bool enable_output_key_checker; +bool enable_mixins_details; +bool enable_tx_cache; +bool enable_block_cache; +bool enable_as_hex; +bool show_cache_times; - bool enable_autorefresh_option; +bool enable_autorefresh_option; - uint64_t no_of_mempool_tx_of_frontpage; - uint64_t no_blocks_on_index; - uint64_t mempool_info_timeout; +uint64_t no_of_mempool_tx_of_frontpage; +uint64_t no_blocks_on_index; +uint64_t mempool_info_timeout; - string testnet_url; - string stagenet_url; - string mainnet_url; +string testnet_url; +string stagenet_url; +string mainnet_url; - string js_html_files; - string js_html_files_all_in_one; +string js_html_files; +string js_html_files_all_in_one; - // instead of constatnly reading template files - // from hard drive for each request, we can read - // them only once, when the explorer starts into this map - // this will improve performance of the explorer and reduce - // read operation in OS - map template_file; +// instead of constatnly reading template files +// from hard drive for each request, we can read +// them only once, when the explorer starts into this map +// this will improve performance of the explorer and reduce +// read operation in OS +map template_file; - // alias for easy class typing - template - using lru_cache_t = caches::fixed_sized_cache>; +// alias for easy class typing +template +using lru_cache_t = caches::fixed_sized_cache>; - // alias for easy class typing - template - using fifo_cache_t = caches::fixed_sized_cache>; +// alias for easy class typing +template +using fifo_cache_t = caches::fixed_sized_cache>; - // 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. - fifo_cache_t>> block_tx_cache; +// 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. +fifo_cache_t>> block_tx_cache; - lru_cache_t tx_context_cache; +lru_cache_t tx_context_cache; public: - page(MicroCore* _mcore, - Blockchain* _core_storage, - string _deamon_url, - cryptonote::network_type _nettype, - bool _enable_pusher, - bool _enable_js, - bool _enable_as_hex, - bool _enable_key_image_checker, - bool _enable_output_key_checker, - bool _enable_autorefresh_option, - bool _enable_mixins_details, - bool _enable_tx_cache, - bool _enable_block_cache, - bool _show_cache_times, - uint64_t _no_blocks_on_index, - uint64_t _mempool_info_timeout, - string _testnet_url, - string _stagenet_url, - string _mainnet_url) - : mcore {_mcore}, - core_storage {_core_storage}, - rpc {_deamon_url}, - server_timestamp {std::time(nullptr)}, - nettype {_nettype}, - enable_pusher {_enable_pusher}, - enable_js {_enable_js}, - enable_as_hex {_enable_as_hex}, - enable_key_image_checker {_enable_key_image_checker}, - enable_output_key_checker {_enable_output_key_checker}, - enable_autorefresh_option {_enable_autorefresh_option}, - enable_mixins_details {_enable_mixins_details}, - enable_tx_cache {_enable_tx_cache}, - enable_block_cache {_enable_block_cache}, - show_cache_times {_show_cache_times}, - no_blocks_on_index {_no_blocks_on_index}, - mempool_info_timeout {_mempool_info_timeout}, - testnet_url {_testnet_url}, - stagenet_url {_stagenet_url}, - mainnet_url {_mainnet_url}, - block_tx_cache(200), - tx_context_cache(1000) - { - mainnet = nettype == cryptonote::network_type::MAINNET; - testnet = nettype == cryptonote::network_type::TESTNET; - stagenet = nettype == cryptonote::network_type::STAGENET; - - - no_of_mempool_tx_of_frontpage = 25; - - // read template files for all the pages - // into template_file map - - template_file["css_styles"] = xmreg::read(TMPL_CSS_STYLES); - template_file["header"] = xmreg::read(TMPL_HEADER); - template_file["footer"] = get_footer(); - template_file["index2"] = get_full_page(xmreg::read(TMPL_INDEX2)); - template_file["mempool"] = xmreg::read(TMPL_MEMPOOL); - template_file["altblocks"] = get_full_page(xmreg::read(TMPL_ALTBLOCKS)); - 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)); - template_file["my_outputs"] = get_full_page(xmreg::read(TMPL_MY_OUTPUTS)); - template_file["rawtx"] = get_full_page(xmreg::read(TMPL_MY_RAWTX)); - template_file["checkrawtx"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWTX)); - template_file["pushrawtx"] = get_full_page(xmreg::read(TMPL_MY_PUSHRAWTX)); - template_file["rawkeyimgs"] = get_full_page(xmreg::read(TMPL_MY_RAWKEYIMGS)); - template_file["rawoutputkeys"] = get_full_page(xmreg::read(TMPL_MY_RAWOUTPUTKEYS)); - template_file["checkrawkeyimgs"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWKEYIMGS)); - template_file["checkoutputkeys"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWOUTPUTKEYS)); - template_file["address"] = get_full_page(xmreg::read(TMPL_ADDRESS)); - template_file["search_results"] = get_full_page(xmreg::read(TMPL_SEARCH_RESULTS)); - template_file["tx_details"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html"); - template_file["tx_table_header"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html"); - template_file["tx_table_row"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html"); - - if (enable_js) { - // JavaScript files - template_file["jquery.min.js"] = xmreg::read(JS_JQUERY); - template_file["crc32.js"] = xmreg::read(JS_CRC32); - template_file["crypto.js"] = xmreg::read(JS_CRYPTO); - template_file["cn_util.js"] = xmreg::read(JS_CNUTIL); - template_file["base58.js"] = xmreg::read(JS_BASE58); - template_file["nacl-fast-cn.js"] = xmreg::read(JS_NACLFAST); - template_file["sha3.js"] = xmreg::read(JS_SHA3); - template_file["config.js"] = xmreg::read(JS_CONFIG); - template_file["biginteger.js"] = xmreg::read(JS_BIGINT); - - // need to set "testnet: false," flag to reflect - // if we are running testnet or mainnet explorer - - if (testnet) - { - template_file["config.js"] = std::regex_replace( - template_file["config.js"], - std::regex("testnet: false"), - "testnet: true"); - } - - // the same idea as above for the stagenet - - if (stagenet) - { - template_file["config.js"] = std::regex_replace( - template_file["config.js"], - std::regex("stagenet: false"), - "stagenet: true"); - } - - template_file["all_in_one.js"] = template_file["jquery.min.js"] + - template_file["crc32.js"] + - template_file["biginteger.js"] + - template_file["config.js"] + - template_file["nacl-fast-cn.js"] + - template_file["crypto.js"] + - template_file["base58.js"] + - template_file["cn_util.js"] + - template_file["sha3.js"]; - - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - js_html_files += ""; - - // /js/all_in_one.js file does not exist. it is generated on the fly - // from the above real files. - js_html_files_all_in_one = ""; - } - +page(MicroCore* _mcore, + Blockchain* _core_storage, + string _deamon_url, + cryptonote::network_type _nettype, + bool _enable_pusher, + bool _enable_js, + bool _enable_as_hex, + bool _enable_key_image_checker, + bool _enable_output_key_checker, + bool _enable_autorefresh_option, + bool _enable_mixins_details, + bool _enable_tx_cache, + bool _enable_block_cache, + bool _show_cache_times, + uint64_t _no_blocks_on_index, + uint64_t _mempool_info_timeout, + string _testnet_url, + string _stagenet_url, + string _mainnet_url) + : mcore {_mcore}, + core_storage {_core_storage}, + rpc {_deamon_url}, + server_timestamp {std::time(nullptr)}, + nettype {_nettype}, + enable_pusher {_enable_pusher}, + enable_js {_enable_js}, + enable_as_hex {_enable_as_hex}, + enable_key_image_checker {_enable_key_image_checker}, + enable_output_key_checker {_enable_output_key_checker}, + enable_autorefresh_option {_enable_autorefresh_option}, + enable_mixins_details {_enable_mixins_details}, + enable_tx_cache {_enable_tx_cache}, + enable_block_cache {_enable_block_cache}, + show_cache_times {_show_cache_times}, + no_blocks_on_index {_no_blocks_on_index}, + mempool_info_timeout {_mempool_info_timeout}, + testnet_url {_testnet_url}, + stagenet_url {_stagenet_url}, + mainnet_url {_mainnet_url}, + block_tx_cache(200), + tx_context_cache(1000) +{ + mainnet = nettype == cryptonote::network_type::MAINNET; + testnet = nettype == cryptonote::network_type::TESTNET; + stagenet = nettype == cryptonote::network_type::STAGENET; + + + no_of_mempool_tx_of_frontpage = 25; + + // read template files for all the pages + // into template_file map + + template_file["css_styles"] = xmreg::read(TMPL_CSS_STYLES); + template_file["header"] = xmreg::read(TMPL_HEADER); + template_file["footer"] = get_footer(); + template_file["index2"] = get_full_page(xmreg::read(TMPL_INDEX2)); + template_file["mempool"] = xmreg::read(TMPL_MEMPOOL); + template_file["altblocks"] = get_full_page(xmreg::read(TMPL_ALTBLOCKS)); + 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)); + template_file["my_outputs"] = get_full_page(xmreg::read(TMPL_MY_OUTPUTS)); + template_file["rawtx"] = get_full_page(xmreg::read(TMPL_MY_RAWTX)); + template_file["checkrawtx"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWTX)); + template_file["pushrawtx"] = get_full_page(xmreg::read(TMPL_MY_PUSHRAWTX)); + template_file["rawkeyimgs"] = get_full_page(xmreg::read(TMPL_MY_RAWKEYIMGS)); + template_file["rawoutputkeys"] = get_full_page(xmreg::read(TMPL_MY_RAWOUTPUTKEYS)); + template_file["checkrawkeyimgs"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWKEYIMGS)); + template_file["checkoutputkeys"] = get_full_page(xmreg::read(TMPL_MY_CHECKRAWOUTPUTKEYS)); + template_file["address"] = get_full_page(xmreg::read(TMPL_ADDRESS)); + template_file["search_results"] = get_full_page(xmreg::read(TMPL_SEARCH_RESULTS)); + template_file["tx_details"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html"); + template_file["tx_table_header"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html"); + template_file["tx_table_row"] = xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html"); + + if (enable_js) { + // JavaScript files + template_file["jquery.min.js"] = xmreg::read(JS_JQUERY); + template_file["crc32.js"] = xmreg::read(JS_CRC32); + template_file["crypto.js"] = xmreg::read(JS_CRYPTO); + template_file["cn_util.js"] = xmreg::read(JS_CNUTIL); + template_file["base58.js"] = xmreg::read(JS_BASE58); + template_file["nacl-fast-cn.js"] = xmreg::read(JS_NACLFAST); + template_file["sha3.js"] = xmreg::read(JS_SHA3); + template_file["config.js"] = xmreg::read(JS_CONFIG); + template_file["biginteger.js"] = xmreg::read(JS_BIGINT); + + // need to set "testnet: false," flag to reflect + // if we are running testnet or mainnet explorer + + if (testnet) + { + template_file["config.js"] = std::regex_replace( + template_file["config.js"], + std::regex("testnet: false"), + "testnet: true"); + } + + // the same idea as above for the stagenet + + if (stagenet) + { + template_file["config.js"] = std::regex_replace( + template_file["config.js"], + std::regex("stagenet: false"), + "stagenet: true"); + } + + template_file["all_in_one.js"] = template_file["jquery.min.js"] + + template_file["crc32.js"] + + template_file["biginteger.js"] + + template_file["config.js"] + + template_file["nacl-fast-cn.js"] + + template_file["crypto.js"] + + template_file["base58.js"] + + template_file["cn_util.js"] + + template_file["sha3.js"]; + + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + js_html_files += ""; + + // /js/all_in_one.js file does not exist. it is generated on the fly + // from the above real files. + js_html_files_all_in_one = ""; } - /** - * @brief show recent transactions and mempool - * @param page_no block page to show - * @param refresh_page enable autorefresh - * @return rendered index page - */ - 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 network_info_ftr = std::async(std::launch::async, [&] - { - json j_info; +/** + * @brief show recent transactions and mempool + * @param page_no block page to show + * @param refresh_page enable autorefresh + * @return rendered index page + */ +string +index2(uint64_t page_no = 0, bool refresh_page = false) +{ - get_monero_network_info(j_info); + // 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 network_info_ftr = std::async(std::launch::async, [&] + { + json j_info; - return j_info; - }); + get_monero_network_info(j_info); + return j_info; + }); - // get mempool for the front page also using async future - std::future 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); + // get mempool for the front page also using async future + std::future 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); + + uint64_t local_copy_server_timestamp = server_timestamp; + + // number of last blocks to show + uint64_t no_of_last_blocks {no_blocks_on_index + 1}; + + // get the current blockchain height. Just to check + uint64_t height = core_storage->get_current_blockchain_height(); + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"testnet_url" , testnet_url}, + {"stagenet_url" , stagenet_url}, + {"mainnet_url" , mainnet_url}, + {"refresh" , refresh_page}, + {"height" , height}, + {"server_timestamp" , xmreg::timestamp_to_str_gm(local_copy_server_timestamp)}, + {"age_format" , string("[h:m:d]")}, + {"page_no" , page_no}, + {"total_page_no" , (height / no_of_last_blocks)}, + {"is_page_zero" , !bool(page_no)}, + {"no_of_last_blocks" , no_of_last_blocks}, + {"next_page" , (page_no + 1)}, + {"prev_page" , (page_no > 0 ? page_no - 1 : 0)}, + {"enable_pusher" , enable_pusher}, + {"enable_key_image_checker" , enable_key_image_checker}, + {"enable_output_key_checker", enable_output_key_checker}, + {"enable_autorefresh_option", enable_autorefresh_option}, + {"show_cache_times" , show_cache_times} + }; - uint64_t local_copy_server_timestamp = server_timestamp; + context.emplace("txs", mstch::array()); // will keep tx to show - // number of last blocks to show - uint64_t no_of_last_blocks {no_blocks_on_index + 1}; + // get reference to txs mstch map to be field below + mstch::array& txs = boost::get(context["txs"]); - // get the current blockchain height. Just to check - uint64_t height = core_storage->get_current_blockchain_height(); - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"testnet_url" , testnet_url}, - {"stagenet_url" , stagenet_url}, - {"mainnet_url" , mainnet_url}, - {"refresh" , refresh_page}, - {"height" , height}, - {"server_timestamp" , xmreg::timestamp_to_str_gm(local_copy_server_timestamp)}, - {"age_format" , string("[h:m:d]")}, - {"page_no" , page_no}, - {"total_page_no" , (height / no_of_last_blocks)}, - {"is_page_zero" , !bool(page_no)}, - {"no_of_last_blocks" , no_of_last_blocks}, - {"next_page" , (page_no + 1)}, - {"prev_page" , (page_no > 0 ? page_no - 1 : 0)}, - {"enable_pusher" , enable_pusher}, - {"enable_key_image_checker" , enable_key_image_checker}, - {"enable_output_key_checker", enable_output_key_checker}, - {"enable_autorefresh_option", enable_autorefresh_option}, - {"show_cache_times" , show_cache_times} - }; + // calculate starting and ending block numbers to show + int64_t start_height = height - no_of_last_blocks * (page_no + 1); - context.emplace("txs", mstch::array()); // will keep tx to show + // check if start height is not below range + start_height = start_height < 0 ? 0 : start_height; - // get reference to txs mstch map to be field below - mstch::array& txs = boost::get(context["txs"]); + int64_t end_height = start_height + no_of_last_blocks - 1; - // calculate starting and ending block numbers to show - int64_t start_height = height - no_of_last_blocks * (page_no + 1); + vector blk_sizes; - // check if start height is not below range - start_height = start_height < 0 ? 0 : start_height; + // measure time of cache based execution, and non-cached execution + double duration_cached {0.0}; + double duration_non_cached {0.0}; + uint64_t cache_hits {0}; + uint64_t cache_misses {0}; - int64_t end_height = start_height + no_of_last_blocks - 1; + // loop index + int64_t i = end_height; - vector blk_sizes; + // iterate over last no_of_last_blocks of blocks + while (i >= start_height) + { + // get block at the given height i + block blk; - // measure time of cache based execution, and non-cached execution - double duration_cached {0.0}; - double duration_non_cached {0.0}; - uint64_t cache_hits {0}; - uint64_t cache_misses {0}; + if (!mcore->get_block_by_height(i, blk)) + { + cerr << "Cant get block: " << i << endl; + --i; + continue; + } - // loop index - int64_t i = end_height; + // get block's hash + crypto::hash blk_hash = core_storage->get_block_id_by_height(i); - // iterate over last no_of_last_blocks of blocks - while (i >= start_height) - { - // get block at the given height i - block blk; + // get block size in kB + double blk_size = static_cast(core_storage->get_db().get_block_size(i))/1024.0; - if (!mcore->get_block_by_height(i, blk)) - { - cerr << "Cant get block: " << i << endl; - --i; - continue; - } + string blk_size_str = fmt::format("{:0.2f}", blk_size); - // get block's hash - crypto::hash blk_hash = core_storage->get_block_id_by_height(i); + blk_sizes.push_back(blk_size); - // get block size in kB - double blk_size = static_cast(core_storage->get_db().get_block_size(i))/1024.0; + // remove "<" and ">" from the hash string + string blk_hash_str = pod_to_hex(blk_hash); - string blk_size_str = fmt::format("{:0.2f}", blk_size); + // get block age + pair age = get_age(local_copy_server_timestamp, blk.timestamp); - blk_sizes.push_back(blk_size); + context["age_format"] = age.second; - // remove "<" and ">" from the hash string - string blk_hash_str = pod_to_hex(blk_hash); - // get block age - pair age = get_age(local_copy_server_timestamp, blk.timestamp); + if (enable_block_cache && block_tx_cache.Contains(i)) + { + // get txs info in the ith block from + // our cache - context["age_format"] = age.second; + // start measure time here + auto start = std::chrono::steady_clock::now(); + const vector>& txd_pairs + = block_tx_cache.Get(i); - if (enable_block_cache && block_tx_cache.Contains(i)) + // copy tx maps from txs_maps_tmp into txs array, + // that will go to templates + for (const pair& txd_pair: txd_pairs) { - // get txs info in the ith block from - // our cache + // we need to check if the given transaction is still + // in the same block as when it was cached. it is possible + // the block got orphaned, and this tx is in mempool + // or different block, and what we have in cache + // is thus wrong - // start measure time here - auto start = std::chrono::steady_clock::now(); + // but we do this only for first top blocks. no sense + // doing it for all blocks - const vector>& txd_pairs - = block_tx_cache.Get(i); + bool is_tx_still_in_block_as_expected {true}; - // copy tx maps from txs_maps_tmp into txs array, - // that will go to templates - for (const pair& txd_pair: txd_pairs) + if (i + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > height) { - // we need to check if the given transaction is still - // in the same block as when it was cached. it is possible - // the block got orphaned, and this tx is in mempool - // or different block, and what we have in cache - // is thus wrong - - // but we do this only for first top blocks. no sense - // doing it for all blocks - - bool is_tx_still_in_block_as_expected {true}; + const crypto::hash& tx_hash = txd_pair.first; - if (i + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > height) + if (core_storage->have_tx(tx_hash)) { - const crypto::hash& tx_hash = txd_pair.first; - - if (core_storage->have_tx(tx_hash)) + try { - try + uint64_t tx_height_in_blockchain = + core_storage->get_db().get_tx_block_height(tx_hash); + + // check if height of the given tx that we have in cache, + // denoted by i, is same as what is acctually stored + // in blockchain + if (tx_height_in_blockchain == i) { - uint64_t tx_height_in_blockchain = - core_storage->get_db().get_tx_block_height(tx_hash); - - // check if height of the given tx that we have in cache, - // denoted by i, is same as what is acctually stored - // in blockchain - if (tx_height_in_blockchain == i) - { - is_tx_still_in_block_as_expected = true; - } - else - { - // if no tx in the given block, just stop - // any futher search. no need. we are going - // to ditch the cache, in a monent - is_tx_still_in_block_as_expected = false; - break; - } + is_tx_still_in_block_as_expected = true; } - catch (const TX_DNE& e) + else { - cerr << "Tx from cache" << pod_to_hex(tx_hash) - << " is no longer in the blockchain " - << endl; - + // if no tx in the given block, just stop + // any futher search. no need. we are going + // to ditch the cache, in a monent is_tx_still_in_block_as_expected = false; break; } } - else + catch (const TX_DNE& e) { + cerr << "Tx from cache" << pod_to_hex(tx_hash) + << " is no longer in the blockchain " + << endl; + is_tx_still_in_block_as_expected = false; break; } - - } // if (i + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > height) - - - if (!is_tx_still_in_block_as_expected) - { - // if some tx in cache is not in blockchain - // where it should be, its probably better to - // ditch entire cache, as redo it below. - - block_tx_cache.Clear(); - txs.clear(); - i = end_height; - continue; // reado the main loop } - - // if we got to here, it means that everything went fine - // and no unexpeced things happended. - mstch::map txd_map = boost::get(txd_pair.second); - - // now we need to update age of txs from cashe - if (!boost::get(txd_map["age"]).empty()) + else { - txd_map["age"] = age.first; + is_tx_still_in_block_as_expected = false; + break; } - txs.push_back(txd_map); - - } // for (const pair& txd_pair: txd_pairs) - - auto duration = std::chrono::duration_cast - (std::chrono::steady_clock::now() - start); - - // cout << "block_tx_json_cache from cache" << endl; + } // if (i + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > height) - duration_cached += duration.count(); - - ++cache_hits; - } - else - { - // this is new block. not in cashe. - // need to process its txs and add to cache - // start measure time here - auto start = std::chrono::steady_clock::now(); + if (!is_tx_still_in_block_as_expected) + { + // if some tx in cache is not in blockchain + // where it should be, its probably better to + // ditch entire cache, as redo it below. + + block_tx_cache.Clear(); + txs.clear(); + i = end_height; + continue; // reado the main loop + } - // get all transactions in the block found - // initialize the first list with transaction for solving - // the block i.e. coinbase. - vector blk_txs {blk.miner_tx}; - vector missed_txs; + // if we got to here, it means that everything went fine + // and no unexpeced things happended. + mstch::map txd_map = boost::get(txd_pair.second); - if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) + // now we need to update age of txs from cashe + if (!boost::get(txd_map["age"]).empty()) { - cerr << "Cant get transactions in block: " << i << endl; - --i; - continue; + txd_map["age"] = age.first; } - uint64_t tx_i {0}; + txs.push_back(txd_map); - // this vector will go into block_tx cache - // tx_hash , txd_map - vector> txd_pairs; + } // for (const pair& txd_pair: txd_pairs) - for(auto it = blk_txs.begin(); it != blk_txs.end(); ++it) - { - const cryptonote::transaction& tx = *it; + auto duration = std::chrono::duration_cast + (std::chrono::steady_clock::now() - start); - const tx_details& txd = get_tx_details(tx, false, i, height); + // cout << "block_tx_json_cache from cache" << endl; - mstch::map txd_map = txd.get_mstch_map(); + duration_cached += duration.count(); - //add age to the txd mstch map - txd_map.insert({"height" , i}); - txd_map.insert({"blk_hash" , blk_hash_str}); - txd_map.insert({"age" , age.first}); - txd_map.insert({"is_ringct" , (tx.version > 1)}); - txd_map.insert({"rct_type" , tx.rct_signatures.type}); - txd_map.insert({"blk_size" , blk_size_str}); + ++cache_hits; + } + else + { + // this is new block. not in cashe. + // need to process its txs and add to cache + // start measure time here + auto start = std::chrono::steady_clock::now(); - // do not show block info for other than first tx in a block - if (tx_i > 0) - { - txd_map["height"] = string(""); - txd_map["age"] = string(""); - txd_map["blk_size"] = string(""); - } + // get all transactions in the block found + // initialize the first list with transaction for solving + // the block i.e. coinbase. + vector blk_txs {blk.miner_tx}; + vector missed_txs; - txd_pairs.emplace_back(txd.hash, txd_map); + if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) + { + cerr << "Cant get transactions in block: " << i << endl; + --i; + continue; + } - ++tx_i; + uint64_t tx_i {0}; - } // for(list::reverse_iterator rit = blk_txs.rbegin(); + // this vector will go into block_tx cache + // tx_hash , txd_map + vector> txd_pairs; - // copy tx maps from txs_maps_tmp into txs array, - // that will go to templates - for (const pair& txd_pair: txd_pairs) - { - txs.push_back(boost::get(txd_pair.second)); - } + for(auto it = blk_txs.begin(); it != blk_txs.end(); ++it) + { + const cryptonote::transaction& tx = *it; - auto duration = std::chrono::duration_cast - (std::chrono::steady_clock::now() - start); + const tx_details& txd = get_tx_details(tx, false, i, height); + + mstch::map txd_map = txd.get_mstch_map(); - duration_non_cached += duration.count(); + //add age to the txd mstch map + txd_map.insert({"height" , i}); + txd_map.insert({"blk_hash" , blk_hash_str}); + txd_map.insert({"age" , age.first}); + txd_map.insert({"is_ringct" , (tx.version > 1)}); + txd_map.insert({"rct_type" , tx.rct_signatures.type}); + txd_map.insert({"blk_size" , blk_size_str}); - ++cache_misses; - if (enable_block_cache) + // do not show block info for other than first tx in a block + if (tx_i > 0) { - // save in block_tx cache - block_tx_cache.Put(i, txd_pairs); + txd_map["height"] = string(""); + txd_map["age"] = string(""); + txd_map["blk_size"] = string(""); } - } // else if (block_tx_json_cache.Contains(i)) + txd_pairs.emplace_back(txd.hash, txd_map); - --i; // go to next block number + ++tx_i; - } // while (i <= end_height) + } // for(list::reverse_iterator rit = blk_txs.rbegin(); - // calculate median size of the blocks shown - //double blk_size_median = xmreg::calc_median(blk_sizes.begin(), blk_sizes.end()); + // copy tx maps from txs_maps_tmp into txs array, + // that will go to templates + for (const pair& txd_pair: txd_pairs) + { + txs.push_back(boost::get(txd_pair.second)); + } - // save computational times for disply in the frontend + auto duration = std::chrono::duration_cast + (std::chrono::steady_clock::now() - start); - context["construction_time_cached"] = fmt::format( - "{:0.4f}", duration_cached/1.0e6); + duration_non_cached += duration.count(); - context["construction_time_non_cached"] = fmt::format( - "{:0.4f}", duration_non_cached/1.0e6); + ++cache_misses; - context["construction_time_total"] = fmt::format( - "{:0.4f}", (duration_non_cached+duration_cached)/1.0e6); + if (enable_block_cache) + { + // save in block_tx cache + block_tx_cache.Put(i, txd_pairs); + } - context["cache_hits"] = cache_hits; - context["cache_misses"] = cache_misses; + } // else if (block_tx_json_cache.Contains(i)) + --i; // go to next block number - // get current network info from MemoryStatus thread. - MempoolStatus::network_info current_network_info - = MempoolStatus::current_network_info; + } // while (i <= end_height) - // perapre network info mstch::map for the front page - string hash_rate; + // calculate median size of the blocks shown + //double blk_size_median = xmreg::calc_median(blk_sizes.begin(), blk_sizes.end()); - if (current_network_info.hash_rate > 1e6) - hash_rate = fmt::format("{:0.3f} MH/s", current_network_info.hash_rate/1.0e6); - else if (current_network_info.hash_rate > 1e3) - hash_rate = fmt::format("{:0.3f} kH/s", current_network_info.hash_rate/1.0e3); - else - hash_rate = fmt::format("{:d} H/s", current_network_info.hash_rate); + // save computational times for disply in the frontend - pair network_info_age = get_age(local_copy_server_timestamp, - current_network_info.info_timestamp); + context["construction_time_cached"] = fmt::format( + "{:0.4f}", duration_cached/1.0e6); - // 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) - { - current_network_info.current = true; - } + context["construction_time_non_cached"] = fmt::format( + "{:0.4f}", duration_non_cached/1.0e6); - 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_count}, - {"have_alt_block" , (current_network_info.alt_blocks_count > 0)}, - {"tx_pool_size" , current_network_info.tx_pool_size}, - {"block_size_limit" , string {current_network_info.block_size_limit_str}}, - {"block_size_median" , string {current_network_info.block_size_median_str}}, - {"is_current_info" , current_network_info.current}, - {"is_pool_size_zero" , (current_network_info.tx_pool_size == 0)}, - {"current_hf_version", current_network_info.current_hf_version}, - {"age" , network_info_age.first}, - {"age_format" , network_info_age.second}, - }; + context["construction_time_total"] = fmt::format( + "{:0.4f}", (duration_non_cached+duration_cached)/1.0e6); - // median size of 100 blocks - context["blk_size_median"] = string {current_network_info.block_size_median_str}; + context["cache_hits"] = cache_hits; + context["cache_misses"] = cache_misses; - 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)); + // get current network info from MemoryStatus thread. + MempoolStatus::network_info current_network_info + = MempoolStatus::current_network_info; - 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); - } + // perapre network info mstch::map for the front page + string hash_rate; - if (CurrentBlockchainStatus::is_thread_running()) - { - CurrentBlockchainStatus::Emission current_values - = CurrentBlockchainStatus::get_emission(); + if (current_network_info.hash_rate > 1e6) + hash_rate = fmt::format("{:0.3f} MH/s", current_network_info.hash_rate/1.0e6); + else if (current_network_info.hash_rate > 1e3) + hash_rate = fmt::format("{:0.3f} kH/s", current_network_info.hash_rate/1.0e3); + else + hash_rate = fmt::format("{:d} H/s", current_network_info.hash_rate); - 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}"); + pair network_info_age = get_age(local_copy_server_timestamp, + current_network_info.info_timestamp); - context["emission"] = mstch::map { - {"blk_no" , emission_blk_no}, - {"amount" , emission_coinbase}, - {"fee_amount", emission_fee} - }; - } - else - { - cerr << "emission thread not running, skipping." << endl; - } + // 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) + { + current_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_count}, + {"have_alt_block" , (current_network_info.alt_blocks_count > 0)}, + {"tx_pool_size" , current_network_info.tx_pool_size}, + {"block_size_limit" , string {current_network_info.block_size_limit_str}}, + {"block_size_median" , string {current_network_info.block_size_median_str}}, + {"is_current_info" , current_network_info.current}, + {"is_pool_size_zero" , (current_network_info.tx_pool_size == 0)}, + {"current_hf_version", current_network_info.current_hf_version}, + {"age" , network_info_age.first}, + {"age_format" , network_info_age.second}, + }; - // get memory pool rendered template - //string mempool_html = mempool(false, no_of_mempool_tx_of_frontpage); + // median size of 100 blocks + context["blk_size_median"] = string {current_network_info.block_size_median_str}; - // append mempool_html to the index context map - context["mempool_info"] = mempool_html; + string mempool_html {"Cant get mempool_pool"}; - add_css_style(context); + // 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)); - // render the page - return mstch::render(template_file["index2"], context); + if (mempool_ftr_status == std::future_status::ready) + { + mempool_html = mempool_ftr.get(); } - - /** - * Render mempool data - */ - string - mempool(bool add_header_and_footer = false, uint64_t no_of_mempool_tx = 25) + else { - std::vector mempool_txs; + cerr << "mempool future not ready yet, skipping." << endl; + mempool_html = mstch::render(template_file["mempool_error"], context); + } - if (add_header_and_footer) - { - // get all memmpool txs - mempool_txs = MempoolStatus::get_mempool_txs(); - no_of_mempool_tx = mempool_txs.size(); - } - else - { - // get only first no_of_mempool_tx txs - mempool_txs = MempoolStatus::get_mempool_txs(no_of_mempool_tx); - no_of_mempool_tx = std::min(no_of_mempool_tx, mempool_txs.size()); - } + if (CurrentBlockchainStatus::is_thread_running()) + { + CurrentBlockchainStatus::Emission current_values + = CurrentBlockchainStatus::get_emission(); - // total size of mempool in bytes - uint64_t mempool_size_bytes = MempoolStatus::mempool_size; + 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}"); - // reasign this number, in case no of txs in mempool is smaller - // than what we requested or we want all txs. + context["emission"] = mstch::map { + {"blk_no" , emission_blk_no}, + {"amount" , emission_coinbase}, + {"fee_amount", emission_fee} + }; + } + else + { + cerr << "emission thread not running, skipping." << endl; + } - uint64_t total_no_of_mempool_tx = MempoolStatus::mempool_no; + // get memory pool rendered template + //string mempool_html = mempool(false, no_of_mempool_tx_of_frontpage); - // initalise page tempate map with basic info about mempool - mstch::map context { - {"mempool_size" , static_cast(total_no_of_mempool_tx)}, // total no of mempool txs - {"show_cache_times" , show_cache_times}, - {"mempool_refresh_time" , MempoolStatus::mempool_refresh_time} - }; + // append mempool_html to the index context map + context["mempool_info"] = mempool_html; - context.emplace("mempooltxs" , mstch::array()); + add_css_style(context); - // get reference to blocks template map to be field below - mstch::array& txs = boost::get(context["mempooltxs"]); + // render the page + return mstch::render(template_file["index2"], context); +} - double duration_cached {0.0}; - double duration_non_cached {0.0}; - uint64_t cache_hits {0}; - uint64_t cache_misses {0}; +/** + * Render mempool data + */ +string +mempool(bool add_header_and_footer = false, uint64_t no_of_mempool_tx = 25) +{ + std::vector mempool_txs; - uint64_t local_copy_server_timestamp = server_timestamp; + if (add_header_and_footer) + { + // get all memmpool txs + mempool_txs = MempoolStatus::get_mempool_txs(); + no_of_mempool_tx = mempool_txs.size(); + } + else + { + // get only first no_of_mempool_tx txs + mempool_txs = MempoolStatus::get_mempool_txs(no_of_mempool_tx); + no_of_mempool_tx = std::min(no_of_mempool_tx, mempool_txs.size()); + } - // for each transaction in the memory pool - for (size_t i = 0; i < no_of_mempool_tx; ++i) - { - // get transaction info of the tx in the mempool - const MempoolStatus::mempool_tx& mempool_tx = mempool_txs.at(i); + // total size of mempool in bytes + uint64_t mempool_size_bytes = MempoolStatus::mempool_size; - // calculate difference between tx in mempool and server timestamps - array delta_time = timestamp_difference( - local_copy_server_timestamp, - mempool_tx.receive_time); + // reasign this number, in case no of txs in mempool is smaller + // than what we requested or we want all txs. - // use only hours, so if we have days, add - // it to hours - uint64_t delta_hours {delta_time[1]*24 + delta_time[2]}; - string age_str = fmt::format("{:02d}:{:02d}:{:02d}", - delta_hours, - delta_time[3], delta_time[4]); + uint64_t total_no_of_mempool_tx = MempoolStatus::mempool_no; - // if more than 99 hourse, change formating - // for the template - if (delta_hours > 99) - { - age_str = fmt::format("{:03d}:{:02d}:{:02d}", - delta_hours, - delta_time[3], delta_time[4]); - } + // initalise page tempate map with basic info about mempool + mstch::map context { + {"mempool_size" , static_cast(total_no_of_mempool_tx)}, // total no of mempool txs + {"show_cache_times" , show_cache_times}, + {"mempool_refresh_time" , MempoolStatus::mempool_refresh_time} + }; - // cout << "block_tx_json_cache from cache" << endl; + context.emplace("mempooltxs" , mstch::array()); - // set output page template map - txs.push_back(mstch::map { - {"timestamp_no" , mempool_tx.receive_time}, - {"timestamp" , mempool_tx.timestamp_str}, - {"age" , age_str}, - {"hash" , pod_to_hex(mempool_tx.tx_hash)}, - {"fee" , mempool_tx.fee_str}, - {"payed_for_kB" , mempool_tx.payed_for_kB_str}, - {"xmr_inputs" , mempool_tx.xmr_inputs_str}, - {"xmr_outputs" , mempool_tx.xmr_outputs_str}, - {"no_inputs" , mempool_tx.no_inputs}, - {"no_outputs" , mempool_tx.no_outputs}, - {"pID" , string {mempool_tx.pID}}, - {"no_nonrct_inputs", mempool_tx.num_nonrct_inputs}, - {"mixin" , mempool_tx.mixin_no}, - {"txsize" , mempool_tx.txsize} - }); - } + // get reference to blocks template map to be field below + mstch::array& txs = boost::get(context["mempooltxs"]); - context.insert({"mempool_size_kB", - fmt::format("{:0.2f}", - static_cast(mempool_size_bytes)/1024.0)}); + double duration_cached {0.0}; + double duration_non_cached {0.0}; + uint64_t cache_hits {0}; + uint64_t cache_misses {0}; - if (add_header_and_footer) - { - // this is when mempool is on its own page, /mempool - add_css_style(context); + uint64_t local_copy_server_timestamp = server_timestamp; - context["partial_mempool_shown"] = false; + // for each transaction in the memory pool + for (size_t i = 0; i < no_of_mempool_tx; ++i) + { + // get transaction info of the tx in the mempool + const MempoolStatus::mempool_tx& mempool_tx = mempool_txs.at(i); - // render the page - return mstch::render(template_file["mempool_full"], context); - } + // calculate difference between tx in mempool and server timestamps + array delta_time = timestamp_difference( + local_copy_server_timestamp, + mempool_tx.receive_time); - // this is for partial disply on front page. + // use only hours, so if we have days, add + // it to hours + uint64_t delta_hours {delta_time[1]*24 + delta_time[2]}; - context["mempool_fits_on_front_page"] = (total_no_of_mempool_tx <= mempool_txs.size()); - context["no_of_mempool_tx_of_frontpage"] = no_of_mempool_tx; + string age_str = fmt::format("{:02d}:{:02d}:{:02d}", + delta_hours, + delta_time[3], delta_time[4]); - context["partial_mempool_shown"] = true; + // if more than 99 hourse, change formating + // for the template + if (delta_hours > 99) + { + age_str = fmt::format("{:03d}:{:02d}:{:02d}", + delta_hours, + delta_time[3], delta_time[4]); + } - // render the page - return mstch::render(template_file["mempool"], context); + // cout << "block_tx_json_cache from cache" << endl; + + // set output page template map + txs.push_back(mstch::map { + {"timestamp_no" , mempool_tx.receive_time}, + {"timestamp" , mempool_tx.timestamp_str}, + {"age" , age_str}, + {"hash" , pod_to_hex(mempool_tx.tx_hash)}, + {"fee" , mempool_tx.fee_str}, + {"payed_for_kB" , mempool_tx.payed_for_kB_str}, + {"xmr_inputs" , mempool_tx.xmr_inputs_str}, + {"xmr_outputs" , mempool_tx.xmr_outputs_str}, + {"no_inputs" , mempool_tx.no_inputs}, + {"no_outputs" , mempool_tx.no_outputs}, + {"pID" , string {mempool_tx.pID}}, + {"no_nonrct_inputs", mempool_tx.num_nonrct_inputs}, + {"mixin" , mempool_tx.mixin_no}, + {"txsize" , mempool_tx.txsize} + }); } + context.insert({"mempool_size_kB", + fmt::format("{:0.2f}", + static_cast(mempool_size_bytes)/1024.0)}); - string - altblocks() + if (add_header_and_footer) { + // this is when mempool is on its own page, /mempool + add_css_style(context); - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"blocks" , mstch::array()} - }; - - uint64_t local_copy_server_timestamp = server_timestamp; - - // get reference to alt blocks template map to be field below - mstch::array& blocks = boost::get(context["blocks"]); - - vector atl_blks_hashes; - - if (!rpc.get_alt_blocks(atl_blks_hashes)) - { - cerr << "rpc.get_alt_blocks(atl_blks_hashes) failed" << endl; - } + context["partial_mempool_shown"] = false; - context.emplace("no_alt_blocks", (uint64_t)atl_blks_hashes.size()); + // render the page + return mstch::render(template_file["mempool_full"], context); + } - for (const string& alt_blk_hash: atl_blks_hashes) - { - block alt_blk; - string error_msg; + // this is for partial disply on front page. - int64_t no_of_txs {-1}; - int64_t blk_height {-1}; + context["mempool_fits_on_front_page"] = (total_no_of_mempool_tx <= mempool_txs.size()); + context["no_of_mempool_tx_of_frontpage"] = no_of_mempool_tx; - // get block age - pair age {"-1", "-1"}; + context["partial_mempool_shown"] = true; + // render the page + return mstch::render(template_file["mempool"], context); +} - if (rpc.get_block(alt_blk_hash, alt_blk, error_msg)) - { - no_of_txs = alt_blk.tx_hashes.size(); - blk_height = get_block_height(alt_blk); +string +altblocks() +{ - age = get_age(local_copy_server_timestamp, alt_blk.timestamp); - } + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"blocks" , mstch::array()} + }; - blocks.push_back(mstch::map { - {"height" , blk_height}, - {"age" , age.first}, - {"hash" , alt_blk_hash}, - {"no_of_txs", no_of_txs} - }); + uint64_t local_copy_server_timestamp = server_timestamp; - } + // get reference to alt blocks template map to be field below + mstch::array& blocks = boost::get(context["blocks"]); - add_css_style(context); + vector atl_blks_hashes; - // render the page - return mstch::render(template_file["altblocks"], context); + if (!rpc.get_alt_blocks(atl_blks_hashes)) + { + cerr << "rpc.get_alt_blocks(atl_blks_hashes) failed" << endl; } + context.emplace("no_alt_blocks", (uint64_t)atl_blks_hashes.size()); - string - show_block(uint64_t _blk_height) + for (const string& alt_blk_hash: atl_blks_hashes) { + block alt_blk; + string error_msg; - // get block at the given height i - block blk; + int64_t no_of_txs {-1}; + int64_t blk_height {-1}; - //cout << "_blk_height: " << _blk_height << endl; + // get block age + pair age {"-1", "-1"}; - uint64_t current_blockchain_height - = core_storage->get_current_blockchain_height(); - if (_blk_height > current_blockchain_height) + if (rpc.get_block(alt_blk_hash, alt_blk, error_msg)) { - cerr << "Cant get block: " << _blk_height - << " since its higher than current blockchain height" - << " i.e., " << current_blockchain_height - << endl; - return fmt::format("Cant get block {:d} since its higher than current blockchain height!", - _blk_height); - } + no_of_txs = alt_blk.tx_hashes.size(); + blk_height = get_block_height(alt_blk); - if (!mcore->get_block_by_height(_blk_height, blk)) - { - cerr << "Cant get block: " << _blk_height << endl; - return fmt::format("Cant get block {:d}!", _blk_height); + age = get_age(local_copy_server_timestamp, alt_blk.timestamp); } - // get block's hash - crypto::hash blk_hash = core_storage->get_block_id_by_height(_blk_height); - - crypto::hash prev_hash = blk.prev_id; - crypto::hash next_hash = null_hash; - - if (_blk_height + 1 <= current_blockchain_height) - { - next_hash = core_storage->get_block_id_by_height(_blk_height + 1); - } + blocks.push_back(mstch::map { + {"height" , blk_height}, + {"age" , age.first}, + {"hash" , alt_blk_hash}, + {"no_of_txs", no_of_txs} + }); - bool have_next_hash = (next_hash == null_hash ? false : true); - bool have_prev_hash = (prev_hash == null_hash ? false : true); + } - // remove "<" and ">" from the hash string - string prev_hash_str = pod_to_hex(prev_hash); - string next_hash_str = pod_to_hex(next_hash); + add_css_style(context); - // remove "<" and ">" from the hash string - string blk_hash_str = pod_to_hex(blk_hash); + // render the page + return mstch::render(template_file["altblocks"], context); +} - // get block timestamp in user friendly format - string blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); - // get age of the block relative to the server time - pair age = get_age(server_timestamp, blk.timestamp); +string +show_block(uint64_t _blk_height) +{ - // get time from the last block - string delta_time {"N/A"}; + // get block at the given height i + block blk; - if (have_prev_hash) - { - block prev_blk = core_storage->get_db().get_block(prev_hash); + //cout << "_blk_height: " << _blk_height << endl; - pair delta_diff = get_age(blk.timestamp, prev_blk.timestamp); + uint64_t current_blockchain_height + = core_storage->get_current_blockchain_height(); - delta_time = delta_diff.first; - } + if (_blk_height > current_blockchain_height) + { + cerr << "Cant get block: " << _blk_height + << " since its higher than current blockchain height" + << " i.e., " << current_blockchain_height + << endl; + return fmt::format("Cant get block {:d} since its higher than current blockchain height!", + _blk_height); + } - // get block size in bytes - uint64_t blk_size = core_storage->get_db().get_block_size(_blk_height); - - // miner reward tx - transaction coinbase_tx = blk.miner_tx; - - // transcation in the block - vector tx_hashes = blk.tx_hashes; - - bool have_txs = !blk.tx_hashes.empty(); - - // sum of all transactions in the block - uint64_t sum_fees = 0; - - // get tx details for the coinbase tx, i.e., miners reward - tx_details txd_coinbase = get_tx_details(blk.miner_tx, true, - _blk_height, current_blockchain_height); - - // initalise page tempate map with basic info about blockchain - - string blk_pow_hash_str = pod_to_hex(get_block_longhash(blk, _blk_height)); - uint64_t blk_difficulty = core_storage->get_db().get_block_difficulty(_blk_height); - - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"blk_hash" , blk_hash_str}, - {"blk_height" , _blk_height}, - {"blk_timestamp" , blk_timestamp}, - {"blk_timestamp_epoch" , blk.timestamp}, - {"prev_hash" , prev_hash_str}, - {"next_hash" , next_hash_str}, - {"enable_as_hex" , enable_as_hex}, - {"have_next_hash" , have_next_hash}, - {"have_prev_hash" , have_prev_hash}, - {"have_txs" , have_txs}, - {"no_txs" , std::to_string( - blk.tx_hashes.size())}, - {"blk_age" , age.first}, - {"delta_time" , delta_time}, - {"blk_nonce" , blk.nonce}, - {"blk_pow_hash" , blk_pow_hash_str}, - {"blk_difficulty" , blk_difficulty}, - {"age_format" , age.second}, - {"major_ver" , std::to_string(blk.major_version)}, - {"minor_ver" , std::to_string(blk.minor_version)}, - {"blk_size" , fmt::format("{:0.4f}", - static_cast(blk_size) / 1024.0)}, - }; - context.emplace("coinbase_txs", mstch::array{{txd_coinbase.get_mstch_map()}}); - context.emplace("blk_txs" , mstch::array()); - // .push_back(txd_coinbase.get_mstch_map() + if (!mcore->get_block_by_height(_blk_height, blk)) + { + cerr << "Cant get block: " << _blk_height << endl; + return fmt::format("Cant get block {:d}!", _blk_height); + } - // boost::get(context["blk_txs"]).push_back(txd_coinbase.get_mstch_map()); + // get block's hash + crypto::hash blk_hash = core_storage->get_block_id_by_height(_blk_height); - // now process nomral transactions - // get reference to blocks template map to be field below - mstch::array& txs = boost::get(context["blk_txs"]); + crypto::hash prev_hash = blk.prev_id; + crypto::hash next_hash = null_hash; - // timescale representation for each tx in the block - vector mixin_timescales_str; + if (_blk_height + 1 <= current_blockchain_height) + { + next_hash = core_storage->get_block_id_by_height(_blk_height + 1); + } - // for each transaction in the block - for (size_t i = 0; i < blk.tx_hashes.size(); ++i) - { - // get transaction info of the tx in the mempool - const crypto::hash& tx_hash = blk.tx_hashes.at(i); + bool have_next_hash = (next_hash == null_hash ? false : true); + bool have_prev_hash = (prev_hash == null_hash ? false : true); - // remove "<" and ">" from the hash string - string tx_hash_str = pod_to_hex(tx_hash); + // remove "<" and ">" from the hash string + string prev_hash_str = pod_to_hex(prev_hash); + string next_hash_str = pod_to_hex(next_hash); + // remove "<" and ">" from the hash string + string blk_hash_str = pod_to_hex(blk_hash); - // get transaction - transaction tx; + // get block timestamp in user friendly format + string blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx: " << tx_hash << endl; - continue; - } + // get age of the block relative to the server time + pair age = get_age(server_timestamp, blk.timestamp); - tx_details txd = get_tx_details(tx, false, - _blk_height, - current_blockchain_height); + // get time from the last block + string delta_time {"N/A"}; - // add fee to the rest - sum_fees += txd.fee; + if (have_prev_hash) + { + block prev_blk = core_storage->get_db().get_block(prev_hash); + pair delta_diff = get_age(blk.timestamp, prev_blk.timestamp); - // get mixins in time scale for visual representation - //string mixin_times_scale = xmreg::timestamps_time_scale(mixin_timestamps, - // server_timestamp); + delta_time = delta_diff.first; + } + // get block size in bytes + uint64_t blk_size = core_storage->get_db().get_block_size(_blk_height); + + // miner reward tx + transaction coinbase_tx = blk.miner_tx; + + // transcation in the block + vector tx_hashes = blk.tx_hashes; + + bool have_txs = !blk.tx_hashes.empty(); + + // sum of all transactions in the block + uint64_t sum_fees = 0; + + // get tx details for the coinbase tx, i.e., miners reward + tx_details txd_coinbase = get_tx_details(blk.miner_tx, true, + _blk_height, current_blockchain_height); + + // initalise page tempate map with basic info about blockchain + + string blk_pow_hash_str = pod_to_hex(get_block_longhash(blk, _blk_height)); + uint64_t blk_difficulty = core_storage->get_db().get_block_difficulty(_blk_height); + + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"blk_hash" , blk_hash_str}, + {"blk_height" , _blk_height}, + {"blk_timestamp" , blk_timestamp}, + {"blk_timestamp_epoch" , blk.timestamp}, + {"prev_hash" , prev_hash_str}, + {"next_hash" , next_hash_str}, + {"enable_as_hex" , enable_as_hex}, + {"have_next_hash" , have_next_hash}, + {"have_prev_hash" , have_prev_hash}, + {"have_txs" , have_txs}, + {"no_txs" , std::to_string( + blk.tx_hashes.size())}, + {"blk_age" , age.first}, + {"delta_time" , delta_time}, + {"blk_nonce" , blk.nonce}, + {"blk_pow_hash" , blk_pow_hash_str}, + {"blk_difficulty" , blk_difficulty}, + {"age_format" , age.second}, + {"major_ver" , std::to_string(blk.major_version)}, + {"minor_ver" , std::to_string(blk.minor_version)}, + {"blk_size" , fmt::format("{:0.4f}", + static_cast(blk_size) / 1024.0)}, + }; + context.emplace("coinbase_txs", mstch::array{{txd_coinbase.get_mstch_map()}}); + context.emplace("blk_txs" , mstch::array()); - // add tx details mstch map to context - txs.push_back(txd.get_mstch_map()); - } + // .push_back(txd_coinbase.get_mstch_map() + // boost::get(context["blk_txs"]).push_back(txd_coinbase.get_mstch_map()); - // add total fees in the block to the context - context["sum_fees"] - = xmreg::xmr_amount_to_str(sum_fees, "{:0.6f}", false); + // now process nomral transactions + // get reference to blocks template map to be field below + mstch::array& txs = boost::get(context["blk_txs"]); - // get xmr in the block reward - context["blk_reward"] - = xmreg::xmr_amount_to_str(txd_coinbase.xmr_outputs - sum_fees, "{:0.6f}"); + // timescale representation for each tx in the block + vector mixin_timescales_str; - add_css_style(context); + // for each transaction in the block + for (size_t i = 0; i < blk.tx_hashes.size(); ++i) + { + // get transaction info of the tx in the mempool + const crypto::hash& tx_hash = blk.tx_hashes.at(i); - // render the page - return mstch::render(template_file["block"], context); - } + // remove "<" and ">" from the hash string + string tx_hash_str = pod_to_hex(tx_hash); - string - show_block(string _blk_hash) - { - crypto::hash blk_hash; + // get transaction + transaction tx; - if (!xmreg::parse_str_secret_key(_blk_hash, blk_hash)) + if (!mcore->get_tx(tx_hash, tx)) { - cerr << "Cant parse blk hash: " << blk_hash << endl; - return fmt::format("Cant get block {:s} due to block hash parse error!", blk_hash); + cerr << "Cant get tx: " << tx_hash << endl; + continue; } - uint64_t blk_height; - - if (core_storage->have_block(blk_hash)) - { - blk_height = core_storage->get_db().get_block_height(blk_hash); - } - else - { - cerr << "Cant get block: " << blk_hash << endl; - return fmt::format("Cant get block {:s}", blk_hash); - } + tx_details txd = get_tx_details(tx, false, + _blk_height, + current_blockchain_height); - return show_block(blk_height); - } + // add fee to the rest + sum_fees += txd.fee; - string - show_tx(string tx_hash_str, uint16_t with_ring_signatures = 0) - { - // parse tx hash string to hash object - crypto::hash tx_hash; + // get mixins in time scale for visual representation + //string mixin_times_scale = xmreg::timestamps_time_scale(mixin_timestamps, + // server_timestamp); - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) - { - cerr << "Cant parse tx hash: " << tx_hash_str << endl; - return string("Cant get tx hash due to parse error: " + tx_hash_str); - } - // tx age - pair age; + // add tx details mstch map to context + txs.push_back(txd.get_mstch_map()); + } - string blk_timestamp {"N/A"}; - // get transaction - transaction tx; + // add total fees in the block to the context + context["sum_fees"] + = xmreg::xmr_amount_to_str(sum_fees, "{:0.6f}", false); - bool show_more_details_link {true}; + // get xmr in the block reward + context["blk_reward"] + = xmreg::xmr_amount_to_str(txd_coinbase.xmr_outputs - sum_fees, "{:0.6f}"); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + add_css_style(context); - vector found_txs; + // render the page + return mstch::render(template_file["block"], context); +} - search_mempool(tx_hash, found_txs); - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).tx; +string +show_block(string _blk_hash) +{ + crypto::hash blk_hash; - // since its tx in mempool, it has no blk yet - // so use its recive_time as timestamp to show + if (!xmreg::parse_str_secret_key(_blk_hash, blk_hash)) + { + cerr << "Cant parse blk hash: " << blk_hash << endl; + return fmt::format("Cant get block {:s} due to block hash parse error!", blk_hash); + } - uint64_t tx_recieve_timestamp - = found_txs.at(0).receive_time; + uint64_t blk_height; - blk_timestamp = xmreg::timestamp_to_str_gm(tx_recieve_timestamp); + if (core_storage->have_block(blk_hash)) + { + blk_height = core_storage->get_db().get_block_height(blk_hash); + } + else + { + cerr << "Cant get block: " << blk_hash << endl; + return fmt::format("Cant get block {:s}", blk_hash); + } - age = get_age(server_timestamp, tx_recieve_timestamp, - FULL_AGE_FORMAT); + return show_block(blk_height); +} - // for mempool tx, we dont show more details, e.g., json tx representation - // so no need for the link - // show_more_details_link = false; - } - else - { - // tx is nowhere to be found :-( - return string("Cant get tx: " + tx_hash_str); - } - } +string +show_tx(string tx_hash_str, uint16_t with_ring_signatures = 0) +{ - mstch::map tx_context; + // parse tx hash string to hash object + crypto::hash tx_hash; - if (enable_tx_cache && tx_context_cache.Contains({tx_hash, static_cast(with_ring_signatures)})) - { - // with_ring_signatures == 0 means that cache is not used - // when obtaining detailed information about tx is requested. + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + cerr << "Cant parse tx hash: " << tx_hash_str << endl; + return string("Cant get tx hash due to parse error: " + tx_hash_str); + } - // we are going to measure time for the construction of the - // tx context from cashe. just for fun, to see if cache is any faster. - auto start = std::chrono::steady_clock::now(); + // tx age + pair age; - const tx_info_cache& tx_info_cashed - = tx_context_cache.Get({tx_hash, static_cast(with_ring_signatures)}); + string blk_timestamp {"N/A"}; - tx_context = tx_info_cashed.tx_map; + // get transaction + transaction tx; - //cout << "get tx from cash: " << tx_hash_str <(tx_context["tx_blk_height"]) <(tx_context["blk_timestamp_uint"]) <get_tx(tx_hash, tx)) + { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; - uint64_t tx_blk_height = boost::get(tx_context["tx_blk_height"]); - uint64_t blk_timestamp_uint = boost::get(tx_context["blk_timestamp_uint"]); + vector found_txs; - if (tx_blk_height > 0) - { - // seems to be in blockchain. off course it could have been orphaned - // so double check if its for sure in blockchain + search_mempool(tx_hash, found_txs); - if (core_storage->have_tx(tx_hash)) - { - // ok, it is still in blockchain - // update its age and number of confirmations + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).tx; - pair age - = get_age(std::time(nullptr), - blk_timestamp_uint, - FULL_AGE_FORMAT); + // since its tx in mempool, it has no blk yet + // so use its recive_time as timestamp to show - tx_context["delta_time"] = age.first; + uint64_t tx_recieve_timestamp + = found_txs.at(0).receive_time; - uint64_t bc_height = core_storage->get_current_blockchain_height(); + blk_timestamp = xmreg::timestamp_to_str_gm(tx_recieve_timestamp); - tx_context["confirmations"] = bc_height - (tx_blk_height - 1); + age = get_age(server_timestamp, tx_recieve_timestamp, + FULL_AGE_FORMAT); - // marke it as from cashe. useful if we want to show - // info about cashed/not cashed in frontend. - tx_context["from_cache"] = true; + // for mempool tx, we dont show more details, e.g., json tx representation + // so no need for the link + // show_more_details_link = false; + } + else + { + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); + } + } - auto duration = std::chrono::duration_cast - (std::chrono::steady_clock::now() - start); + mstch::map tx_context; - tx_context["construction_time"] = fmt::format( - "{:0.4f}", static_cast(duration.count())/1.0e6); + if (enable_tx_cache && tx_context_cache.Contains({tx_hash, static_cast(with_ring_signatures)})) + { + // with_ring_signatures == 0 means that cache is not used + // when obtaining detailed information about tx is requested. - // normally we should update this into in the cache. - // but since we make this check all the time, - // we can skip updating cashed version + // we are going to measure time for the construction of the + // tx context from cashe. just for fun, to see if cache is any faster. + auto start = std::chrono::steady_clock::now(); - } // if (core_storage->have_tx(tx_hash)) - else - { - // its not in blockchain, but it was there when we cashed it. - // so we update it in cash, as it should be back in mempool + const tx_info_cache& tx_info_cashed + = tx_context_cache.Get({tx_hash, static_cast(with_ring_signatures)}); - tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); + tx_context = tx_info_cashed.tx_map; - tx_context_cache.Put( - {tx_hash, static_cast(with_ring_signatures)}, - tx_info_cache { - boost::get(tx_context["tx_blk_height"]), - boost::get(tx_context["blk_timestamp_uint"]), - tx_context} - ); - } - } // if (tx_blk_height > 0) - else + //cout << "get tx from cash: " << tx_hash_str <(tx_context["tx_blk_height"]) <(tx_context["blk_timestamp_uint"]) <(tx_context["tx_blk_height"]); + uint64_t blk_timestamp_uint = boost::get(tx_context["blk_timestamp_uint"]); + + if (tx_blk_height > 0) + { + // seems to be in blockchain. off course it could have been orphaned + // so double check if its for sure in blockchain + + if (core_storage->have_tx(tx_hash)) { - // the tx was cashed when in mempool. - // since then, it might have been included in some block. - // so we check it. + // ok, it is still in blockchain + // update its age and number of confirmations - if (core_storage->have_tx(tx_hash)) - { - // checking if in blockchain already - // it was before in mempool, but now maybe already in blockchain + pair age + = get_age(std::time(nullptr), + blk_timestamp_uint, + FULL_AGE_FORMAT); - tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); + tx_context["delta_time"] = age.first; - tx_context_cache.Put( - {tx_hash, static_cast(with_ring_signatures)}, - tx_info_cache { - boost::get(tx_context["tx_blk_height"]), - boost::get(tx_context["blk_timestamp_uint"]), - tx_context}); + uint64_t bc_height = core_storage->get_current_blockchain_height(); + tx_context["confirmations"] = bc_height - (tx_blk_height - 1); - } // if (core_storage->have_tx(tx_hash)) - else - { - // still seems to be in mempool only. - // so just get its time duration, as its read only - // from cache + // marke it as from cashe. useful if we want to show + // info about cashed/not cashed in frontend. + tx_context["from_cache"] = true; - tx_context["from_cache"] = true; + auto duration = std::chrono::duration_cast + (std::chrono::steady_clock::now() - start); - auto duration = std::chrono::duration_cast - (std::chrono::steady_clock::now() - start); + tx_context["construction_time"] = fmt::format( + "{:0.4f}", static_cast(duration.count())/1.0e6); - tx_context["construction_time"] = fmt::format( - "{:0.4f}", static_cast(duration.count())/1.0e6); + // normally we should update this into in the cache. + // but since we make this check all the time, + // we can skip updating cashed version - } + } // if (core_storage->have_tx(tx_hash)) + else + { + // its not in blockchain, but it was there when we cashed it. + // so we update it in cash, as it should be back in mempool - } // else if (tx_blk_height > 0) + tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); - } // if (tx_context_cache.Contains(tx_hash)) + tx_context_cache.Put( + {tx_hash, static_cast(with_ring_signatures)}, + tx_info_cache { + boost::get(tx_context["tx_blk_height"]), + boost::get(tx_context["blk_timestamp_uint"]), + tx_context} + ); + } + } // if (tx_blk_height > 0) else { + // the tx was cashed when in mempool. + // since then, it might have been included in some block. + // so we check it. - // we are going to measure time for the construction of the - // tx context. just for fun, to see if cache is any faster. - auto start = std::chrono::steady_clock::now(); - - tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); + if (core_storage->have_tx(tx_hash)) + { + // checking if in blockchain already + // it was before in mempool, but now maybe already in blockchain - auto duration = std::chrono::duration_cast - (std::chrono::steady_clock::now() - start); + tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); - if (enable_tx_cache) - { tx_context_cache.Put( {tx_hash, static_cast(with_ring_signatures)}, tx_info_cache { boost::get(tx_context["tx_blk_height"]), boost::get(tx_context["blk_timestamp_uint"]), tx_context}); + + + } // if (core_storage->have_tx(tx_hash)) + else + { + // still seems to be in mempool only. + // so just get its time duration, as its read only + // from cache + + tx_context["from_cache"] = true; + + auto duration = std::chrono::duration_cast + (std::chrono::steady_clock::now() - start); + + tx_context["construction_time"] = fmt::format( + "{:0.4f}", static_cast(duration.count())/1.0e6); + } - tx_context["construction_time"] = fmt::format( - "{:0.4f}", static_cast(duration.count())/1.0e6); + } // else if (tx_blk_height > 0) + + } // if (tx_context_cache.Contains(tx_hash)) + else + { - } // else if (tx_context_cache.Contains(tx_hash)) + // we are going to measure time for the construction of the + // tx context. just for fun, to see if cache is any faster. + auto start = std::chrono::steady_clock::now(); + tx_context = construct_tx_context(tx, static_cast(with_ring_signatures)); - tx_context["show_more_details_link"] = show_more_details_link; + auto duration = std::chrono::duration_cast + (std::chrono::steady_clock::now() - start); - if (boost::get(tx_context["has_error"])) + if (enable_tx_cache) { - return boost::get(tx_context["error_msg"]); + tx_context_cache.Put( + {tx_hash, static_cast(with_ring_signatures)}, + tx_info_cache { + boost::get(tx_context["tx_blk_height"]), + boost::get(tx_context["blk_timestamp_uint"]), + tx_context}); } - mstch::map context { - {"testnet" , this->testnet}, - {"stagenet" , this->stagenet}, - {"show_cache_times" , show_cache_times}, - {"txs" , mstch::array{}} - }; - - boost::get(context["txs"]).push_back(tx_context); + tx_context["construction_time"] = fmt::format( + "{:0.4f}", static_cast(duration.count())/1.0e6); - map partials { - {"tx_details", template_file["tx_details"]}, - }; + } // else if (tx_context_cache.Contains(tx_hash)) - add_css_style(context); - if (enable_js) - add_js_files(context); + tx_context["show_more_details_link"] = show_more_details_link; - // render the page - return mstch::render(template_file["tx"], context, partials); + if (boost::get(tx_context["has_error"])) + { + return boost::get(tx_context["error_msg"]); } - string - show_tx_hex(string tx_hash_str) - { - crypto::hash tx_hash; + mstch::map context { + {"testnet" , this->testnet}, + {"stagenet" , this->stagenet}, + {"show_cache_times" , show_cache_times}, + {"txs" , mstch::array{}} + }; - if (!epee::string_tools::hex_to_pod(tx_hash_str, tx_hash)) - { - string msg = fmt::format("Cant parse {:s} as tx hash!", tx_hash_str); - cerr << msg << endl; - return msg; - } + boost::get(context["txs"]).push_back(tx_context); - // get transaction - transaction tx; + map partials { + {"tx_details", template_file["tx_details"]}, + }; - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + add_css_style(context); - vector found_txs; + if (enable_js) + add_js_files(context); - search_mempool(tx_hash, found_txs); + // render the page + return mstch::render(template_file["tx"], context, partials); +} - if (found_txs.empty()) - { - // tx is nowhere to be found :-( - return string("Cant get tx: " + tx_hash_str); - } +string +show_tx_hex(string tx_hash_str) +{ + crypto::hash tx_hash; - tx = found_txs.at(0).tx; - } + if (!epee::string_tools::hex_to_pod(tx_hash_str, tx_hash)) + { + string msg = fmt::format("Cant parse {:s} as tx hash!", tx_hash_str); + cerr << msg << endl; + return msg; + } - try + // get transaction + transaction tx; + + if (!mcore->get_tx(tx_hash, tx)) + { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; + + vector found_txs; + + search_mempool(tx_hash, found_txs); + + if (found_txs.empty()) { - return tx_to_hex(tx); + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); } - catch (std::exception const& e) - { - cerr << e.what() << endl; - return string {"Failed to obtain hex of tx due to: "} + e.what(); - } + + tx = found_txs.at(0).tx; } - string - show_block_hex(size_t block_height, bool complete_blk) + try { + return tx_to_hex(tx); + } + catch (std::exception const& e) + { + cerr << e.what() << endl; + return string {"Failed to obtain hex of tx due to: "} + e.what(); + } +} - // get transaction - block blk; +string +show_block_hex(size_t block_height, bool complete_blk) +{ - if (!mcore->get_block_by_height(block_height, blk)) - { - cerr << "Cant get block in blockchain: " << block_height - << ". \n Check mempool now\n"; - } + // get transaction + block blk; - try - { - if (complete_blk == false) - { - // get only block data as hex + if (!mcore->get_block_by_height(block_height, blk)) + { + cerr << "Cant get block in blockchain: " << block_height + << ". \n Check mempool now\n"; + } - return epee::string_tools::buff_to_hex_nodelimer( - t_serializable_object_to_blob(blk)); - } - else - { - // get block_complete_entry (block and its txs) as hex + try + { + if (complete_blk == false) + { + // get only block data as hex - block_complete_entry complete_block_data; + return epee::string_tools::buff_to_hex_nodelimer( + t_serializable_object_to_blob(blk)); + } + else + { + // get block_complete_entry (block and its txs) as hex - if (!mcore->get_block_complete_entry(blk, complete_block_data)) - { - cerr << "Failed to obtain complete block data " << endl; - return string {"Failed to obtain complete block data "}; - } + block_complete_entry complete_block_data; - std::string complete_block_data_str; + if (!mcore->get_block_complete_entry(blk, complete_block_data)) + { + cerr << "Failed to obtain complete block data " << endl; + return string {"Failed to obtain complete block data "}; + } - if(!epee::serialization::store_t_to_binary( - complete_block_data, complete_block_data_str)) - { - cerr << "Failed to serialize complete_block_data\n"; - return string {"Failed to obtain complete block data"}; - } + std::string complete_block_data_str; - return epee::string_tools - ::buff_to_hex_nodelimer(complete_block_data_str); + if(!epee::serialization::store_t_to_binary( + complete_block_data, complete_block_data_str)) + { + cerr << "Failed to serialize complete_block_data\n"; + return string {"Failed to obtain complete block data"}; } + + return epee::string_tools + ::buff_to_hex_nodelimer(complete_block_data_str); } - catch (std::exception const& e) - { - cerr << e.what() << endl; - return string {"Failed to obtain hex of a block due to: "} + e.what(); - } } - - string - show_my_outputs(string tx_hash_str, - string xmr_address_str, - string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ - string raw_tx_data, - string domain, - bool tx_prove = false) + catch (std::exception const& e) { + cerr << e.what() << endl; + return string {"Failed to obtain hex of a block due to: "} + e.what(); + } +} - // remove white characters - boost::trim(tx_hash_str); - boost::trim(xmr_address_str); - boost::trim(viewkey_str); - boost::trim(raw_tx_data); +string +show_ringmembers_hex(string const& tx_hash_str) +{ + crypto::hash tx_hash; - if (tx_hash_str.empty()) - { - return string("tx hash not provided!"); - } + if (!epee::string_tools::hex_to_pod(tx_hash_str, tx_hash)) + { + string msg = fmt::format("Cant parse {:s} as tx hash!", tx_hash_str); + cerr << msg << endl; + return msg; + } - if (xmr_address_str.empty()) - { - return string("Monero address not provided!"); - } + // get transaction + transaction tx; - if (viewkey_str.empty()) - { - if (!tx_prove) - return string("Viewkey not provided!"); - else - return string("Tx private key not provided!"); - } + if (!mcore->get_tx(tx_hash, tx)) + { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; - // parse tx hash string to hash object - crypto::hash tx_hash; + vector found_txs; - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + search_mempool(tx_hash, found_txs); + + if (found_txs.empty()) { - cerr << "Cant parse tx hash: " << tx_hash_str << endl; - return string("Cant get tx hash due to parse error: " + tx_hash_str); + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); } - // parse string representing given monero address - cryptonote::address_parse_info address_info; + tx = found_txs.at(0).tx; + } - if (!xmreg::parse_str_address(xmr_address_str, address_info, nettype)) - { - cerr << "Cant parse string address: " << xmr_address_str << endl; - return string("Cant parse xmr address: " + xmr_address_str); - } + vector input_key_imgs = xmreg::get_key_images(tx); + + std::vector all_mixin_outputs; - // parse string representing given private key - crypto::secret_key prv_view_key; + // make timescale maps for mixins in input + for (txin_to_key const& in_key: input_key_imgs) + { + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - std::vector multiple_tx_secret_keys; + // get public keys of outputs used in the mixins that + // match to the offests + std::vector mixin_outputs; - if (!xmreg::parse_str_secret_key(viewkey_str, multiple_tx_secret_keys)) + try { - cerr << "Cant parse the private key: " << viewkey_str << endl; - return string("Cant parse private key: " + viewkey_str); + // before proceeding with geting the outputs based on + // the amount and absolute offset + // check how many outputs there are for that amount + // go to next input if a too large offset was found + if (are_absolute_offsets_good(absolute_offsets, in_key) == false) + continue; + + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + mixin_outputs); } - if (multiple_tx_secret_keys.size() == 1) + catch (const OUTPUT_DNE& e) { - prv_view_key = multiple_tx_secret_keys[0]; + cerr << "get_output_keys: " << e.what() << endl; + continue; } - else if (!tx_prove) + + for (auto const& mo: mixin_outputs) { - cerr << "Concatenated secret keys are only for tx proving!" << endl; - return string("Concatenated secret keys are only for tx proving!"); + all_mixin_outputs.emplace_back(pod_to_hex(mo)); } + } // for (txin_to_key const& in_key: input_key_imgs) - // just to see how would having spend keys could worked - // this is from testnet wallet: A2VTvE8bC9APsWFn3mQzgW8Xfcy2SP2CRUArD6ZtthNaWDuuvyhtBcZ8WDuYMRt1HhcnNQvpXVUavEiZ9waTbyBhP6RM8TV - // view key: 041a241325326f9d86519b714a9b7f78b29111551757eeb6334d39c21f8b7400 - // example tx: 430b070e213659a864ec82d674fddb5ccf7073cae231b019ba1ebb4bfdc07a15 -// string spend_key_str("643fedcb8dca1f3b406b84575ecfa94ba01257d56f20d55e8535385503dacc08"); -// -// crypto::secret_key prv_spend_key; -// if (!xmreg::parse_str_secret_key(spend_key_str, prv_spend_key)) -// { -// cerr << "Cant parse the prv_spend_key : " << spend_key_str << endl; -// return string("Cant parse prv_spend_key : " + spend_key_str); -// } + if (all_mixin_outputs.empty()) + return string {"No ring members to serialize"}; - // tx age - pair age; - string blk_timestamp {"N/A"}; + std::ostringstream oss; + boost::archive::portable_binary_oarchive archive(oss); + archive << all_mixin_outputs; - // get transaction - transaction tx; + return epee::string_tools + ::buff_to_hex_nodelimer(oss.str()); - if (!raw_tx_data.empty()) - { - // we want to check outputs of tx submited through tx pusher. - // it is raw tx data, it is not in blockchain nor in mempool. - // so we need to reconstruct tx object from this string +} - cryptonote::blobdata tx_data_blob; - if (!epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_data_blob)) - { - string msg = fmt::format("Cant obtain tx_data_blob from raw_tx_data"); - cerr << msg << endl; +string +show_my_outputs(string tx_hash_str, + string xmr_address_str, + string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ + string raw_tx_data, + string domain, + bool tx_prove = false) +{ - return msg; - } + // remove white characters + boost::trim(tx_hash_str); + boost::trim(xmr_address_str); + boost::trim(viewkey_str); + boost::trim(raw_tx_data); - crypto::hash tx_hash_from_blob; - crypto::hash tx_prefix_hash_from_blob; + if (tx_hash_str.empty()) + { + return string("tx hash not provided!"); + } - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data_blob, - tx, - tx_hash_from_blob, - tx_prefix_hash_from_blob)) - { - string msg = fmt::format("cant parse_and_validate_tx_from_blob"); + if (xmr_address_str.empty()) + { + return string("Monero address not provided!"); + } - cerr << msg << endl; + if (viewkey_str.empty()) + { + if (!tx_prove) + return string("Viewkey not provided!"); + else + return string("Tx private key not provided!"); + } - return msg; - } + // parse tx hash string to hash object + crypto::hash tx_hash; - } - else if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + cerr << "Cant parse tx hash: " << tx_hash_str << endl; + return string("Cant get tx hash due to parse error: " + tx_hash_str); + } - vector found_txs; + // parse string representing given monero address + cryptonote::address_parse_info address_info; - search_mempool(tx_hash, found_txs); + if (!xmreg::parse_str_address(xmr_address_str, address_info, nettype)) + { + cerr << "Cant parse string address: " << xmr_address_str << endl; + return string("Cant parse xmr address: " + xmr_address_str); + } - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).tx; + // parse string representing given private key + crypto::secret_key prv_view_key; - // since its tx in mempool, it has no blk yet - // so use its recive_time as timestamp to show + std::vector multiple_tx_secret_keys; - uint64_t tx_recieve_timestamp - = found_txs.at(0).receive_time; + if (!xmreg::parse_str_secret_key(viewkey_str, multiple_tx_secret_keys)) + { + cerr << "Cant parse the private key: " << viewkey_str << endl; + return string("Cant parse private key: " + viewkey_str); + } + if (multiple_tx_secret_keys.size() == 1) + { + prv_view_key = multiple_tx_secret_keys[0]; + } + else if (!tx_prove) + { + cerr << "Concatenated secret keys are only for tx proving!" << endl; + return string("Concatenated secret keys are only for tx proving!"); + } - blk_timestamp = xmreg::timestamp_to_str_gm(tx_recieve_timestamp); - age = get_age(server_timestamp, - tx_recieve_timestamp, - FULL_AGE_FORMAT); - } - else - { - // tx is nowhere to be found :-( - return string("Cant get tx: " + tx_hash_str); - } - } + // just to see how would having spend keys could worked + // this is from testnet wallet: A2VTvE8bC9APsWFn3mQzgW8Xfcy2SP2CRUArD6ZtthNaWDuuvyhtBcZ8WDuYMRt1HhcnNQvpXVUavEiZ9waTbyBhP6RM8TV + // view key: 041a241325326f9d86519b714a9b7f78b29111551757eeb6334d39c21f8b7400 + // example tx: 430b070e213659a864ec82d674fddb5ccf7073cae231b019ba1ebb4bfdc07a15 +// string spend_key_str("643fedcb8dca1f3b406b84575ecfa94ba01257d56f20d55e8535385503dacc08"); +// +// crypto::secret_key prv_spend_key; +// if (!xmreg::parse_str_secret_key(spend_key_str, prv_spend_key)) +// { +// cerr << "Cant parse the prv_spend_key : " << spend_key_str << endl; +// return string("Cant parse prv_spend_key : " + spend_key_str); +// } - tx_details txd = get_tx_details(tx); + // tx age + pair age; - uint64_t tx_blk_height {0}; + string blk_timestamp {"N/A"}; - bool tx_blk_found {false}; + // get transaction + transaction tx; - try - { - tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); - tx_blk_found = true; - } - catch (exception& e) - { - cerr << "Cant get block height: " << tx_hash - << e.what() << endl; - } + if (!raw_tx_data.empty()) + { + // we want to check outputs of tx submited through tx pusher. + // it is raw tx data, it is not in blockchain nor in mempool. + // so we need to reconstruct tx object from this string - // get block cointaining this tx - block blk; + cryptonote::blobdata tx_data_blob; - if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + if (!epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_data_blob)) { - cerr << "Cant get block: " << tx_blk_height << endl; + string msg = fmt::format("Cant obtain tx_data_blob from raw_tx_data"); + + cerr << msg << endl; + + return msg; } - string tx_blk_height_str {"N/A"}; + crypto::hash tx_hash_from_blob; + crypto::hash tx_prefix_hash_from_blob; - if (tx_blk_found) + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data_blob, + tx, + tx_hash_from_blob, + tx_prefix_hash_from_blob)) { - // calculate difference between tx and server timestamps - age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + string msg = fmt::format("cant parse_and_validate_tx_from_blob"); - blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); + cerr << msg << endl; - tx_blk_height_str = std::to_string(tx_blk_height); + return msg; } - // payments id. both normal and encrypted (payment_id8) - string pid_str = pod_to_hex(txd.payment_id); - string pid8_str = pod_to_hex(txd.payment_id8); - - string shortcut_url = domain - + (tx_prove ? "/prove" : "/myoutputs") - + "/" + tx_hash_str - + "/" + xmr_address_str - + "/" + viewkey_str; - - - string viewkey_str_partial = viewkey_str; - - // dont show full private keys. Only file first and last letters - for (size_t i = 3; i < viewkey_str_partial.length() - 2; ++i) - viewkey_str_partial[i] = '*'; - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"tx_hash" , tx_hash_str}, - {"tx_prefix_hash" , pod_to_hex(txd.prefix_hash)}, - {"xmr_address" , xmr_address_str}, - {"viewkey" , viewkey_str_partial}, - {"tx_pub_key" , pod_to_hex(txd.pk)}, - {"blk_height" , tx_blk_height_str}, - {"tx_size" , fmt::format("{:0.4f}", - static_cast(txd.size) / 1024.0)}, - {"tx_fee" , xmreg::xmr_amount_to_str(txd.fee, "{:0.12f}", true)}, - {"blk_timestamp" , blk_timestamp}, - {"delta_time" , age.first}, - {"outputs_no" , static_cast(txd.output_pub_keys.size())}, - {"has_payment_id" , txd.payment_id != null_hash}, - {"has_payment_id8" , txd.payment_id8 != null_hash8}, - {"payment_id" , pid_str}, - {"payment_id8" , pid8_str}, - {"decrypted_payment_id8", string{}}, - {"tx_prove" , tx_prove}, - {"shortcut_url" , shortcut_url} - }; + } + else if (!mcore->get_tx(tx_hash, tx)) + { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; - string server_time_str = xmreg::timestamp_to_str_gm(server_timestamp, "%F"); + vector found_txs; + search_mempool(tx_hash, found_txs); + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).tx; - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; - std::vector additional_derivations(txd.additional_pks.size()); + // 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).receive_time; - //cout << multiple_tx_secret_keys.size() << " " << txd.additional_pks.size() + 1 << '\n'; + blk_timestamp = xmreg::timestamp_to_str_gm(tx_recieve_timestamp); - if (tx_prove && multiple_tx_secret_keys.size() != txd.additional_pks.size() + 1) + age = get_age(server_timestamp, + tx_recieve_timestamp, + FULL_AGE_FORMAT); + } + else { - return string("This transaction includes additional tx pubkeys whose " - "size doesn't match with the provided tx secret keys"); + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); } + } - public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk; + tx_details txd = get_tx_details(tx); - //cout << "txd.pk: " << pod_to_hex(txd.pk) << endl; + uint64_t tx_blk_height {0}; - if (!generate_key_derivation(pub_key, - tx_prove ? multiple_tx_secret_keys[0] : prv_view_key, - derivation)) - { - cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << pub_key << " and " - << "prv_view_key" << prv_view_key << endl; + bool tx_blk_found {false}; - return string("Cant get key_derivation"); - } + try + { + tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); + tx_blk_found = true; + } + catch (exception& e) + { + cerr << "Cant get block height: " << tx_hash + << e.what() << endl; + } - for (size_t i = 0; i < txd.additional_pks.size(); ++i) + // get block cointaining this tx + block blk; + + if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + { + 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 + age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + + blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); + + tx_blk_height_str = std::to_string(tx_blk_height); + } + + // payments id. both normal and encrypted (payment_id8) + string pid_str = pod_to_hex(txd.payment_id); + string pid8_str = pod_to_hex(txd.payment_id8); + + string shortcut_url = domain + + (tx_prove ? "/prove" : "/myoutputs") + + "/" + tx_hash_str + + "/" + xmr_address_str + + "/" + viewkey_str; + + + string viewkey_str_partial = viewkey_str; + + // dont show full private keys. Only file first and last letters + for (size_t i = 3; i < viewkey_str_partial.length() - 2; ++i) + viewkey_str_partial[i] = '*'; + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"tx_hash" , tx_hash_str}, + {"tx_prefix_hash" , pod_to_hex(txd.prefix_hash)}, + {"xmr_address" , xmr_address_str}, + {"viewkey" , viewkey_str_partial}, + {"tx_pub_key" , pod_to_hex(txd.pk)}, + {"blk_height" , tx_blk_height_str}, + {"tx_size" , fmt::format("{:0.4f}", + static_cast(txd.size) / 1024.0)}, + {"tx_fee" , xmreg::xmr_amount_to_str(txd.fee, "{:0.12f}", true)}, + {"blk_timestamp" , blk_timestamp}, + {"delta_time" , age.first}, + {"outputs_no" , static_cast(txd.output_pub_keys.size())}, + {"has_payment_id" , txd.payment_id != null_hash}, + {"has_payment_id8" , txd.payment_id8 != null_hash8}, + {"payment_id" , pid_str}, + {"payment_id8" , pid8_str}, + {"decrypted_payment_id8", string{}}, + {"tx_prove" , tx_prove}, + {"shortcut_url" , shortcut_url} + }; + + string server_time_str = xmreg::timestamp_to_str_gm(server_timestamp, "%F"); + + + + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; + std::vector additional_derivations(txd.additional_pks.size()); + + //cout << multiple_tx_secret_keys.size() << " " << txd.additional_pks.size() + 1 << '\n'; + + if (tx_prove && multiple_tx_secret_keys.size() != txd.additional_pks.size() + 1) + { + return string("This transaction includes additional tx pubkeys whose " + "size doesn't match with the provided tx secret keys"); + } + + public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk; + + //cout << "txd.pk: " << pod_to_hex(txd.pk) << endl; + + if (!generate_key_derivation(pub_key, + tx_prove ? multiple_tx_secret_keys[0] : prv_view_key, + derivation)) + { + cerr << "Cant get derived key for: " << "\n" + << "pub_tx_key: " << pub_key << " and " + << "prv_view_key" << prv_view_key << endl; + + return string("Cant get key_derivation"); + } + + for (size_t i = 0; i < txd.additional_pks.size(); ++i) + { + if (!generate_key_derivation(tx_prove ? pub_key : txd.additional_pks[i], + tx_prove ? multiple_tx_secret_keys[i + 1] : prv_view_key, + additional_derivations[i])) { - if (!generate_key_derivation(tx_prove ? pub_key : txd.additional_pks[i], - tx_prove ? multiple_tx_secret_keys[i + 1] : prv_view_key, - additional_derivations[i])) - { - cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << txd.additional_pks[i] << " and " - << "prv_view_key" << prv_view_key << endl; + cerr << "Cant get derived key for: " << "\n" + << "pub_tx_key: " << txd.additional_pks[i] << " and " + << "prv_view_key" << prv_view_key << endl; - return string("Cant get key_derivation"); - } + return string("Cant get key_derivation"); } + } - // decrypt encrypted payment id, as used in integreated addresses - crypto::hash8 decrypted_payment_id8 = txd.payment_id8; + // decrypt encrypted payment id, as used in integreated addresses + crypto::hash8 decrypted_payment_id8 = txd.payment_id8; - if (decrypted_payment_id8 != null_hash8) + if (decrypted_payment_id8 != null_hash8) + { + if (mcore->get_device()->decrypt_payment_id(decrypted_payment_id8, pub_key, prv_view_key)) { - if (mcore->get_device()->decrypt_payment_id(decrypted_payment_id8, pub_key, prv_view_key)) - { - context["decrypted_payment_id8"] = pod_to_hex(decrypted_payment_id8); - } + context["decrypted_payment_id8"] = pod_to_hex(decrypted_payment_id8); } + } - mstch::array outputs; + mstch::array outputs; - uint64_t sum_xmr {0}; + uint64_t sum_xmr {0}; - std::vector money_transfered(tx.vout.size(), 0); + std::vector money_transfered(tx.vout.size(), 0); - //std::deque mask(tx.vout.size()); + //std::deque mask(tx.vout.size()); - uint64_t output_idx {0}; + uint64_t output_idx {0}; - for (pair& outp: txd.output_pub_keys) - { + for (pair& outp: txd.output_pub_keys) + { - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey; + // get the tx output public key + // that normally would be generated for us, + // if someone had sent us some xmr. + public_key tx_pubkey; - derive_public_key(derivation, + derive_public_key(derivation, + output_idx, + address_info.address.m_spend_public_key, + tx_pubkey); + + //cout << pod_to_hex(outp.first.key) << endl; + //cout << pod_to_hex(tx_pubkey) << endl; + + // check if generated public key matches the current output's key + bool mine_output = (outp.first.key == tx_pubkey); + + bool with_additional = false; + + if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) + { + derive_public_key(additional_derivations[output_idx], output_idx, address_info.address.m_spend_public_key, tx_pubkey); - //cout << pod_to_hex(outp.first.key) << endl; - //cout << pod_to_hex(tx_pubkey) << endl; - - // check if generated public key matches the current output's key - bool mine_output = (outp.first.key == tx_pubkey); + mine_output = (outp.first.key == tx_pubkey); - bool with_additional = false; + with_additional = true; + } - if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) + // if mine output has RingCT, i.e., tx version is 2 + if (mine_output && tx.version == 2) + { + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) { - derive_public_key(additional_derivations[output_idx], - output_idx, - address_info.address.m_spend_public_key, - tx_pubkey); - mine_output = (outp.first.key == tx_pubkey); + // initialize with regular amount + uint64_t rct_amount = money_transfered[output_idx]; - with_additional = true; - } + bool r; - // if mine output has RingCT, i.e., tx version is 2 - if (mine_output && tx.version == 2) - { - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) + r = decode_ringct(tx.rct_signatures, + with_additional ? additional_derivations[output_idx] : derivation, + output_idx, + tx.rct_signatures.ecdhInfo[output_idx].mask, + rct_amount); + + if (!r) { + cerr << "\nshow_my_outputs: Cant decode ringCT! " << endl; + } - // initialize with regular amount - uint64_t rct_amount = money_transfered[output_idx]; + outp.second = rct_amount; + money_transfered[output_idx] = rct_amount; + } - bool r; + } - r = decode_ringct(tx.rct_signatures, - with_additional ? additional_derivations[output_idx] : derivation, - output_idx, - tx.rct_signatures.ecdhInfo[output_idx].mask, - rct_amount); + if (mine_output) + { + sum_xmr += outp.second; + } - if (!r) - { - cerr << "\nshow_my_outputs: Cant decode ringCT! " << endl; - } + outputs.push_back(mstch::map { + {"out_pub_key" , pod_to_hex(outp.first.key)}, + {"amount" , xmreg::xmr_amount_to_str(outp.second)}, + {"mine_output" , mine_output}, + {"output_idx" , fmt::format("{:02d}", output_idx)} + }); - outp.second = rct_amount; - money_transfered[output_idx] = rct_amount; - } + ++output_idx; + } - } + // we can also test ouputs used in mixins for key images + // this can show possible spending. Only possible, because + // without a spend key, we cant know for sure. It might be + // that our output was used by someone else for their mixins. - if (mine_output) - { - sum_xmr += outp.second; - } + bool show_key_images {false}; - outputs.push_back(mstch::map { - {"out_pub_key" , pod_to_hex(outp.first.key)}, - {"amount" , xmreg::xmr_amount_to_str(outp.second)}, - {"mine_output" , mine_output}, - {"output_idx" , fmt::format("{:02d}", output_idx)} - }); - ++output_idx; - } + mstch::array inputs; - // we can also test ouputs used in mixins for key images - // this can show possible spending. Only possible, because - // without a spend key, we cant know for sure. It might be - // that our output was used by someone else for their mixins. + vector input_key_imgs = xmreg::get_key_images(tx); - bool show_key_images {false}; + // to hold sum of xmr in matched mixins, those that + // perfectly match mixin public key with outputs in mixn_tx. + uint64_t sum_mixin_xmr {0}; + // this is used for the final check. we assument that number of + // parefct matches must be equal to number of inputs in a tx. + uint64_t no_of_matched_mixins {0}; - mstch::array inputs; + for (const txin_to_key& in_key: input_key_imgs) + { - vector input_key_imgs = xmreg::get_key_images(tx); + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - // to hold sum of xmr in matched mixins, those that - // perfectly match mixin public key with outputs in mixn_tx. - uint64_t sum_mixin_xmr {0}; + // get public keys of outputs used in the mixins that match to the offests + std::vector mixin_outputs; - // this is used for the final check. we assument that number of - // parefct matches must be equal to number of inputs in a tx. - uint64_t no_of_matched_mixins {0}; - for (const txin_to_key& in_key: input_key_imgs) + try { + // before proceeding with geting the outputs based on + // the amount and absolute offset + // check how many outputs there are for that amount + // go to next input if a too large offset was found + if (are_absolute_offsets_good(absolute_offsets, in_key) == false) + continue; - // get absolute offsets of mixins - std::vector absolute_offsets - = cryptonote::relative_output_offsets_to_absolute( - in_key.key_offsets); + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + mixin_outputs); + } + catch (const OUTPUT_DNE& e) + { + cerr << "get_output_keys: " << e.what() << endl; + continue; + } - // get public keys of outputs used in the mixins that match to the offests - std::vector mixin_outputs; + inputs.push_back(mstch::map{ + {"key_image" , pod_to_hex(in_key.k_image)}, + {"key_image_amount", xmreg::xmr_amount_to_str(in_key.amount)}, + make_pair(string("mixins"), mstch::array{}) + }); + mstch::array& mixins = boost::get( + boost::get(inputs.back())["mixins"] + ); - try - { - // before proceeding with geting the outputs based on the amount and absolute offset - // check how many outputs there are for that amount - // go to next input if a too large offset was found - if (are_absolute_offsets_good(absolute_offsets, in_key) == false) - continue; + // to store our mixins found for the given key image + vector> our_mixins_found; - core_storage->get_db().get_output_key(in_key.amount, - absolute_offsets, - mixin_outputs); - } - catch (const OUTPUT_DNE& e) - { - cerr << "get_output_keys: " << e.what() << endl; - continue; - } + // mixin counter + size_t count = 0; - inputs.push_back(mstch::map{ - {"key_image" , pod_to_hex(in_key.k_image)}, - {"key_image_amount", xmreg::xmr_amount_to_str(in_key.amount)}, - make_pair(string("mixins"), mstch::array{}) - }); + // there can be more than one our output used for mixin in a single + // input. For example, if two outputs are matched (marked by *) in html, + // one of them will be our real spending, and second will be used as a fake + // one. ideally, to determine which is which, spendkey is required. + // obvisouly we dont have it here, so we need to pick one in other way. + // for now I will just pick the first one we find, and threat it as the + // real spending output. The no_of_output_matches_found variable + // is used for this purporse. + // testnet tx 430b070e213659a864ec82d674fddb5ccf7073cae231b019ba1ebb4bfdc07a15 + // and testnet wallet details provided earier for spend key, + // demonstrate this. this txs has one input that uses two of our ouputs. + // without spent key, its imposible to know which one is real spendking + // and which one is fake. + size_t no_of_output_matches_found {0}; - mstch::array& mixins = boost::get( - boost::get(inputs.back())["mixins"] - ); + // for each found output public key check if its ours or not + for (const uint64_t& abs_offset: absolute_offsets) + { - // to store our mixins found for the given key image - vector> our_mixins_found; - - // mixin counter - size_t count = 0; - - // there can be more than one our output used for mixin in a single - // input. For example, if two outputs are matched (marked by *) in html, - // one of them will be our real spending, and second will be used as a fake - // one. ideally, to determine which is which, spendkey is required. - // obvisouly we dont have it here, so we need to pick one in other way. - // for now I will just pick the first one we find, and threat it as the - // real spending output. The no_of_output_matches_found variable - // is used for this purporse. - // testnet tx 430b070e213659a864ec82d674fddb5ccf7073cae231b019ba1ebb4bfdc07a15 - // and testnet wallet details provided earier for spend key, - // demonstrate this. this txs has one input that uses two of our ouputs. - // without spent key, its imposible to know which one is real spendking - // and which one is fake. - size_t no_of_output_matches_found {0}; - - // for each found output public key check if its ours or not - for (const uint64_t& abs_offset: absolute_offsets) - { + // get basic information about mixn's output + cryptonote::output_data_t output_data = mixin_outputs.at(count); - // get basic information about mixn's output - cryptonote::output_data_t output_data = mixin_outputs.at(count); + tx_out_index tx_out_idx; - tx_out_index tx_out_idx; + try + { + // get pair pair where first is tx hash + // and second is local index of the output i in that tx + tx_out_idx = core_storage->get_db() + .get_output_tx_and_index(in_key.amount, abs_offset); + } + catch (const OUTPUT_DNE& e) + { - try - { - // get pair pair where first is tx hash - // and second is local index of the output i in that tx - tx_out_idx = core_storage->get_db() - .get_output_tx_and_index(in_key.amount, abs_offset); - } - catch (const OUTPUT_DNE& e) - { + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + in_key.amount, abs_offset + ); - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - in_key.amount, abs_offset - ); + cerr << out_msg << endl; - cerr << out_msg << endl; + break; + } - break; - } + string out_pub_key_str = pod_to_hex(output_data.pubkey); - string out_pub_key_str = pod_to_hex(output_data.pubkey); + //cout << "out_pub_key_str: " << out_pub_key_str << endl; - //cout << "out_pub_key_str: " << out_pub_key_str << endl; + // get mixin transaction + transaction mixin_tx; - // get mixin transaction - transaction mixin_tx; + if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) + { + cerr << "Cant get tx: " << tx_out_idx.first << endl; - if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) - { - cerr << "Cant get tx: " << tx_out_idx.first << endl; + break; + } - break; - } + string mixin_tx_hash_str = pod_to_hex(tx_out_idx.first); - string mixin_tx_hash_str = pod_to_hex(tx_out_idx.first); + mixins.push_back(mstch::map{ + {"mixin_pub_key" , out_pub_key_str}, + make_pair("mixin_outputs" , mstch::array{}), + {"has_mixin_outputs" , false} + }); - mixins.push_back(mstch::map{ - {"mixin_pub_key" , out_pub_key_str}, - make_pair("mixin_outputs" , mstch::array{}), - {"has_mixin_outputs" , false} - }); + mstch::array& mixin_outputs = boost::get( + boost::get(mixins.back())["mixin_outputs"] + ); - mstch::array& mixin_outputs = boost::get( - boost::get(mixins.back())["mixin_outputs"] - ); + mstch::node& has_mixin_outputs + = boost::get(mixins.back())["has_mixin_outputs"]; - mstch::node& has_mixin_outputs - = boost::get(mixins.back())["has_mixin_outputs"]; + bool found_something {false}; - bool found_something {false}; + public_key mixin_tx_pub_key + = xmreg::get_tx_pub_key_from_received_outs(mixin_tx); + std::vector mixin_additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(mixin_tx); - public_key mixin_tx_pub_key - = xmreg::get_tx_pub_key_from_received_outs(mixin_tx); - std::vector mixin_additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(mixin_tx); + string mixin_tx_pub_key_str = pod_to_hex(mixin_tx_pub_key); - string mixin_tx_pub_key_str = pod_to_hex(mixin_tx_pub_key); + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; + std::vector additional_derivations(mixin_additional_tx_pub_keys.size()); - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; - std::vector additional_derivations(mixin_additional_tx_pub_keys.size()); + if (!generate_key_derivation(mixin_tx_pub_key, prv_view_key, derivation)) + { + cerr << "Cant get derived key for: " << "\n" + << "pub_tx_key: " << mixin_tx_pub_key << " and " + << "prv_view_key" << prv_view_key << endl; - if (!generate_key_derivation(mixin_tx_pub_key, prv_view_key, derivation)) + continue; + } + for (size_t i = 0; i < mixin_additional_tx_pub_keys.size(); ++i) + { + if (!generate_key_derivation(mixin_additional_tx_pub_keys[i], prv_view_key, additional_derivations[i])) { cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << mixin_tx_pub_key << " and " + << "pub_tx_key: " << mixin_additional_tx_pub_keys[i] << " and " << "prv_view_key" << prv_view_key << endl; continue; } - for (size_t i = 0; i < mixin_additional_tx_pub_keys.size(); ++i) - { - if (!generate_key_derivation(mixin_additional_tx_pub_keys[i], prv_view_key, additional_derivations[i])) - { - cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << mixin_additional_tx_pub_keys[i] << " and " - << "prv_view_key" << prv_view_key << endl; - - continue; - } - } + } - // - vector> output_pub_keys; + // + vector> output_pub_keys; - output_pub_keys = xmreg::get_ouputs_tuple(mixin_tx); + output_pub_keys = xmreg::get_ouputs_tuple(mixin_tx); - mixin_outputs.push_back(mstch::map{ - {"mix_tx_hash" , mixin_tx_hash_str}, - {"mix_tx_pub_key" , mixin_tx_pub_key_str}, - make_pair("found_outputs" , mstch::array{}), - {"has_found_outputs", false} - }); + mixin_outputs.push_back(mstch::map{ + {"mix_tx_hash" , mixin_tx_hash_str}, + {"mix_tx_pub_key" , mixin_tx_pub_key_str}, + make_pair("found_outputs" , mstch::array{}), + {"has_found_outputs", false} + }); - mstch::array& found_outputs = boost::get( - boost::get(mixin_outputs.back())["found_outputs"] - ); + mstch::array& found_outputs = boost::get( + boost::get(mixin_outputs.back())["found_outputs"] + ); - mstch::node& has_found_outputs - = boost::get(mixin_outputs.back())["has_found_outputs"]; + mstch::node& has_found_outputs + = boost::get(mixin_outputs.back())["has_found_outputs"]; - // for each output in mixin tx, find the one from key_image - // and check if its ours. - for (const auto& mix_out: output_pub_keys) - { + // for each output in mixin tx, find the one from key_image + // and check if its ours. + for (const auto& mix_out: output_pub_keys) + { - txout_to_key txout_k = std::get<0>(mix_out); - uint64_t amount = std::get<1>(mix_out); - uint64_t output_idx_in_tx = std::get<2>(mix_out); + txout_to_key txout_k = std::get<0>(mix_out); + uint64_t amount = std::get<1>(mix_out); + uint64_t output_idx_in_tx = std::get<2>(mix_out); - //cout << " - " << pod_to_hex(txout_k.key) << endl; + //cout << " - " << pod_to_hex(txout_k.key) << endl; // // analyze only those output keys // // that were used in mixins @@ -2292,117 +2383,117 @@ public: // continue; // } - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey_generated; + // get the tx output public key + // that normally would be generated for us, + // if someone had sent us some xmr. + public_key tx_pubkey_generated; + + derive_public_key(derivation, + output_idx_in_tx, + address_info.address.m_spend_public_key, + tx_pubkey_generated); - derive_public_key(derivation, + // check if generated public key matches the current output's key + bool mine_output = (txout_k.key == tx_pubkey_generated); + bool with_additional = false; + if (!mine_output && mixin_additional_tx_pub_keys.size() == output_pub_keys.size()) + { + derive_public_key(additional_derivations[output_idx_in_tx], output_idx_in_tx, address_info.address.m_spend_public_key, tx_pubkey_generated); + mine_output = (txout_k.key == tx_pubkey_generated); + with_additional = true; + } - // check if generated public key matches the current output's key - bool mine_output = (txout_k.key == tx_pubkey_generated); - bool with_additional = false; - if (!mine_output && mixin_additional_tx_pub_keys.size() == output_pub_keys.size()) + + if (mine_output && mixin_tx.version == 2) + { + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(mixin_tx)) { - derive_public_key(additional_derivations[output_idx_in_tx], - output_idx_in_tx, - address_info.address.m_spend_public_key, - tx_pubkey_generated); - mine_output = (txout_k.key == tx_pubkey_generated); - with_additional = true; - } + // initialize with regular amount + uint64_t rct_amount = amount; + bool r; - if (mine_output && mixin_tx.version == 2) - { - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(mixin_tx)) + r = decode_ringct(mixin_tx.rct_signatures, + with_additional ? additional_derivations[output_idx_in_tx] : derivation, + output_idx_in_tx, + mixin_tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask, + rct_amount); + + if (!r) { - // initialize with regular amount - uint64_t rct_amount = amount; + cerr << "show_my_outputs: key images: Cant decode ringCT!" << endl; + } - bool r; + amount = rct_amount; - r = decode_ringct(mixin_tx.rct_signatures, - with_additional ? additional_derivations[output_idx_in_tx] : derivation, - output_idx_in_tx, - mixin_tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask, - rct_amount); + } // if (mine_output && mixin_tx.version == 2) + } - if (!r) - { - cerr << "show_my_outputs: key images: Cant decode ringCT!" << endl; - } + // makre only + bool output_match = (txout_k.key == output_data.pubkey); + + // mark only first output_match as the "real" one + // due to luck of better method of gussing which output + // is real if two are found in a single input. + output_match = output_match && no_of_output_matches_found == 0; + + // save our mixnin's public keys + found_outputs.push_back(mstch::map { + {"my_public_key" , pod_to_hex(txout_k.key)}, + {"tx_hash" , tx_hash_str}, + {"mine_output" , mine_output}, + {"out_idx" , output_idx_in_tx}, + {"formed_output_pk", out_pub_key_str}, + {"out_in_match" , output_match}, + {"amount" , xmreg::xmr_amount_to_str(amount)} + }); - amount = rct_amount; + //cout << "txout_k.key == output_data.pubkey" << endl; + //cout << pod_to_hex(txout_k.key) << " == " << pod_to_hex(output_data.pubkey) << endl; - } // if (mine_output && mixin_tx.version == 2) - } + if (mine_output) + { - // makre only - bool output_match = (txout_k.key == output_data.pubkey); - - // mark only first output_match as the "real" one - // due to luck of better method of gussing which output - // is real if two are found in a single input. - output_match = output_match && no_of_output_matches_found == 0; - - // save our mixnin's public keys - found_outputs.push_back(mstch::map { - {"my_public_key" , pod_to_hex(txout_k.key)}, - {"tx_hash" , tx_hash_str}, - {"mine_output" , mine_output}, - {"out_idx" , output_idx_in_tx}, - {"formed_output_pk", out_pub_key_str}, - {"out_in_match" , output_match}, - {"amount" , xmreg::xmr_amount_to_str(amount)} - }); - - //cout << "txout_k.key == output_data.pubkey" << endl; - //cout << pod_to_hex(txout_k.key) << " == " << pod_to_hex(output_data.pubkey) << endl; - - if (mine_output) - { + found_something = true; + show_key_images = true; - found_something = true; - show_key_images = true; + // increase sum_mixin_xmr only when + // public key of an outputs used in ring signature, + // matches a public key in a mixin_tx + if (txout_k.key != output_data.pubkey) + { + continue; + } - // increase sum_mixin_xmr only when - // public key of an outputs used in ring signature, - // matches a public key in a mixin_tx - if (txout_k.key != output_data.pubkey) + // sum up only first output matched found in each input + if (no_of_output_matches_found == 0) + { + // for regular txs, just concentrated on outputs + // which have same amount as the key image. + // for ringct its not possible to know for sure amount + // in key image without spend key, so we just use all + // for regular/old txs there must be also a match + // in amounts, not only in output public keys + if (mixin_tx.version < 2 && amount == in_key.amount) { - continue; + sum_mixin_xmr += amount; } - - // sum up only first output matched found in each input - if (no_of_output_matches_found == 0) + else if (mixin_tx.version == 2) // ringct { - // for regular txs, just concentrated on outputs - // which have same amount as the key image. - // for ringct its not possible to know for sure amount - // in key image without spend key, so we just use all - // for regular/old txs there must be also a match - // in amounts, not only in output public keys - if (mixin_tx.version < 2 && amount == in_key.amount) - { - sum_mixin_xmr += amount; - } - else if (mixin_tx.version == 2) // ringct - { - sum_mixin_xmr += amount; - } - - no_of_matched_mixins++; + sum_mixin_xmr += amount; } + no_of_matched_mixins++; + } + - // generate key_image using this output - // just to see how would having spend keys worked + // generate key_image using this output + // just to see how would having spend keys worked // crypto::key_image key_img; // // if (!xmreg::generate_key_image(derivation, @@ -2422,239 +2513,290 @@ public: - no_of_output_matches_found++; + no_of_output_matches_found++; - } + } - } // for (const pair& mix_out: txd.output_pub_keys) + } // for (const pair& mix_out: txd.output_pub_keys) - has_found_outputs = !found_outputs.empty(); + has_found_outputs = !found_outputs.empty(); - has_mixin_outputs = found_something; + has_mixin_outputs = found_something; - ++count; + ++count; - } // for (const cryptonote::output_data_t& output_data: mixin_outputs) + } // for (const cryptonote::output_data_t& output_data: mixin_outputs) - } // for (const txin_to_key& in_key: input_key_imgs) + } // for (const txin_to_key& in_key: input_key_imgs) - context.emplace("outputs", outputs); + context.emplace("outputs", outputs); - context["found_our_outputs"] = (sum_xmr > 0); - context["sum_xmr"] = xmreg::xmr_amount_to_str(sum_xmr); + context["found_our_outputs"] = (sum_xmr > 0); + context["sum_xmr"] = xmreg::xmr_amount_to_str(sum_xmr); - context.emplace("inputs", inputs); + context.emplace("inputs", inputs); - context["show_inputs"] = show_key_images; - context["inputs_no"] = static_cast(inputs.size()); - context["sum_mixin_xmr"] = xmreg::xmr_amount_to_str( - sum_mixin_xmr, "{:0.12f}", false); + context["show_inputs"] = show_key_images; + context["inputs_no"] = static_cast(inputs.size()); + context["sum_mixin_xmr"] = xmreg::xmr_amount_to_str( + sum_mixin_xmr, "{:0.12f}", false); - uint64_t possible_spending {0}; + uint64_t possible_spending {0}; - // show spending only if sum of mixins is more than - // what we get + fee, and number of perferctly matched - // mixis is equal to number of inputs - if (sum_mixin_xmr > (sum_xmr + txd.fee) - && no_of_matched_mixins == inputs.size()) - { - // (outcoming - incoming) - fee - possible_spending = (sum_mixin_xmr - sum_xmr) - txd.fee; - } + // show spending only if sum of mixins is more than + // what we get + fee, and number of perferctly matched + // mixis is equal to number of inputs + if (sum_mixin_xmr > (sum_xmr + txd.fee) + && no_of_matched_mixins == inputs.size()) + { + // (outcoming - incoming) - fee + possible_spending = (sum_mixin_xmr - sum_xmr) - txd.fee; + } - context["possible_spending"] = xmreg::xmr_amount_to_str( - possible_spending, "{:0.12f}", false); + context["possible_spending"] = xmreg::xmr_amount_to_str( + possible_spending, "{:0.12f}", false); - add_css_style(context); + add_css_style(context); - // render the page - return mstch::render(template_file["my_outputs"], context); - } + // render the page + return mstch::render(template_file["my_outputs"], context); +} - string - show_prove(string tx_hash_str, - string xmr_address_str, - string tx_prv_key_str, - string const& raw_tx_data, - string domain) - { +string +show_prove(string tx_hash_str, + string xmr_address_str, + string tx_prv_key_str, + string const& raw_tx_data, + string domain) +{ - return show_my_outputs(tx_hash_str, xmr_address_str, - tx_prv_key_str, raw_tx_data, - domain, true); - } + return show_my_outputs(tx_hash_str, xmr_address_str, + tx_prv_key_str, raw_tx_data, + domain, true); +} - string - show_rawtx() - { +string +show_rawtx() +{ - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet} - }; + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet} + }; - add_css_style(context); + add_css_style(context); - // render the page - return mstch::render(template_file["rawtx"], context); - } + // render the page + return mstch::render(template_file["rawtx"], context); +} - string - show_checkrawtx(string raw_tx_data, string action) - { - clean_post_data(raw_tx_data); +string +show_checkrawtx(string raw_tx_data, string action) +{ + clean_post_data(raw_tx_data); - string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); - //cout << decoded_raw_tx_data << endl; + //cout << decoded_raw_tx_data << endl; - const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); - string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); + string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); - bool unsigned_tx_given {false}; + bool unsigned_tx_given {false}; - if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) - { - unsigned_tx_given = true; - } + if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) + { + unsigned_tx_given = true; + } - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"unsigned_tx_given" , unsigned_tx_given}, - {"have_raw_tx" , true}, - {"has_error" , false}, - {"error_msg" , string {}}, - {"data_prefix" , data_prefix}, - }; + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"unsigned_tx_given" , unsigned_tx_given}, + {"have_raw_tx" , true}, + {"has_error" , false}, + {"error_msg" , string {}}, + {"data_prefix" , data_prefix}, + }; - context.emplace("txs", mstch::array{}); + context.emplace("txs", mstch::array{}); - string full_page = template_file["checkrawtx"]; + string full_page = template_file["checkrawtx"]; - add_css_style(context); + add_css_style(context); - if (enable_js) - add_js_files(context); + if (enable_js) + add_js_files(context); - if (unsigned_tx_given) - { + if (unsigned_tx_given) + { - bool r {false}; + bool r {false}; - string s = decoded_raw_tx_data.substr(magiclen); + string s = decoded_raw_tx_data.substr(magiclen); - ::tools::wallet2::unsigned_tx_set exported_txs; + ::tools::wallet2::unsigned_tx_set exported_txs; - try - { - std::istringstream iss(s); - boost::archive::portable_binary_iarchive ar(iss); - ar >> exported_txs; + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; - r = true; - } - catch (...) - { - cerr << "Failed to parse unsigned tx data " << endl; - } + r = true; + } + catch (...) + { + cerr << "Failed to parse unsigned tx data " << endl; + } + + if (r) + { + mstch::array& txs = boost::get(context["txs"]); - if (r) + for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) { - mstch::array& txs = boost::get(context["txs"]); + size_t no_of_sources = tx_cd.sources.size(); + + const tx_destination_entry& tx_change = tx_cd.change_dts; + + crypto::hash payment_id = null_hash; + crypto::hash8 payment_id8 = null_hash8; + + get_payment_id(tx_cd.extra, payment_id, payment_id8); + + // payments id. both normal and encrypted (payment_id8) + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); - for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + + mstch::map tx_cd_data { + {"no_of_sources" , static_cast(no_of_sources)}, + {"use_rct" , tx_cd.use_rct}, + {"change_amount" , xmreg::xmr_amount_to_str(tx_change.amount)}, + {"has_payment_id" , (payment_id != null_hash)}, + {"has_payment_id8" , (payment_id8 != null_hash8)}, + {"payment_id" , pid_str}, + {"payment_id8" , pid8_str}, + }; + tx_cd_data.emplace("dest_sources" , mstch::array{}); + tx_cd_data.emplace("dest_infos" , mstch::array{}); + + mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); + mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); + + for (const tx_destination_entry& a_dest: tx_cd.splitted_dsts) { - size_t no_of_sources = tx_cd.sources.size(); + mstch::map dest_info { + {"dest_address" , get_account_address_as_str( + nettype, a_dest.is_subaddress, a_dest.addr)}, + {"dest_amount" , xmreg::xmr_amount_to_str(a_dest.amount)} + }; - const tx_destination_entry& tx_change = tx_cd.change_dts; + dest_infos.push_back(dest_info); + } - crypto::hash payment_id = null_hash; - crypto::hash8 payment_id8 = null_hash8; + vector> mixin_timestamp_groups; + vector real_output_indices; - get_payment_id(tx_cd.extra, payment_id, payment_id8); + uint64_t sum_outputs_amounts {0}; - // payments id. both normal and encrypted (payment_id8) - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", payment_id8)); + for (size_t i = 0; i < no_of_sources; ++i) + { + const tx_source_entry& tx_source = tx_cd.sources.at(i); - mstch::map tx_cd_data { - {"no_of_sources" , static_cast(no_of_sources)}, - {"use_rct" , tx_cd.use_rct}, - {"change_amount" , xmreg::xmr_amount_to_str(tx_change.amount)}, - {"has_payment_id" , (payment_id != null_hash)}, - {"has_payment_id8" , (payment_id8 != null_hash8)}, - {"payment_id" , pid_str}, - {"payment_id8" , pid8_str}, + mstch::map single_dest_source { + {"output_amount" , xmreg::xmr_amount_to_str(tx_source.amount)}, + {"real_output" , static_cast(tx_source.real_output)}, + {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, + {"real_output_in_tx_index" , static_cast(tx_source.real_output_in_tx_index)}, }; - tx_cd_data.emplace("dest_sources" , mstch::array{}); - tx_cd_data.emplace("dest_infos" , mstch::array{}); + single_dest_source.emplace("outputs", mstch::array{}); - mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); - mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); + sum_outputs_amounts += tx_source.amount; - for (const tx_destination_entry& a_dest: tx_cd.splitted_dsts) + //cout << "tx_source.real_output: " << tx_source.real_output << endl; + //cout << "tx_source.real_out_tx_key: " << tx_source.real_out_tx_key << endl; + //cout << "tx_source.real_output_in_tx_index: " << tx_source.real_output_in_tx_index << endl; + + uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; + + tx_out_index real_toi; + + uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); + + try { - mstch::map dest_info { - {"dest_address" , get_account_address_as_str( - nettype, a_dest.is_subaddress, a_dest.addr)}, - {"dest_amount" , xmreg::xmr_amount_to_str(a_dest.amount)} - }; + // get tx of the real output + real_toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, + index_of_real_output); + } + catch (const OUTPUT_DNE& e) + { + + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + tx_source_amount, index_of_real_output + ); + + cerr << out_msg << endl; - dest_infos.push_back(dest_info); + return string(out_msg); } - vector> mixin_timestamp_groups; - vector real_output_indices; - uint64_t sum_outputs_amounts {0}; - for (size_t i = 0; i < no_of_sources; ++i) + transaction real_source_tx; + + if (!mcore->get_tx(real_toi.first, real_source_tx)) { + cerr << "Cant get tx in blockchain: " << real_toi.first << endl; + return string("Cant get tx: " + pod_to_hex(real_toi.first)); + } - const tx_source_entry& tx_source = tx_cd.sources.at(i); + tx_details real_txd = get_tx_details(real_source_tx); - mstch::map single_dest_source { - {"output_amount" , xmreg::xmr_amount_to_str(tx_source.amount)}, - {"real_output" , static_cast(tx_source.real_output)}, - {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, - {"real_output_in_tx_index" , static_cast(tx_source.real_output_in_tx_index)}, - }; - single_dest_source.emplace("outputs", mstch::array{}); + real_output_indices.push_back(tx_source.real_output); + + public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - sum_outputs_amounts += tx_source.amount; + //cout << "real_txd.hash: " << pod_to_hex(real_txd.hash) << endl; + //cout << "real_txd.pk: " << pod_to_hex(real_txd.pk) << endl; + //cout << "real_out_pub_key: " << pod_to_hex(real_out_pub_key) << endl; - //cout << "tx_source.real_output: " << tx_source.real_output << endl; - //cout << "tx_source.real_out_tx_key: " << tx_source.real_out_tx_key << endl; - //cout << "tx_source.real_output_in_tx_index: " << tx_source.real_output_in_tx_index << endl; + mstch::array& outputs = boost::get(single_dest_source["outputs"]); - uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; + vector mixin_timestamps; - tx_out_index real_toi; + size_t output_i {0}; - uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); + for(const tx_source_entry::output_entry& oe: tx_source.outputs) + { + + tx_out_index toi; try { + // get tx of the real output - real_toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, - index_of_real_output); + toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, oe.first); } - catch (const OUTPUT_DNE& e) + catch (OUTPUT_DNE& e) { string out_msg = fmt::format( "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, index_of_real_output + tx_source_amount, oe.first ); cerr << out_msg << endl; @@ -2662,1023 +2804,1005 @@ public: return string(out_msg); } + transaction tx; - - transaction real_source_tx; - - if (!mcore->get_tx(real_toi.first, real_source_tx)) + if (!mcore->get_tx(toi.first, tx)) { - cerr << "Cant get tx in blockchain: " << real_toi.first << endl; - return string("Cant get tx: " + pod_to_hex(real_toi.first)); + cerr << "Cant get tx in blockchain: " << toi.first + << ". \n Check mempool now" << endl; + // tx is nowhere to be found :-( + return string("Cant get tx: " + pod_to_hex(toi.first)); } - tx_details real_txd = get_tx_details(real_source_tx); - - real_output_indices.push_back(tx_source.real_output); - - public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - - //cout << "real_txd.hash: " << pod_to_hex(real_txd.hash) << endl; - //cout << "real_txd.pk: " << pod_to_hex(real_txd.pk) << endl; - //cout << "real_out_pub_key: " << pod_to_hex(real_out_pub_key) << endl; + tx_details txd = get_tx_details(tx); - mstch::array& outputs = boost::get(single_dest_source["outputs"]); + public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; - vector mixin_timestamps; - size_t output_i {0}; + // get block cointaining this tx + block blk; - for(const tx_source_entry::output_entry& oe: tx_source.outputs) + if (!mcore->get_block_by_height(txd.blk_height, blk)) { + cerr << "Cant get block: " << txd.blk_height << endl; + return string("Cant get block: " + to_string(txd.blk_height)); + } - tx_out_index toi; - - try - { - - // get tx of the real output - toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, oe.first); - } - catch (OUTPUT_DNE& e) - { + pair age = get_age(server_timestamp, blk.timestamp); - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, oe.first - ); + mstch::map single_output { + {"out_index" , oe.first}, + {"tx_hash" , pod_to_hex(txd.hash)}, + {"out_pub_key" , pod_to_hex(out_pub_key)}, + {"ctkey" , pod_to_hex(oe.second)}, + {"output_age" , age.first}, + {"is_real" , (out_pub_key == real_out_pub_key)} + }; - cerr << out_msg << endl; + single_dest_source.insert({"age_format" , age.second}); - return string(out_msg); - } + outputs.push_back(single_output); - transaction tx; + mixin_timestamps.push_back(blk.timestamp); - if (!mcore->get_tx(toi.first, tx)) - { - cerr << "Cant get tx in blockchain: " << toi.first - << ". \n Check mempool now" << endl; - // tx is nowhere to be found :-( - return string("Cant get tx: " + pod_to_hex(toi.first)); - } + ++output_i; - tx_details txd = get_tx_details(tx); + } // for(const tx_source_entry::output_entry& oe: tx_source.outputs) - public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; + dest_sources.push_back(single_dest_source); + mixin_timestamp_groups.push_back(mixin_timestamps); - // get block cointaining this tx - block blk; + } // for (size_t i = 0; i < no_of_sources; ++i) - if (!mcore->get_block_by_height(txd.blk_height, blk)) - { - cerr << "Cant get block: " << txd.blk_height << endl; - return string("Cant get block: " + to_string(txd.blk_height)); - } + tx_cd_data.insert({"sum_outputs_amounts" , + xmreg::xmr_amount_to_str(sum_outputs_amounts)}); - pair age = get_age(server_timestamp, blk.timestamp); - mstch::map single_output { - {"out_index" , oe.first}, - {"tx_hash" , pod_to_hex(txd.hash)}, - {"out_pub_key" , pod_to_hex(out_pub_key)}, - {"ctkey" , pod_to_hex(oe.second)}, - {"output_age" , age.first}, - {"is_real" , (out_pub_key == real_out_pub_key)} - }; + uint64_t min_mix_timestamp; + uint64_t max_mix_timestamp; - single_dest_source.insert({"age_format" , age.second}); + pair mixins_timescales + = construct_mstch_mixin_timescales( + mixin_timestamp_groups, + min_mix_timestamp, + max_mix_timestamp + ); - outputs.push_back(single_output); + tx_cd_data.emplace("timescales", mixins_timescales.first); + tx_cd_data["min_mix_time"] = xmreg::timestamp_to_str_gm(min_mix_timestamp); + tx_cd_data["max_mix_time"] = xmreg::timestamp_to_str_gm(max_mix_timestamp); + tx_cd_data["timescales_scale"] = fmt::format("{:0.2f}", + mixins_timescales.second + / 3600.0 / 24.0); // in days - mixin_timestamps.push_back(blk.timestamp); + // mark real mixing in the mixins timescale graph + mark_real_mixins_on_timescales(real_output_indices, tx_cd_data); - ++output_i; + txs.push_back(tx_cd_data); - } // for(const tx_source_entry::output_entry& oe: tx_source.outputs) + } // for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + } + else + { + cerr << "deserialization of unsigned tx data NOT successful" << endl; + return string("deserialization of unsigned tx data NOT successful. " + "Maybe its not base64 encoded?"); + } + } // if (unsigned_tx_given) + else + { + // if raw data is not unsigined tx, then assume it is signed tx - dest_sources.push_back(single_dest_source); + const size_t magiclen = strlen(SIGNED_TX_PREFIX); - mixin_timestamp_groups.push_back(mixin_timestamps); + string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); - } // for (size_t i = 0; i < no_of_sources; ++i) + if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + { - tx_cd_data.insert({"sum_outputs_amounts" , - xmreg::xmr_amount_to_str(sum_outputs_amounts)}); + // ok, so its not signed tx data. but maybe it is raw tx data + // used in rpc call "/sendrawtransaction". This is for example + // used in mymonero and openmonero projects. + // to check this, first we need to encode data back to base64. + // the reason is that txs submited to "/sendrawtransaction" + // are not base64, and we earlier always asume it is base64. - uint64_t min_mix_timestamp; - uint64_t max_mix_timestamp; + // string reencoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); - pair mixins_timescales - = construct_mstch_mixin_timescales( - mixin_timestamp_groups, - min_mix_timestamp, - max_mix_timestamp - ); + //cout << "raw_tx_data: " << raw_tx_data << endl; - tx_cd_data.emplace("timescales", mixins_timescales.first); - tx_cd_data["min_mix_time"] = xmreg::timestamp_to_str_gm(min_mix_timestamp); - tx_cd_data["max_mix_time"] = xmreg::timestamp_to_str_gm(max_mix_timestamp); - tx_cd_data["timescales_scale"] = fmt::format("{:0.2f}", - mixins_timescales.second - / 3600.0 / 24.0); // in days + cryptonote::blobdata tx_data_blob; - // mark real mixing in the mixins timescale graph - mark_real_mixins_on_timescales(real_output_indices, tx_cd_data); + if (!epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_data_blob)) + { + string msg = fmt::format("The data is neither unsigned, signed tx or raw tx! " + "Its prefix is: {:s}", + data_prefix); - txs.push_back(tx_cd_data); + cerr << msg << endl; - } // for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) - } - else - { - cerr << "deserialization of unsigned tx data NOT successful" << endl; - return string("deserialization of unsigned tx data NOT successful. " - "Maybe its not base64 encoded?"); + return string(msg); } - } // if (unsigned_tx_given) - else - { - // if raw data is not unsigined tx, then assume it is signed tx - const size_t magiclen = strlen(SIGNED_TX_PREFIX); + crypto::hash tx_hash_from_blob; + crypto::hash tx_prefix_hash_from_blob; + cryptonote::transaction tx_from_blob; - string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); +// std::stringstream ss; +// ss << tx_data_blob; +// binary_archive ba(ss); +// serialization::serialize(ba, tx_from_blob); - if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data_blob, + tx_from_blob, + tx_hash_from_blob, + tx_prefix_hash_from_blob)) { + string error_msg = fmt::format("failed to validate transaction"); - // ok, so its not signed tx data. but maybe it is raw tx data - // used in rpc call "/sendrawtransaction". This is for example - // used in mymonero and openmonero projects. + context["has_error"] = true; + context["error_msg"] = error_msg; - // to check this, first we need to encode data back to base64. - // the reason is that txs submited to "/sendrawtransaction" - // are not base64, and we earlier always asume it is base64. + return mstch::render(full_page, context); + } - // string reencoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + //cout << "tx_from_blob.vout.size(): " << tx_from_blob.vout.size() << endl; - //cout << "raw_tx_data: " << raw_tx_data << endl; + // tx has been correctly deserialized. So + // we just dispaly it. We dont have any information about real mixins, etc, + // so there is not much more we can do with tx data. - cryptonote::blobdata tx_data_blob; + mstch::map tx_context = construct_tx_context(tx_from_blob); - if (!epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_data_blob)) - { - string msg = fmt::format("The data is neither unsigned, signed tx or raw tx! " - "Its prefix is: {:s}", - data_prefix); + if (boost::get(tx_context["has_error"])) + { + return boost::get(tx_context["error_msg"]); + } - cerr << msg << endl; + // this will be stored in html for for checking outputs + // we need this data if we want to use "Decode outputs" + // to see which outputs are ours, and decode amounts in ringct txs + tx_context["raw_tx_data"] = raw_tx_data; + tx_context["show_more_details_link"] = false; - return string(msg); - } + context["data_prefix"] = string("none as this is pure raw tx data"); + context["tx_json"] = obj_to_json_str(tx_from_blob); - crypto::hash tx_hash_from_blob; - crypto::hash tx_prefix_hash_from_blob; - cryptonote::transaction tx_from_blob; + context.emplace("txs" , mstch::array{}); -// std::stringstream ss; -// ss << tx_data_blob; -// binary_archive ba(ss); -// serialization::serialize(ba, tx_from_blob); + boost::get(context["txs"]).push_back(tx_context); - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data_blob, - tx_from_blob, - tx_hash_from_blob, - tx_prefix_hash_from_blob)) - { - string error_msg = fmt::format("failed to validate transaction"); + map partials { + {"tx_details", template_file["tx_details"]}, + }; - context["has_error"] = true; - context["error_msg"] = error_msg; + add_css_style(context); - return mstch::render(full_page, context); - } + if (enable_js) + add_js_files(context); - //cout << "tx_from_blob.vout.size(): " << tx_from_blob.vout.size() << endl; + // render the page + return mstch::render(template_file["checkrawtx"], context, partials); - // tx has been correctly deserialized. So - // we just dispaly it. We dont have any information about real mixins, etc, - // so there is not much more we can do with tx data. + } // if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) - mstch::map tx_context = construct_tx_context(tx_from_blob); + context["data_prefix"] = data_prefix; - if (boost::get(tx_context["has_error"])) - { - return boost::get(tx_context["error_msg"]); - } + bool r {false}; - // this will be stored in html for for checking outputs - // we need this data if we want to use "Decode outputs" - // to see which outputs are ours, and decode amounts in ringct txs - tx_context["raw_tx_data"] = raw_tx_data; - tx_context["show_more_details_link"] = false; + string s = decoded_raw_tx_data.substr(magiclen); - context["data_prefix"] = string("none as this is pure raw tx data"); - context["tx_json"] = obj_to_json_str(tx_from_blob); + ::tools::wallet2::signed_tx_set signed_txs; - context.emplace("txs" , mstch::array{}); + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> signed_txs; - boost::get(context["txs"]).push_back(tx_context); + r = true; + } + catch (...) + { + cerr << "Failed to parse signed tx data " << endl; + } - map partials { - {"tx_details", template_file["tx_details"]}, - }; + if (!r) + { + cerr << "deserialization of signed tx data NOT successful" << endl; + return string("deserialization of signed tx data NOT successful. " + "Maybe its not base64 encoded?"); + } - add_css_style(context); + std::vector ptxs = signed_txs.ptx; - if (enable_js) - add_js_files(context); + context.insert({"txs", mstch::array{}}); - // render the page - return mstch::render(template_file["checkrawtx"], context, partials); + for (tools::wallet2::pending_tx& ptx: ptxs) + { + mstch::map tx_context = construct_tx_context(ptx.tx, 1); - } // if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + if (boost::get(tx_context["has_error"])) + { + return boost::get(tx_context["error_msg"]); + } - context["data_prefix"] = data_prefix; + tx_context["tx_prv_key"] = fmt::format("{:s}", ptx.tx_key); - bool r {false}; + mstch::array destination_addresses; + vector real_ammounts; + uint64_t outputs_xmr_sum {0}; - string s = decoded_raw_tx_data.substr(magiclen); + // destiantion address for this tx + for (tx_destination_entry& a_dest: ptx.construction_data.splitted_dsts) + { + //stealth_address_amount.insert({dest.addr, dest.amount}); + //cout << get_account_address_as_str(testnet, a_dest.addr) << endl; + //address_amounts.push_back(a_dest.amount); - ::tools::wallet2::signed_tx_set signed_txs; + destination_addresses.push_back( + mstch::map { + {"dest_address" , get_account_address_as_str( + nettype, a_dest.is_subaddress, a_dest.addr)}, + {"dest_amount" , xmreg::xmr_amount_to_str(a_dest.amount)}, + {"is_this_change" , false} + } + ); - try - { - std::istringstream iss(s); - boost::archive::portable_binary_iarchive ar(iss); - ar >> signed_txs; + outputs_xmr_sum += a_dest.amount; - r = true; - } - catch (...) - { - cerr << "Failed to parse signed tx data " << endl; + real_ammounts.push_back(a_dest.amount); } - if (!r) + // get change address and amount info + if (ptx.construction_data.change_dts.amount > 0) { - cerr << "deserialization of signed tx data NOT successful" << endl; - return string("deserialization of signed tx data NOT successful. " - "Maybe its not base64 encoded?"); - } + destination_addresses.push_back( + mstch::map { + {"dest_address" , get_account_address_as_str( + nettype, ptx.construction_data.change_dts.is_subaddress, ptx.construction_data.change_dts.addr)}, + {"dest_amount" , + xmreg::xmr_amount_to_str(ptx.construction_data.change_dts.amount)}, + {"is_this_change" , true} + } + ); - std::vector ptxs = signed_txs.ptx; + real_ammounts.push_back(ptx.construction_data.change_dts.amount); + }; - context.insert({"txs", mstch::array{}}); + tx_context["outputs_xmr_sum"] = xmreg::xmr_amount_to_str(outputs_xmr_sum); - for (tools::wallet2::pending_tx& ptx: ptxs) - { - mstch::map tx_context = construct_tx_context(ptx.tx, 1); + tx_context.insert({"dest_infos", destination_addresses}); - if (boost::get(tx_context["has_error"])) - { - return boost::get(tx_context["error_msg"]); - } + // get reference to inputs array created of the tx + mstch::array& outputs = boost::get(tx_context["outputs"]); - tx_context["tx_prv_key"] = fmt::format("{:s}", ptx.tx_key); + // show real output amount for ringct outputs. + // otherwise its only 0.000000000 + for (size_t i = 0; i < outputs.size(); ++i) + { + mstch::map& output_map = boost::get(outputs.at(i)); - mstch::array destination_addresses; - vector real_ammounts; - uint64_t outputs_xmr_sum {0}; + string& out_amount_str = boost::get(output_map["amount"]); - // destiantion address for this tx - for (tx_destination_entry& a_dest: ptx.construction_data.splitted_dsts) - { - //stealth_address_amount.insert({dest.addr, dest.amount}); - //cout << get_account_address_as_str(testnet, a_dest.addr) << endl; - //address_amounts.push_back(a_dest.amount); - - destination_addresses.push_back( - mstch::map { - {"dest_address" , get_account_address_as_str( - nettype, a_dest.is_subaddress, a_dest.addr)}, - {"dest_amount" , xmreg::xmr_amount_to_str(a_dest.amount)}, - {"is_this_change" , false} - } - ); + //cout << boost::get(output_map["out_pub_key"]) + // <<", " << out_amount_str << endl; - outputs_xmr_sum += a_dest.amount; + uint64_t output_amount; - real_ammounts.push_back(a_dest.amount); + if (parse_amount(output_amount, out_amount_str)) + { + if (output_amount == 0) + { + out_amount_str = xmreg::xmr_amount_to_str(real_ammounts.at(i)); + } } + } - // get change address and amount info - if (ptx.construction_data.change_dts.amount > 0) - { - destination_addresses.push_back( - mstch::map { - {"dest_address" , get_account_address_as_str( - nettype, ptx.construction_data.change_dts.is_subaddress, ptx.construction_data.change_dts.addr)}, - {"dest_amount" , - xmreg::xmr_amount_to_str(ptx.construction_data.change_dts.amount)}, - {"is_this_change" , true} - } - ); + // get public keys of real outputs + vector real_output_pub_keys; + vector real_output_indices; + vector real_amounts; - real_ammounts.push_back(ptx.construction_data.change_dts.amount); - }; + uint64_t inputs_xmr_sum {0}; - tx_context["outputs_xmr_sum"] = xmreg::xmr_amount_to_str(outputs_xmr_sum); + for (const tx_source_entry& tx_source: ptx.construction_data.sources) + { + transaction real_source_tx; - tx_context.insert({"dest_infos", destination_addresses}); + uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; - // get reference to inputs array created of the tx - mstch::array& outputs = boost::get(tx_context["outputs"]); + uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); - // show real output amount for ringct outputs. - // otherwise its only 0.000000000 - for (size_t i = 0; i < outputs.size(); ++i) - { - mstch::map& output_map = boost::get(outputs.at(i)); + tx_out_index real_toi; - string& out_amount_str = boost::get(output_map["amount"]); + try + { + // get tx of the real output + real_toi = core_storage->get_db() + .get_output_tx_and_index(tx_source_amount, index_of_real_output); + } + catch (const OUTPUT_DNE& e) + { - //cout << boost::get(output_map["out_pub_key"]) - // <<", " << out_amount_str << endl; + string out_msg = fmt::format( + "Output with amount {:d} and index {:d} does not exist!", + tx_source_amount, index_of_real_output + ); - uint64_t output_amount; + cerr << out_msg << endl; - if (parse_amount(output_amount, out_amount_str)) - { - if (output_amount == 0) - { - out_amount_str = xmreg::xmr_amount_to_str(real_ammounts.at(i)); - } - } + return string(out_msg); } - // get public keys of real outputs - vector real_output_pub_keys; - vector real_output_indices; - vector real_amounts; - - uint64_t inputs_xmr_sum {0}; - - for (const tx_source_entry& tx_source: ptx.construction_data.sources) + if (!mcore->get_tx(real_toi.first, real_source_tx)) { - transaction real_source_tx; - - uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; + cerr << "Cant get tx in blockchain: " << real_toi.first << endl; + return string("Cant get tx: " + pod_to_hex(real_toi.first)); + } - uint64_t tx_source_amount = (tx_source.rct ? 0 : tx_source.amount); + tx_details real_txd = get_tx_details(real_source_tx); - tx_out_index real_toi; + public_key real_out_pub_key + = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - try - { - // get tx of the real output - real_toi = core_storage->get_db() - .get_output_tx_and_index(tx_source_amount, index_of_real_output); - } - catch (const OUTPUT_DNE& e) - { + real_output_pub_keys.push_back( + REMOVE_HASH_BRAKETS(fmt::format("{:s}",real_out_pub_key)) + ); - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - tx_source_amount, index_of_real_output - ); + real_output_indices.push_back(tx_source.real_output); + real_amounts.push_back(tx_source.amount); - cerr << out_msg << endl; + inputs_xmr_sum += tx_source.amount; + } - return string(out_msg); - } + // mark that we have signed tx data for use in mstch + tx_context["have_raw_tx"] = true; - if (!mcore->get_tx(real_toi.first, real_source_tx)) - { - cerr << "Cant get tx in blockchain: " << real_toi.first << endl; - return string("Cant get tx: " + pod_to_hex(real_toi.first)); - } + // provide total mount of inputs xmr + tx_context["inputs_xmr_sum"] = xmreg::xmr_amount_to_str(inputs_xmr_sum); - tx_details real_txd = get_tx_details(real_source_tx); + // get reference to inputs array created of the tx + mstch::array& inputs = boost::get(tx_context["inputs"]); - public_key real_out_pub_key - = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; + uint64_t input_idx {0}; - real_output_pub_keys.push_back( - REMOVE_HASH_BRAKETS(fmt::format("{:s}",real_out_pub_key)) - ); + // mark which mixin is real in each input's mstch context + for (mstch::node& input_node: inputs) + { - real_output_indices.push_back(tx_source.real_output); - real_amounts.push_back(tx_source.amount); + mstch::map& input_map = boost::get(input_node); - inputs_xmr_sum += tx_source.amount; - } + // show input amount + string& amount = boost::get( + boost::get(input_node)["amount"] + ); - // mark that we have signed tx data for use in mstch - tx_context["have_raw_tx"] = true; + amount = xmreg::xmr_amount_to_str(real_amounts.at(input_idx)); - // provide total mount of inputs xmr - tx_context["inputs_xmr_sum"] = xmreg::xmr_amount_to_str(inputs_xmr_sum); + // check if key images are spend or not - // get reference to inputs array created of the tx - mstch::array& inputs = boost::get(tx_context["inputs"]); + string& in_key_img_str = boost::get( + boost::get(input_node)["in_key_img"] + ); - uint64_t input_idx {0}; + key_image key_imgage; - // mark which mixin is real in each input's mstch context - for (mstch::node& input_node: inputs) + if (epee::string_tools::hex_to_pod(in_key_img_str, key_imgage)) { + input_map["already_spent"] = core_storage->get_db().has_key_image(key_imgage); + } - mstch::map& input_map = boost::get(input_node); - - // show input amount - string& amount = boost::get( - boost::get(input_node)["amount"] - ); + // mark real mixings - amount = xmreg::xmr_amount_to_str(real_amounts.at(input_idx)); + mstch::array& mixins = boost::get( + boost::get(input_node)["mixins"] + ); - // check if key images are spend or not + for (mstch::node& mixin_node: mixins) + { + mstch::map& mixin = boost::get(mixin_node); - string& in_key_img_str = boost::get( - boost::get(input_node)["in_key_img"] - ); + string mix_pub_key_str = boost::get(mixin["mix_pub_key"]); - key_image key_imgage; + //cout << mix_pub_key_str << endl; - if (epee::string_tools::hex_to_pod(in_key_img_str, key_imgage)) + if (std::find( + real_output_pub_keys.begin(), + real_output_pub_keys.end(), + mix_pub_key_str) != real_output_pub_keys.end()) { - input_map["already_spent"] = core_storage->get_db().has_key_image(key_imgage); + mixin["mix_is_it_real"] = true; } + } - // mark real mixings - - mstch::array& mixins = boost::get( - boost::get(input_node)["mixins"] - ); + ++input_idx; + } - for (mstch::node& mixin_node: mixins) - { - mstch::map& mixin = boost::get(mixin_node); + // mark real mixing in the mixins timescale graph + mark_real_mixins_on_timescales(real_output_indices, tx_context); - string mix_pub_key_str = boost::get(mixin["mix_pub_key"]); + boost::get(context["txs"]).push_back(tx_context); + } - //cout << mix_pub_key_str << endl; + } - if (std::find( - real_output_pub_keys.begin(), - real_output_pub_keys.end(), - mix_pub_key_str) != real_output_pub_keys.end()) - { - mixin["mix_is_it_real"] = true; - } - } - ++input_idx; - } + map partials { + {"tx_details", template_file["tx_details"]}, + }; - // mark real mixing in the mixins timescale graph - mark_real_mixins_on_timescales(real_output_indices, tx_context); + // render the page + return mstch::render(full_page, context, partials); +} - boost::get(context["txs"]).push_back(tx_context); - } +string +show_pushrawtx(string raw_tx_data, string action) +{ + clean_post_data(raw_tx_data); + + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"have_raw_tx" , true}, + {"has_error" , false}, + {"error_msg" , string {}}, + }; - } + // add header and footer + string full_page = template_file["pushrawtx"]; + add_css_style(context); - map partials { - {"tx_details", template_file["tx_details"]}, - }; + std::vector ptx_vector; - // render the page - return mstch::render(full_page, context, partials); + // first try reading raw_tx_data as a raw hex string + std::string tx_blob; + cryptonote::transaction parsed_tx; + crypto::hash parsed_tx_hash, parsed_tx_prefixt_hash; + if (epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_blob) && parse_and_validate_tx_from_blob(tx_blob, parsed_tx, parsed_tx_hash, parsed_tx_prefixt_hash)) + { + ptx_vector.push_back({}); + ptx_vector.back().tx = parsed_tx; } - - string - show_pushrawtx(string raw_tx_data, string action) + // if failed, treat raw_tx_data as base64 encoding of signed_monero_tx + else { - clean_post_data(raw_tx_data); + string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"have_raw_tx" , true}, - {"has_error" , false}, - {"error_msg" , string {}}, - }; + const size_t magiclen = strlen(SIGNED_TX_PREFIX); - // add header and footer - string full_page = template_file["pushrawtx"]; - - add_css_style(context); + string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); - std::vector ptx_vector; + context["data_prefix"] = data_prefix; - // first try reading raw_tx_data as a raw hex string - std::string tx_blob; - cryptonote::transaction parsed_tx; - crypto::hash parsed_tx_hash, parsed_tx_prefixt_hash; - if (epee::string_tools::parse_hexstr_to_binbuff(raw_tx_data, tx_blob) && parse_and_validate_tx_from_blob(tx_blob, parsed_tx, parsed_tx_hash, parsed_tx_prefixt_hash)) + if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) { - ptx_vector.push_back({}); - ptx_vector.back().tx = parsed_tx; + string error_msg = fmt::format("The data does not appear to be signed raw tx! Data prefix: {:s}", + data_prefix); + + context["has_error"] = true; + context["error_msg"] = error_msg; + + return mstch::render(full_page, context); } - // if failed, treat raw_tx_data as base64 encoding of signed_monero_tx - else + + if (this->enable_pusher == false) { - string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + string error_msg = fmt::format( + "Pushing disabled!\n " + "Run explorer with --enable-pusher flag to enable it."); - const size_t magiclen = strlen(SIGNED_TX_PREFIX); + context["has_error"] = true; + context["error_msg"] = error_msg; - string data_prefix = xmreg::make_printable(decoded_raw_tx_data.substr(0, magiclen)); + return mstch::render(full_page, context); + } - context["data_prefix"] = data_prefix; + bool r {false}; - if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) - { - string error_msg = fmt::format("The data does not appear to be signed raw tx! Data prefix: {:s}", - data_prefix); + string s = decoded_raw_tx_data.substr(magiclen); - context["has_error"] = true; - context["error_msg"] = error_msg; + ::tools::wallet2::signed_tx_set signed_txs; - return mstch::render(full_page, context); - } + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> signed_txs; - if (this->enable_pusher == false) - { - string error_msg = fmt::format( - "Pushing disabled!\n " - "Run explorer with --enable-pusher flag to enable it."); + r = true; + } + catch (...) + { + cerr << "Failed to parse signed tx data " << endl; + } - context["has_error"] = true; - context["error_msg"] = error_msg; - return mstch::render(full_page, context); - } + if (!r) + { + string error_msg = fmt::format("Deserialization of signed tx data NOT successful! " + "Maybe its not base64 encoded?"); - bool r {false}; + context["has_error"] = true; + context["error_msg"] = error_msg; - string s = decoded_raw_tx_data.substr(magiclen); + return mstch::render(full_page, context); + } - ::tools::wallet2::signed_tx_set signed_txs; + ptx_vector = signed_txs.ptx; + } - try - { - std::istringstream iss(s); - boost::archive::portable_binary_iarchive ar(iss); - ar >> signed_txs; + context.emplace("txs", mstch::array{}); - r = true; - } - catch (...) - { - cerr << "Failed to parse signed tx data " << endl; - } + mstch::array& txs = boost::get(context["txs"]); + // actually commit the transactions + while (!ptx_vector.empty()) + { + tools::wallet2::pending_tx& ptx = ptx_vector.back(); - if (!r) - { - string error_msg = fmt::format("Deserialization of signed tx data NOT successful! " - "Maybe its not base64 encoded?"); + tx_details txd = get_tx_details(ptx.tx); - context["has_error"] = true; - context["error_msg"] = error_msg; + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.hash)); - return mstch::render(full_page, context); - } + mstch::map tx_cd_data { + {"tx_hash" , tx_hash_str} + }; - ptx_vector = signed_txs.ptx; - } + // check in mempool already contains tx to be submited + vector found_mempool_txs; + + search_mempool(txd.hash, found_mempool_txs); + + if (!found_mempool_txs.empty()) + { + string error_msg = fmt::format("Tx already exist in the mempool: {:s}\n", + tx_hash_str); - context.emplace("txs", mstch::array{}); + context["has_error"] = true; + context["error_msg"] = error_msg; - mstch::array& txs = boost::get(context["txs"]); + break; + } - // actually commit the transactions - while (!ptx_vector.empty()) + // check if tx to be submited already exists in the blockchain + if (core_storage->have_tx(txd.hash)) { - tools::wallet2::pending_tx& ptx = ptx_vector.back(); + string error_msg = fmt::format("Tx already exist in the blockchain: {:s}\n", + tx_hash_str); - tx_details txd = get_tx_details(ptx.tx); + context["has_error"] = true; + context["error_msg"] = error_msg; - string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.hash)); + break; + } - mstch::map tx_cd_data { - {"tx_hash" , tx_hash_str} - }; + // check if any key images of the tx to be submited are already spend + vector key_images_spent; - // check in mempool already contains tx to be submited - vector found_mempool_txs; + for (const txin_to_key& tx_in: txd.input_key_imgs) + { + if (core_storage->have_tx_keyimg_as_spent(tx_in.k_image)) + key_images_spent.push_back(tx_in.k_image); + } - search_mempool(txd.hash, found_mempool_txs); + if (!key_images_spent.empty()) + { + string error_msg = fmt::format("Tx with hash {:s} has already spent inputs\n", + tx_hash_str); - if (!found_mempool_txs.empty()) + for (key_image& k_img: key_images_spent) { - string error_msg = fmt::format("Tx already exist in the mempool: {:s}\n", - tx_hash_str); + error_msg += REMOVE_HASH_BRAKETS(fmt::format("{:s}", k_img)); + error_msg += "
"; + } - context["has_error"] = true; - context["error_msg"] = error_msg; + context["has_error"] = true; + context["error_msg"] = error_msg; - break; - } + break; + } - // check if tx to be submited already exists in the blockchain - if (core_storage->have_tx(txd.hash)) - { - string error_msg = fmt::format("Tx already exist in the blockchain: {:s}\n", - tx_hash_str); + string rpc_error_msg; - context["has_error"] = true; - context["error_msg"] = error_msg; + if (this->enable_pusher == false) + { + string error_msg = fmt::format( + "Pushing signed transactions is disabled. " + "Run explorer with --enable-pusher flag to enable it.\n"); - break; - } + context["has_error"] = true; + context["error_msg"] = error_msg; - // check if any key images of the tx to be submited are already spend - vector key_images_spent; + break; + } - for (const txin_to_key& tx_in: txd.input_key_imgs) - { - if (core_storage->have_tx_keyimg_as_spent(tx_in.k_image)) - key_images_spent.push_back(tx_in.k_image); - } + if (!rpc.commit_tx(ptx, rpc_error_msg)) + { + string error_msg = fmt::format( + "Submitting signed tx {:s} to daemon failed: {:s}\n", + tx_hash_str, rpc_error_msg); - if (!key_images_spent.empty()) - { - string error_msg = fmt::format("Tx with hash {:s} has already spent inputs\n", - tx_hash_str); + context["has_error"] = true; + context["error_msg"] = error_msg; - for (key_image& k_img: key_images_spent) - { - error_msg += REMOVE_HASH_BRAKETS(fmt::format("{:s}", k_img)); - error_msg += "
"; - } + break; + } - context["has_error"] = true; - context["error_msg"] = error_msg; + txs.push_back(tx_cd_data); - break; - } + // if no exception, remove element from vector + ptx_vector.pop_back(); + } - string rpc_error_msg; + // render the page + return mstch::render(full_page, context); +} - if (this->enable_pusher == false) - { - string error_msg = fmt::format( - "Pushing signed transactions is disabled. " - "Run explorer with --enable-pusher flag to enable it.\n"); - context["has_error"] = true; - context["error_msg"] = error_msg; +string +show_rawkeyimgs() +{ + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + }; - break; - } + add_css_style(context); - if (!rpc.commit_tx(ptx, rpc_error_msg)) - { - string error_msg = fmt::format( - "Submitting signed tx {:s} to daemon failed: {:s}\n", - tx_hash_str, rpc_error_msg); + // render the page + return mstch::render(template_file["rawkeyimgs"], context); +} - context["has_error"] = true; - context["error_msg"] = error_msg; +string +show_rawoutputkeys() +{ + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet} + }; - break; - } + add_css_style(context); - txs.push_back(tx_cd_data); + // render the page + return mstch::render(template_file["rawoutputkeys"], context); +} - // if no exception, remove element from vector - ptx_vector.pop_back(); - } +string +show_checkrawkeyimgs(string raw_data, string viewkey_str) +{ - // render the page - return mstch::render(full_page, context); - } + clean_post_data(raw_data); + // remove white characters + boost::trim(viewkey_str); - string - show_rawkeyimgs() - { - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - }; + string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); + secret_key prv_view_key; - add_css_style(context); + // initalize page template context map + mstch::map context{ + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"has_error" , false}, + {"error_msg" , string{}}, + }; - // render the page - return mstch::render(template_file["rawkeyimgs"], context); - } + // add header and footer + string full_page = template_file["checkrawkeyimgs"]; - string - show_rawoutputkeys() + add_css_style(context); + + if (viewkey_str.empty()) { - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet} - }; + string error_msg = fmt::format("View key not given. Cant decode " + "the key image data without it!"); - add_css_style(context); + context["has_error"] = true; + context["error_msg"] = error_msg; - // render the page - return mstch::render(template_file["rawoutputkeys"], context); + return mstch::render(full_page, context); } - string - show_checkrawkeyimgs(string raw_data, string viewkey_str) + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) { + string error_msg = fmt::format("Cant parse the private key: " + viewkey_str); - clean_post_data(raw_data); + context["has_error"] = true; + context["error_msg"] = error_msg; - // remove white characters - boost::trim(viewkey_str); - - string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); - secret_key prv_view_key; + return mstch::render(full_page, context); + } - // initalize page template context map - mstch::map context{ - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"has_error" , false}, - {"error_msg" , string{}}, - }; + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); - // add header and footer - string full_page = template_file["checkrawkeyimgs"]; + string data_prefix = xmreg::make_printable(decoded_raw_data.substr(0, magiclen)); - add_css_style(context); + context["data_prefix"] = data_prefix; - if (viewkey_str.empty()) - { - string error_msg = fmt::format("View key not given. Cant decode " - "the key image data without it!"); + if (strncmp(decoded_raw_data.c_str(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen) != 0) + { + string error_msg = fmt::format("This does not seem to be key image export data."); - context["has_error"] = true; - context["error_msg"] = error_msg; + context["has_error"] = true; + context["error_msg"] = error_msg; - return mstch::render(full_page, context); - } + return mstch::render(full_page, context); + } - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - string error_msg = fmt::format("Cant parse the private key: " + viewkey_str); + // decrypt key images data using private view key + decoded_raw_data = xmreg::decrypt( + std::string(decoded_raw_data, magiclen), + prv_view_key, true); - context["has_error"] = true; - context["error_msg"] = error_msg; + if (decoded_raw_data.empty()) + { + string error_msg = fmt::format("Failed to authenticate key images data. " + "Maybe wrong viewkey was porvided?"); - return mstch::render(full_page, context); - } + context["has_error"] = true; + context["error_msg"] = error_msg; - const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); + return mstch::render(full_page, context); + } - string data_prefix = xmreg::make_printable(decoded_raw_data.substr(0, magiclen)); + // header is public spend and keys + const size_t header_lenght = 2 * sizeof(crypto::public_key); + const size_t key_img_size = sizeof(crypto::key_image); + const size_t record_lenght = key_img_size + sizeof(crypto::signature); + const size_t chacha_length = sizeof(crypto::chacha_key); - context["data_prefix"] = data_prefix; + if (decoded_raw_data.size() < header_lenght) + { + string error_msg = fmt::format("Bad data size from submitted key images raw data."); - if (strncmp(decoded_raw_data.c_str(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen) != 0) - { - string error_msg = fmt::format("This does not seem to be key image export data."); + context["has_error"] = true; + context["error_msg"] = error_msg; - context["has_error"] = true; - context["error_msg"] = error_msg; + return mstch::render(full_page, context); - return mstch::render(full_page, context); - } + } - // decrypt key images data using private view key - decoded_raw_data = xmreg::decrypt( - std::string(decoded_raw_data, magiclen), - prv_view_key, true); + // get xmr address stored in this key image file + const account_public_address* xmr_address = + reinterpret_cast( + decoded_raw_data.data()); - if (decoded_raw_data.empty()) - { - string error_msg = fmt::format("Failed to authenticate key images data. " - "Maybe wrong viewkey was porvided?"); + address_parse_info address_info {*xmr_address, false}; - context["has_error"] = true; - context["error_msg"] = error_msg; - return mstch::render(full_page, context); - } + context.insert({"address" , REMOVE_HASH_BRAKETS( + xmreg::print_address(address_info, nettype))}); + context.insert({"viewkey" , REMOVE_HASH_BRAKETS( + fmt::format("{:s}", prv_view_key))}); + context.insert({"has_total_xmr" , false}); + context.insert({"total_xmr" , string{}}); + context.insert({"key_imgs" , mstch::array{}}); - // header is public spend and keys - const size_t header_lenght = 2 * sizeof(crypto::public_key); - const size_t key_img_size = sizeof(crypto::key_image); - const size_t record_lenght = key_img_size + sizeof(crypto::signature); - const size_t chacha_length = sizeof(crypto::chacha_key); - if (decoded_raw_data.size() < header_lenght) - { - string error_msg = fmt::format("Bad data size from submitted key images raw data."); + size_t no_key_images = (decoded_raw_data.size() - header_lenght) / record_lenght; - context["has_error"] = true; - context["error_msg"] = error_msg; + //vector> signed_key_images; - return mstch::render(full_page, context); + mstch::array& key_imgs_ctx = boost::get(context["key_imgs"]); - } + for (size_t n = 0; n < no_key_images; ++n) + { + const char* record_ptr = decoded_raw_data.data() + header_lenght + n * record_lenght; + + crypto::key_image key_image + = *reinterpret_cast(record_ptr); + + crypto::signature signature + = *reinterpret_cast(record_ptr + key_img_size); + + mstch::map key_img_info { + {"key_no" , fmt::format("{:03d}", n)}, + {"key_image" , pod_to_hex(key_image)}, + {"signature" , fmt::format("{:s}", signature)}, + {"address" , xmreg::print_address( + address_info, nettype)}, + {"is_spent" , core_storage->have_tx_keyimg_as_spent(key_image)}, + {"tx_hash" , string{}} + }; - // get xmr address stored in this key image file - const account_public_address* xmr_address = - reinterpret_cast( - decoded_raw_data.data()); - address_parse_info address_info {*xmr_address, false}; + key_imgs_ctx.push_back(key_img_info); + } // for (size_t n = 0; n < no_key_images; ++n) - context.insert({"address" , REMOVE_HASH_BRAKETS( - xmreg::print_address(address_info, nettype))}); - context.insert({"viewkey" , REMOVE_HASH_BRAKETS( - fmt::format("{:s}", prv_view_key))}); - context.insert({"has_total_xmr" , false}); - context.insert({"total_xmr" , string{}}); - context.insert({"key_imgs" , mstch::array{}}); + // render the page + return mstch::render(full_page, context); +} +string +show_checkcheckrawoutput(string raw_data, string viewkey_str) +{ + clean_post_data(raw_data); - size_t no_key_images = (decoded_raw_data.size() - header_lenght) / record_lenght; + // remove white characters + boost::trim(viewkey_str); - //vector> signed_key_images; + string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); + secret_key prv_view_key; - mstch::array& key_imgs_ctx = boost::get(context["key_imgs"]); + // initalize page template context map + mstch::map context{ + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"has_error" , false}, + {"error_msg" , string{}} + }; - for (size_t n = 0; n < no_key_images; ++n) - { - const char* record_ptr = decoded_raw_data.data() + header_lenght + n * record_lenght; - - crypto::key_image key_image - = *reinterpret_cast(record_ptr); - - crypto::signature signature - = *reinterpret_cast(record_ptr + key_img_size); - - mstch::map key_img_info { - {"key_no" , fmt::format("{:03d}", n)}, - {"key_image" , pod_to_hex(key_image)}, - {"signature" , fmt::format("{:s}", signature)}, - {"address" , xmreg::print_address( - address_info, nettype)}, - {"is_spent" , core_storage->have_tx_keyimg_as_spent(key_image)}, - {"tx_hash" , string{}} - }; + // add header and footer + string full_page = template_file["checkoutputkeys"]; + add_css_style(context); - key_imgs_ctx.push_back(key_img_info); + if (viewkey_str.empty()) + { + string error_msg = fmt::format("View key not given. Cant decode " + "the outputs data without it!"); - } // for (size_t n = 0; n < no_key_images; ++n) + context["has_error"] = true; + context["error_msg"] = error_msg; - // render the page return mstch::render(full_page, context); } - string - show_checkcheckrawoutput(string raw_data, string viewkey_str) + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) { - clean_post_data(raw_data); + string error_msg = fmt::format("Cant parse the private key: " + viewkey_str); - // remove white characters - boost::trim(viewkey_str); + context["has_error"] = true; + context["error_msg"] = error_msg; - string decoded_raw_data = epee::string_encoding::base64_decode(raw_data); - secret_key prv_view_key; + return mstch::render(full_page, context); + } - // initalize page template context map - mstch::map context{ - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"has_error" , false}, - {"error_msg" , string{}} - }; + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); - // add header and footer - string full_page = template_file["checkoutputkeys"]; + string data_prefix = xmreg::make_printable(decoded_raw_data.substr(0, magiclen)); - add_css_style(context); + context["data_prefix"] = data_prefix; - if (viewkey_str.empty()) - { - string error_msg = fmt::format("View key not given. Cant decode " - "the outputs data without it!"); + if (strncmp(decoded_raw_data.c_str(), OUTPUT_EXPORT_FILE_MAGIC, magiclen) != 0) + { + string error_msg = fmt::format("This does not seem to be output keys export data."); - context["has_error"] = true; - context["error_msg"] = error_msg; + context["has_error"] = true; + context["error_msg"] = error_msg; - return mstch::render(full_page, context); - } + return mstch::render(full_page, context); + } - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - string error_msg = fmt::format("Cant parse the private key: " + viewkey_str); - context["has_error"] = true; - context["error_msg"] = error_msg; + // decrypt key images data using private view key + decoded_raw_data = xmreg::decrypt( + std::string(decoded_raw_data, magiclen), + prv_view_key, true); - return mstch::render(full_page, context); - } - const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (decoded_raw_data.empty()) + { + string error_msg = fmt::format("Failed to authenticate outputs data. " + "Maybe wrong viewkey was porvided?"); - string data_prefix = xmreg::make_printable(decoded_raw_data.substr(0, magiclen)); + context["has_error"] = true; + context["error_msg"] = error_msg; - context["data_prefix"] = data_prefix; + return mstch::render(full_page, context); + } - if (strncmp(decoded_raw_data.c_str(), OUTPUT_EXPORT_FILE_MAGIC, magiclen) != 0) - { - string error_msg = fmt::format("This does not seem to be output keys export data."); - context["has_error"] = true; - context["error_msg"] = error_msg; + // header is public spend and keys + const size_t header_lenght = 2 * sizeof(crypto::public_key); - return mstch::render(full_page, context); - } + // get xmr address stored in this key image file + const account_public_address* xmr_address = + reinterpret_cast( + decoded_raw_data.data()); + address_parse_info address_info {*xmr_address, false, false, crypto::null_hash8}; - // decrypt key images data using private view key - decoded_raw_data = xmreg::decrypt( - std::string(decoded_raw_data, magiclen), - prv_view_key, true); + context.insert({"address" , REMOVE_HASH_BRAKETS( + xmreg::print_address(address_info, nettype))}); + context.insert({"viewkey" , pod_to_hex(prv_view_key)}); + context.insert({"has_total_xmr" , false}); + context.insert({"total_xmr" , string{}}); + context.insert({"output_keys" , mstch::array{}}); + mstch::array& output_keys_ctx = boost::get(context["output_keys"]); - if (decoded_raw_data.empty()) - { - string error_msg = fmt::format("Failed to authenticate outputs data. " - "Maybe wrong viewkey was porvided?"); - context["has_error"] = true; - context["error_msg"] = error_msg; + std::vector outputs; - return mstch::render(full_page, context); - } + try + { + std::string body(decoded_raw_data, header_lenght); + std::stringstream iss; + iss << body; + boost::archive::portable_binary_iarchive ar(iss); + //boost::archive::binary_iarchive ar(iss); + ar >> outputs; - // header is public spend and keys - const size_t header_lenght = 2 * sizeof(crypto::public_key); + //size_t n_outputs = m_wallet->import_outputs(outputs); + } + catch (const std::exception &e) + { + string error_msg = fmt::format("Failed to import outputs: {:s}", e.what()); - // get xmr address stored in this key image file - const account_public_address* xmr_address = - reinterpret_cast( - decoded_raw_data.data()); + context["has_error"] = true; + context["error_msg"] = error_msg; - address_parse_info address_info {*xmr_address, false, false, crypto::null_hash8}; + return mstch::render(full_page, context); + } - context.insert({"address" , REMOVE_HASH_BRAKETS( - xmreg::print_address(address_info, nettype))}); - context.insert({"viewkey" , pod_to_hex(prv_view_key)}); - context.insert({"has_total_xmr" , false}); - context.insert({"total_xmr" , string{}}); - context.insert({"output_keys" , mstch::array{}}); + uint64_t total_xmr {0}; + uint64_t output_no {0}; - mstch::array& output_keys_ctx = boost::get(context["output_keys"]); + context["are_key_images_known"] = false; + for (const tools::wallet2::transfer_details& td: outputs) + { - std::vector outputs; + const transaction_prefix& txp = td.m_tx; - try - { - std::string body(decoded_raw_data, header_lenght); - std::stringstream iss; - iss << body; - boost::archive::portable_binary_iarchive ar(iss); - //boost::archive::binary_iarchive ar(iss); + txout_to_key txout_key = boost::get( + txp.vout[td.m_internal_output_index].target); - ar >> outputs; + uint64_t xmr_amount = td.amount(); - //size_t n_outputs = m_wallet->import_outputs(outputs); - } - catch (const std::exception &e) + // if the output is RingCT, i.e., tx version is 2 + // need to decode its amount + if (td.is_rct()) { - string error_msg = fmt::format("Failed to import outputs: {:s}", e.what()); - - context["has_error"] = true; - context["error_msg"] = error_msg; - - return mstch::render(full_page, context); - } - - uint64_t total_xmr {0}; - uint64_t output_no {0}; - - context["are_key_images_known"] = false; + // get tx associated with the given output + transaction tx; - for (const tools::wallet2::transfer_details& td: outputs) - { + if (!mcore->get_tx(td.m_txid, tx)) + { + string error_msg = fmt::format("Cant get tx of hash: {:s}", td.m_txid); - const transaction_prefix& txp = td.m_tx; + context["has_error"] = true; + context["error_msg"] = error_msg; - txout_to_key txout_key = boost::get( - txp.vout[td.m_internal_output_index].target); + return mstch::render(full_page, context); + } - uint64_t xmr_amount = td.amount(); + public_key tx_pub_key = xmreg::get_tx_pub_key_from_received_outs(tx); + std::vector additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(tx); - // if the output is RingCT, i.e., tx version is 2 - // need to decode its amount - if (td.is_rct()) + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) { - // get tx associated with the given output - transaction tx; - if (!mcore->get_tx(td.m_txid, tx)) + bool r = decode_ringct(tx.rct_signatures, + tx_pub_key, + prv_view_key, + td.m_internal_output_index, + tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask, + xmr_amount); + r = r || decode_ringct(tx.rct_signatures, + additional_tx_pub_keys[td.m_internal_output_index], + prv_view_key, + td.m_internal_output_index, + tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask, + xmr_amount); + + if (!r) { - string error_msg = fmt::format("Cant get tx of hash: {:s}", td.m_txid); + string error_msg = fmt::format( + "Cant decode RingCT for output: {:s}", + txout_key.key); context["has_error"] = true; context["error_msg"] = error_msg; @@ -3686,2265 +3810,2297 @@ public: return mstch::render(full_page, context); } - public_key tx_pub_key = xmreg::get_tx_pub_key_from_received_outs(tx); - std::vector additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(tx); + } // if (!is_coinbase(tx)) - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) - { + } // if (td.is_rct()) - bool r = decode_ringct(tx.rct_signatures, - tx_pub_key, - prv_view_key, - td.m_internal_output_index, - tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask, - xmr_amount); - r = r || decode_ringct(tx.rct_signatures, - additional_tx_pub_keys[td.m_internal_output_index], - prv_view_key, - td.m_internal_output_index, - tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask, - xmr_amount); + uint64_t blk_timestamp = core_storage + ->get_db().get_block_timestamp(td.m_block_height); - if (!r) - { - string error_msg = fmt::format( - "Cant decode RingCT for output: {:s}", - txout_key.key); + const key_image* output_key_img; - context["has_error"] = true; - context["error_msg"] = error_msg; + bool is_output_spent {false}; - return mstch::render(full_page, context); - } + if (td.m_key_image_known) + { + //are_key_images_known - } // if (!is_coinbase(tx)) + output_key_img = &td.m_key_image; - } // if (td.is_rct()) + is_output_spent = core_storage->have_tx_keyimg_as_spent(*output_key_img); - uint64_t blk_timestamp = core_storage - ->get_db().get_block_timestamp(td.m_block_height); + context["are_key_images_known"] = true; + } - const key_image* output_key_img; + mstch::map output_info { + {"output_no" , fmt::format("{:03d}", output_no)}, + {"output_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txout_key.key))}, + {"amount" , xmreg::xmr_amount_to_str(xmr_amount)}, + {"tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", td.m_txid))}, + {"timestamp" , xmreg::timestamp_to_str_gm(blk_timestamp)}, + {"is_spent" , is_output_spent}, + {"is_ringct" , td.m_rct} + }; - bool is_output_spent {false}; + ++output_no; - if (td.m_key_image_known) - { - //are_key_images_known + if (!is_output_spent) + { + total_xmr += xmr_amount; + } - output_key_img = &td.m_key_image; + output_keys_ctx.push_back(output_info); + } - is_output_spent = core_storage->have_tx_keyimg_as_spent(*output_key_img); + if (total_xmr > 0) + { + context["has_total_xmr"] = true; + context["total_xmr"] = xmreg::xmr_amount_to_str(total_xmr); + } - context["are_key_images_known"] = true; - } + return mstch::render(full_page, context);; +} - mstch::map output_info { - {"output_no" , fmt::format("{:03d}", output_no)}, - {"output_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txout_key.key))}, - {"amount" , xmreg::xmr_amount_to_str(xmr_amount)}, - {"tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", td.m_txid))}, - {"timestamp" , xmreg::timestamp_to_str_gm(blk_timestamp)}, - {"is_spent" , is_output_spent}, - {"is_ringct" , td.m_rct} - }; - ++output_no; - - if (!is_output_spent) - { - total_xmr += xmr_amount; - } +string +search(string search_text) +{ + // remove white characters + boost::trim(search_text); - output_keys_ctx.push_back(output_info); - } + string default_txt {"No such thing found: " + search_text}; - if (total_xmr > 0) - { - context["has_total_xmr"] = true; - context["total_xmr"] = xmreg::xmr_amount_to_str(total_xmr); - } + string result_html {default_txt}; - return mstch::render(full_page, context);; - } + uint64_t search_str_length = search_text.length(); + // first let try searching for tx + result_html = show_tx(search_text); - string - search(string search_text) + // nasty check if output is "Cant get" as a sign of + // a not found tx. Later need to think of something better. + if (result_html.find("Cant get") == string::npos) { - // remove white characters - boost::trim(search_text); - - string default_txt {"No such thing found: " + search_text}; - - string result_html {default_txt}; - - uint64_t search_str_length = search_text.length(); - - // first let try searching for tx - result_html = show_tx(search_text); + return result_html; + } - // nasty check if output is "Cant get" as a sign of - // a not found tx. Later need to think of something better. - if (result_html.find("Cant get") == string::npos) - { - return result_html; - } + // first check if searching for block of given height + if (search_text.size() < 12) + { + uint64_t blk_height; - // first check if searching for block of given height - if (search_text.size() < 12) + try { - uint64_t blk_height; + blk_height = boost::lexical_cast(search_text); - try - { - blk_height = boost::lexical_cast(search_text); + result_html = show_block(blk_height); - result_html = show_block(blk_height); - - // nasty check if output is "Cant get" as a sign of - // a not found tx. Later need to think of something better. - if (result_html.find("Cant get") == string::npos) - { - return result_html; - } - } - catch(boost::bad_lexical_cast &e) + // nasty check if output is "Cant get" as a sign of + // a not found tx. Later need to think of something better. + if (result_html.find("Cant get") == string::npos) { - cerr << fmt::format("Parsing {:s} into uint64_t failed", search_text) - << endl; + return result_html; } } - - // if tx search not successful, check if we are looking - // for a block with given hash - result_html = show_block(search_text); - - if (result_html.find("Cant get") == string::npos) + catch(boost::bad_lexical_cast &e) { - return result_html; + cerr << fmt::format("Parsing {:s} into uint64_t failed", search_text) + << endl; } + } - result_html = default_txt; + // if tx search not successful, check if we are looking + // for a block with given hash + result_html = show_block(search_text); + + if (result_html.find("Cant get") == string::npos) + { + return result_html; + } + result_html = default_txt; - // check if monero address is given based on its length - // if yes, then we can only show its public components - if (search_str_length == 95) - { - // parse string representing given monero address - address_parse_info address_info; - cryptonote::network_type nettype_addr {cryptonote::network_type::MAINNET}; + // check if monero address is given based on its length + // if yes, then we can only show its public components + if (search_str_length == 95) + { + // parse string representing given monero address + address_parse_info address_info; - if (search_text[0] == '9' || search_text[0] == 'A' || search_text[0] == 'B') - nettype_addr = cryptonote::network_type::TESTNET; - if (search_text[0] == '5' || search_text[0] == '7') - nettype_addr = cryptonote::network_type::STAGENET; + cryptonote::network_type nettype_addr {cryptonote::network_type::MAINNET}; - if (!xmreg::parse_str_address(search_text, address_info, nettype_addr)) - { - cerr << "Cant parse string address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; - } + if (search_text[0] == '9' || search_text[0] == 'A' || search_text[0] == 'B') + nettype_addr = cryptonote::network_type::TESTNET; + if (search_text[0] == '5' || search_text[0] == '7') + nettype_addr = cryptonote::network_type::STAGENET; - return show_address_details(address_info, nettype_addr); + if (!xmreg::parse_str_address(search_text, address_info, nettype_addr)) + { + cerr << "Cant parse string address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; } - // check if integrated monero address is given based on its length - // if yes, then show its public components search tx based on encrypted id - if (search_str_length == 106) - { + return show_address_details(address_info, nettype_addr); + } - cryptonote::account_public_address address; + // check if integrated monero address is given based on its length + // if yes, then show its public components search tx based on encrypted id + if (search_str_length == 106) + { - address_parse_info address_info; + cryptonote::account_public_address address; - if (!get_account_address_from_str(address_info, nettype, search_text)) - { - cerr << "Cant parse string integerated address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; - } + address_parse_info address_info; - return show_integrated_address_details(address_info, - address_info.payment_id, - nettype); + if (!get_account_address_from_str(address_info, nettype, search_text)) + { + cerr << "Cant parse string integerated address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; } - // all_possible_tx_hashes was field using custom lmdb database - // it was dropped, so all_possible_tx_hashes will be alwasy empty - // for now - vector>> all_possible_tx_hashes; + return show_integrated_address_details(address_info, + address_info.payment_id, + nettype); + } - result_html = show_search_results(search_text, all_possible_tx_hashes); + // all_possible_tx_hashes was field using custom lmdb database + // it was dropped, so all_possible_tx_hashes will be alwasy empty + // for now + vector>> all_possible_tx_hashes; - return result_html; - } + result_html = show_search_results(search_text, all_possible_tx_hashes); - string - show_address_details(const address_parse_info& address_info, cryptonote::network_type nettype = cryptonote::network_type::MAINNET) - { + return result_html; +} - string address_str = xmreg::print_address(address_info, nettype); - string pub_viewkey_str = fmt::format("{:s}", address_info.address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address_info.address.m_spend_public_key); +string +show_address_details(const address_parse_info& address_info, cryptonote::network_type nettype = cryptonote::network_type::MAINNET) +{ - mstch::map context { - {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, - {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, - {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, - {"is_integrated_addr" , false}, - {"testnet" , testnet}, - {"stagenet" , stagenet}, - }; + string address_str = xmreg::print_address(address_info, nettype); + string pub_viewkey_str = fmt::format("{:s}", address_info.address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address_info.address.m_spend_public_key); + + mstch::map context { + {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, + {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, + {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, + {"is_integrated_addr" , false}, + {"testnet" , testnet}, + {"stagenet" , stagenet}, + }; - add_css_style(context); + add_css_style(context); - // render the page - return mstch::render(template_file["address"], context); - } + // render the page + return mstch::render(template_file["address"], context); +} - // ; - string - show_integrated_address_details(const address_parse_info& address_info, - const crypto::hash8& encrypted_payment_id, - cryptonote::network_type nettype = cryptonote::network_type::MAINNET) - { - - string address_str = xmreg::print_address(address_info, nettype); - string pub_viewkey_str = fmt::format("{:s}", address_info.address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address_info.address.m_spend_public_key); - string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); - - mstch::map context { - {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, - {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, - {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, - {"encrypted_payment_id" , REMOVE_HASH_BRAKETS(enc_payment_id_str)}, - {"is_integrated_addr" , true}, - {"testnet" , testnet}, - {"stagenet" , stagenet}, - }; +// ; +string +show_integrated_address_details(const address_parse_info& address_info, + const crypto::hash8& encrypted_payment_id, + cryptonote::network_type nettype = cryptonote::network_type::MAINNET) +{ - add_css_style(context); + string address_str = xmreg::print_address(address_info, nettype); + string pub_viewkey_str = fmt::format("{:s}", address_info.address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address_info.address.m_spend_public_key); + string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); + + mstch::map context { + {"xmr_address" , REMOVE_HASH_BRAKETS(address_str)}, + {"public_viewkey" , REMOVE_HASH_BRAKETS(pub_viewkey_str)}, + {"public_spendkey" , REMOVE_HASH_BRAKETS(pub_spendkey_str)}, + {"encrypted_payment_id" , REMOVE_HASH_BRAKETS(enc_payment_id_str)}, + {"is_integrated_addr" , true}, + {"testnet" , testnet}, + {"stagenet" , stagenet}, + }; - // render the page - return mstch::render(template_file["address"], context); - } + add_css_style(context); - map> - search_txs(vector txs, const string& search_text) - { - map> tx_hashes; + // render the page + return mstch::render(template_file["address"], context); +} - // initlizte the map with empty results - tx_hashes["key_images"] = {}; - tx_hashes["tx_public_keys"] = {}; - tx_hashes["payments_id"] = {}; - tx_hashes["encrypted_payments_id"] = {}; - tx_hashes["output_public_keys"] = {}; +map> +search_txs(vector txs, const string& search_text) +{ + map> tx_hashes; - for (const transaction& tx: txs) - { + // initlizte the map with empty results + tx_hashes["key_images"] = {}; + tx_hashes["tx_public_keys"] = {}; + tx_hashes["payments_id"] = {}; + tx_hashes["encrypted_payments_id"] = {}; + tx_hashes["output_public_keys"] = {}; - tx_details txd = get_tx_details(tx); + for (const transaction& tx: txs) + { - string tx_hash_str = pod_to_hex(txd.hash); + tx_details txd = get_tx_details(tx); - // check if any key_image matches the search_text + string tx_hash_str = pod_to_hex(txd.hash); - vector::iterator it1 = - find_if(begin(txd.input_key_imgs), end(txd.input_key_imgs), - [&](const txin_to_key& key_img) - { - return pod_to_hex(key_img.k_image) == search_text; - }); + // check if any key_image matches the search_text - if (it1 != txd.input_key_imgs.end()) - { - tx_hashes["key_images"].push_back(tx_hash_str); - } + vector::iterator it1 = + find_if(begin(txd.input_key_imgs), end(txd.input_key_imgs), + [&](const txin_to_key& key_img) + { + return pod_to_hex(key_img.k_image) == search_text; + }); - // check if tx_public_key matches the search_text + if (it1 != txd.input_key_imgs.end()) + { + tx_hashes["key_images"].push_back(tx_hash_str); + } - if (pod_to_hex(txd.pk) == search_text) - { - tx_hashes["tx_public_keys"].push_back(tx_hash_str); - } + // check if tx_public_key matches the search_text - // check if payments_id matches the search_text + if (pod_to_hex(txd.pk) == search_text) + { + tx_hashes["tx_public_keys"].push_back(tx_hash_str); + } - if (pod_to_hex(txd.payment_id) == search_text) - { - tx_hashes["payment_id"].push_back(tx_hash_str); - } + // check if payments_id matches the search_text - // check if encrypted_payments_id matches the search_text + if (pod_to_hex(txd.payment_id) == search_text) + { + tx_hashes["payment_id"].push_back(tx_hash_str); + } - if (pod_to_hex(txd.payment_id8) == search_text) - { - tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); - } + // check if encrypted_payments_id matches the search_text - // check if output_public_keys matche the search_text + if (pod_to_hex(txd.payment_id8) == search_text) + { + tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); + } - vector>::iterator it2 = - find_if(begin(txd.output_pub_keys), end(txd.output_pub_keys), - [&](const pair& tx_out_pk) - { - return pod_to_hex(tx_out_pk.first.key) == search_text; - }); + // check if output_public_keys matche the search_text - if (it2 != txd.output_pub_keys.end()) - { - tx_hashes["output_public_keys"].push_back(tx_hash_str); - } + vector>::iterator it2 = + find_if(begin(txd.output_pub_keys), end(txd.output_pub_keys), + [&](const pair& tx_out_pk) + { + return pod_to_hex(tx_out_pk.first.key) == search_text; + }); + if (it2 != txd.output_pub_keys.end()) + { + tx_hashes["output_public_keys"].push_back(tx_hash_str); } - return tx_hashes; - } - string - show_search_results(const string& search_text, - const vector>>& all_possible_tx_hashes) + return tx_hashes; + +} + +string +show_search_results(const string& search_text, + const vector>>& all_possible_tx_hashes) +{ + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"search_text" , search_text}, + {"no_results" , true}, + {"to_many_results" , false} + }; + + for (const pair>& found_txs: all_possible_tx_hashes) { + // define flag, e.g., has_key_images denoting that + // tx hashes for key_image searched were found + context.insert({"has_" + found_txs.first, !found_txs.second.empty()}); - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"search_text" , search_text}, - {"no_results" , true}, - {"to_many_results" , false} - }; + // cout << "found_txs.first: " << found_txs.first << endl; - for (const pair>& found_txs: all_possible_tx_hashes) - { - // define flag, e.g., has_key_images denoting that - // tx hashes for key_image searched were found - context.insert({"has_" + found_txs.first, !found_txs.second.empty()}); + // insert new array based on what we found to context if not exist + pair< mstch::map::iterator, bool> res + = context.insert({found_txs.first, mstch::array{}}); - // cout << "found_txs.first: " << found_txs.first << endl; + if (!found_txs.second.empty()) + { - // insert new array based on what we found to context if not exist - pair< mstch::map::iterator, bool> res - = context.insert({found_txs.first, mstch::array{}}); + uint64_t tx_i {0}; - if (!found_txs.second.empty()) + // for each found tx_hash, get the corresponding tx + // and its details, and put into mstch for rendering + for (const string& tx_hash: found_txs.second) { - uint64_t tx_i {0}; + crypto::hash tx_hash_pod; - // for each found tx_hash, get the corresponding tx - // and its details, and put into mstch for rendering - for (const string& tx_hash: found_txs.second) - { + epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); - crypto::hash tx_hash_pod; + transaction tx; - epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); + uint64_t blk_height {0}; - transaction tx; + int64_t blk_timestamp; - uint64_t blk_height {0}; + // first check in the blockchain + if (mcore->get_tx(tx_hash, tx)) + { - int64_t blk_timestamp; + // get timestamp of the tx's block + blk_height = core_storage + ->get_db().get_tx_block_height(tx_hash_pod); - // first check in the blockchain - if (mcore->get_tx(tx_hash, tx)) - { + blk_timestamp = core_storage + ->get_db().get_block_timestamp(blk_height); - // get timestamp of the tx's block - blk_height = core_storage - ->get_db().get_tx_block_height(tx_hash_pod); + } + else + { + // check in mempool if tx_hash not found in the + // blockchain + vector found_txs; - blk_timestamp = core_storage - ->get_db().get_block_timestamp(blk_height); + search_mempool(tx_hash_pod, found_txs); + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).tx; } else { - // check in mempool if tx_hash not found in the - // blockchain - vector found_txs; - - search_mempool(tx_hash_pod, found_txs); - - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).tx; - } - else - { - return string("Cant get tx of hash (show_search_results): " + tx_hash); - } - - // tx in mempool have no blk_timestamp - // but can use their recive time - blk_timestamp = found_txs.at(0).receive_time; - + return string("Cant get tx of hash (show_search_results): " + tx_hash); } - tx_details txd = get_tx_details(tx); + // tx in mempool have no blk_timestamp + // but can use their recive time + blk_timestamp = found_txs.at(0).receive_time; - mstch::map txd_map = txd.get_mstch_map(); + } + tx_details txd = get_tx_details(tx); - // add the timestamp to tx mstch map - txd_map.insert({"timestamp", xmreg::timestamp_to_str_gm(blk_timestamp)}); + mstch::map txd_map = txd.get_mstch_map(); - boost::get((res.first)->second).push_back(txd_map); - // dont show more than 500 results - if (tx_i > 500) - { - context["to_many_results"] = true; - break; - } + // add the timestamp to tx mstch map + txd_map.insert({"timestamp", xmreg::timestamp_to_str_gm(blk_timestamp)}); + + boost::get((res.first)->second).push_back(txd_map); - ++tx_i; + // dont show more than 500 results + if (tx_i > 500) + { + context["to_many_results"] = true; + break; } - // if found something, set this flag to indicate this fact - context["no_results"] = false; + ++tx_i; } + + // if found something, set this flag to indicate this fact + context["no_results"] = false; } + } - // add header and footer - string full_page = template_file["search_results"]; + // add header and footer + string full_page = template_file["search_results"]; - // read partial for showing details of tx(s) found - map partials { - {"tx_table_head", template_file["tx_table_header"]}, - {"tx_table_row" , template_file["tx_table_row"]} - }; + // read partial for showing details of tx(s) found + map partials { + {"tx_table_head", template_file["tx_table_header"]}, + {"tx_table_row" , template_file["tx_table_row"]} + }; - add_css_style(context); + add_css_style(context); + + // render the page + return mstch::render(full_page, context, partials); +} + +string +get_js_file(string const& fname) +{ + if (template_file.count(fname)) + return template_file[fname]; + + return string{}; +} - // render the page - return mstch::render(full_page, context, partials); - } - string - get_js_file(string const& fname) - { - if (template_file.count(fname)) - return template_file[fname]; - return string{}; - } +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_transaction(string tx_hash_str) +{ + json j_response { + {"status", "fail"}, + {"data" , json {}} + }; + json& j_data = j_response["data"]; + // parse tx hash string to hash object + crypto::hash tx_hash; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_transaction(string tx_hash_str) + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) { - json j_response { - {"status", "fail"}, - {"data" , json {}} - }; + j_data["title"] = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); + return j_response; + } - json& j_data = j_response["data"]; + // get transaction + transaction tx; - // parse tx hash string to hash object - crypto::hash tx_hash; + // flag to indicate if tx is in mempool + bool found_in_mempool {false}; - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) - { - j_data["title"] = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); - return j_response; - } + // for tx in blocks we get block timestamp + // for tx in mempool we get recievive time + uint64_t tx_timestamp {0}; - // get transaction - transaction tx; + if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) + { + j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); + return j_response; + } + + uint64_t block_height {0}; + uint64_t is_coinbase_tx = is_coinbase(tx); + uint64_t no_confirmations {0}; + + if (found_in_mempool == false) + { - // flag to indicate if tx is in mempool - bool found_in_mempool {false}; + block blk; + + try + { + // get block cointaining this tx + block_height = core_storage->get_db().get_tx_block_height(tx_hash); - // for tx in blocks we get block timestamp - // for tx in mempool we get recievive time - uint64_t tx_timestamp {0}; + if (!mcore->get_block_by_height(block_height, blk)) + { + j_data["title"] = fmt::format("Cant get block: {:d}", block_height); + return j_response; + } - if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) + tx_timestamp = blk.timestamp; + } + catch (const exception& e) { - j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); + j_response["status"] = "error"; + j_response["message"] = fmt::format("Tx does not exist in blockchain, " + "but was there before: {:s}", tx_hash_str); return j_response; } + } - uint64_t block_height {0}; - uint64_t is_coinbase_tx = is_coinbase(tx); - uint64_t no_confirmations {0}; + string blk_timestamp_utc = xmreg::timestamp_to_str_gm(tx_timestamp); - if (found_in_mempool == false) - { + // get the current blockchain height. Just to check + uint64_t bc_height = core_storage->get_current_blockchain_height(); - block blk; + tx_details txd = get_tx_details(tx, is_coinbase_tx, block_height, bc_height); - try - { - // get block cointaining this tx - block_height = core_storage->get_db().get_tx_block_height(tx_hash); + json outputs; - if (!mcore->get_block_by_height(block_height, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:d}", block_height); - return j_response; - } + for (const auto& output: txd.output_pub_keys) + { + outputs.push_back(json { + {"public_key", pod_to_hex(output.first.key)}, + {"amount" , output.second} + }); + } - tx_timestamp = blk.timestamp; - } - catch (const exception& e) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Tx does not exist in blockchain, " - "but was there before: {:s}", tx_hash_str); - return j_response; - } - } + json inputs; - string blk_timestamp_utc = xmreg::timestamp_to_str_gm(tx_timestamp); + for (const txin_to_key &in_key: txd.input_key_imgs) + { - // get the current blockchain height. Just to check - uint64_t bc_height = core_storage->get_current_blockchain_height(); + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - tx_details txd = get_tx_details(tx, is_coinbase_tx, block_height, bc_height); + // get public keys of outputs used in the mixins that match to the offests + std::vector outputs; - json outputs; + try + { + // before proceeding with geting the outputs based on the amount and absolute offset + // check how many outputs there are for that amount + // go to next input if a too large offset was found + if (are_absolute_offsets_good(absolute_offsets, in_key) == false) + continue; - for (const auto& output: txd.output_pub_keys) + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + outputs); + } + catch (const OUTPUT_DNE &e) { - outputs.push_back(json { - {"public_key", pod_to_hex(output.first.key)}, - {"amount" , output.second} - }); + j_response["status"] = "error"; + j_response["message"] = "Failed to retrive outputs (mixins) used in key images"; + return j_response; } - json inputs; + inputs.push_back(json { + {"key_image" , pod_to_hex(in_key.k_image)}, + {"amount" , in_key.amount}, + {"mixins" , json {}} + }); + + json& mixins = inputs.back()["mixins"]; - for (const txin_to_key &in_key: txd.input_key_imgs) + for (const output_data_t& output_data: outputs) { + mixins.push_back(json { + {"public_key" , pod_to_hex(output_data.pubkey)}, + {"block_no" , output_data.height}, + }); + } + } - // get absolute offsets of mixins - std::vector absolute_offsets - = cryptonote::relative_output_offsets_to_absolute( - in_key.key_offsets); - - // get public keys of outputs used in the mixins that match to the offests - std::vector outputs; + if (found_in_mempool == false) + { + no_confirmations = txd.no_confirmations; + } - try - { - // before proceeding with geting the outputs based on the amount and absolute offset - // check how many outputs there are for that amount - // go to next input if a too large offset was found - if (are_absolute_offsets_good(absolute_offsets, in_key) == false) - continue; + // get tx from tx fetched. can be use to double check + // if what we return in the json response agrees with + // what tx_hash was requested + string tx_hash_str_again = pod_to_hex(get_transaction_hash(tx)); - core_storage->get_db().get_output_key(in_key.amount, - absolute_offsets, - outputs); - } - catch (const OUTPUT_DNE &e) - { - j_response["status"] = "error"; - j_response["message"] = "Failed to retrive outputs (mixins) used in key images"; - return j_response; - } + // get basic tx info + j_data = get_tx_json(tx, txd); - inputs.push_back(json { - {"key_image" , pod_to_hex(in_key.k_image)}, - {"amount" , in_key.amount}, - {"mixins" , json {}} - }); + // append additional info from block, as we don't + // return block data in this function + j_data["timestamp"] = tx_timestamp; + j_data["timestamp_utc"] = blk_timestamp_utc; + j_data["block_height"] = block_height; + j_data["confirmations"] = no_confirmations; + j_data["outputs"] = outputs; + j_data["inputs"] = inputs; + j_data["current_height"] = bc_height; - json& mixins = inputs.back()["mixins"]; + j_response["status"] = "success"; - for (const output_data_t& output_data: outputs) - { - mixins.push_back(json { - {"public_key" , pod_to_hex(output_data.pubkey)}, - {"block_no" , output_data.height}, - }); - } - } + return j_response; +} - if (found_in_mempool == false) - { - no_confirmations = txd.no_confirmations; - } - // get tx from tx fetched. can be use to double check - // if what we return in the json response agrees with - // what tx_hash was requested - string tx_hash_str_again = pod_to_hex(get_transaction_hash(tx)); - // get basic tx info - j_data = get_tx_json(tx, txd); +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_rawtransaction(string tx_hash_str) +{ + json j_response { + {"status", "fail"}, + {"data" , json {}} + }; - // append additional info from block, as we don't - // return block data in this function - j_data["timestamp"] = tx_timestamp; - j_data["timestamp_utc"] = blk_timestamp_utc; - j_data["block_height"] = block_height; - j_data["confirmations"] = no_confirmations; - j_data["outputs"] = outputs; - j_data["inputs"] = inputs; - j_data["current_height"] = bc_height; + json& j_data = j_response["data"]; - j_response["status"] = "success"; + // parse tx hash string to hash object + crypto::hash tx_hash; + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + j_data["title"] = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); return j_response; } + // get transaction + transaction tx; + // flag to indicate if tx is in mempool + bool found_in_mempool {false}; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_rawtransaction(string tx_hash_str) + // for tx in blocks we get block timestamp + // for tx in mempool we get recievive time + uint64_t tx_timestamp {0}; + + if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) { - json j_response { - {"status", "fail"}, - {"data" , json {}} - }; + j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); + return j_response; + } - json& j_data = j_response["data"]; + if (found_in_mempool == false) + { - // parse tx hash string to hash object - crypto::hash tx_hash; + block blk; - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + try { - j_data["title"] = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); - return j_response; - } - - // get transaction - transaction tx; - - // flag to indicate if tx is in mempool - bool found_in_mempool {false}; + // get block cointaining this tx + uint64_t block_height = core_storage->get_db().get_tx_block_height(tx_hash); - // for tx in blocks we get block timestamp - // for tx in mempool we get recievive time - uint64_t tx_timestamp {0}; - - if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) + if (!mcore->get_block_by_height(block_height, blk)) + { + j_data["title"] = fmt::format("Cant get block: {:d}", block_height); + return j_response; + } + } + catch (const exception& e) { - j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); - return j_response; - } - - if (found_in_mempool == false) - { - - block blk; - - try - { - // get block cointaining this tx - uint64_t block_height = core_storage->get_db().get_tx_block_height(tx_hash); - - if (!mcore->get_block_by_height(block_height, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:d}", block_height); - return j_response; - } - } - catch (const exception& e) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Tx does not exist in blockchain, " - "but was there before: {:s}", - tx_hash_str); - return j_response; - } - } - - // get raw tx json as in monero - - try - { - j_data = json::parse(obj_to_json_str(tx)); - } - catch (std::invalid_argument& e) - { - j_response["status"] = "error"; - j_response["message"] = "Faild parsing raw tx data into json"; + j_response["status"] = "error"; + j_response["message"] = fmt::format("Tx does not exist in blockchain, " + "but was there before: {:s}", + tx_hash_str); return j_response; } + } - j_response["status"] = "success"; + // get raw tx json as in monero + try + { + j_data = json::parse(obj_to_json_str(tx)); + } + catch (std::invalid_argument& e) + { + j_response["status"] = "error"; + j_response["message"] = "Faild parsing raw tx data into json"; return j_response; } + j_response["status"] = "success"; - json - json_detailedtransaction(string tx_hash_str) - { - json j_response { - {"status", "fail"}, - {"data" , json {}} - }; + return j_response; +} - json& j_data = j_response["data"]; - transaction tx; +json +json_detailedtransaction(string tx_hash_str) +{ + json j_response { + {"status", "fail"}, + {"data" , json {}} + }; - bool found_in_mempool {false}; - uint64_t tx_timestamp {0}; - string error_message; + json& j_data = j_response["data"]; - if (!find_tx_for_json(tx_hash_str, tx, found_in_mempool, tx_timestamp, error_message)) - { - j_data["title"] = error_message; - return j_response; - } + transaction tx; - // get detailed tx information - mstch::map tx_context = construct_tx_context(tx, 1 /*full detailed */); - - // remove some page specific and html stuff - tx_context.erase("timescales"); - tx_context.erase("tx_json"); - tx_context.erase("tx_json_raw"); - tx_context.erase("enable_mixins_details"); - tx_context.erase("with_ring_signatures"); - tx_context.erase("show_part_of_inputs"); - tx_context.erase("show_more_details_link"); - tx_context.erase("max_no_of_inputs_to_show"); - tx_context.erase("inputs_xmr_sum_not_zero"); - tx_context.erase("have_raw_tx"); - tx_context.erase("have_any_unknown_amount"); - tx_context.erase("has_error"); - tx_context.erase("error_msg"); - tx_context.erase("server_time"); - tx_context.erase("construction_time"); - - j_data = tx_context; - - j_response["status"] = "success"; + bool found_in_mempool {false}; + uint64_t tx_timestamp {0}; + string error_message; + if (!find_tx_for_json(tx_hash_str, tx, found_in_mempool, tx_timestamp, error_message)) + { + j_data["title"] = error_message; return j_response; } - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_block(string block_no_or_hash) - { - json j_response { - {"status", "fail"}, - {"data" , json {}} - }; - - json& j_data = j_response["data"]; - - uint64_t current_blockchain_height - = core_storage->get_current_blockchain_height(); - - uint64_t block_height {0}; + // get detailed tx information + mstch::map tx_context = construct_tx_context(tx, 1 /*full detailed */); + + // remove some page specific and html stuff + tx_context.erase("timescales"); + tx_context.erase("tx_json"); + tx_context.erase("tx_json_raw"); + tx_context.erase("enable_mixins_details"); + tx_context.erase("with_ring_signatures"); + tx_context.erase("show_part_of_inputs"); + tx_context.erase("show_more_details_link"); + tx_context.erase("max_no_of_inputs_to_show"); + tx_context.erase("inputs_xmr_sum_not_zero"); + tx_context.erase("have_raw_tx"); + tx_context.erase("have_any_unknown_amount"); + tx_context.erase("has_error"); + tx_context.erase("error_msg"); + tx_context.erase("server_time"); + tx_context.erase("construction_time"); + + j_data = tx_context; + + j_response["status"] = "success"; + + return j_response; +} - crypto::hash blk_hash; +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_block(string block_no_or_hash) +{ + json j_response { + {"status", "fail"}, + {"data" , json {}} + }; - block blk; + json& j_data = j_response["data"]; - if (block_no_or_hash.length() <= 8) - { - // we have something that seems to be a block number - try - { - block_height = boost::lexical_cast(block_no_or_hash); - } - catch (const boost::bad_lexical_cast& e) - { - j_data["title"] = fmt::format( - "Cant parse block number: {:s}", block_no_or_hash); - return j_response; - } + uint64_t current_blockchain_height + = core_storage->get_current_blockchain_height(); - if (block_height > current_blockchain_height) - { - j_data["title"] = fmt::format( - "Requested block is higher than blockchain:" - " {:d}, {:d}", block_height,current_blockchain_height); - return j_response; - } + uint64_t block_height {0}; - if (!mcore->get_block_by_height(block_height, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:d}", block_height); - return j_response; - } + crypto::hash blk_hash; - blk_hash = core_storage->get_block_id_by_height(block_height); + block blk; + if (block_no_or_hash.length() <= 8) + { + // we have something that seems to be a block number + try + { + block_height = boost::lexical_cast(block_no_or_hash); } - else if (block_no_or_hash.length() == 64) + catch (const boost::bad_lexical_cast& e) { - // this seems to be block hash - if (!xmreg::parse_str_secret_key(block_no_or_hash, blk_hash)) - { - j_data["title"] = fmt::format("Cant parse blk hash: {:s}", block_no_or_hash); - return j_response; - } - - if (!core_storage->get_block_by_hash(blk_hash, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:s}", blk_hash); - return j_response; - } + j_data["title"] = fmt::format( + "Cant parse block number: {:s}", block_no_or_hash); + return j_response; + } - block_height = core_storage->get_db().get_block_height(blk_hash); + if (block_height > current_blockchain_height) + { + j_data["title"] = fmt::format( + "Requested block is higher than blockchain:" + " {:d}, {:d}", block_height,current_blockchain_height); + return j_response; } - else + + if (!mcore->get_block_by_height(block_height, blk)) { - j_data["title"] = fmt::format("Cant find blk using search string: {:s}", block_no_or_hash); + j_data["title"] = fmt::format("Cant get block: {:d}", block_height); return j_response; } + blk_hash = core_storage->get_block_id_by_height(block_height); - // get block size in bytes - uint64_t blk_size = core_storage->get_db().get_block_size(block_height); + } + else if (block_no_or_hash.length() == 64) + { + // this seems to be block hash + if (!xmreg::parse_str_secret_key(block_no_or_hash, blk_hash)) + { + j_data["title"] = fmt::format("Cant parse blk hash: {:s}", block_no_or_hash); + return j_response; + } - // miner reward tx - transaction coinbase_tx = blk.miner_tx; + if (!core_storage->get_block_by_hash(blk_hash, blk)) + { + j_data["title"] = fmt::format("Cant get block: {:s}", blk_hash); + return j_response; + } - // transcation in the block - vector tx_hashes = blk.tx_hashes; + block_height = core_storage->get_db().get_block_height(blk_hash); + } + else + { + j_data["title"] = fmt::format("Cant find blk using search string: {:s}", block_no_or_hash); + return j_response; + } - // sum of all transactions in the block - uint64_t sum_fees = 0; - // get tx details for the coinbase tx, i.e., miners reward - tx_details txd_coinbase = get_tx_details(blk.miner_tx, true, - block_height, - current_blockchain_height); + // get block size in bytes + uint64_t blk_size = core_storage->get_db().get_block_size(block_height); - json j_txs; + // miner reward tx + transaction coinbase_tx = blk.miner_tx; - j_txs.push_back(get_tx_json(coinbase_tx, txd_coinbase)); + // transcation in the block + vector tx_hashes = blk.tx_hashes; - // for each transaction in the block - for (size_t i = 0; i < blk.tx_hashes.size(); ++i) - { - const crypto::hash &tx_hash = blk.tx_hashes.at(i); + // sum of all transactions in the block + uint64_t sum_fees = 0; - // get transaction - transaction tx; + // get tx details for the coinbase tx, i.e., miners reward + tx_details txd_coinbase = get_tx_details(blk.miner_tx, true, + block_height, + current_blockchain_height); - if (!mcore->get_tx(tx_hash, tx)) - { - j_response["status"] = "error"; - j_response["message"] - = fmt::format("Cant get transactions in block: {:d}", block_height); - return j_response; - } + json j_txs; - tx_details txd = get_tx_details(tx, false, - block_height, - current_blockchain_height); + j_txs.push_back(get_tx_json(coinbase_tx, txd_coinbase)); - j_txs.push_back(get_tx_json(tx, txd)); + // for each transaction in the block + for (size_t i = 0; i < blk.tx_hashes.size(); ++i) + { + const crypto::hash &tx_hash = blk.tx_hashes.at(i); + + // get transaction + transaction tx; - // add fee to the rest - sum_fees += txd.fee; + if (!mcore->get_tx(tx_hash, tx)) + { + j_response["status"] = "error"; + j_response["message"] + = fmt::format("Cant get transactions in block: {:d}", block_height); + return j_response; } - j_data = json { - {"block_height" , block_height}, - {"hash" , pod_to_hex(blk_hash)}, - {"timestamp" , blk.timestamp}, - {"timestamp_utc" , xmreg::timestamp_to_str_gm(blk.timestamp)}, - {"block_height" , block_height}, - {"size" , blk_size}, - {"txs" , j_txs}, - {"current_height", current_blockchain_height} - }; + tx_details txd = get_tx_details(tx, false, + block_height, + current_blockchain_height); - j_response["status"] = "success"; + j_txs.push_back(get_tx_json(tx, txd)); - return j_response; + // add fee to the rest + sum_fees += txd.fee; } + j_data = json { + {"block_height" , block_height}, + {"hash" , pod_to_hex(blk_hash)}, + {"timestamp" , blk.timestamp}, + {"timestamp_utc" , xmreg::timestamp_to_str_gm(blk.timestamp)}, + {"block_height" , block_height}, + {"size" , blk_size}, + {"txs" , j_txs}, + {"current_height", current_blockchain_height} + }; + j_response["status"] = "success"; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_rawblock(string block_no_or_hash) - { - json j_response { - {"status", "fail"}, - {"data" , json {}} - }; - - json& j_data = j_response["data"]; + return j_response; +} - uint64_t current_blockchain_height - = core_storage->get_current_blockchain_height(); - uint64_t block_height {0}; - crypto::hash blk_hash; +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_rawblock(string block_no_or_hash) +{ + json j_response { + {"status", "fail"}, + {"data" , json {}} + }; - block blk; + json& j_data = j_response["data"]; - if (block_no_or_hash.length() <= 8) - { - // we have something that seems to be a block number - try - { - block_height = boost::lexical_cast(block_no_or_hash); - } - catch (const boost::bad_lexical_cast& e) - { - j_data["title"] = fmt::format( - "Cant parse block number: {:s}", block_no_or_hash); - return j_response; - } + uint64_t current_blockchain_height + = core_storage->get_current_blockchain_height(); - if (block_height > current_blockchain_height) - { - j_data["title"] = fmt::format( - "Requested block is higher than blockchain:" - " {:d}, {:d}", block_height,current_blockchain_height); - return j_response; - } + uint64_t block_height {0}; - if (!mcore->get_block_by_height(block_height, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:d}", block_height); - return j_response; - } + crypto::hash blk_hash; - blk_hash = core_storage->get_block_id_by_height(block_height); + block blk; - } - else if (block_no_or_hash.length() == 64) + if (block_no_or_hash.length() <= 8) + { + // we have something that seems to be a block number + try { - // this seems to be block hash - if (!xmreg::parse_str_secret_key(block_no_or_hash, blk_hash)) - { - j_data["title"] = fmt::format("Cant parse blk hash: {:s}", block_no_or_hash); - return j_response; - } - - if (!core_storage->get_block_by_hash(blk_hash, blk)) - { - j_data["title"] = fmt::format("Cant get block: {:s}", blk_hash); - return j_response; - } - - block_height = core_storage->get_db().get_block_height(blk_hash); + block_height = boost::lexical_cast(block_no_or_hash); } - else + catch (const boost::bad_lexical_cast& e) { - j_data["title"] = fmt::format("Cant find blk using search string: {:s}", block_no_or_hash); + j_data["title"] = fmt::format( + "Cant parse block number: {:s}", block_no_or_hash); return j_response; } - // get raw tx json as in monero - - try + if (block_height > current_blockchain_height) { - j_data = json::parse(obj_to_json_str(blk)); + j_data["title"] = fmt::format( + "Requested block is higher than blockchain:" + " {:d}, {:d}", block_height,current_blockchain_height); + return j_response; } - catch (std::invalid_argument& e) + + if (!mcore->get_block_by_height(block_height, blk)) { - j_response["status"] = "error"; - j_response["message"] = "Faild parsing raw blk data into json"; + j_data["title"] = fmt::format("Cant get block: {:d}", block_height); return j_response; } - j_response["status"] = "success"; + blk_hash = core_storage->get_block_id_by_height(block_height); - return j_response; } - - - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_transactions(string _page, string _limit) + else if (block_no_or_hash.length() == 64) { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; - - json& j_data = j_response["data"]; - - // parse page and limit into numbers - - uint64_t page {0}; - uint64_t limit {0}; - - try + // this seems to be block hash + if (!xmreg::parse_str_secret_key(block_no_or_hash, blk_hash)) { - page = boost::lexical_cast(_page); - limit = boost::lexical_cast(_limit); + j_data["title"] = fmt::format("Cant parse blk hash: {:s}", block_no_or_hash); + return j_response; } - catch (const boost::bad_lexical_cast& e) + + if (!core_storage->get_block_by_hash(blk_hash, blk)) { - j_data["title"] = fmt::format( - "Cant parse page and/or limit numbers: {:s}, {:s}", _page, _limit); + j_data["title"] = fmt::format("Cant get block: {:s}", blk_hash); return j_response; } - // enforce maximum number of blocks per page to 100 - limit = limit > 100 ? 100 : limit; - - //get current server timestamp - server_timestamp = std::time(nullptr); + block_height = core_storage->get_db().get_block_height(blk_hash); + } + else + { + j_data["title"] = fmt::format("Cant find blk using search string: {:s}", block_no_or_hash); + return j_response; + } - uint64_t local_copy_server_timestamp = server_timestamp; + // get raw tx json as in monero - uint64_t height = core_storage->get_current_blockchain_height(); + try + { + j_data = json::parse(obj_to_json_str(blk)); + } + catch (std::invalid_argument& e) + { + j_response["status"] = "error"; + j_response["message"] = "Faild parsing raw blk data into json"; + return j_response; + } - // calculate starting and ending block numbers to show - int64_t start_height = height - limit * (page + 1); + j_response["status"] = "success"; - // check if start height is not below range - start_height = start_height < 0 ? 0 : start_height; + return j_response; +} - int64_t end_height = start_height + limit - 1; - // loop index - int64_t i = end_height; +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_transactions(string _page, string _limit) +{ + json j_response { + {"status", "fail"}, + {"data", json {}} + }; - j_data["blocks"] = json::array(); - json& j_blocks = j_data["blocks"]; + json& j_data = j_response["data"]; - // iterate over last no_of_last_blocks of blocks - while (i >= start_height) - { - // get block at the given height i - block blk; + // parse page and limit into numbers - if (!mcore->get_block_by_height(i, blk)) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Cant get block: {:d}", i); - return j_response; - } + uint64_t page {0}; + uint64_t limit {0}; - // get block size in bytes - double blk_size = core_storage->get_db().get_block_size(i); + try + { + page = boost::lexical_cast(_page); + limit = boost::lexical_cast(_limit); + } + catch (const boost::bad_lexical_cast& e) + { + j_data["title"] = fmt::format( + "Cant parse page and/or limit numbers: {:s}, {:s}", _page, _limit); + return j_response; + } - crypto::hash blk_hash = core_storage->get_block_id_by_height(i); + // enforce maximum number of blocks per page to 100 + limit = limit > 100 ? 100 : limit; - // get block age - pair age = get_age(local_copy_server_timestamp, blk.timestamp); + //get current server timestamp + server_timestamp = std::time(nullptr); - j_blocks.push_back(json { - {"height" , i}, - {"hash" , pod_to_hex(blk_hash)}, - {"age" , age.first}, - {"size" , blk_size}, - {"timestamp" , blk.timestamp}, - {"timestamp_utc", xmreg::timestamp_to_str_gm(blk.timestamp)}, - {"txs" , json::array()} - }); + uint64_t local_copy_server_timestamp = server_timestamp; - json& j_txs = j_blocks.back()["txs"]; + uint64_t height = core_storage->get_current_blockchain_height(); - vector blk_txs {blk.miner_tx}; - vector missed_txs; + // calculate starting and ending block numbers to show + int64_t start_height = height - limit * (page + 1); - if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Cant get transactions in block: {:d}", i); - return j_response; - } + // check if start height is not below range + start_height = start_height < 0 ? 0 : start_height; - (void) missed_txs; + int64_t end_height = start_height + limit - 1; - for(auto it = blk_txs.begin(); it != blk_txs.end(); ++it) - { - const cryptonote::transaction &tx = *it; + // loop index + int64_t i = end_height; - const tx_details& txd = get_tx_details(tx, false, i, height); + j_data["blocks"] = json::array(); + json& j_blocks = j_data["blocks"]; - j_txs.push_back(get_tx_json(tx, txd)); - } + // iterate over last no_of_last_blocks of blocks + while (i >= start_height) + { + // get block at the given height i + block blk; - --i; + if (!mcore->get_block_by_height(i, blk)) + { + j_response["status"] = "error"; + j_response["message"] = fmt::format("Cant get block: {:d}", i); + return j_response; } - j_data["page"] = page; - j_data["limit"] = limit; - j_data["current_height"] = height; - - j_data["total_page_no"] = limit > 0 ? (height / limit) : 0; - - j_response["status"] = "success"; - - return j_response; - } + // get block size in bytes + double blk_size = core_storage->get_db().get_block_size(i); + crypto::hash blk_hash = core_storage->get_block_id_by_height(i); - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_mempool(string _page, string _limit) - { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + // get block age + pair age = get_age(local_copy_server_timestamp, blk.timestamp); - json& j_data = j_response["data"]; + j_blocks.push_back(json { + {"height" , i}, + {"hash" , pod_to_hex(blk_hash)}, + {"age" , age.first}, + {"size" , blk_size}, + {"timestamp" , blk.timestamp}, + {"timestamp_utc", xmreg::timestamp_to_str_gm(blk.timestamp)}, + {"txs" , json::array()} + }); - // parse page and limit into numbers + json& j_txs = j_blocks.back()["txs"]; - uint64_t page {0}; - uint64_t limit {0}; + vector blk_txs {blk.miner_tx}; + vector missed_txs; - try - { - page = boost::lexical_cast(_page); - limit = boost::lexical_cast(_limit); - } - catch (const boost::bad_lexical_cast& e) + if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) { - j_data["title"] = fmt::format( - "Cant parse page and/or limit numbers: {:s}, {:s}", _page, _limit); + j_response["status"] = "error"; + j_response["message"] = fmt::format("Cant get transactions in block: {:d}", i); return j_response; } - //get current server timestamp - server_timestamp = std::time(nullptr); + (void) missed_txs; - uint64_t local_copy_server_timestamp = server_timestamp; - - uint64_t height = core_storage->get_current_blockchain_height(); - - // get mempool tx from mempoolstatus thread - vector mempool_data - = MempoolStatus::get_mempool_txs(); + for(auto it = blk_txs.begin(); it != blk_txs.end(); ++it) + { + const cryptonote::transaction &tx = *it; - uint64_t no_mempool_txs = mempool_data.size(); + const tx_details& txd = get_tx_details(tx, false, i, height); - // calculate starting and ending block numbers to show - int64_t start_height = limit * page; + j_txs.push_back(get_tx_json(tx, txd)); + } - int64_t end_height = start_height + limit; + --i; + } - end_height = end_height > no_mempool_txs ? no_mempool_txs : end_height; + j_data["page"] = page; + j_data["limit"] = limit; + j_data["current_height"] = height; - // check if start height is not below range - start_height = start_height > end_height ? end_height - limit : start_height; + j_data["total_page_no"] = limit > 0 ? (height / limit) : 0; - start_height = start_height < 0 ? 0 : start_height; + j_response["status"] = "success"; - // loop index - int64_t i = start_height; + return j_response; +} - json j_txs = json::array(); - // for each transaction in the memory pool in current page - while (i < end_height) - { - const MempoolStatus::mempool_tx* mempool_tx {nullptr}; +/* +* Lets use this json api convention for success and error +* https://labs.omniti.com/labs/jsend +*/ +json +json_mempool(string _page, string _limit) +{ + json j_response { + {"status", "fail"}, + {"data", json {}} + }; - try - { - mempool_tx = &(mempool_data.at(i)); - } - catch (const std::out_of_range& e) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Getting mempool txs failed due to std::out_of_range"); + json& j_data = j_response["data"]; - return j_response; - } + // parse page and limit into numbers - const tx_details& txd = get_tx_details(mempool_tx->tx, false, 1, height); // 1 is dummy here + uint64_t page {0}; + uint64_t limit {0}; - // get basic tx info - json j_tx = get_tx_json(mempool_tx->tx, txd); + try + { + page = boost::lexical_cast(_page); + limit = boost::lexical_cast(_limit); + } + catch (const boost::bad_lexical_cast& e) + { + j_data["title"] = fmt::format( + "Cant parse page and/or limit numbers: {:s}, {:s}", _page, _limit); + return j_response; + } - // we add some extra data, for mempool txs, such as recieve timestamp - j_tx["timestamp"] = mempool_tx->receive_time; - j_tx["timestamp_utc"] = mempool_tx->timestamp_str; + //get current server timestamp + server_timestamp = std::time(nullptr); - j_txs.push_back(j_tx); + uint64_t local_copy_server_timestamp = server_timestamp; - ++i; - } + uint64_t height = core_storage->get_current_blockchain_height(); - j_data["txs"] = j_txs; - j_data["page"] = page; - j_data["limit"] = limit; - j_data["txs_no"] = no_mempool_txs; - j_data["total_page_no"] = limit > 0 ? (no_mempool_txs / limit) : 0; + // get mempool tx from mempoolstatus thread + vector mempool_data + = MempoolStatus::get_mempool_txs(); - j_response["status"] = "success"; + uint64_t no_mempool_txs = mempool_data.size(); - return j_response; - } + // calculate starting and ending block numbers to show + int64_t start_height = limit * page; + int64_t end_height = start_height + limit; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_search(const string& search_text) - { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + end_height = end_height > no_mempool_txs ? no_mempool_txs : end_height; - json& j_data = j_response["data"]; + // check if start height is not below range + start_height = start_height > end_height ? end_height - limit : start_height; - //get current server timestamp - server_timestamp = std::time(nullptr); + start_height = start_height < 0 ? 0 : start_height; - uint64_t local_copy_server_timestamp = server_timestamp; + // loop index + int64_t i = start_height; - uint64_t height = core_storage->get_current_blockchain_height(); + json j_txs = json::array(); - uint64_t search_str_length = search_text.length(); + // for each transaction in the memory pool in current page + while (i < end_height) + { + const MempoolStatus::mempool_tx* mempool_tx {nullptr}; - // first let check if the search_text matches any tx or block hash - if (search_str_length == 64) + try { - // first check for tx - json j_tx = json_transaction(search_text); + mempool_tx = &(mempool_data.at(i)); + } + catch (const std::out_of_range& e) + { + j_response["status"] = "error"; + j_response["message"] = fmt::format("Getting mempool txs failed due to std::out_of_range"); - if (j_tx["status"] == "success") - { - j_response["data"] = j_tx["data"]; - j_response["data"]["title"] = "transaction"; - j_response["status"] = "success"; - return j_response; - } + return j_response; + } - // now check for block + const tx_details& txd = get_tx_details(mempool_tx->tx, false, 1, height); // 1 is dummy here - json j_block = json_block(search_text); + // get basic tx info + json j_tx = get_tx_json(mempool_tx->tx, txd); - if (j_block["status"] == "success") - { - j_response["data"] = j_block["data"]; - j_response["data"]["title"] = "block"; - j_response["status"] = "success"; - return j_response; - } - } + // we add some extra data, for mempool txs, such as recieve timestamp + j_tx["timestamp"] = mempool_tx->receive_time; + j_tx["timestamp_utc"] = mempool_tx->timestamp_str; - // now lets see if this is a block number - if (search_str_length <= 8) - { - json j_block = json_block(search_text); + j_txs.push_back(j_tx); - if (j_block["status"] == "success") - { - j_response["data"] = j_block["data"]; - j_response["data"]["title"] = "block"; - j_response["status"] = "success"; - return j_response; - } - } + ++i; + } - j_data["title"] = "Nothing was found that matches search string: " + search_text; + j_data["txs"] = j_txs; + j_data["page"] = page; + j_data["limit"] = limit; + j_data["txs_no"] = no_mempool_txs; + j_data["total_page_no"] = limit > 0 ? (no_mempool_txs / limit) : 0; - return j_response; - } + j_response["status"] = "success"; - json - json_outputs(string tx_hash_str, - string address_str, - string viewkey_str, - bool tx_prove = false) - { - boost::trim(tx_hash_str); - boost::trim(address_str); - boost::trim(viewkey_str); + return j_response; +} - json j_response { - {"status", "fail"}, - {"data", json {}} - }; - json& j_data = j_response["data"]; +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_search(const string& search_text) +{ + json j_response { + {"status", "fail"}, + {"data", json {}} + }; + json& j_data = j_response["data"]; - if (tx_hash_str.empty()) - { - j_response["status"] = "error"; - j_response["message"] = "Tx hash not provided"; - return j_response; - } + //get current server timestamp + server_timestamp = std::time(nullptr); - if (address_str.empty()) - { - j_response["status"] = "error"; - j_response["message"] = "Monero address not provided"; - return j_response; - } + uint64_t local_copy_server_timestamp = server_timestamp; - if (viewkey_str.empty()) - { - if (!tx_prove) - { - j_response["status"] = "error"; - j_response["message"] = "Viewkey not provided"; - return j_response; - } - else - { - j_response["status"] = "error"; - j_response["message"] = "Tx private key not provided"; - return j_response; - } - } + uint64_t height = core_storage->get_current_blockchain_height(); + uint64_t search_str_length = search_text.length(); - // parse tx hash string to hash object - crypto::hash tx_hash; + // first let check if the search_text matches any tx or block hash + if (search_str_length == 64) + { + // first check for tx + json j_tx = json_transaction(search_text); - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + if (j_tx["status"] == "success") { - j_response["status"] = "error"; - j_response["message"] = "Cant parse tx hash: " + tx_hash_str; + j_response["data"] = j_tx["data"]; + j_response["data"]["title"] = "transaction"; + j_response["status"] = "success"; return j_response; } - // parse string representing given monero address - address_parse_info address_info; + // now check for block - if (!xmreg::parse_str_address(address_str, address_info, nettype)) + json j_block = json_block(search_text); + + if (j_block["status"] == "success") { - j_response["status"] = "error"; - j_response["message"] = "Cant parse monero address: " + address_str; + j_response["data"] = j_block["data"]; + j_response["data"]["title"] = "block"; + j_response["status"] = "success"; return j_response; - } + } - // parse string representing given private key - crypto::secret_key prv_view_key; + // now lets see if this is a block number + if (search_str_length <= 8) + { + json j_block = json_block(search_text); - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + if (j_block["status"] == "success") { - j_response["status"] = "error"; - j_response["message"] = "Cant parse view key or tx private key: " - + viewkey_str; + j_response["data"] = j_block["data"]; + j_response["data"]["title"] = "block"; + j_response["status"] = "success"; return j_response; } + } - // get transaction - transaction tx; - - // flag to indicate if tx is in mempool - bool found_in_mempool {false}; + j_data["title"] = "Nothing was found that matches search string: " + search_text; - // for tx in blocks we get block timestamp - // for tx in mempool we get recievive time - uint64_t tx_timestamp {0}; + return j_response; +} - if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) - { - j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); - return j_response; - } +json +json_outputs(string tx_hash_str, + string address_str, + string viewkey_str, + bool tx_prove = false) +{ + boost::trim(tx_hash_str); + boost::trim(address_str); + boost::trim(viewkey_str); - (void) tx_timestamp; - (void) found_in_mempool; + json j_response { + {"status", "fail"}, + {"data", json {}} + }; - tx_details txd = get_tx_details(tx); + json& j_data = j_response["data"]; - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; - std::vector additional_derivations(txd.additional_pks.size()); - public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk; + if (tx_hash_str.empty()) + { + j_response["status"] = "error"; + j_response["message"] = "Tx hash not provided"; + return j_response; + } - //cout << "txd.pk: " << pod_to_hex(txd.pk) << endl; + if (address_str.empty()) + { + j_response["status"] = "error"; + j_response["message"] = "Monero address not provided"; + return j_response; + } - if (!generate_key_derivation(pub_key, prv_view_key, derivation)) + if (viewkey_str.empty()) + { + if (!tx_prove) { j_response["status"] = "error"; - j_response["message"] = "Cant calculate key_derivation"; + j_response["message"] = "Viewkey not provided"; return j_response; } - for (size_t i = 0; i < txd.additional_pks.size(); ++i) + else { - if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i])) - { - j_response["status"] = "error"; - j_response["message"] = "Cant calculate key_derivation"; - return j_response; - } + j_response["status"] = "error"; + j_response["message"] = "Tx private key not provided"; + return j_response; } + } - uint64_t output_idx {0}; - - std::vector money_transfered(tx.vout.size(), 0); - - j_data["outputs"] = json::array(); - json& j_outptus = j_data["outputs"]; - - for (pair& outp: txd.output_pub_keys) - { - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey; + // parse tx hash string to hash object + crypto::hash tx_hash; - derive_public_key(derivation, - output_idx, - address_info.address.m_spend_public_key, - tx_pubkey); + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant parse tx hash: " + tx_hash_str; + return j_response; + } - // check if generated public key matches the current output's key - bool mine_output = (outp.first.key == tx_pubkey); - bool with_additional = false; - if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) - { - derive_public_key(additional_derivations[output_idx], - output_idx, - address_info.address.m_spend_public_key, - tx_pubkey); - mine_output = (outp.first.key == tx_pubkey); - with_additional = true; - } + // parse string representing given monero address + address_parse_info address_info; - // if mine output has RingCT, i.e., tx version is 2 - if (mine_output && tx.version == 2) - { - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) - { + if (!xmreg::parse_str_address(address_str, address_info, nettype)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant parse monero address: " + address_str; + return j_response; - // initialize with regular amount - uint64_t rct_amount = money_transfered[output_idx]; + } - bool r; + // parse string representing given private key + crypto::secret_key prv_view_key; - r = decode_ringct(tx.rct_signatures, - with_additional ? additional_derivations[output_idx] : derivation, - output_idx, - tx.rct_signatures.ecdhInfo[output_idx].mask, - rct_amount); + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant parse view key or tx private key: " + + viewkey_str; + return j_response; + } - if (!r) - { - cerr << "\nshow_my_outputs: Cant decode ringCT! " << endl; - } + // get transaction + transaction tx; - outp.second = rct_amount; - money_transfered[output_idx] = rct_amount; + // flag to indicate if tx is in mempool + bool found_in_mempool {false}; - } // if (!is_coinbase(tx)) + // for tx in blocks we get block timestamp + // for tx in mempool we get recievive time + uint64_t tx_timestamp {0}; - } // if (mine_output && tx.version == 2) + if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) + { + j_data["title"] = fmt::format("Cant find tx hash: {:s}", tx_hash_str); + return j_response; + } - j_outptus.push_back(json { - {"output_pubkey", pod_to_hex(outp.first.key)}, - {"amount" , outp.second}, - {"match" , mine_output}, - {"output_idx" , output_idx}, - }); + (void) tx_timestamp; + (void) found_in_mempool; - ++output_idx; + tx_details txd = get_tx_details(tx); - } // for (pair& outp: txd.output_pub_keys) + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; + std::vector additional_derivations(txd.additional_pks.size()); - // return parsed values. can be use to double - // check if submited data in the request - // matches to what was used to produce response. - j_data["tx_hash"] = pod_to_hex(txd.hash); - j_data["address"] = pod_to_hex(address_info.address); - j_data["viewkey"] = pod_to_hex(prv_view_key); - j_data["tx_prove"] = tx_prove; + public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk; - j_response["status"] = "success"; + //cout << "txd.pk: " << pod_to_hex(txd.pk) << endl; + if (!generate_key_derivation(pub_key, prv_view_key, derivation)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant calculate key_derivation"; return j_response; } + for (size_t i = 0; i < txd.additional_pks.size(); ++i) + { + if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i])) + { + j_response["status"] = "error"; + j_response["message"] = "Cant calculate key_derivation"; + return j_response; + } + } + uint64_t output_idx {0}; + std::vector money_transfered(tx.vout.size(), 0); - json - json_outputsblocks(string _limit, - string address_str, - string viewkey_str, - bool in_mempool_aswell = false) - { - boost::trim(_limit); - boost::trim(address_str); - boost::trim(viewkey_str); + j_data["outputs"] = json::array(); + json& j_outptus = j_data["outputs"]; - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + for (pair& outp: txd.output_pub_keys) + { - json& j_data = j_response["data"]; + // get the tx output public key + // that normally would be generated for us, + // if someone had sent us some xmr. + public_key tx_pubkey; - uint64_t no_of_last_blocks {3}; + derive_public_key(derivation, + output_idx, + address_info.address.m_spend_public_key, + tx_pubkey); - try + // check if generated public key matches the current output's key + bool mine_output = (outp.first.key == tx_pubkey); + bool with_additional = false; + if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) { - no_of_last_blocks = boost::lexical_cast(_limit); + derive_public_key(additional_derivations[output_idx], + output_idx, + address_info.address.m_spend_public_key, + tx_pubkey); + mine_output = (outp.first.key == tx_pubkey); + with_additional = true; } - catch (const boost::bad_lexical_cast& e) + + // if mine output has RingCT, i.e., tx version is 2 + if (mine_output && tx.version == 2) { - j_data["title"] = fmt::format( - "Cant parse page and/or limit numbers: {:s}", _limit); - return j_response; - } + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) + { + + // initialize with regular amount + uint64_t rct_amount = money_transfered[output_idx]; + + bool r; + + r = decode_ringct(tx.rct_signatures, + with_additional ? additional_derivations[output_idx] : derivation, + output_idx, + tx.rct_signatures.ecdhInfo[output_idx].mask, + rct_amount); + + if (!r) + { + cerr << "\nshow_my_outputs: Cant decode ringCT! " << endl; + } + + outp.second = rct_amount; + money_transfered[output_idx] = rct_amount; + + } // if (!is_coinbase(tx)) + + } // if (mine_output && tx.version == 2) + + j_outptus.push_back(json { + {"output_pubkey", pod_to_hex(outp.first.key)}, + {"amount" , outp.second}, + {"match" , mine_output}, + {"output_idx" , output_idx}, + }); + + ++output_idx; - // maxium five last blocks - no_of_last_blocks = std::min(no_of_last_blocks, 5ul); + } // for (pair& outp: txd.output_pub_keys) - if (address_str.empty()) - { - j_response["status"] = "error"; - j_response["message"] = "Monero address not provided"; - return j_response; - } + // return parsed values. can be use to double + // check if submited data in the request + // matches to what was used to produce response. + j_data["tx_hash"] = pod_to_hex(txd.hash); + j_data["address"] = pod_to_hex(address_info.address); + j_data["viewkey"] = pod_to_hex(prv_view_key); + j_data["tx_prove"] = tx_prove; - if (viewkey_str.empty()) - { - j_response["status"] = "error"; - j_response["message"] = "Viewkey not provided"; - return j_response; - } + j_response["status"] = "success"; - // parse string representing given monero address - address_parse_info address_info; + return j_response; +} - if (!xmreg::parse_str_address(address_str, address_info, nettype)) - { - j_response["status"] = "error"; - j_response["message"] = "Cant parse monero address: " + address_str; - return j_response; - } - // parse string representing given private key - crypto::secret_key prv_view_key; +json +json_outputsblocks(string _limit, + string address_str, + string viewkey_str, + bool in_mempool_aswell = false) +{ + boost::trim(_limit); + boost::trim(address_str); + boost::trim(viewkey_str); - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - j_response["status"] = "error"; - j_response["message"] = "Cant parse view key: " - + viewkey_str; - return j_response; - } + json j_response { + {"status", "fail"}, + {"data", json {}} + }; - string error_msg; + json& j_data = j_response["data"]; - j_data["outputs"] = json::array(); - json& j_outptus = j_data["outputs"]; + uint64_t no_of_last_blocks {3}; + try + { + no_of_last_blocks = boost::lexical_cast(_limit); + } + catch (const boost::bad_lexical_cast& e) + { + j_data["title"] = fmt::format( + "Cant parse page and/or limit numbers: {:s}", _limit); + return j_response; + } - if (in_mempool_aswell) - { - // first check if there is something for us in the mempool - // get mempool tx from mempoolstatus thread - vector mempool_txs - = MempoolStatus::get_mempool_txs(); + // maxium five last blocks + no_of_last_blocks = std::min(no_of_last_blocks, 5ul); - uint64_t no_mempool_txs = mempool_txs.size(); + if (address_str.empty()) + { + j_response["status"] = "error"; + j_response["message"] = "Monero address not provided"; + return j_response; + } - // need to use vector, - // not vector - vector tmp_vector; - tmp_vector.reserve(no_mempool_txs); + if (viewkey_str.empty()) + { + j_response["status"] = "error"; + j_response["message"] = "Viewkey not provided"; + return j_response; + } - for (size_t i = 0; i < no_mempool_txs; ++i) - { - // get transaction info of the tx in the mempool - tmp_vector.push_back(std::move(mempool_txs.at(i).tx)); - } + // parse string representing given monero address + address_parse_info address_info; - if (!find_our_outputs( - address_info.address, prv_view_key, - 0 /* block_no */, true /*is mempool*/, - tmp_vector.cbegin(), tmp_vector.cend(), - j_outptus /* found outputs are pushed to this*/, - error_msg)) - { - j_response["status"] = "error"; - j_response["message"] = error_msg; - return j_response; - } + if (!xmreg::parse_str_address(address_str, address_info, nettype)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant parse monero address: " + address_str; + return j_response; - } // if (in_mempool_aswell) + } + // parse string representing given private key + crypto::secret_key prv_view_key; - // and now serach for outputs in last few blocks in the blockchain + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant parse view key: " + + viewkey_str; + return j_response; + } - uint64_t height = core_storage->get_current_blockchain_height(); + string error_msg; - // calculate starting and ending block numbers to show - int64_t start_height = height - no_of_last_blocks; + j_data["outputs"] = json::array(); + json& j_outptus = j_data["outputs"]; - // check if start height is not below range - start_height = start_height < 0 ? 0 : start_height; - int64_t end_height = start_height + no_of_last_blocks - 1; + if (in_mempool_aswell) + { + // first check if there is something for us in the mempool + // get mempool tx from mempoolstatus thread + vector mempool_txs + = MempoolStatus::get_mempool_txs(); - // loop index - int64_t block_no = end_height; + uint64_t no_mempool_txs = mempool_txs.size(); + // need to use vector, + // not vector + vector tmp_vector; + tmp_vector.reserve(no_mempool_txs); - // iterate over last no_of_last_blocks of blocks - while (block_no >= start_height) + for (size_t i = 0; i < no_mempool_txs; ++i) { - // get block at the given height block_no - block blk; + // get transaction info of the tx in the mempool + tmp_vector.push_back(std::move(mempool_txs.at(i).tx)); + } - if (!mcore->get_block_by_height(block_no, blk)) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Cant get block: {:d}", block_no); - return j_response; - } + if (!find_our_outputs( + address_info.address, prv_view_key, + 0 /* block_no */, true /*is mempool*/, + tmp_vector.cbegin(), tmp_vector.cend(), + j_outptus /* found outputs are pushed to this*/, + error_msg)) + { + j_response["status"] = "error"; + j_response["message"] = error_msg; + return j_response; + } - // get transactions in the given block - vector blk_txs{blk.miner_tx}; - vector missed_txs; + } // if (in_mempool_aswell) - if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) - { - j_response["status"] = "error"; - j_response["message"] = fmt::format("Cant get transactions in block: {:d}", block_no); - return j_response; - } - (void) missed_txs; + // and now serach for outputs in last few blocks in the blockchain - if (!find_our_outputs( - address_info.address, prv_view_key, - block_no, false /*is mempool*/, - blk_txs.cbegin(), blk_txs.cend(), - j_outptus /* found outputs are pushed to this*/, - error_msg)) - { - j_response["status"] = "error"; - j_response["message"] = error_msg; - return j_response; - } + uint64_t height = core_storage->get_current_blockchain_height(); - --block_no; + // calculate starting and ending block numbers to show + int64_t start_height = height - no_of_last_blocks; - } // while (block_no >= start_height) + // check if start height is not below range + start_height = start_height < 0 ? 0 : start_height; - // return parsed values. can be use to double - // check if submited data in the request - // matches to what was used to produce response. - j_data["address"] = pod_to_hex(address_info.address); - j_data["viewkey"] = pod_to_hex(prv_view_key); - j_data["limit"] = _limit; - j_data["height"] = height; - j_data["mempool"] = in_mempool_aswell; + int64_t end_height = start_height + no_of_last_blocks - 1; - j_response["status"] = "success"; + // loop index + int64_t block_no = end_height; - return j_response; - } - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_networkinfo() + // iterate over last no_of_last_blocks of blocks + while (block_no >= start_height) { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + // get block at the given height block_no + block blk; - json& j_data = j_response["data"]; + if (!mcore->get_block_by_height(block_no, blk)) + { + j_response["status"] = "error"; + j_response["message"] = fmt::format("Cant get block: {:d}", block_no); + return j_response; + } - json j_info; + // get transactions in the given block + vector blk_txs{blk.miner_tx}; + vector missed_txs; - // get basic network info - if (!get_monero_network_info(j_info)) + if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs)) { - j_response["status"] = "error"; - j_response["message"] = "Cant get monero network info"; + j_response["status"] = "error"; + j_response["message"] = fmt::format("Cant get transactions in block: {:d}", block_no); return j_response; } - uint64_t fee_estimated {0}; + (void) missed_txs; - // get dynamic fee estimate from last 10 blocks - if (!get_dynamic_per_kb_fee_estimate(fee_estimated)) + if (!find_our_outputs( + address_info.address, prv_view_key, + block_no, false /*is mempool*/, + blk_txs.cbegin(), blk_txs.cend(), + j_outptus /* found outputs are pushed to this*/, + error_msg)) { - j_response["status"] = "error"; - j_response["message"] = "Cant get dynamic fee esimate"; + j_response["status"] = "error"; + j_response["message"] = error_msg; return j_response; } - j_info["fee_per_kb"] = fee_estimated; + --block_no; + + } // while (block_no >= start_height) + + // return parsed values. can be use to double + // check if submited data in the request + // matches to what was used to produce response. + j_data["address"] = pod_to_hex(address_info.address); + j_data["viewkey"] = pod_to_hex(prv_view_key); + j_data["limit"] = _limit; + j_data["height"] = height; + j_data["mempool"] = in_mempool_aswell; + + j_response["status"] = "success"; + + return j_response; +} - j_info["tx_pool_size"] = MempoolStatus::mempool_no.load(); - j_info["tx_pool_size_kbytes"] = MempoolStatus::mempool_size.load(); +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_networkinfo() +{ + json j_response { + {"status", "fail"}, + {"data", json {}} + }; - j_data = j_info; + json& j_data = j_response["data"]; - j_response["status"] = "success"; + json j_info; + // get basic network info + if (!get_monero_network_info(j_info)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant get monero network info"; return j_response; } + uint64_t fee_estimated {0}; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_emission() + // get dynamic fee estimate from last 10 blocks + if (!get_dynamic_per_kb_fee_estimate(fee_estimated)) { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + j_response["status"] = "error"; + j_response["message"] = "Cant get dynamic fee esimate"; + return j_response; + } - json& j_data = j_response["data"]; + j_info["fee_per_kb"] = fee_estimated; - json j_info; + j_info["tx_pool_size"] = MempoolStatus::mempool_no.load(); + j_info["tx_pool_size_kbytes"] = MempoolStatus::mempool_size.load(); - // 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(); + j_data = j_info; - 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}", false); + j_response["status"] = "success"; - j_data = json { - {"blk_no" , current_values.blk_no - 1}, - {"coinbase", current_values.coinbase}, - {"fee" , current_values.fee}, - }; - } + return j_response; +} - j_response["status"] = "success"; - return j_response; - } +/* +* 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"]; - /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ - json - json_version() + json j_info; + + // get basic network info + if (!CurrentBlockchainStatus::is_thread_running()) { - json j_response { - {"status", "fail"}, - {"data", json {}} - }; + j_data["title"] = "Emission monitoring thread not enabled."; + return j_response; + } + else + { + CurrentBlockchainStatus::Emission current_values + = CurrentBlockchainStatus::get_emission(); - json& j_data = j_response["data"]; + 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}", false); j_data = json { - {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, - {"last_git_commit_date", string {GIT_COMMIT_DATETIME}}, - {"git_branch_name" , string {GIT_BRANCH_NAME}}, - {"monero_version_full" , string {MONERO_VERSION_FULL}}, - {"api" , ONIONEXPLORER_RPC_VERSION}, - {"blockchain_height" , core_storage->get_current_blockchain_height()} + {"blk_no" , current_values.blk_no - 1}, + {"coinbase", current_values.coinbase}, + {"fee" , current_values.fee}, }; + } - j_response["status"] = "success"; + j_response["status"] = "success"; - return j_response; - } + return j_response; +} + + +/* + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ +json +json_version() +{ + json j_response { + {"status", "fail"}, + {"data", json {}} + }; + + json& j_data = j_response["data"]; + + j_data = json { + {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, + {"last_git_commit_date", string {GIT_COMMIT_DATETIME}}, + {"git_branch_name" , string {GIT_BRANCH_NAME}}, + {"monero_version_full" , string {MONERO_VERSION_FULL}}, + {"api" , ONIONEXPLORER_RPC_VERSION}, + {"blockchain_height" , core_storage->get_current_blockchain_height()} + }; + + j_response["status"] = "success"; + + return j_response; +} private: - string - get_payment_id_as_string( - tx_details const& txd, - secret_key const& prv_view_key) - { - string payment_id; +string +get_payment_id_as_string( + tx_details const& txd, + secret_key const& prv_view_key) +{ + string payment_id; - // decrypt encrypted payment id, as used in integreated addresses - crypto::hash8 decrypted_payment_id8 = txd.payment_id8; + // decrypt encrypted payment id, as used in integreated addresses + crypto::hash8 decrypted_payment_id8 = txd.payment_id8; - if (decrypted_payment_id8 != null_hash8) - { - if (mcore->get_device()->decrypt_payment_id(decrypted_payment_id8, txd.pk, prv_view_key)) - { - payment_id = pod_to_hex(decrypted_payment_id8); - } - } - else if(txd.payment_id != null_hash) + if (decrypted_payment_id8 != null_hash8) + { + if (mcore->get_device()->decrypt_payment_id(decrypted_payment_id8, txd.pk, prv_view_key)) { - payment_id = pod_to_hex(txd.payment_id); + payment_id = pod_to_hex(decrypted_payment_id8); } - - return payment_id; } + else if(txd.payment_id != null_hash) + { + payment_id = pod_to_hex(txd.payment_id); + } + + return payment_id; +} - template - bool - find_our_outputs( - account_public_address const& address, - secret_key const& prv_view_key, - uint64_t const& block_no, - bool const& is_mempool, - Iterator const& txs_begin, - Iterator const& txs_end, - json& j_outptus, - string& error_msg) +template +bool +find_our_outputs( + account_public_address const& address, + secret_key const& prv_view_key, + uint64_t const& block_no, + bool const& is_mempool, + Iterator const& txs_begin, + Iterator const& txs_end, + json& j_outptus, + string& error_msg) +{ + + // for each tx, perform output search using provided + // address and viewkey + for (auto it = txs_begin; it != txs_end; ++it) { + cryptonote::transaction const& tx = *it; - // for each tx, perform output search using provided - // address and viewkey - for (auto it = txs_begin; it != txs_end; ++it) - { - cryptonote::transaction const& tx = *it; + tx_details txd = get_tx_details(tx); - tx_details txd = get_tx_details(tx); + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; + if (!generate_key_derivation(txd.pk, prv_view_key, derivation)) + { + error_msg = "Cant calculate key_derivation"; + return false; + } - if (!generate_key_derivation(txd.pk, prv_view_key, derivation)) + std::vector additional_derivations(txd.additional_pks.size()); + for (size_t i = 0; i < txd.additional_pks.size(); ++i) + { + if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i])) { error_msg = "Cant calculate key_derivation"; return false; } + } - std::vector additional_derivations(txd.additional_pks.size()); - for (size_t i = 0; i < txd.additional_pks.size(); ++i) - { - if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i])) - { - error_msg = "Cant calculate key_derivation"; - return false; - } - } + uint64_t output_idx{0}; - uint64_t output_idx{0}; + std::vector money_transfered(tx.vout.size(), 0); - std::vector money_transfered(tx.vout.size(), 0); + //j_data["outputs"] = json::array(); + //json& j_outptus = j_data["outputs"]; - //j_data["outputs"] = json::array(); - //json& j_outptus = j_data["outputs"]; + for (pair &outp: txd.output_pub_keys) + { - for (pair &outp: txd.output_pub_keys) - { + // get the tx output public key + // that normally would be generated for us, + // if someone had sent us some xmr. + public_key tx_pubkey; - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey; + derive_public_key(derivation, + output_idx, + address.m_spend_public_key, + tx_pubkey); - derive_public_key(derivation, + // check if generated public key matches the current output's key + bool mine_output = (outp.first.key == tx_pubkey); + bool with_additional = false; + if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) + { + derive_public_key(additional_derivations[output_idx], output_idx, address.m_spend_public_key, tx_pubkey); + mine_output = (outp.first.key == tx_pubkey); + with_additional = true; + } - // check if generated public key matches the current output's key - bool mine_output = (outp.first.key == tx_pubkey); - bool with_additional = false; - if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size()) + // if mine output has RingCT, i.e., tx version is 2 + if (mine_output && tx.version == 2) + { + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) { - derive_public_key(additional_derivations[output_idx], + + // initialize with regular amount + uint64_t rct_amount = money_transfered[output_idx]; + + bool r {false}; + + rct::key mask = tx.rct_signatures.ecdhInfo[output_idx].mask; + + r = decode_ringct(tx.rct_signatures, + with_additional ? additional_derivations[output_idx] : derivation, output_idx, - address.m_spend_public_key, - tx_pubkey); - mine_output = (outp.first.key == tx_pubkey); - with_additional = true; - } + mask, + rct_amount); - // if mine output has RingCT, i.e., tx version is 2 - if (mine_output && tx.version == 2) - { - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) + if (!r) { + error_msg = "Cant decode ringct for tx: " + + pod_to_hex(txd.hash); + return false; + } - // initialize with regular amount - uint64_t rct_amount = money_transfered[output_idx]; + outp.second = rct_amount; + money_transfered[output_idx] = rct_amount; - bool r {false}; + } // if (!is_coinbase(tx)) - rct::key mask = tx.rct_signatures.ecdhInfo[output_idx].mask; + } // if (mine_output && tx.version == 2) - r = decode_ringct(tx.rct_signatures, - with_additional ? additional_derivations[output_idx] : derivation, - output_idx, - mask, - rct_amount); + if (mine_output) + { + string payment_id_str = get_payment_id_as_string(txd, prv_view_key); + + j_outptus.push_back(json { + {"output_pubkey" , pod_to_hex(outp.first.key)}, + {"amount" , outp.second}, + {"block_no" , block_no}, + {"in_mempool" , is_mempool}, + {"output_idx" , output_idx}, + {"tx_hash" , pod_to_hex(txd.hash)}, + {"payment_id" , payment_id_str} + }); + } - if (!r) - { - error_msg = "Cant decode ringct for tx: " - + pod_to_hex(txd.hash); - return false; - } + ++output_idx; - outp.second = rct_amount; - money_transfered[output_idx] = rct_amount; + } // for (pair& outp: txd.output_pub_keys) - } // if (!is_coinbase(tx)) + } // for (auto it = blk_txs.begin(); it != blk_txs.end(); ++it) - } // if (mine_output && tx.version == 2) + return true; +} - if (mine_output) - { - string payment_id_str = get_payment_id_as_string(txd, prv_view_key); - - j_outptus.push_back(json { - {"output_pubkey" , pod_to_hex(outp.first.key)}, - {"amount" , outp.second}, - {"block_no" , block_no}, - {"in_mempool" , is_mempool}, - {"output_idx" , output_idx}, - {"tx_hash" , pod_to_hex(txd.hash)}, - {"payment_id" , payment_id_str} - }); - } +json +get_tx_json(const transaction& tx, const tx_details& txd) +{ - ++output_idx; + json j_tx { + {"tx_hash" , pod_to_hex(txd.hash)}, + {"tx_fee" , txd.fee}, + {"mixin" , txd.mixin_no}, + {"tx_size" , txd.size}, + {"xmr_outputs" , txd.xmr_outputs}, + {"xmr_inputs" , txd.xmr_inputs}, + {"tx_version" , static_cast(txd.version)}, + {"rct_type" , tx.rct_signatures.type}, + {"coinbase" , is_coinbase(tx)}, + {"mixin" , txd.mixin_no}, + {"extra" , txd.get_extra_str()}, + {"payment_id" , (txd.payment_id != null_hash ? pod_to_hex(txd.payment_id) : "")}, + {"payment_id8" , (txd.payment_id8 != null_hash8 ? pod_to_hex(txd.payment_id8) : "")}, + }; - } // for (pair& outp: txd.output_pub_keys) + return j_tx; +} - } // for (auto it = blk_txs.begin(); it != blk_txs.end(); ++it) - return true; - } +bool +find_tx(const crypto::hash& tx_hash, + transaction& tx, + bool& found_in_mempool, + uint64_t& tx_timestamp) +{ - json - get_tx_json(const transaction& tx, const tx_details& txd) + found_in_mempool = false; + + if (!mcore->get_tx(tx_hash, tx)) { + cerr << "Cant get tx in blockchain: " << tx_hash + << ". \n Check mempool now" << endl; - json j_tx { - {"tx_hash" , pod_to_hex(txd.hash)}, - {"tx_fee" , txd.fee}, - {"mixin" , txd.mixin_no}, - {"tx_size" , txd.size}, - {"xmr_outputs" , txd.xmr_outputs}, - {"xmr_inputs" , txd.xmr_inputs}, - {"tx_version" , static_cast(txd.version)}, - {"rct_type" , tx.rct_signatures.type}, - {"coinbase" , is_coinbase(tx)}, - {"mixin" , txd.mixin_no}, - {"extra" , txd.get_extra_str()}, - {"payment_id" , (txd.payment_id != null_hash ? pod_to_hex(txd.payment_id) : "")}, - {"payment_id8" , (txd.payment_id8 != null_hash8 ? pod_to_hex(txd.payment_id8) : "")}, - }; + vector found_txs; - return j_tx; + search_mempool(tx_hash, found_txs); + + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).tx; + found_in_mempool = true; + tx_timestamp = found_txs.at(0).receive_time; + } + else + { + // tx is nowhere to be found :-( + return false; + } } + return true; +} + - bool - find_tx(const crypto::hash& tx_hash, - transaction& tx, - bool& found_in_mempool, - uint64_t& tx_timestamp) +void +mark_real_mixins_on_timescales( + const vector& real_output_indices, + mstch::map& tx_context) +{ + // mark real mixing in the mixins timescale graph + mstch::array& mixins_timescales + = boost::get(tx_context["timescales"]); + + uint64_t idx {0}; + + for (mstch::node& timescale_node: mixins_timescales) { - found_in_mempool = false; + string& timescale = boost::get( + boost::get(timescale_node)["timescale"] + ); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + // claculated number of timescale points + // due to resolution, no of points might be lower than no of mixins + size_t no_points = std::count(timescale.begin(), timescale.end(), '*'); - vector found_txs; + size_t point_to_find = real_output_indices.at(idx); - search_mempool(tx_hash, found_txs); + // adjust point to find based on total number of points + if (point_to_find >= no_points) + point_to_find = no_points - 1; - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).tx; - found_in_mempool = true; - tx_timestamp = found_txs.at(0).receive_time; - } - else - { - // tx is nowhere to be found :-( - return false; - } - } + boost::iterator_range r + = boost::find_nth(timescale, "*", point_to_find); + + *(r.begin()) = 'R'; + + ++idx; + } +} - return true; - } +mstch::map +construct_tx_context(transaction tx, uint16_t with_ring_signatures = 0) +{ + tx_details txd = get_tx_details(tx); + const crypto::hash& tx_hash = txd.hash; - void - mark_real_mixins_on_timescales( - const vector& real_output_indices, - mstch::map& tx_context) - { - // mark real mixing in the mixins timescale graph - mstch::array& mixins_timescales - = boost::get(tx_context["timescales"]); + string tx_hash_str = pod_to_hex(tx_hash); - uint64_t idx {0}; + uint64_t tx_blk_height {0}; - for (mstch::node& timescale_node: mixins_timescales) - { + bool tx_blk_found {false}; - string& timescale = boost::get( - boost::get(timescale_node)["timescale"] - ); + bool detailed_view {enable_mixins_details || static_cast(with_ring_signatures)}; - // claculated number of timescale points - // due to resolution, no of points might be lower than no of mixins - size_t no_points = std::count(timescale.begin(), timescale.end(), '*'); + if (core_storage->have_tx(tx_hash)) + { + // currently get_tx_block_height seems to return a block hight + // +1. Before it was not like this. + tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); + tx_blk_found = true; + } - size_t point_to_find = real_output_indices.at(idx); + // get block cointaining this tx + block blk; - // adjust point to find based on total number of points - if (point_to_find >= no_points) - point_to_find = no_points - 1; + if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + { + cerr << "Cant get block: " << tx_blk_height << endl; + } - boost::iterator_range r - = boost::find_nth(timescale, "*", point_to_find); + string tx_blk_height_str {"N/A"}; - *(r.begin()) = 'R'; + // tx age + pair age; - ++idx; - } - } + string blk_timestamp {"N/A"}; - mstch::map - construct_tx_context(transaction tx, uint16_t with_ring_signatures = 0) + if (tx_blk_found) { - tx_details txd = get_tx_details(tx); + // calculate difference between tx and server timestamps + age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); - const crypto::hash& tx_hash = txd.hash; + blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); - string tx_hash_str = pod_to_hex(tx_hash); + tx_blk_height_str = std::to_string(tx_blk_height); + } - uint64_t tx_blk_height {0}; + // payments id. both normal and encrypted (payment_id8) + string pid_str = pod_to_hex(txd.payment_id); + string pid8_str = pod_to_hex(txd.payment_id8); + + + string tx_json = obj_to_json_str(tx); + + // use this regex to remove all non friendly characters in payment_id_as_ascii string + static std::regex e {"[^a-zA-Z0-9 ./\\\\!]"}; + + double tx_size = static_cast(txd.size) / 1024.0; + + double payed_for_kB = XMR_AMOUNT(txd.fee) / tx_size; + + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"stagenet" , stagenet}, + {"tx_hash" , tx_hash_str}, + {"tx_prefix_hash" , string{}}, + {"tx_pub_key" , pod_to_hex(txd.pk)}, + {"blk_height" , tx_blk_height_str}, + {"tx_blk_height" , tx_blk_height}, + {"tx_size" , fmt::format("{:0.4f}", tx_size)}, + {"tx_fee" , xmreg::xmr_amount_to_str(txd.fee, "{:0.12f}", false)}, + {"payed_for_kB" , fmt::format("{:0.12f}", payed_for_kB)}, + {"tx_version" , static_cast(txd.version)}, + {"blk_timestamp" , blk_timestamp}, + {"blk_timestamp_uint" , blk.timestamp}, + {"delta_time" , age.first}, + {"inputs_no" , static_cast(txd.input_key_imgs.size())}, + {"has_inputs" , !txd.input_key_imgs.empty()}, + {"outputs_no" , static_cast(txd.output_pub_keys.size())}, + {"has_payment_id" , txd.payment_id != null_hash}, + {"has_payment_id8" , txd.payment_id8 != null_hash8}, + {"confirmations" , txd.no_confirmations}, + {"payment_id" , pid_str}, + {"payment_id_as_ascii" , remove_bad_chars(txd.payment_id_as_ascii)}, + {"payment_id8" , pid8_str}, + {"extra" , txd.get_extra_str()}, + {"with_ring_signatures" , static_cast( + with_ring_signatures)}, + {"tx_json" , tx_json}, + {"is_ringct" , (tx.version > 1)}, + {"rct_type" , tx.rct_signatures.type}, + {"has_error" , false}, + {"error_msg" , string("")}, + {"have_raw_tx" , false}, + {"show_more_details_link", true}, + {"from_cache" , false}, + {"construction_time" , string {}}, + }; - bool tx_blk_found {false}; + // append tx_json as in raw format to html + context["tx_json_raw"] = mstch::lambda{[=](const std::string& text) -> mstch::node { + return tx_json; + }}; - bool detailed_view {enable_mixins_details || static_cast(with_ring_signatures)}; + // append additional public tx keys, if there are any, to the html context - if (core_storage->have_tx(tx_hash)) - { - // currently get_tx_block_height seems to return a block hight - // +1. Before it was not like this. - tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); - tx_blk_found = true; - } + string add_tx_pub_keys; - // get block cointaining this tx - block blk; + for (auto const& apk: txd.additional_pks) + add_tx_pub_keys += pod_to_hex(apk) + ";"; - if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) - { - cerr << "Cant get block: " << tx_blk_height << endl; - } + context["add_tx_pub_keys"] = add_tx_pub_keys; - string tx_blk_height_str {"N/A"}; + string server_time_str = xmreg::timestamp_to_str_gm(server_timestamp, "%F"); - // tx age - pair age; + mstch::array inputs = mstch::array{}; - string blk_timestamp {"N/A"}; + uint64_t input_idx {0}; - if (tx_blk_found) - { - // calculate difference between tx and server timestamps - age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + uint64_t inputs_xmr_sum {0}; - blk_timestamp = xmreg::timestamp_to_str_gm(blk.timestamp); + // ringct inputs can be mixture of known amounts (when old outputs) + // are spent, and unknown umounts (makrked in explorer by '?') when + // ringct outputs are spent. thus we totalling input amounts + // in such case, we need to show sum of known umounts, and + // indicate that this is minium sum, as we dont know the unknown + // umounts. + bool have_any_unknown_amount {false}; - tx_blk_height_str = std::to_string(tx_blk_height); - } + uint64_t max_no_of_inputs_to_show {10}; - // payments id. both normal and encrypted (payment_id8) - string pid_str = pod_to_hex(txd.payment_id); - string pid8_str = pod_to_hex(txd.payment_id8); - - - string tx_json = obj_to_json_str(tx); - - // use this regex to remove all non friendly characters in payment_id_as_ascii string - static std::regex e {"[^a-zA-Z0-9 ./\\\\!]"}; - - double tx_size = static_cast(txd.size) / 1024.0; - - double payed_for_kB = XMR_AMOUNT(txd.fee) / tx_size; - - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"stagenet" , stagenet}, - {"tx_hash" , tx_hash_str}, - {"tx_prefix_hash" , string{}}, - {"tx_pub_key" , pod_to_hex(txd.pk)}, - {"blk_height" , tx_blk_height_str}, - {"tx_blk_height" , tx_blk_height}, - {"tx_size" , fmt::format("{:0.4f}", tx_size)}, - {"tx_fee" , xmreg::xmr_amount_to_str(txd.fee, "{:0.12f}", false)}, - {"payed_for_kB" , fmt::format("{:0.12f}", payed_for_kB)}, - {"tx_version" , static_cast(txd.version)}, - {"blk_timestamp" , blk_timestamp}, - {"blk_timestamp_uint" , blk.timestamp}, - {"delta_time" , age.first}, - {"inputs_no" , static_cast(txd.input_key_imgs.size())}, - {"has_inputs" , !txd.input_key_imgs.empty()}, - {"outputs_no" , static_cast(txd.output_pub_keys.size())}, - {"has_payment_id" , txd.payment_id != null_hash}, - {"has_payment_id8" , txd.payment_id8 != null_hash8}, - {"confirmations" , txd.no_confirmations}, - {"payment_id" , pid_str}, - {"payment_id_as_ascii" , remove_bad_chars(txd.payment_id_as_ascii)}, - {"payment_id8" , pid8_str}, - {"extra" , txd.get_extra_str()}, - {"with_ring_signatures" , static_cast( - with_ring_signatures)}, - {"tx_json" , tx_json}, - {"is_ringct" , (tx.version > 1)}, - {"rct_type" , tx.rct_signatures.type}, - {"has_error" , false}, - {"error_msg" , string("")}, - {"have_raw_tx" , false}, - {"show_more_details_link", true}, - {"from_cache" , false}, - {"construction_time" , string {}}, - }; + // if a tx has more inputs than max_no_of_inputs_to_show, + // we only show 10 first. + bool show_part_of_inputs = (txd.input_key_imgs.size() > max_no_of_inputs_to_show); + + // but if we show all details, i.e., + // the show all inputs, regardless of their number + if (detailed_view) + { + show_part_of_inputs = false; + } - // append tx_json as in raw format to html - context["tx_json_raw"] = mstch::lambda{[=](const std::string& text) -> mstch::node { - return tx_json; - }}; + vector> mixin_timestamp_groups; - // append additional public tx keys, if there are any, to the html context + // make timescale maps for mixins in input + for (const txin_to_key &in_key: txd.input_key_imgs) + { + + if (show_part_of_inputs && (input_idx > max_no_of_inputs_to_show)) + break; - string add_tx_pub_keys; + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - for (auto const& apk: txd.additional_pks) - add_tx_pub_keys += pod_to_hex(apk) + ";"; + // get public keys of outputs used in the mixins that match to the offests + std::vector outputs; - context["add_tx_pub_keys"] = add_tx_pub_keys; + try + { + // before proceeding with geting the outputs based on the amount and absolute offset + // check how many outputs there are for that amount + // go to next input if a too large offset was found + if (are_absolute_offsets_good(absolute_offsets, in_key) == false) + continue; - string server_time_str = xmreg::timestamp_to_str_gm(server_timestamp, "%F"); + // offsets seems good, so try to get the outputs for the amount and + // offsets given + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + outputs); + } + catch (const std::exception& e) + { + string out_msg = fmt::format( + "Outputs with amount {:d} do not exist and indexes ", + in_key.amount + ); - mstch::array inputs = mstch::array{}; + for (auto offset: absolute_offsets) + out_msg += ", " + to_string(offset); - uint64_t input_idx {0}; + out_msg += " don't exist! " + string {e.what()}; - uint64_t inputs_xmr_sum {0}; + cerr << out_msg << '\n'; - // ringct inputs can be mixture of known amounts (when old outputs) - // are spent, and unknown umounts (makrked in explorer by '?') when - // ringct outputs are spent. thus we totalling input amounts - // in such case, we need to show sum of known umounts, and - // indicate that this is minium sum, as we dont know the unknown - // umounts. - bool have_any_unknown_amount {false}; + context["has_error"] = true; + context["error_msg"] = out_msg; - uint64_t max_no_of_inputs_to_show {10}; + return context; + } - // if a tx has more inputs than max_no_of_inputs_to_show, - // we only show 10 first. - bool show_part_of_inputs = (txd.input_key_imgs.size() > max_no_of_inputs_to_show); + inputs.push_back(mstch::map { + {"in_key_img" , pod_to_hex(in_key.k_image)}, + {"amount" , xmreg::xmr_amount_to_str(in_key.amount)}, + {"input_idx" , fmt::format("{:02d}", input_idx)}, + {"mixins" , mstch::array{}}, + {"ring_sigs" , mstch::array{}}, + {"already_spent", false} // placeholder for later + }); - // but if we show all details, i.e., - // the show all inputs, regardless of their number if (detailed_view) { - show_part_of_inputs = false; + boost::get(inputs.back())["ring_sigs"] + = txd.get_ring_sig_for_input(input_idx); } - vector> mixin_timestamp_groups; - // make timescale maps for mixins in input - for (const txin_to_key &in_key: txd.input_key_imgs) + inputs_xmr_sum += in_key.amount; + + if (in_key.amount == 0) { + // if any input has amount equal to zero, + // it is really an unkown amount + have_any_unknown_amount = true; + } - if (show_part_of_inputs && (input_idx > max_no_of_inputs_to_show)) - break; + vector mixin_timestamps; + + // get reference to mixins array created above + mstch::array& mixins = boost::get( + boost::get(inputs.back())["mixins"]); + + // mixin counter + size_t count = 0; - // get absolute offsets of mixins - std::vector absolute_offsets - = cryptonote::relative_output_offsets_to_absolute( - in_key.key_offsets); + // for each found output public key find its block to get timestamp + for (const uint64_t& i: absolute_offsets) + { + // get basic information about mixn's output + cryptonote::output_data_t output_data = outputs.at(count); - // get public keys of outputs used in the mixins that match to the offests - std::vector outputs; + tx_out_index tx_out_idx; try { - // before proceeding with geting the outputs based on the amount and absolute offset - // check how many outputs there are for that amount - // go to next input if a too large offset was found - if (are_absolute_offsets_good(absolute_offsets, in_key) == false) - continue; - - // offsets seems good, so try to get the outputs for the amount and - // offsets given - core_storage->get_db().get_output_key(in_key.amount, - absolute_offsets, - outputs); + // get pair pair where first is tx hash + // and second is local index of the output i in that tx + tx_out_idx = core_storage->get_db() + .get_output_tx_and_index(in_key.amount, i); } - catch (const std::exception& e) + catch (const OUTPUT_DNE &e) { + string out_msg = fmt::format( - "Outputs with amount {:d} do not exist and indexes ", - in_key.amount + "Output with amount {:d} and index {:d} does not exist!", + in_key.amount, i ); - for (auto offset: absolute_offsets) - out_msg += ", " + to_string(offset); - - out_msg += " don't exist! " + string {e.what()}; - - cerr << out_msg << '\n'; + cerr << out_msg << endl; context["has_error"] = true; context["error_msg"] = out_msg; @@ -5952,663 +6108,598 @@ private: return context; } - inputs.push_back(mstch::map { - {"in_key_img" , pod_to_hex(in_key.k_image)}, - {"amount" , xmreg::xmr_amount_to_str(in_key.amount)}, - {"input_idx" , fmt::format("{:02d}", input_idx)}, - {"mixins" , mstch::array{}}, - {"ring_sigs" , mstch::array{}}, - {"already_spent", false} // placeholder for later - }); if (detailed_view) { - boost::get(inputs.back())["ring_sigs"] - = txd.get_ring_sig_for_input(input_idx); - } - - - inputs_xmr_sum += in_key.amount; - - if (in_key.amount == 0) - { - // if any input has amount equal to zero, - // it is really an unkown amount - have_any_unknown_amount = true; - } - - vector mixin_timestamps; - - // get reference to mixins array created above - mstch::array& mixins = boost::get( - boost::get(inputs.back())["mixins"]); - - // mixin counter - size_t count = 0; - - // for each found output public key find its block to get timestamp - for (const uint64_t& i: absolute_offsets) - { - // get basic information about mixn's output - cryptonote::output_data_t output_data = outputs.at(count); - - tx_out_index tx_out_idx; + // get block of given height, as we want to get its timestamp + cryptonote::block blk; - try - { - // get pair pair where first is tx hash - // and second is local index of the output i in that tx - tx_out_idx = core_storage->get_db() - .get_output_tx_and_index(in_key.amount, i); - } - catch (const OUTPUT_DNE &e) + if (!mcore->get_block_by_height(output_data.height, blk)) { - - string out_msg = fmt::format( - "Output with amount {:d} and index {:d} does not exist!", - in_key.amount, i - ); - - cerr << out_msg << endl; + cerr << "- cant get block of height: " << output_data.height << endl; context["has_error"] = true; - context["error_msg"] = out_msg; - - return context; + context["error_msg"] = fmt::format("- cant get block of height: {}", + output_data.height); } + // get age of mixin relative to server time + pair mixin_age = get_age(server_timestamp, + blk.timestamp, + FULL_AGE_FORMAT); + // get mixin transaction + transaction mixin_tx; - if (detailed_view) + if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) { - // get block of given height, as we want to get its timestamp - cryptonote::block blk; - - if (!mcore->get_block_by_height(output_data.height, blk)) - { - cerr << "- cant get block of height: " << output_data.height << endl; + cerr << "Cant get tx: " << tx_out_idx.first << endl; - context["has_error"] = true; - context["error_msg"] = fmt::format("- cant get block of height: {}", - output_data.height); - } + context["has_error"] = true; + context["error_msg"] = fmt::format("Cant get tx: {:s}", tx_out_idx.first); + } - // get age of mixin relative to server time - pair mixin_age = get_age(server_timestamp, - blk.timestamp, - FULL_AGE_FORMAT); - // get mixin transaction - transaction mixin_tx; + // mixin tx details + tx_details mixin_txd = get_tx_details(mixin_tx, true); + + mixins.push_back(mstch::map { + {"mix_blk", fmt::format("{:08d}", output_data.height)}, + {"mix_pub_key", pod_to_hex(output_data.pubkey)}, + {"mix_tx_hash", pod_to_hex(tx_out_idx.first)}, + {"mix_out_indx", tx_out_idx.second}, + {"mix_timestamp", xmreg::timestamp_to_str_gm(blk.timestamp)}, + {"mix_age", mixin_age.first}, + {"mix_mixin_no", mixin_txd.mixin_no}, + {"mix_inputs_no", static_cast(mixin_txd.input_key_imgs.size())}, + {"mix_outputs_no", static_cast(mixin_txd.output_pub_keys.size())}, + {"mix_age_format", mixin_age.second}, + {"mix_idx", fmt::format("{:02d}", count)}, + {"mix_is_it_real", false}, // a placeholder for future + }); - if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) - { - cerr << "Cant get tx: " << tx_out_idx.first << endl; + // get mixin timestamp from its orginal block + mixin_timestamps.push_back(blk.timestamp); + } + else // if (detailed_view) + { + mixins.push_back(mstch::map { + {"mix_blk", fmt::format("{:08d}", output_data.height)}, + {"mix_pub_key", pod_to_hex(output_data.pubkey)}, + {"mix_idx", fmt::format("{:02d}", count)}, + {"mix_is_it_real", false}, // a placeholder for future + }); - context["has_error"] = true; - context["error_msg"] = fmt::format("Cant get tx: {:s}", tx_out_idx.first); - } + } // else if (enable_mixins_details) - // mixin tx details - tx_details mixin_txd = get_tx_details(mixin_tx, true); - - mixins.push_back(mstch::map { - {"mix_blk", fmt::format("{:08d}", output_data.height)}, - {"mix_pub_key", pod_to_hex(output_data.pubkey)}, - {"mix_tx_hash", pod_to_hex(tx_out_idx.first)}, - {"mix_out_indx", tx_out_idx.second}, - {"mix_timestamp", xmreg::timestamp_to_str_gm(blk.timestamp)}, - {"mix_age", mixin_age.first}, - {"mix_mixin_no", mixin_txd.mixin_no}, - {"mix_inputs_no", static_cast(mixin_txd.input_key_imgs.size())}, - {"mix_outputs_no", static_cast(mixin_txd.output_pub_keys.size())}, - {"mix_age_format", mixin_age.second}, - {"mix_idx", fmt::format("{:02d}", count)}, - {"mix_is_it_real", false}, // a placeholder for future - }); - - // get mixin timestamp from its orginal block - mixin_timestamps.push_back(blk.timestamp); - } - else // if (detailed_view) - { - mixins.push_back(mstch::map { - {"mix_blk", fmt::format("{:08d}", output_data.height)}, - {"mix_pub_key", pod_to_hex(output_data.pubkey)}, - {"mix_idx", fmt::format("{:02d}", count)}, - {"mix_is_it_real", false}, // a placeholder for future - }); + ++count; - } // else if (enable_mixins_details) + } // for (const uint64_t &i: absolute_offsets) - ++count; + mixin_timestamp_groups.push_back(mixin_timestamps); - } // for (const uint64_t &i: absolute_offsets) + input_idx++; - mixin_timestamp_groups.push_back(mixin_timestamps); + } // for (const txin_to_key& in_key: txd.input_key_imgs) - input_idx++; - } // for (const txin_to_key& in_key: txd.input_key_imgs) + if (detailed_view) + { + uint64_t min_mix_timestamp {0}; + uint64_t max_mix_timestamp {0}; + + pair mixins_timescales + = construct_mstch_mixin_timescales( + mixin_timestamp_groups, + min_mix_timestamp, + max_mix_timestamp + ); - if (detailed_view) - { - uint64_t min_mix_timestamp {0}; - uint64_t max_mix_timestamp {0}; - - pair mixins_timescales - = construct_mstch_mixin_timescales( - mixin_timestamp_groups, - min_mix_timestamp, - max_mix_timestamp - ); + context["min_mix_time"] = xmreg::timestamp_to_str_gm(min_mix_timestamp); + context["max_mix_time"] = xmreg::timestamp_to_str_gm(max_mix_timestamp); + context.emplace("timescales", mixins_timescales.first); - context["min_mix_time"] = xmreg::timestamp_to_str_gm(min_mix_timestamp); - context["max_mix_time"] = xmreg::timestamp_to_str_gm(max_mix_timestamp); - context.emplace("timescales", mixins_timescales.first); + context["timescales_scale"] = fmt::format("{:0.2f}", + mixins_timescales.second / 3600.0 / 24.0); // in days + context["tx_prefix_hash"] = pod_to_hex(get_transaction_prefix_hash(tx)); - context["timescales_scale"] = fmt::format("{:0.2f}", - mixins_timescales.second / 3600.0 / 24.0); // in days + } - context["tx_prefix_hash"] = pod_to_hex(get_transaction_prefix_hash(tx)); - } + context["have_any_unknown_amount"] = have_any_unknown_amount; + context["inputs_xmr_sum_not_zero"] = (inputs_xmr_sum > 0); + context["inputs_xmr_sum"] = xmreg::xmr_amount_to_str(inputs_xmr_sum); + context["server_time"] = server_time_str; + context["enable_mixins_details"] = detailed_view; + context["enable_as_hex"] = enable_as_hex; + context["show_part_of_inputs"] = show_part_of_inputs; + context["max_no_of_inputs_to_show"] = max_no_of_inputs_to_show; - context["have_any_unknown_amount"] = have_any_unknown_amount; - context["inputs_xmr_sum_not_zero"] = (inputs_xmr_sum > 0); - context["inputs_xmr_sum"] = xmreg::xmr_amount_to_str(inputs_xmr_sum); - context["server_time"] = server_time_str; - context["enable_mixins_details"] = detailed_view; - context["enable_as_hex"] = enable_as_hex; - context["show_part_of_inputs"] = show_part_of_inputs; - context["max_no_of_inputs_to_show"] = max_no_of_inputs_to_show; + context.emplace("inputs", inputs); + // get indices of outputs in amounts tables + vector out_amount_indices; - context.emplace("inputs", inputs); + try + { - // get indices of outputs in amounts tables - vector out_amount_indices; + uint64_t tx_index; - try + if (core_storage->get_db().tx_exists(txd.hash, tx_index)) { - - uint64_t tx_index; - - if (core_storage->get_db().tx_exists(txd.hash, tx_index)) - { - out_amount_indices = core_storage->get_db() - .get_tx_amount_output_indices(tx_index); - } - else - { - cerr << "get_tx_outputs_gindexs failed to find transaction with id = " << txd.hash; - } - + out_amount_indices = core_storage->get_db() + .get_tx_amount_output_indices(tx_index); } - catch(const exception& e) + else { - cerr << e.what() << endl; + cerr << "get_tx_outputs_gindexs failed to find transaction with id = " << txd.hash; } - uint64_t output_idx {0}; + } + catch(const exception& e) + { + cerr << e.what() << endl; + } - mstch::array outputs; + uint64_t output_idx {0}; - uint64_t outputs_xmr_sum {0}; + mstch::array outputs; - for (pair& outp: txd.output_pub_keys) - { + uint64_t outputs_xmr_sum {0}; - // total number of ouputs in the blockchain for this amount - uint64_t num_outputs_amount = core_storage->get_db() - .get_num_outputs(outp.second); + for (pair& outp: txd.output_pub_keys) + { - string out_amount_index_str {"N/A"}; + // total number of ouputs in the blockchain for this amount + uint64_t num_outputs_amount = core_storage->get_db() + .get_num_outputs(outp.second); - // outputs in tx in them mempool dont have yet global indices - // thus for them, we print N/A - if (!out_amount_indices.empty()) - { - out_amount_index_str - = std::to_string(out_amount_indices.at(output_idx)); - } + string out_amount_index_str {"N/A"}; - outputs_xmr_sum += outp.second; + // outputs in tx in them mempool dont have yet global indices + // thus for them, we print N/A + if (!out_amount_indices.empty()) + { + out_amount_index_str + = std::to_string(out_amount_indices.at(output_idx)); + } - outputs.push_back(mstch::map { - {"out_pub_key" , pod_to_hex(outp.first.key)}, - {"amount" , xmreg::xmr_amount_to_str(outp.second)}, - {"amount_idx" , out_amount_index_str}, - {"num_outputs" , num_outputs_amount}, - {"unformated_output_idx" , output_idx}, - {"output_idx" , fmt::format("{:02d}", output_idx++)} - }); + outputs_xmr_sum += outp.second; - } // for (pair& outp: txd.output_pub_keys) + outputs.push_back(mstch::map { + {"out_pub_key" , pod_to_hex(outp.first.key)}, + {"amount" , xmreg::xmr_amount_to_str(outp.second)}, + {"amount_idx" , out_amount_index_str}, + {"num_outputs" , num_outputs_amount}, + {"unformated_output_idx" , output_idx}, + {"output_idx" , fmt::format("{:02d}", output_idx++)} + }); - context["outputs_xmr_sum"] = xmreg::xmr_amount_to_str(outputs_xmr_sum); + } // for (pair& outp: txd.output_pub_keys) - context.emplace("outputs", outputs); + context["outputs_xmr_sum"] = xmreg::xmr_amount_to_str(outputs_xmr_sum); + context.emplace("outputs", outputs); - return context; - } - pair - construct_mstch_mixin_timescales( - const vector>& mixin_timestamp_groups, - uint64_t& min_mix_timestamp, - uint64_t& max_mix_timestamp - ) - { - mstch::array mixins_timescales; + return context; +} - double timescale_scale {0.0}; // size of one '_' in days +pair +construct_mstch_mixin_timescales( + const vector>& mixin_timestamp_groups, + uint64_t& min_mix_timestamp, + uint64_t& max_mix_timestamp +) +{ + mstch::array mixins_timescales; - // initialize with some large and some numbers - min_mix_timestamp = server_timestamp*2L; - max_mix_timestamp = 0; + double timescale_scale {0.0}; // size of one '_' in days - // find min and maximum timestamps - for (const vector& mixn_timestamps : mixin_timestamp_groups) - { + // initialize with some large and some numbers + min_mix_timestamp = server_timestamp*2L; + max_mix_timestamp = 0; - uint64_t min_found = *min_element(mixn_timestamps.begin(), mixn_timestamps.end()); - uint64_t max_found = *max_element(mixn_timestamps.begin(), mixn_timestamps.end()); + // find min and maximum timestamps + for (const vector& mixn_timestamps : mixin_timestamp_groups) + { - if (min_found < min_mix_timestamp) - min_mix_timestamp = min_found; + uint64_t min_found = *min_element(mixn_timestamps.begin(), mixn_timestamps.end()); + uint64_t max_found = *max_element(mixn_timestamps.begin(), mixn_timestamps.end()); - if (max_found > max_mix_timestamp) - max_mix_timestamp = max_found; - } + if (min_found < min_mix_timestamp) + min_mix_timestamp = min_found; + if (max_found > max_mix_timestamp) + max_mix_timestamp = max_found; + } - min_mix_timestamp -= 3600; - max_mix_timestamp += 3600; - // make timescale maps for mixins in input with adjusted range - for (auto& mixn_timestamps : mixin_timestamp_groups) - { - // get mixins in time scale for visual representation - pair mixin_times_scale = xmreg::timestamps_time_scale( - mixn_timestamps, - max_mix_timestamp, - 170, - min_mix_timestamp); - - // save resolution of mixin timescales - timescale_scale = mixin_times_scale.second; - - // save the string timescales for later to show - mixins_timescales.push_back(mstch::map { - {"timescale", mixin_times_scale.first} - }); - } + min_mix_timestamp -= 3600; + max_mix_timestamp += 3600; - return make_pair(mixins_timescales, timescale_scale); + // make timescale maps for mixins in input with adjusted range + for (auto& mixn_timestamps : mixin_timestamp_groups) + { + // get mixins in time scale for visual representation + pair mixin_times_scale = xmreg::timestamps_time_scale( + mixn_timestamps, + max_mix_timestamp, + 170, + min_mix_timestamp); + + // save resolution of mixin timescales + timescale_scale = mixin_times_scale.second; + + // save the string timescales for later to show + mixins_timescales.push_back(mstch::map { + {"timescale", mixin_times_scale.first} + }); } + return make_pair(mixins_timescales, timescale_scale); +} - tx_details - get_tx_details(const transaction& tx, - bool coinbase = false, - uint64_t blk_height = 0, - uint64_t bc_height = 0) - { - tx_details txd; - // get tx hash - txd.hash = get_transaction_hash(tx); +tx_details +get_tx_details(const transaction& tx, + bool coinbase = false, + uint64_t blk_height = 0, + uint64_t bc_height = 0) +{ + tx_details txd; + + // get tx hash + txd.hash = get_transaction_hash(tx); - // get tx public key from extra - // this check if there are two public keys - // due to previous bug with sining txs: - // https://github.com/monero-project/monero/pull/1358/commits/7abfc5474c0f86e16c405f154570310468b635c2 - txd.pk = xmreg::get_tx_pub_key_from_received_outs(tx); - txd.additional_pks = cryptonote::get_additional_tx_pub_keys_from_extra(tx); + // get tx public key from extra + // this check if there are two public keys + // due to previous bug with sining txs: + // https://github.com/monero-project/monero/pull/1358/commits/7abfc5474c0f86e16c405f154570310468b635c2 + txd.pk = xmreg::get_tx_pub_key_from_received_outs(tx); + txd.additional_pks = cryptonote::get_additional_tx_pub_keys_from_extra(tx); - // sum xmr in inputs and ouputs in the given tx - const array& sum_data = summary_of_in_out_rct( - tx, txd.output_pub_keys, txd.input_key_imgs); + // sum xmr in inputs and ouputs in the given tx + const array& sum_data = summary_of_in_out_rct( + tx, txd.output_pub_keys, txd.input_key_imgs); - txd.xmr_outputs = sum_data[0]; - txd.xmr_inputs = sum_data[1]; - txd.mixin_no = sum_data[2]; - txd.num_nonrct_inputs = sum_data[3]; + txd.xmr_outputs = sum_data[0]; + txd.xmr_inputs = sum_data[1]; + txd.mixin_no = sum_data[2]; + txd.num_nonrct_inputs = sum_data[3]; - txd.fee = 0; + txd.fee = 0; - if (!coinbase && tx.vin.size() > 0) + if (!coinbase && tx.vin.size() > 0) + { + // check if not miner tx + // i.e., for blocks without any user transactions + if (tx.vin.at(0).type() != typeid(txin_gen)) { - // check if not miner tx - // i.e., for blocks without any user transactions - if (tx.vin.at(0).type() != typeid(txin_gen)) - { - // get tx fee - txd.fee = get_tx_fee(tx); - } + // get tx fee + txd.fee = get_tx_fee(tx); } + } - txd.pID = '-'; // no payment ID - - get_payment_id(tx, txd.payment_id, txd.payment_id8); - - // get tx size in bytes - txd.size = get_object_blobsize(tx); + txd.pID = '-'; // no payment ID - txd.extra = tx.extra; + get_payment_id(tx, txd.payment_id, txd.payment_id8); - if (txd.payment_id != null_hash) - { - txd.payment_id_as_ascii = std::string(txd.payment_id.data, crypto::HASH_SIZE); - txd.pID = 'l'; // legacy payment id - } - else if (txd.payment_id8 != null_hash8) - { - txd.pID = 'e'; // encrypted payment id - } - else if (txd.additional_pks.empty() == false) - { - // if multioutput tx have additional public keys, - // mark it so that it represents that it has at least - // one sub-address - txd.pID = 's'; - } + // get tx size in bytes + txd.size = get_object_blobsize(tx); - // get tx signatures for each input - txd.signatures = tx.signatures; + txd.extra = tx.extra; - // get tx version - txd.version = tx.version; + if (txd.payment_id != null_hash) + { + txd.payment_id_as_ascii = std::string(txd.payment_id.data, crypto::HASH_SIZE); + txd.pID = 'l'; // legacy payment id + } + else if (txd.payment_id8 != null_hash8) + { + txd.pID = 'e'; // encrypted payment id + } + else if (txd.additional_pks.empty() == false) + { + // if multioutput tx have additional public keys, + // mark it so that it represents that it has at least + // one sub-address + txd.pID = 's'; + } - // get unlock time - txd.unlock_time = tx.unlock_time; + // get tx signatures for each input + txd.signatures = tx.signatures; - txd.no_confirmations = 0; + // get tx version + txd.version = tx.version; - if (blk_height == 0 && core_storage->have_tx(txd.hash)) - { - // if blk_height is zero then search for tx block in - // the blockchain. but since often block height is know a priory - // this is not needed + // get unlock time + txd.unlock_time = tx.unlock_time; - txd.blk_height = core_storage->get_db().get_tx_block_height(txd.hash); + txd.no_confirmations = 0; - // get the current blockchain height. Just to check - uint64_t bc_height = core_storage->get_current_blockchain_height(); + if (blk_height == 0 && core_storage->have_tx(txd.hash)) + { + // if blk_height is zero then search for tx block in + // the blockchain. but since often block height is know a priory + // this is not needed - txd.no_confirmations = bc_height - (txd.blk_height); - } - else - { - // if we know blk_height, and current blockchan height - // just use it to get no_confirmations. + txd.blk_height = core_storage->get_db().get_tx_block_height(txd.hash); - txd.no_confirmations = bc_height - (blk_height); - } + // get the current blockchain height. Just to check + uint64_t bc_height = core_storage->get_current_blockchain_height(); - return txd; + txd.no_confirmations = bc_height - (txd.blk_height); } - - void - clean_post_data(string& raw_tx_data) + else { - // remove white characters - boost::trim(raw_tx_data); - boost::erase_all(raw_tx_data, "\r\n"); - boost::erase_all(raw_tx_data, "\n"); + // if we know blk_height, and current blockchan height + // just use it to get no_confirmations. - // remove header and footer from base64 data - // produced by certutil.exe in windows - boost::erase_all(raw_tx_data, "-----BEGIN CERTIFICATE-----"); - boost::erase_all(raw_tx_data, "-----END CERTIFICATE-----"); + txd.no_confirmations = bc_height - (blk_height); } + return txd; +} - bool - find_tx_for_json( - string const& tx_hash_str, - transaction& tx, - bool& found_in_mempool, - uint64_t& tx_timestamp, - string& error_message) - { - // parse tx hash string to hash object - crypto::hash tx_hash; +void +clean_post_data(string& raw_tx_data) +{ + // remove white characters + boost::trim(raw_tx_data); + boost::erase_all(raw_tx_data, "\r\n"); + boost::erase_all(raw_tx_data, "\n"); + + // remove header and footer from base64 data + // produced by certutil.exe in windows + boost::erase_all(raw_tx_data, "-----BEGIN CERTIFICATE-----"); + boost::erase_all(raw_tx_data, "-----END CERTIFICATE-----"); +} - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) - { - error_message = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); - return false; - } - if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) - { - error_message = fmt::format("Cant find tx hash: {:s}", tx_hash_str); - return false; - } +bool +find_tx_for_json( + string const& tx_hash_str, + transaction& tx, + bool& found_in_mempool, + uint64_t& tx_timestamp, + string& error_message) +{ + // parse tx hash string to hash object + crypto::hash tx_hash; - return true; + if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + { + error_message = fmt::format("Cant parse tx hash: {:s}", tx_hash_str); + return false; } - bool - search_mempool(crypto::hash tx_hash, - vector& found_txs) + if (!find_tx(tx_hash, tx, found_in_mempool, tx_timestamp)) { - // if tx_hash == null_hash then this method - // will just return the vector containing all - // txs in mempool + error_message = fmt::format("Cant find tx hash: {:s}", tx_hash_str); + return false; + } + return true; +} +bool +search_mempool(crypto::hash tx_hash, + vector& found_txs) +{ + // if tx_hash == null_hash then this method + // will just return the vector containing all + // txs in mempool - // get mempool tx from mempoolstatus thread - vector mempool_txs - = MempoolStatus::get_mempool_txs(); - // if dont have tx_blob member, construct tx - // from json obtained from the rpc call - for (size_t i = 0; i < mempool_txs.size(); ++i) - { - // get transaction info of the tx in the mempool - const MempoolStatus::mempool_tx& mempool_tx = mempool_txs.at(i); + // get mempool tx from mempoolstatus thread + vector mempool_txs + = MempoolStatus::get_mempool_txs(); - if (tx_hash == mempool_tx.tx_hash || tx_hash == null_hash) - { - found_txs.push_back(mempool_tx); + // if dont have tx_blob member, construct tx + // from json obtained from the rpc call - if (tx_hash != null_hash) - break; - } + for (size_t i = 0; i < mempool_txs.size(); ++i) + { + // get transaction info of the tx in the mempool + const MempoolStatus::mempool_tx& mempool_tx = mempool_txs.at(i); - } // for (size_t i = 0; i < mempool_txs.size(); ++i) + if (tx_hash == mempool_tx.tx_hash || tx_hash == null_hash) + { + found_txs.push_back(mempool_tx); - return true; - } + if (tx_hash != null_hash) + break; + } - pair - get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0) - { + } // for (size_t i = 0; i < mempool_txs.size(); ++i) - pair age_pair; + return true; +} - // calculate difference between server and block timestamps - array delta_time = timestamp_difference( - timestamp1, timestamp2); +pair +get_age(uint64_t timestamp1, uint64_t timestamp2, bool full_format = 0) +{ - // default format for age - string age_str = fmt::format("{:02d}:{:02d}:{:02d}", - delta_time[2], delta_time[3], - delta_time[4]); + pair age_pair; - string age_format {"[h:m:s]"}; + // calculate difference between server and block timestamps + array delta_time = timestamp_difference( + timestamp1, timestamp2); - // if have days or years, change age format - if (delta_time[0] > 0 || full_format == true) - { - age_str = fmt::format("{:02d}:{:03d}:{:02d}:{:02d}:{:02d}", - delta_time[0], delta_time[1], delta_time[2], - delta_time[3], delta_time[4]); + // default format for age + string age_str = fmt::format("{:02d}:{:02d}:{:02d}", + delta_time[2], delta_time[3], + delta_time[4]); - age_format = string("[y:d:h:m:s]"); - } - else if (delta_time[1] > 0) - { - age_str = fmt::format("{:02d}:{:02d}:{:02d}:{:02d}", - delta_time[1], delta_time[2], - delta_time[3], delta_time[4]); + string age_format {"[h:m:s]"}; - age_format = string("[d:h:m:s]"); - } + // if have days or years, change age format + if (delta_time[0] > 0 || full_format == true) + { + age_str = fmt::format("{:02d}:{:03d}:{:02d}:{:02d}:{:02d}", + delta_time[0], delta_time[1], delta_time[2], + delta_time[3], delta_time[4]); - age_pair.first = age_str; - age_pair.second = age_format; + age_format = string("[y:d:h:m:s]"); + } + else if (delta_time[1] > 0) + { + age_str = fmt::format("{:02d}:{:02d}:{:02d}:{:02d}", + delta_time[1], delta_time[2], + delta_time[3], delta_time[4]); - return age_pair; + age_format = string("[d:h:m:s]"); } + age_pair.first = age_str; + age_pair.second = age_format; - string - get_full_page(const string& middle) - { - return template_file["header"] - + middle - + template_file["footer"]; - } - - bool - get_monero_network_info(json& j_info) - { - MempoolStatus::network_info local_copy_network_info - = MempoolStatus::current_network_info; - - j_info = json { - {"status" , local_copy_network_info.current}, - {"current" , local_copy_network_info.current}, - {"height" , local_copy_network_info.height}, - {"target_height" , local_copy_network_info.target_height}, - {"difficulty" , local_copy_network_info.difficulty}, - {"target" , local_copy_network_info.target}, - {"hash_rate" , local_copy_network_info.hash_rate}, - {"tx_count" , local_copy_network_info.tx_count}, - {"tx_pool_size" , local_copy_network_info.tx_pool_size}, - {"alt_blocks_count" , local_copy_network_info.alt_blocks_count}, - {"outgoing_connections_count", local_copy_network_info.outgoing_connections_count}, - {"incoming_connections_count", local_copy_network_info.incoming_connections_count}, - {"white_peerlist_size" , local_copy_network_info.white_peerlist_size}, - {"grey_peerlist_size" , local_copy_network_info.grey_peerlist_size}, - {"testnet" , local_copy_network_info.nettype == cryptonote::network_type::TESTNET}, - {"stagenet" , local_copy_network_info.nettype == cryptonote::network_type::STAGENET}, - {"top_block_hash" , pod_to_hex(local_copy_network_info.top_block_hash)}, - {"cumulative_difficulty" , local_copy_network_info.cumulative_difficulty}, - {"block_size_limit" , local_copy_network_info.block_size_limit}, - {"block_size_median" , local_copy_network_info.block_size_median}, - {"start_time" , local_copy_network_info.start_time}, - {"fee_per_kb" , local_copy_network_info.fee_per_kb}, - {"current_hf_version" , local_copy_network_info.current_hf_version} - }; + return age_pair; +} - return local_copy_network_info.current; - } - bool - get_dynamic_per_kb_fee_estimate(uint64_t& fee_estimated) - { +string +get_full_page(const string& middle) +{ + return template_file["header"] + + middle + + template_file["footer"]; +} - string error_msg; +bool +get_monero_network_info(json& j_info) +{ + MempoolStatus::network_info local_copy_network_info + = MempoolStatus::current_network_info; + + j_info = json { + {"status" , local_copy_network_info.current}, + {"current" , local_copy_network_info.current}, + {"height" , local_copy_network_info.height}, + {"target_height" , local_copy_network_info.target_height}, + {"difficulty" , local_copy_network_info.difficulty}, + {"target" , local_copy_network_info.target}, + {"hash_rate" , local_copy_network_info.hash_rate}, + {"tx_count" , local_copy_network_info.tx_count}, + {"tx_pool_size" , local_copy_network_info.tx_pool_size}, + {"alt_blocks_count" , local_copy_network_info.alt_blocks_count}, + {"outgoing_connections_count", local_copy_network_info.outgoing_connections_count}, + {"incoming_connections_count", local_copy_network_info.incoming_connections_count}, + {"white_peerlist_size" , local_copy_network_info.white_peerlist_size}, + {"grey_peerlist_size" , local_copy_network_info.grey_peerlist_size}, + {"testnet" , local_copy_network_info.nettype == cryptonote::network_type::TESTNET}, + {"stagenet" , local_copy_network_info.nettype == cryptonote::network_type::STAGENET}, + {"top_block_hash" , pod_to_hex(local_copy_network_info.top_block_hash)}, + {"cumulative_difficulty" , local_copy_network_info.cumulative_difficulty}, + {"block_size_limit" , local_copy_network_info.block_size_limit}, + {"block_size_median" , local_copy_network_info.block_size_median}, + {"start_time" , local_copy_network_info.start_time}, + {"fee_per_kb" , local_copy_network_info.fee_per_kb}, + {"current_hf_version" , local_copy_network_info.current_hf_version} + }; - 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; - } + return local_copy_network_info.current; +} + +bool +get_dynamic_per_kb_fee_estimate(uint64_t& fee_estimated) +{ - (void) error_msg; + string error_msg; - return true; + 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; } - bool - are_absolute_offsets_good( - std::vector const& absolute_offsets, - txin_to_key const& in_key) - { - // before proceeding with geting the outputs based on the amount and absolute offset - // check how many outputs there are for that amount - uint64_t no_outputs = core_storage->get_db().get_num_outputs(in_key.amount); + (void) error_msg; - bool offset_too_large {false}; + return true; +} - int offset_idx {-1}; +bool +are_absolute_offsets_good( + std::vector const& absolute_offsets, + txin_to_key const& in_key) +{ + // before proceeding with geting the outputs based on the amount and absolute offset + // check how many outputs there are for that amount + uint64_t no_outputs = core_storage->get_db().get_num_outputs(in_key.amount); - for (auto o: absolute_offsets) - { - offset_idx++; + bool offset_too_large {false}; - if (o >= no_outputs) - { - offset_too_large = true; - cerr << "Absolute offset (" << o << ") of an output in a key image " - << pod_to_hex(in_key.k_image) - << " (ring member no: " << offset_idx << ") " - << "for amount " << in_key.amount - << " is too large. There are only " - << no_outputs << " such outputs!\n"; - continue; - } - } + int offset_idx {-1}; + + for (auto o: absolute_offsets) + { + offset_idx++; - return !offset_too_large; + if (o >= no_outputs) + { + offset_too_large = true; + cerr << "Absolute offset (" << o << ") of an output in a key image " + << pod_to_hex(in_key.k_image) + << " (ring member no: " << offset_idx << ") " + << "for amount " << in_key.amount + << " is too large. There are only " + << no_outputs << " such outputs!\n"; + continue; + } } - string - get_footer() - { - // set last git commit date based on - // autogenrated version.h during compilation - static const mstch::map footer_context { - {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, - {"last_git_commit_date", string {GIT_COMMIT_DATETIME}}, - {"git_branch_name" , string {GIT_BRANCH_NAME}}, - {"monero_version_full" , string {MONERO_VERSION_FULL}}, - {"api" , std::to_string(ONIONEXPLORER_RPC_VERSION_MAJOR) - + "." - + std::to_string(ONIONEXPLORER_RPC_VERSION_MINOR)}, - }; + return !offset_too_large; +} - string footer_html = mstch::render(xmreg::read(TMPL_FOOTER), footer_context); +string +get_footer() +{ + // set last git commit date based on + // autogenrated version.h during compilation + static const mstch::map footer_context { + {"last_git_commit_hash", string {GIT_COMMIT_HASH}}, + {"last_git_commit_date", string {GIT_COMMIT_DATETIME}}, + {"git_branch_name" , string {GIT_BRANCH_NAME}}, + {"monero_version_full" , string {MONERO_VERSION_FULL}}, + {"api" , std::to_string(ONIONEXPLORER_RPC_VERSION_MAJOR) + + "." + + std::to_string(ONIONEXPLORER_RPC_VERSION_MINOR)}, + }; - return footer_html; - } + string footer_html = mstch::render(xmreg::read(TMPL_FOOTER), footer_context); - void - add_css_style(mstch::map& context) - { - // add_css_style goes to every subpage so here we mark - // if js is anabled or not. - context["enable_js"] = enable_js; + return footer_html; +} - context["css_styles"] = mstch::lambda{[&](const std::string& text) -> mstch::node { - return template_file["css_styles"]; - }}; - } +void +add_css_style(mstch::map& context) +{ + // add_css_style goes to every subpage so here we mark + // if js is anabled or not. + context["enable_js"] = enable_js; - void - add_js_files(mstch::map& context) - { - context["js_files"] = mstch::lambda{[&](const std::string& text) -> mstch::node { - //return this->js_html_files; - return this->js_html_files_all_in_one; - }}; - } + context["css_styles"] = mstch::lambda{[&](const std::string& text) -> mstch::node { + return template_file["css_styles"]; + }}; +} + +void +add_js_files(mstch::map& context) +{ + context["js_files"] = mstch::lambda{[&](const std::string& text) -> mstch::node { + //return this->js_html_files; + return this->js_html_files_all_in_one; + }}; +} }; } diff --git a/src/templates/partials/tx_details.html b/src/templates/partials/tx_details.html index 0d2f89c..5ca84e0 100644 --- a/src/templates/partials/tx_details.html +++ b/src/templates/partials/tx_details.html @@ -535,6 +535,7 @@ More details {{#enable_as_hex}} | Tx as hex + | Tx ring members as hex {{/enable_as_hex}} {{/show_more_details_link}}