diff --git a/src/page.h b/src/page.h index 52ebcb8..35539f1 100644 --- a/src/page.h +++ b/src/page.h @@ -943,6 +943,115 @@ namespace xmreg { } } + mstch::map context = construct_tx_context(tx, with_ring_signatures); + + // read tx.html + string tx_html = xmreg::read(TMPL_TX); + + // add header and footer + string full_page = get_full_page(tx_html); + + // render the page + return mstch::render(full_page, context); + } + + string + show_my_outputs(string tx_hash_str, + string xmr_address_str, + string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ + bool tx_prove = false) + { + + // remove white characters + boost::trim(tx_hash_str); + boost::trim(xmr_address_str); + boost::trim(viewkey_str); + + if (tx_hash_str.empty()) + { + return string("tx hash not provided!"); + } + + if (xmr_address_str.empty()) + { + return string("Monero address not provided!"); + } + + if (viewkey_str.empty()) + { + if (!tx_prove) + return string("Viewkey not provided!"); + else + return string("Tx private key not provided!"); + } + + // parse tx hash string to hash object + crypto::hash tx_hash; + + 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); + } + + // parse string representing given monero address + cryptonote::account_public_address address; + + if (!xmreg::parse_str_address(xmr_address_str, address, testnet)) + { + cerr << "Cant parse string address: " << xmr_address_str << endl; + return string("Cant parse xmr address: " + xmr_address_str); + } + + // parse string representing given private key + crypto::secret_key prv_view_key; + + if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) + { + cerr << "Cant parse the private key: " << viewkey_str << endl; + return string("Cant parse private key: " + viewkey_str); + } + + // tx age + pair age; + + string blk_timestamp {"N/A"}; + + // 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); + + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).second; + + // since its tx in mempool, it has no blk yet + // so use its recive_time as timestamp to show + + uint64_t tx_recieve_timestamp + = found_txs.at(0).first.receive_time; + + blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); + + age = get_age(server_timestamp, + tx_recieve_timestamp, + FULL_AGE_FORMAT); + } + else + { + // tx is nowhere to be found :-( + return string("Cant get tx: " + tx_hash_str); + } + } + tx_details txd = get_tx_details(tx); uint64_t tx_blk_height {0}; @@ -985,1408 +1094,1319 @@ namespace xmreg { string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); - - string tx_json = obj_to_json_str(tx); - // initalise page tempate map with basic info about blockchain mstch::map context { {"testnet" , testnet}, {"tx_hash" , tx_hash_str}, + {"xmr_address" , xmr_address_str}, + {"viewkey" , viewkey_str}, {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, {"blk_height" , tx_blk_height_str}, {"tx_size" , fmt::format("{:0.4f}", - static_cast(txd.size) / 1024.0)}, + static_cast(txd.size) / 1024.0)}, {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, - {"tx_version" , fmt::format("{:d}", txd.version)}, {"blk_timestamp" , blk_timestamp}, - {"blk_timestamp_uint" , blk.timestamp}, {"delta_time" , age.first}, - {"inputs_no" , txd.input_key_imgs.size()}, - {"has_inputs" , !txd.input_key_imgs.empty()}, {"outputs_no" , 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}, - {"extra" , txd.get_extra_str()}, - {"with_ring_signatures" , static_cast( - with_ring_signatures)}, - {"tx_json" , tx_json} + {"tx_prove" , tx_prove} }; string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); - mstch::array inputs = mstch::array{}; - - mstch::array mixins_timescales; + uint64_t output_idx {0}; - double timescale_scale {0.0}; // size of one '_' in days + // public transaction key is combined with our viewkey + // to create, so called, derived key. + key_derivation derivation; - uint64_t input_idx {0}; + public_key pub_key = tx_prove ? address.m_view_public_key : txd.pk; - uint64_t inputs_xmr_sum {0}; + if (!generate_key_derivation(pub_key, prv_view_key, derivation)) + { + cerr << "Cant get derived key for: " << "\n" + << "pub_tx_key: " << pub_key << " and " + << "prv_view_key" << prv_view_key << endl; - // initialize with some large and some numbers - uint64_t min_mix_timestamp = server_timestamp*2L; + return string("Cant get key_derivation"); + } - uint64_t max_mix_timestamp {0}; + mstch::array outputs; - vector> mixin_timestamp_groups; + uint64_t sum_xmr {0}; - // make timescale maps for mixins in input - for (const txin_to_key& in_key: txd.input_key_imgs) - { - // get absolute offsets of mixins - std::vector absolute_offsets - = cryptonote::relative_output_offsets_to_absolute( - in_key.key_offsets); + std::vector money_transfered(tx.vout.size(), 0); - // get public keys of outputs used in the mixins that match to the offests - std::vector outputs; - core_storage->get_db().get_output_key(in_key.amount, - absolute_offsets, - outputs); + std::deque mask(tx.vout.size()); - inputs.push_back(mstch::map { - {"in_key_img", REMOVE_HASH_BRAKETS(fmt::format("{:s}", in_key.k_image))}, - {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(in_key.amount))}, - {"input_idx" , fmt::format("{:02d}", input_idx)}, - {"mixins" , mstch::array{}}, - {"ring_sigs" , txd.get_ring_sig_for_input(input_idx)} - }); + uint64_t i {0}; - inputs_xmr_sum += in_key.amount; + for (pair& outp: txd.output_pub_keys) + { - vector mixin_timestamps; + // 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 reference to mixins array created above - mstch::array& mixins = boost::get( - boost::get(inputs.back())["mixins"]); + derive_public_key(derivation, + output_idx, + address.m_spend_public_key, + tx_pubkey); - // mixin counter - size_t count = 0; + // check if generated public key matches the current output's key + bool mine_output = (outp.first.key == tx_pubkey); - // for each found output public key find its block to get timestamp - for (const uint64_t &i: absolute_offsets) + // if mine output has RingCT, i.e., tx version is 2 + if (mine_output && tx.version == 2) { - // get basic information about mixn's output - cryptonote::output_data_t output_data = outputs.at(count); + // initialize with regular amount + uint64_t rct_amount = money_transfered[i]; - // get pair pair where first is tx hash - // and second is local index of the output i in that tx - tx_out_index tx_out_idx = - core_storage->get_db().get_output_tx_and_index(in_key.amount, i); + bool r; - // get block of given height, as we want to get its timestamp - cryptonote::block blk; + r = decode_ringct(tx.rct_signatures, + pub_key, + prv_view_key, + i, + tx.rct_signatures.ecdhInfo[i].mask, + rct_amount); - if (!mcore->get_block_by_height(output_data.height, blk)) + if (!r) { - cerr << "- cant get block of height: " << output_data.height << endl; - return fmt::format("- cant get block of height: {}", output_data.height); + cerr << "Cant decode ringCT!" << endl; } - // 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 (!mcore->get_tx(tx_out_idx.first, mixin_tx)) + // cointbase txs have amounts in plain sight. + // so use amount from ringct, only for non-coinbase txs + if (!is_coinbase(tx)) { - cerr << "Cant get tx: " << tx_out_idx.first << endl; - return fmt::format("Cant get tx: {:s}", tx_out_idx.first); + outp.second = rct_amount; + money_transfered[i] = rct_amount; } - // 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" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", - output_data.pubkey))}, - {"mix_tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", - tx_out_idx.first))}, - {"mix_out_indx" , fmt::format("{:d}", tx_out_idx.second)}, - {"mix_timestamp" , xmreg::timestamp_to_str(blk.timestamp)}, - {"mix_age" , mixin_age.first}, - {"mix_mixin_no" , mixin_txd.mixin_no}, - {"mix_inputs_no" , mixin_txd.input_key_imgs.size()}, - {"mix_outputs_no" , mixin_txd.output_pub_keys.size()}, - {"mix_age_format" , mixin_age.second}, - {"mix_idx" , fmt::format("{:02d}", count)}, - }); + if (mine_output) + { + sum_xmr += outp.second; + } - if (blk.timestamp < min_mix_timestamp) - min_mix_timestamp = blk.timestamp; + outputs.push_back(mstch::map { + {"out_pub_key" , REMOVE_HASH_BRAKETS( + fmt::format("{:s}", + outp.first.key))}, + {"amount" , fmt::format("{:0.12f}", + XMR_AMOUNT(outp.second))}, + {"mine_output" , mine_output}, + {"output_idx" , fmt::format("{:02d}", output_idx++)} + }); - if (blk.timestamp > max_mix_timestamp) - max_mix_timestamp = blk.timestamp; + ++i; + } - // get mixin timestamp from its orginal block - mixin_timestamps.push_back(blk.timestamp); - - ++count; - - } // for (const uint64_t &i: absolute_offsets) - - mixin_timestamp_groups.push_back(mixin_timestamps); - - input_idx++; - } // for (const txin_to_key& in_key: txd.input_key_imgs) - - 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}}); - } - - - context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); - context["server_time"] = server_time_str; - context["inputs"] = inputs; - context["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); - context["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); - context["timescales"] = mixins_timescales; - context["timescales_scale"] = fmt::format("{:0.2f}", - timescale_scale / 3600.0 / 24.0); // in days - - // get indices of outputs in amounts tables - vector out_amount_indices; - - try - { - - 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; - } - - } - catch(const exception& e) - { - cerr << e.what() << endl; - } - - uint64_t output_idx {0}; - - mstch::array outputs; - - uint64_t outputs_xmr_sum {0}; - - for (pair& outp: txd.output_pub_keys) - { + cout << "outputs.size(): " << outputs.size() << endl; - // total number of ouputs in the blockchain for this amount - uint64_t num_outputs_amount = core_storage->get_db() - .get_num_outputs(outp.second); + context["outputs"] = outputs; + context["sum_xmr"] = XMR_AMOUNT(sum_xmr); - string out_amount_index_str {"N/A"}; + // read my_outputs.html + string my_outputs_html = xmreg::read(TMPL_MY_OUTPUTS); - // 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 = fmt::format("{:d}", - out_amount_indices.at(output_idx)); - } + // add header and footer + string full_page = get_full_page(my_outputs_html); - outputs_xmr_sum += outp.second; + // render the page + return mstch::render(full_page, context); + } - outputs.push_back(mstch::map { - {"out_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", outp.first.key))}, - {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(outp.second))}, - {"amount_idx" , out_amount_index_str}, - {"num_outputs" , fmt::format("{:d}", num_outputs_amount)}, - {"output_idx" , fmt::format("{:02d}", output_idx++)} - }); - } + string + show_prove(string tx_hash_str, + string xmr_address_str, + string tx_prv_key_str) + { + return show_my_outputs(tx_hash_str, xmr_address_str, tx_prv_key_str, true); + } - context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); + string + show_rawtx() + { - context["outputs"] = outputs; + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet} + }; - // read tx.html - string tx_html = xmreg::read(TMPL_TX); + // read rawtx.html + string rawtx_html = xmreg::read(TMPL_MY_RAWTX); // add header and footer - string full_page = get_full_page(tx_html); + string full_page = rawtx_html + xmreg::read(TMPL_FOOTER); // render the page return mstch::render(full_page, context); } string - show_my_outputs(string tx_hash_str, - string xmr_address_str, - string viewkey_str, /* or tx_prv_key_str when tx_prove == true */ - bool tx_prove = false) + show_checkandpushtx(string raw_tx_data, string action) { - // remove white characters - boost::trim(tx_hash_str); - boost::trim(xmr_address_str); - boost::trim(viewkey_str); + boost::trim(raw_tx_data); + boost::erase_all(raw_tx_data, "\r\n"); + boost::erase_all(raw_tx_data, "\n"); - if (tx_hash_str.empty()) - { - return string("tx hash not provided!"); - } + //cout << raw_tx_data << endl; - if (xmr_address_str.empty()) - { - return string("Monero address not provided!"); - } + string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); - if (viewkey_str.empty()) - { - if (!tx_prove) - return string("Viewkey not provided!"); - else - return string("Tx private key not provided!"); - } + //cout << decoded_raw_tx_data << endl; - // parse tx hash string to hash object - crypto::hash tx_hash; + const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); - if (!xmreg::parse_str_secret_key(tx_hash_str, tx_hash)) + bool unsigned_tx_given {false}; + + if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) { - cerr << "Cant parse tx hash: " << tx_hash_str << endl; - return string("Cant get tx hash due to parse error: " + tx_hash_str); + unsigned_tx_given = true; + cout << "UNSIGNED_TX_PREFIX data given" << endl; } - // parse string representing given monero address - cryptonote::account_public_address address; + // initalize page template context map + mstch::map context { + {"testnet" , testnet}, + {"unsigned_tx_given" , unsigned_tx_given}, + {"txs" , mstch::array{}} + }; - if (!xmreg::parse_str_address(xmr_address_str, address, testnet)) + if (unsigned_tx_given) { - cerr << "Cant parse string address: " << xmr_address_str << endl; - return string("Cant parse xmr address: " + xmr_address_str); - } + ::tools::wallet2::unsigned_tx_set exported_txs; - // parse string representing given private key - crypto::secret_key prv_view_key; + bool r = serialization::parse_binary(std::string( + decoded_raw_tx_data.c_str() + magiclen, + decoded_raw_tx_data.size() - magiclen), + exported_txs); + if (r) + { + mstch::array& txs = boost::get(context["txs"]); - if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key)) - { - cerr << "Cant parse the private key: " << viewkey_str << endl; - return string("Cant parse private key: " + viewkey_str); - } + for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) + { + size_t no_of_sources = tx_cd.sources.size(); - // tx age - pair age; + const tx_destination_entry& tx_change = tx_cd.change_dts; - string blk_timestamp {"N/A"}; + mstch::map tx_cd_data { + {"no_of_sources" , no_of_sources}, + {"use_rct" , tx_cd.use_rct}, + {"change_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(tx_change.amount))}, + {"dest_sources" , mstch::array{}}, + {"dest_infos" , mstch::array{}}, + }; - // get transaction - transaction tx; + mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); + mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); - if (!mcore->get_tx(tx_hash, tx)) - { - cerr << "Cant get tx in blockchain: " << tx_hash - << ". \n Check mempool now" << endl; + for (const tx_destination_entry& a_dest: tx_cd.destinations) + { + mstch::map dest_info { + {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, + {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))} + }; - vector> found_txs - = search_mempool(tx_hash); + dest_infos.push_back(dest_info); + } - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).second; + uint64_t sum_outputs_amounts {0}; - // since its tx in mempool, it has no blk yet - // so use its recive_time as timestamp to show + for (size_t i = 0; i < no_of_sources; ++i) + { - uint64_t tx_recieve_timestamp - = found_txs.at(0).first.receive_time; + const tx_source_entry& tx_source = tx_cd.sources.at(i); - blk_timestamp = xmreg::timestamp_to_str(tx_recieve_timestamp); + mstch::map single_dest_source { + {"output_amount" , fmt::format("{:0.12f}", + XMR_AMOUNT(tx_source.amount))}, + {"real_output" , tx_source.real_output}, + {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, + {"real_output_in_tx_index" , tx_source.real_output_in_tx_index}, + {"outputs" , mstch::array{}} + }; - 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); - } - } + sum_outputs_amounts += tx_source.amount; - tx_details txd = get_tx_details(tx); + //cout << tx_source.real_output << endl; + //cout << tx_source.real_out_tx_key << endl; + //cout << tx_source.real_output_in_tx_index << endl; - uint64_t tx_blk_height {0}; + uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; - bool tx_blk_found {false}; + // get tx of the real output + tx_out_index real_toi = core_storage->get_db() + .get_output_tx_and_index(0, index_of_real_output); - 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; - } + 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)); + } - // get block cointaining this tx - block blk; + tx_details real_txd = get_tx_details(real_source_tx); - if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) - { - cerr << "Cant get block: " << tx_blk_height << endl; - } + public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; - string tx_blk_height_str {"N/A"}; + //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; - if (tx_blk_found) - { - // calculate difference between tx and server timestamps - age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); + mstch::array& outputs = boost::get(single_dest_source["outputs"]); - blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); + size_t output_i {0}; - tx_blk_height_str = std::to_string(tx_blk_height); - } + // cout << tx_source.amount << endl; - // payments id. both normal and encrypted (payment_id8) - string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); - string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); + for(const tx_source_entry::output_entry& oe: tx_source.outputs) + { - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"tx_hash" , tx_hash_str}, - {"xmr_address" , xmr_address_str}, - {"viewkey" , viewkey_str}, - {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, - {"blk_height" , tx_blk_height_str}, - {"tx_size" , fmt::format("{:0.4f}", - static_cast(txd.size) / 1024.0)}, - {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, - {"blk_timestamp" , blk_timestamp}, - {"delta_time" , age.first}, - {"outputs_no" , 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}, - {"tx_prove" , tx_prove} - }; + tx_out_index toi = core_storage->get_db() + .get_output_tx_and_index(0, oe.first); - string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); - uint64_t output_idx {0}; + transaction tx; - // public transaction key is combined with our viewkey - // to create, so called, derived key. - key_derivation derivation; + 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)); + } - public_key pub_key = tx_prove ? address.m_view_public_key : txd.pk; + tx_details txd = get_tx_details(tx); - if (!generate_key_derivation(pub_key, prv_view_key, derivation)) - { - cerr << "Cant get derived key for: " << "\n" - << "pub_tx_key: " << pub_key << " and " - << "prv_view_key" << prv_view_key << endl; + public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; - return string("Cant get key_derivation"); - } - mstch::array outputs; + // get block cointaining this tx + block blk; - uint64_t sum_xmr {0}; + 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)); + } - std::vector money_transfered(tx.vout.size(), 0); + pair age = get_age(server_timestamp, blk.timestamp); - std::deque mask(tx.vout.size()); + 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 i {0}; + single_dest_source.insert({"age_format" , age.second}); - for (pair& outp: txd.output_pub_keys) - { + outputs.push_back(single_output); - // get the tx output public key - // that normally would be generated for us, - // if someone had sent us some xmr. - public_key tx_pubkey; + ++output_i; + } - derive_public_key(derivation, - output_idx, - address.m_spend_public_key, - tx_pubkey); + dest_sources.push_back(single_dest_source); + } - // check if generated public key matches the current output's key - bool mine_output = (outp.first.key == tx_pubkey); + tx_cd_data.insert({"sum_outputs_amounts" , fmt::format("{:0.12f}", XMR_AMOUNT(sum_outputs_amounts))}); - // if mine output has RingCT, i.e., tx version is 2 - if (mine_output && tx.version == 2) + txs.push_back(tx_cd_data); + } + } + else { - // initialize with regular amount - uint64_t rct_amount = money_transfered[i]; + 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 - bool r; + const size_t magiclen = strlen(SIGNED_TX_PREFIX); - r = decode_ringct(tx.rct_signatures, - pub_key, - prv_view_key, - i, - tx.rct_signatures.ecdhInfo[i].mask, - rct_amount); + if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) + { + cout << "The data is neigther unsigned nor signed tx!" << endl; + return string( "The data is neither unsigned nor signed tx!"); + } - if (!r) - { - cerr << "Cant decode ringCT!" << endl; - } + ::tools::wallet2::signed_tx_set signed_txs; - // cointbase txs have amounts in plain sight. - // so use amount from ringct, only for non-coinbase txs - if (!is_coinbase(tx)) - { - outp.second = rct_amount; - money_transfered[i] = rct_amount; - } + bool r = serialization::parse_binary(std::string( + decoded_raw_tx_data.c_str() + magiclen, + decoded_raw_tx_data.size() - magiclen), + signed_txs); + 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?"); } - if (mine_output) + std::vector ptxs = signed_txs.ptx; + + context.insert({"signed_txs", mstch::array{}}); + + for (tools::wallet2::pending_tx& ptx: ptxs) { - sum_xmr += outp.second; - } + tx_details txd = get_tx_details(ptx.tx); - outputs.push_back(mstch::map { - {"out_pub_key" , REMOVE_HASH_BRAKETS( - fmt::format("{:s}", - outp.first.key))}, - {"amount" , fmt::format("{:0.12f}", - XMR_AMOUNT(outp.second))}, - {"mine_output" , mine_output}, - {"output_idx" , fmt::format("{:02d}", output_idx++)} - }); + mstch::map txd_map = txd.get_mstch_map(); - ++i; - } + boost::get(context["signed_txs"]).push_back(txd_map); + } - cout << "outputs.size(): " << outputs.size() << endl; + } - context["outputs"] = outputs; - context["sum_xmr"] = XMR_AMOUNT(sum_xmr); + map partials { + {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, + }; - // read my_outputs.html - string my_outputs_html = xmreg::read(TMPL_MY_OUTPUTS); + // read checkrawtx.html + string checkrawtx_html = xmreg::read(TMPL_MY_CHECKRAWTX); // add header and footer - string full_page = get_full_page(my_outputs_html); + string full_page = checkrawtx_html + xmreg::read(TMPL_FOOTER); // render the page - return mstch::render(full_page, context); + return mstch::render(full_page, context, partials); } - string - show_prove(string tx_hash_str, - string xmr_address_str, - string tx_prv_key_str) - { - return show_my_outputs(tx_hash_str, xmr_address_str, tx_prv_key_str, true); - } string - show_rawtx() + search(string search_text) { - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet} - }; + // remove white characters + boost::trim(search_text); - // read rawtx.html - string rawtx_html = xmreg::read(TMPL_MY_RAWTX); + string default_txt {"No such thing found: " + search_text}; - // add header and footer - string full_page = rawtx_html + xmreg::read(TMPL_FOOTER); + string result_html {default_txt}; - // render the page - return mstch::render(full_page, context); - } + // check first if we look for output with given global index + // such search start with "goi_", e.g., "goi_543" + bool search_for_global_output_idx = (search_text.substr(0, 4) == "goi_"); - string - show_checkandpushtx(string raw_tx_data, string action) - { - // remove white characters - boost::trim(raw_tx_data); - boost::erase_all(raw_tx_data, "\r\n"); - boost::erase_all(raw_tx_data, "\n"); + // check if we look for output with amout index and amount + // such search start with "aoi_", e.g., "aoi_444-23.00" + bool search_for_amount_output_idx = (search_text.substr(0, 4) == "aoi_"); - //cout << raw_tx_data << endl; + // first check if searching for block of given height + if (search_text.size() < 12 && + (search_for_global_output_idx == false + ||search_for_amount_output_idx == false)) + { + uint64_t blk_height; - string decoded_raw_tx_data = epee::string_encoding::base64_decode(raw_tx_data); + try + { + blk_height = boost::lexical_cast(search_text); - //cout << decoded_raw_tx_data << endl; + result_html = show_block(blk_height); - const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); + // 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; + } - bool unsigned_tx_given {false}; + } + catch(boost::bad_lexical_cast &e) + { + return result_html; + } + } - if (strncmp(decoded_raw_tx_data.c_str(), UNSIGNED_TX_PREFIX, magiclen) == 0) + + // check if monero address is given based on its length + // if yes, then we can only show its public components + if (search_text.length() == 95) { - unsigned_tx_given = true; - cout << "UNSIGNED_TX_PREFIX data given" << endl; - } + // parse string representing given monero address + cryptonote::account_public_address address; - // initalize page template context map - mstch::map context { - {"testnet" , testnet}, - {"unsigned_tx_given" , unsigned_tx_given}, - {"txs" , mstch::array{}} - }; + bool testnet_addr {false}; - if (unsigned_tx_given) + if (search_text[0] == '9' || search_text[0] == 'A') + testnet_addr = true; + + if (!xmreg::parse_str_address(search_text, address, testnet_addr)) + { + cerr << "Cant parse string address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; + } + + return show_address_details(address, testnet_addr); + } + + // 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_text.length() == 106) { - ::tools::wallet2::unsigned_tx_set exported_txs; - bool r = serialization::parse_binary(std::string( - decoded_raw_tx_data.c_str() + magiclen, - decoded_raw_tx_data.size() - magiclen), - exported_txs); - if (r) + cryptonote::account_public_address address; + + bool has_payment_id; + + crypto::hash8 encrypted_payment_id; + + bool testnet; + + if (!get_account_integrated_address_from_str(address, + has_payment_id, + encrypted_payment_id, + testnet, + search_text)) { - mstch::array& txs = boost::get(context["txs"]); + cerr << "Cant parse string integerated address: " << search_text << endl; + return string("Cant parse address (probably incorrect format): ") + + search_text; + } - for (const ::tools::wallet2::tx_construction_data& tx_cd: exported_txs.txes) - { - size_t no_of_sources = tx_cd.sources.size(); + return show_integrated_address_details(address, encrypted_payment_id, testnet); + } - const tx_destination_entry& tx_change = tx_cd.change_dts; - mstch::map tx_cd_data { - {"no_of_sources" , no_of_sources}, - {"use_rct" , tx_cd.use_rct}, - {"change_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(tx_change.amount))}, - {"dest_sources" , mstch::array{}}, - {"dest_infos" , mstch::array{}}, - }; + // second let try searching for tx + result_html = show_tx(search_text); - mstch::array& dest_sources = boost::get(tx_cd_data["dest_sources"]); - mstch::array& dest_infos = boost::get(tx_cd_data["dest_infos"]); + // 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; + } - for (const tx_destination_entry& a_dest: tx_cd.destinations) - { - mstch::map dest_info { - {"dest_address" , get_account_address_as_str(testnet, a_dest.addr)}, - {"dest_amount" , fmt::format("{:0.12f}", XMR_AMOUNT(a_dest.amount))} - }; + // if tx search not successful, check if we are looking + // for a block with given hash + result_html = show_block(search_text); - dest_infos.push_back(dest_info); - } + if (result_html.find("Cant get") == string::npos) + { + return result_html; + } - uint64_t sum_outputs_amounts {0}; + result_html = default_txt; - for (size_t i = 0; i < no_of_sources; ++i) - { + // get mempool transaction so that what we search, + // might be there. Note: show_tx above already searches it + // but only looks for tx hash. Now want to check + // for key_images, public_keys, payments_id, etc. + vector mempool_txs = get_mempool_txs(); - const tx_source_entry& tx_source = tx_cd.sources.at(i); + // key is string indicating where search_text was found. + map> tx_search_results + = search_txs(mempool_txs, search_text); - mstch::map single_dest_source { - {"output_amount" , fmt::format("{:0.12f}", - XMR_AMOUNT(tx_source.amount))}, - {"real_output" , tx_source.real_output}, - {"real_out_tx_key" , pod_to_hex(tx_source.real_out_tx_key)}, - {"real_output_in_tx_index" , tx_source.real_output_in_tx_index}, - {"outputs" , mstch::array{}} - }; + // now search my own custom lmdb database + // with key_images, public_keys, payments_id etc. - sum_outputs_amounts += tx_source.amount; + vector>> all_possible_tx_hashes; - //cout << tx_source.real_output << endl; - //cout << tx_source.real_out_tx_key << endl; - //cout << tx_source.real_output_in_tx_index << endl; - uint64_t index_of_real_output = tx_source.outputs[tx_source.real_output].first; - // get tx of the real output - tx_out_index real_toi = core_storage->get_db() - .get_output_tx_and_index(0, index_of_real_output); - transaction real_source_tx; + try + { + unique_ptr mylmdb; - 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)); - } + if (!bf::is_directory(lmdb2_path)) + { + throw std::runtime_error(lmdb2_path + " does not exist"); + } - tx_details real_txd = get_tx_details(real_source_tx); + cout << "Custom lmdb database seem to exist at: " << lmdb2_path << endl; + cout << "So lets try to search there for what we are after." << endl; - public_key real_out_pub_key = real_txd.output_pub_keys[tx_source.real_output_in_tx_index].first.key; + mylmdb = make_unique(lmdb2_path); - //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; - mstch::array& outputs = boost::get(single_dest_source["outputs"]); + mylmdb->search(search_text, + tx_search_results["key_images"], + "key_images"); - size_t output_i {0}; + cout << "size: " << tx_search_results["key_images"].size() << endl; - // cout << tx_source.amount << endl; + all_possible_tx_hashes.push_back( + make_pair("key_images", + tx_search_results["key_images"])); - for(const tx_source_entry::output_entry& oe: tx_source.outputs) - { - tx_out_index toi = core_storage->get_db() - .get_output_tx_and_index(0, oe.first); + // search the custum lmdb for tx_public_keys and append the result + // to those from the mempool search if found + + mylmdb->search(search_text, + tx_search_results["tx_public_keys"], + "tx_public_keys"); + + all_possible_tx_hashes.push_back( + make_pair("tx_public_keys", + tx_search_results["tx_public_keys"])); + // search the custum lmdb for payments_id and append the result + // to those from the mempool search if found - transaction tx; + mylmdb->search(search_text, + tx_search_results["payments_id"], + "payments_id"); - 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)); - } + all_possible_tx_hashes.push_back( + make_pair("payments_id", + tx_search_results["payments_id"])); - tx_details txd = get_tx_details(tx); + // search the custum lmdb for encrypted_payments_id and append the result + // to those from the mempool search if found - public_key out_pub_key = txd.output_pub_keys[toi.second].first.key; + mylmdb->search(search_text, + tx_search_results["encrypted_payments_id"], + "encrypted_payments_id"); + all_possible_tx_hashes.push_back( + make_pair("encrypted_payments_id", + tx_search_results["encrypted_payments_id"])); - // get block cointaining this tx - block blk; + // search the custum lmdb for output_public_keys and append the result + // to those from the mempool search if found - 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)); - } + mylmdb->search(search_text, + tx_search_results["output_public_keys"], + "output_public_keys"); - pair age = get_age(server_timestamp, blk.timestamp); + all_possible_tx_hashes.push_back( + make_pair("output_public_keys", + tx_search_results["output_public_keys"])); - 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)} - }; - single_dest_source.insert({"age_format" , age.second}); + // seach for output using output global index - outputs.push_back(single_output); + if (search_for_global_output_idx) + { + try + { + uint64_t global_idx = boost::lexical_cast( + search_text.substr(4)); - ++output_i; - } - dest_sources.push_back(single_dest_source); - } + //cout << "global_idx: " << global_idx << endl; - tx_cd_data.insert({"sum_outputs_amounts" , fmt::format("{:0.12f}", XMR_AMOUNT(sum_outputs_amounts))}); + // get info about output of a given global index + output_data_t output_data = core_storage->get_db() + .get_output_key(global_idx); - txs.push_back(tx_cd_data); - } - } - 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 + //tx_out_index tx_out = core_storage->get_db() + // .get_output_tx_and_index_from_global(global_idx); - const size_t magiclen = strlen(SIGNED_TX_PREFIX); + //cout << "tx_out.first: " << tx_out.first << endl; + //cout << "tx_out.second: " << tx_out.second << endl; - if (strncmp(decoded_raw_tx_data.c_str(), SIGNED_TX_PREFIX, magiclen) != 0) - { - cout << "The data is neigther unsigned nor signed tx!" << endl; - return string( "The data is neither unsigned nor signed tx!"); - } + string output_pub_key = pod_to_hex(output_data.pubkey); - ::tools::wallet2::signed_tx_set signed_txs; + //cout << "output_pub_key: " << output_pub_key << endl; - bool r = serialization::parse_binary(std::string( - decoded_raw_tx_data.c_str() + magiclen, - decoded_raw_tx_data.size() - magiclen), - signed_txs); + vector found_outputs; - 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?"); - } + mylmdb->search(output_pub_key, + found_outputs, + "output_public_keys"); - std::vector ptxs = signed_txs.ptx; + //cout << "found_outputs.size(): " << found_outputs.size() << endl; - context.insert({"signed_txs", mstch::array{}}); + all_possible_tx_hashes.push_back( + make_pair("output_public_keys_based_on_global_idx", + found_outputs)); - for (tools::wallet2::pending_tx& ptx: ptxs) - { - tx_details txd = get_tx_details(ptx.tx); + } + catch(boost::bad_lexical_cast &e) + { + cerr << "Cant cast global_idx string: " + << search_text.substr(4) << endl; + } + } // if (search_for_global_output_idx) - mstch::map txd_map = txd.get_mstch_map(); + // seach for output using output amount index and amount - boost::get(context["signed_txs"]).push_back(txd_map); - } + if (search_for_amount_output_idx) + { + try + { - } + string str_to_split = search_text.substr(4); - map partials { - {"tx_details", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_details.html")}, - }; + vector string_parts; - // read checkrawtx.html - string checkrawtx_html = xmreg::read(TMPL_MY_CHECKRAWTX); + boost::split(string_parts, str_to_split, + boost::is_any_of("-")); - // add header and footer - string full_page = checkrawtx_html + xmreg::read(TMPL_FOOTER); + if (string_parts.size() != 2) + { + throw; + } - // render the page - return mstch::render(full_page, context, partials); - } + uint64_t amount_idx = boost::lexical_cast( + string_parts[0]); + uint64_t amount = static_cast + (boost::lexical_cast( + string_parts[1]) * 1e12); - string - search(string search_text) - { - // remove white characters - boost::trim(search_text); + //cout << "amount_idx: " << amount_idx << endl; + //cout << "amount: " << amount << endl; - string default_txt {"No such thing found: " + search_text}; + // get info about output of a given global index + output_data_t output_data = core_storage->get_db() + .get_output_key( + amount, amount_idx); - string result_html {default_txt}; + string output_pub_key = pod_to_hex(output_data.pubkey); - // check first if we look for output with given global index - // such search start with "goi_", e.g., "goi_543" - bool search_for_global_output_idx = (search_text.substr(0, 4) == "goi_"); + //cout << "output_pub_key: " << output_pub_key << endl; - // check if we look for output with amout index and amount - // such search start with "aoi_", e.g., "aoi_444-23.00" - bool search_for_amount_output_idx = (search_text.substr(0, 4) == "aoi_"); + vector found_outputs; - // first check if searching for block of given height - if (search_text.size() < 12 && - (search_for_global_output_idx == false - ||search_for_amount_output_idx == false)) - { - uint64_t blk_height; + mylmdb->search(output_pub_key, + found_outputs, + "output_public_keys"); - try - { - blk_height = boost::lexical_cast(search_text); + //cout << "found_outputs.size(): " << found_outputs.size() << endl; - result_html = show_block(blk_height); + all_possible_tx_hashes.push_back( + make_pair("output_public_keys_based_on_amount_idx", + found_outputs)); - // 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) + } + catch(boost::bad_lexical_cast& e) { - return result_html; + cerr << "Cant parse amout index and amout string: " + << search_text.substr(4) << endl; } + catch(OUTPUT_DNE& e) + { + cerr << "Output not found in the blockchain: " + << search_text.substr(4) << endl; - } - catch(boost::bad_lexical_cast &e) - { - return result_html; - } + return(string("Output not found in the blockchain: ") + + search_text.substr(4)); + } + } // if (search_for_amount_output_idx) + } + catch (const lmdb::runtime_error& e) + { + cerr << "Error opening/accessing custom lmdb database: " + << e.what() << endl; + } + catch (...) + { + std::exception_ptr p = std::current_exception(); + cerr << "Error opening/accessing custom lmdb database: " + << p.__cxa_exception_type()->name() << endl; } - // check if monero address is given based on its length - // if yes, then we can only show its public components - if (search_text.length() == 95) - { - // parse string representing given monero address - cryptonote::account_public_address address; + result_html = show_search_results(search_text, all_possible_tx_hashes); - bool testnet_addr {false}; + return result_html; + } - if (search_text[0] == '9' || search_text[0] == 'A') - testnet_addr = true; + string + show_address_details(const account_public_address& address, bool testnet = false) + { - if (!xmreg::parse_str_address(search_text, address, testnet_addr)) - { - cerr << "Cant parse string address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; - } + string address_str = xmreg::print_address(address, testnet); + string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); - return show_address_details(address, testnet_addr); - } + 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}, + }; - // 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_text.length() == 106) - { + // read address.html + string address_html = xmreg::read(TMPL_ADDRESS); - cryptonote::account_public_address address; + // add header and footer + string full_page = get_full_page(address_html); - bool has_payment_id; + // render the page + return mstch::render(full_page, context); + } - crypto::hash8 encrypted_payment_id; + // ; + string + show_integrated_address_details(const account_public_address& address, + const crypto::hash8& encrypted_payment_id, + bool testnet = false) + { - bool testnet; + string address_str = xmreg::print_address(address, testnet); + string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); + string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); + string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); - if (!get_account_integrated_address_from_str(address, - has_payment_id, - encrypted_payment_id, - testnet, - search_text)) - { - cerr << "Cant parse string integerated address: " << search_text << endl; - return string("Cant parse address (probably incorrect format): ") - + search_text; - } + 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}, + }; - return show_integrated_address_details(address, encrypted_payment_id, testnet); - } + // read address.html + string address_html = xmreg::read(TMPL_ADDRESS); + // add header and footer + string full_page = get_full_page(address_html); - // second let try searching for tx - result_html = show_tx(search_text); + // render the page + return mstch::render(full_page, context); + } - // 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; - } + map> + search_txs(vector txs, const string& search_text) + { + map> tx_hashes; - // if tx search not successful, check if we are looking - // for a block with given hash - result_html = show_block(search_text); + // 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"] = {}; - if (result_html.find("Cant get") == string::npos) + for (const transaction& tx: txs) { - return result_html; - } - - result_html = default_txt; - // get mempool transaction so that what we search, - // might be there. Note: show_tx above already searches it - // but only looks for tx hash. Now want to check - // for key_images, public_keys, payments_id, etc. - vector mempool_txs = get_mempool_txs(); + tx_details txd = get_tx_details(tx); - // key is string indicating where search_text was found. - map> tx_search_results - = search_txs(mempool_txs, search_text); + string tx_hash_str = pod_to_hex(txd.hash); - // now search my own custom lmdb database - // with key_images, public_keys, payments_id etc. + // check if any key_image matches the search_text - vector>> all_possible_tx_hashes; + 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; + }); + if (it1 != txd.input_key_imgs.end()) + { + tx_hashes["key_images"].push_back(tx_hash_str); + } + // check if tx_public_key matches the search_text + if (pod_to_hex(txd.pk) == search_text) + { + tx_hashes["tx_public_keys"].push_back(tx_hash_str); + } - try - { - unique_ptr mylmdb; + // check if payments_id matches the search_text - if (!bf::is_directory(lmdb2_path)) + if (pod_to_hex(txd.payment_id) == search_text) { - throw std::runtime_error(lmdb2_path + " does not exist"); + tx_hashes["payment_id"].push_back(tx_hash_str); } - cout << "Custom lmdb database seem to exist at: " << lmdb2_path << endl; - cout << "So lets try to search there for what we are after." << endl; - - mylmdb = make_unique(lmdb2_path); + // check if encrypted_payments_id matches the search_text + if (pod_to_hex(txd.payment_id8) == search_text) + { + tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); + } - mylmdb->search(search_text, - tx_search_results["key_images"], - "key_images"); + // check if output_public_keys matche the search_text - cout << "size: " << tx_search_results["key_images"].size() << endl; + 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; + }); - all_possible_tx_hashes.push_back( - make_pair("key_images", - tx_search_results["key_images"])); + if (it2 != txd.output_pub_keys.end()) + { + tx_hashes["output_public_keys"].push_back(tx_hash_str); + } + } - // search the custum lmdb for tx_public_keys and append the result - // to those from the mempool search if found + return tx_hashes; - mylmdb->search(search_text, - tx_search_results["tx_public_keys"], - "tx_public_keys"); + } - all_possible_tx_hashes.push_back( - make_pair("tx_public_keys", - tx_search_results["tx_public_keys"])); + vector + get_mempool_txs() + { + // get mempool data using rpc call + vector> mempool_data = search_mempool(); - // search the custum lmdb for payments_id and append the result - // to those from the mempool search if found + // output only transactions + vector mempool_txs; - mylmdb->search(search_text, - tx_search_results["payments_id"], - "payments_id"); + mempool_txs.reserve(mempool_data.size()); - all_possible_tx_hashes.push_back( - make_pair("payments_id", - tx_search_results["payments_id"])); + for (const auto& a_pair: mempool_data) + { + mempool_txs.push_back(a_pair.second); + } - // search the custum lmdb for encrypted_payments_id and append the result - // to those from the mempool search if found + return mempool_txs; + } - mylmdb->search(search_text, - tx_search_results["encrypted_payments_id"], - "encrypted_payments_id"); + string + show_search_results(const string& search_text, + const vector>>& all_possible_tx_hashes) + { - all_possible_tx_hashes.push_back( - make_pair("encrypted_payments_id", - tx_search_results["encrypted_payments_id"])); + // initalise page tempate map with basic info about blockchain + mstch::map context { + {"testnet" , testnet}, + {"search_text" , search_text}, + {"no_results" , true}, + {"to_many_results", false} + }; - // search the custum lmdb for output_public_keys and append the result - // to those from the mempool search if found + 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()}); - mylmdb->search(search_text, - tx_search_results["output_public_keys"], - "output_public_keys"); + cout << "found_txs.first: " << found_txs.first << endl; - all_possible_tx_hashes.push_back( - make_pair("output_public_keys", - tx_search_results["output_public_keys"])); + // 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{}}); + if (!found_txs.second.empty()) + { - // seach for output using output global index + uint64_t tx_i {0}; - if (search_for_global_output_idx) - { - try + // 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 global_idx = boost::lexical_cast( - search_text.substr(4)); + crypto::hash tx_hash_pod; - //cout << "global_idx: " << global_idx << endl; + epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); - // get info about output of a given global index - output_data_t output_data = core_storage->get_db() - .get_output_key(global_idx); + transaction tx; - //tx_out_index tx_out = core_storage->get_db() - // .get_output_tx_and_index_from_global(global_idx); + uint64_t blk_height {0}; - //cout << "tx_out.first: " << tx_out.first << endl; - //cout << "tx_out.second: " << tx_out.second << endl; + int64_t blk_timestamp; - string output_pub_key = pod_to_hex(output_data.pubkey); + // first check in the blockchain + if (mcore->get_tx(tx_hash, tx)) + { - //cout << "output_pub_key: " << output_pub_key << endl; + // get timestamp of the tx's block + blk_height = core_storage + ->get_db().get_tx_block_height(tx_hash_pod); - vector found_outputs; + blk_timestamp = core_storage + ->get_db().get_block_timestamp(blk_height); - mylmdb->search(output_pub_key, - found_outputs, - "output_public_keys"); + } + else + { + // check in mempool if tx_hash not found in the + // blockchain + vector> found_txs + = search_mempool(tx_hash_pod); - //cout << "found_outputs.size(): " << found_outputs.size() << endl; + if (!found_txs.empty()) + { + // there should be only one tx found + tx = found_txs.at(0).second; + } + else + { + return string("Cant get tx of hash (show_search_results): " + tx_hash); + } - all_possible_tx_hashes.push_back( - make_pair("output_public_keys_based_on_global_idx", - found_outputs)); + // tx in mempool have no blk_timestamp + // but can use their recive time + blk_timestamp = found_txs.at(0).first.receive_time; - } - catch(boost::bad_lexical_cast &e) - { - cerr << "Cant cast global_idx string: " - << search_text.substr(4) << endl; - } - } // if (search_for_global_output_idx) + } - // seach for output using output amount index and amount + tx_details txd = get_tx_details(tx); - if (search_for_amount_output_idx) - { - try - { + mstch::map txd_map = txd.get_mstch_map(); - string str_to_split = search_text.substr(4); - vector string_parts; + // add the timestamp to tx mstch map + txd_map.insert({"timestamp", xmreg::timestamp_to_str(blk_timestamp)}); - boost::split(string_parts, str_to_split, - boost::is_any_of("-")); + boost::get((res.first)->second).push_back(txd_map); - if (string_parts.size() != 2) + // dont show more than 500 results + if (tx_i > 500) { - throw; + context["to_many_results"] = true; + break; } - uint64_t amount_idx = boost::lexical_cast( - string_parts[0]); + ++tx_i; + } - uint64_t amount = static_cast - (boost::lexical_cast( - string_parts[1]) * 1e12); + // if found something, set this flag to indicate this fact + context["no_results"] = false; + } + } + + // read search_results.html + string search_results_html = xmreg::read(TMPL_SEARCH_RESULTS); + // add header and footer + string full_page = get_full_page(search_results_html); - //cout << "amount_idx: " << amount_idx << endl; - //cout << "amount: " << amount << endl; + // read partial for showing details of tx(s) found + map partials { + {"tx_table_head", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html")}, + {"tx_table_row" , xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html")} + }; - // get info about output of a given global index - output_data_t output_data = core_storage->get_db() - .get_output_key( - amount, amount_idx); + // render the page + return mstch::render(full_page, context, partials); + } - string output_pub_key = pod_to_hex(output_data.pubkey); - //cout << "output_pub_key: " << output_pub_key << endl; + private: - vector found_outputs; + mstch::map + construct_tx_context(transaction tx, uint with_ring_signatures = 0) + { - mylmdb->search(output_pub_key, - found_outputs, - "output_public_keys"); + tx_details txd = get_tx_details(tx); - //cout << "found_outputs.size(): " << found_outputs.size() << endl; + crypto::hash tx_hash = txd.hash; - all_possible_tx_hashes.push_back( - make_pair("output_public_keys_based_on_amount_idx", - found_outputs)); + string tx_hash_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", tx_hash)); - } - catch(boost::bad_lexical_cast& e) - { - cerr << "Cant parse amout index and amout string: " - << search_text.substr(4) << endl; - } - catch(OUTPUT_DNE& e) - { - cerr << "Output not found in the blockchain: " - << search_text.substr(4) << endl; + uint64_t tx_blk_height {0}; - return(string("Output not found in the blockchain: ") - + search_text.substr(4)); - } - } // if (search_for_amount_output_idx) - } - catch (const lmdb::runtime_error& e) - { - cerr << "Error opening/accessing custom lmdb database: " - << e.what() << endl; - } - catch (...) + bool tx_blk_found {false}; + + if (core_storage->have_tx(tx_hash)) { - std::exception_ptr p = std::current_exception(); - cerr << "Error opening/accessing custom lmdb database: " - << p.__cxa_exception_type()->name() << endl; + tx_blk_height = core_storage->get_db().get_tx_block_height(tx_hash); + tx_blk_found = true; } + // get block cointaining this tx + block blk; - result_html = show_search_results(search_text, all_possible_tx_hashes); + if (tx_blk_found && !mcore->get_block_by_height(tx_blk_height, blk)) + { + cerr << "Cant get block: " << tx_blk_height << endl; + } - return result_html; - } + string tx_blk_height_str {"N/A"}; - string - show_address_details(const account_public_address& address, bool testnet = false) - { + // tx age + pair age; - string address_str = xmreg::print_address(address, testnet); - string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); + string blk_timestamp {"N/A"}; - 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}, - }; + if (tx_blk_found) + { + // calculate difference between tx and server timestamps + age = get_age(server_timestamp, blk.timestamp, FULL_AGE_FORMAT); - // read address.html - string address_html = xmreg::read(TMPL_ADDRESS); + blk_timestamp = xmreg::timestamp_to_str(blk.timestamp); - // add header and footer - string full_page = get_full_page(address_html); + tx_blk_height_str = std::to_string(tx_blk_height); + } - // render the page - return mstch::render(full_page, context); - } + // payments id. both normal and encrypted (payment_id8) + string pid_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id)); + string pid8_str = REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.payment_id8)); - // ; - string - show_integrated_address_details(const account_public_address& address, - const crypto::hash8& encrypted_payment_id, - bool testnet = false) - { - string address_str = xmreg::print_address(address, testnet); - string pub_viewkey_str = fmt::format("{:s}", address.m_view_public_key); - string pub_spendkey_str = fmt::format("{:s}", address.m_spend_public_key); - string enc_payment_id_str = fmt::format("{:s}", encrypted_payment_id); + string tx_json = obj_to_json_str(tx); + // initalise page tempate map with basic info about blockchain 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}, + {"tx_hash" , tx_hash_str}, + {"tx_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", txd.pk))}, + {"blk_height" , tx_blk_height_str}, + {"tx_size" , fmt::format("{:0.4f}", + static_cast(txd.size) / 1024.0)}, + {"tx_fee" , fmt::format("{:0.12f}", XMR_AMOUNT(txd.fee))}, + {"tx_version" , fmt::format("{:d}", txd.version)}, + {"blk_timestamp" , blk_timestamp}, + {"blk_timestamp_uint" , blk.timestamp}, + {"delta_time" , age.first}, + {"inputs_no" , txd.input_key_imgs.size()}, + {"has_inputs" , !txd.input_key_imgs.empty()}, + {"outputs_no" , 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}, + {"extra" , txd.get_extra_str()}, + {"with_ring_signatures" , static_cast( + with_ring_signatures)}, + {"tx_json" , tx_json}, + {"has_error" , false}, + {"error_msg" , string("")} }; - // read address.html - string address_html = xmreg::read(TMPL_ADDRESS); - - // add header and footer - string full_page = get_full_page(address_html); - - // render the page - return mstch::render(full_page, context); - } - - map> - search_txs(vector txs, const string& search_text) - { - map> tx_hashes; + string server_time_str = xmreg::timestamp_to_str(server_timestamp, "%F"); - // 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"] = {}; + mstch::array inputs = mstch::array{}; - for (const transaction& tx: txs) - { + mstch::array mixins_timescales; - tx_details txd = get_tx_details(tx); + double timescale_scale {0.0}; // size of one '_' in days - string tx_hash_str = pod_to_hex(txd.hash); + uint64_t input_idx {0}; - // check if any key_image matches the search_text + uint64_t inputs_xmr_sum {0}; - 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; - }); + // initialize with some large and some numbers + uint64_t min_mix_timestamp = server_timestamp*2L; - if (it1 != txd.input_key_imgs.end()) - { - tx_hashes["key_images"].push_back(tx_hash_str); - } + uint64_t max_mix_timestamp {0}; - // check if tx_public_key matches the search_text + vector> mixin_timestamp_groups; - if (pod_to_hex(txd.pk) == search_text) - { - tx_hashes["tx_public_keys"].push_back(tx_hash_str); - } + // make timescale maps for mixins in input + for (const txin_to_key& in_key: txd.input_key_imgs) + { + // get absolute offsets of mixins + std::vector absolute_offsets + = cryptonote::relative_output_offsets_to_absolute( + in_key.key_offsets); - // check if payments_id matches the search_text + // get public keys of outputs used in the mixins that match to the offests + std::vector outputs; + core_storage->get_db().get_output_key(in_key.amount, + absolute_offsets, + outputs); - if (pod_to_hex(txd.payment_id) == search_text) - { - tx_hashes["payment_id"].push_back(tx_hash_str); - } + inputs.push_back(mstch::map { + {"in_key_img", REMOVE_HASH_BRAKETS(fmt::format("{:s}", in_key.k_image))}, + {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(in_key.amount))}, + {"input_idx" , fmt::format("{:02d}", input_idx)}, + {"mixins" , mstch::array{}}, + {"ring_sigs" , txd.get_ring_sig_for_input(input_idx)} + }); - // check if encrypted_payments_id matches the search_text + inputs_xmr_sum += in_key.amount; - if (pod_to_hex(txd.payment_id8) == search_text) - { - tx_hashes["encrypted_payments_id"].push_back(tx_hash_str); - } + vector mixin_timestamps; - // check if output_public_keys matche the search_text + // get reference to mixins array created above + mstch::array& mixins = boost::get( + boost::get(inputs.back())["mixins"]); - 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; - }); + // mixin counter + size_t count = 0; - if (it2 != txd.output_pub_keys.end()) + // for each found output public key find its block to get timestamp + for (const uint64_t &i: absolute_offsets) { - tx_hashes["output_public_keys"].push_back(tx_hash_str); - } + // get basic information about mixn's output + cryptonote::output_data_t output_data = outputs.at(count); - } + // get pair pair where first is tx hash + // and second is local index of the output i in that tx + tx_out_index tx_out_idx = + core_storage->get_db().get_output_tx_and_index(in_key.amount, i); - return tx_hashes; + // 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; - vector - get_mempool_txs() - { - // get mempool data using rpc call - vector> mempool_data = search_mempool(); + context["has_error"] = true; + context["error_msg"] = fmt::format("- cant get block of height: {}", + output_data.height); + } - // output only transactions - vector mempool_txs; + // 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; - mempool_txs.reserve(mempool_data.size()); + if (!mcore->get_tx(tx_out_idx.first, mixin_tx)) + { + cerr << "Cant get tx: " << tx_out_idx.first << endl; - for (const auto& a_pair: mempool_data) - { - mempool_txs.push_back(a_pair.second); - } + context["has_error"] = true; + context["error_msg"] = fmt::format("Cant get tx: {:s}", tx_out_idx.first); + } - return mempool_txs; - } + // mixin tx details + tx_details mixin_txd = get_tx_details(mixin_tx, true); - string - show_search_results(const string& search_text, - const vector>>& all_possible_tx_hashes) - { + mixins.push_back(mstch::map { + {"mix_blk" , fmt::format("{:08d}", output_data.height)}, + {"mix_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", + output_data.pubkey))}, + {"mix_tx_hash" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", + tx_out_idx.first))}, + {"mix_out_indx" , fmt::format("{:d}", tx_out_idx.second)}, + {"mix_timestamp" , xmreg::timestamp_to_str(blk.timestamp)}, + {"mix_age" , mixin_age.first}, + {"mix_mixin_no" , mixin_txd.mixin_no}, + {"mix_inputs_no" , mixin_txd.input_key_imgs.size()}, + {"mix_outputs_no" , mixin_txd.output_pub_keys.size()}, + {"mix_age_format" , mixin_age.second}, + {"mix_idx" , fmt::format("{:02d}", count)}, + }); - // initalise page tempate map with basic info about blockchain - mstch::map context { - {"testnet" , testnet}, - {"search_text" , search_text}, - {"no_results" , true}, - {"to_many_results", false} - }; + if (blk.timestamp < min_mix_timestamp) + min_mix_timestamp = blk.timestamp; - 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()}); + if (blk.timestamp > max_mix_timestamp) + max_mix_timestamp = blk.timestamp; - cout << "found_txs.first: " << found_txs.first << endl; + // get mixin timestamp from its orginal block + mixin_timestamps.push_back(blk.timestamp); - // 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{}}); + ++count; - if (!found_txs.second.empty()) - { + } // for (const uint64_t &i: absolute_offsets) - uint64_t tx_i {0}; + mixin_timestamp_groups.push_back(mixin_timestamps); - // 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) - { + input_idx++; + } // for (const txin_to_key& in_key: txd.input_key_imgs) - crypto::hash tx_hash_pod; + min_mix_timestamp -= 3600; + max_mix_timestamp += 3600; - epee::string_tools::hex_to_pod(tx_hash, tx_hash_pod); + // 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); - transaction tx; + // save resolution of mixin timescales + timescale_scale = mixin_times_scale.second; - uint64_t blk_height {0}; + // save the string timescales for later to show + mixins_timescales.push_back(mstch::map { + {"timescale", mixin_times_scale.first}}); + } - int64_t blk_timestamp; - // first check in the blockchain - if (mcore->get_tx(tx_hash, tx)) - { + context["inputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(inputs_xmr_sum)); + context["server_time"] = server_time_str; + context["inputs"] = inputs; + context["min_mix_time"] = xmreg::timestamp_to_str(min_mix_timestamp); + context["max_mix_time"] = xmreg::timestamp_to_str(max_mix_timestamp); + context["timescales"] = mixins_timescales; + context["timescales_scale"] = fmt::format("{:0.2f}", + timescale_scale / 3600.0 / 24.0); // in days - // get timestamp of the tx's block - blk_height = core_storage - ->get_db().get_tx_block_height(tx_hash_pod); + // get indices of outputs in amounts tables + vector out_amount_indices; - blk_timestamp = core_storage - ->get_db().get_block_timestamp(blk_height); + try + { - } - else - { - // check in mempool if tx_hash not found in the - // blockchain - vector> found_txs - = search_mempool(tx_hash_pod); + uint64_t tx_index; - if (!found_txs.empty()) - { - // there should be only one tx found - tx = found_txs.at(0).second; - } - else - { - return string("Cant get tx of hash (show_search_results): " + tx_hash); - } + 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; + } - // tx in mempool have no blk_timestamp - // but can use their recive time - blk_timestamp = found_txs.at(0).first.receive_time; + } + catch(const exception& e) + { + cerr << e.what() << endl; + } - } + uint64_t output_idx {0}; - tx_details txd = get_tx_details(tx); + mstch::array outputs; - mstch::map txd_map = txd.get_mstch_map(); + uint64_t outputs_xmr_sum {0}; + for (pair& outp: txd.output_pub_keys) + { - // add the timestamp to tx mstch map - txd_map.insert({"timestamp", xmreg::timestamp_to_str(blk_timestamp)}); + // total number of ouputs in the blockchain for this amount + uint64_t num_outputs_amount = core_storage->get_db() + .get_num_outputs(outp.second); - boost::get((res.first)->second).push_back(txd_map); + string out_amount_index_str {"N/A"}; - // dont show more than 500 results - if (tx_i > 500) - { - context["to_many_results"] = true; - break; - } + // 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 = fmt::format("{:d}", + out_amount_indices.at(output_idx)); + } - ++tx_i; - } + outputs_xmr_sum += outp.second; - // if found something, set this flag to indicate this fact - context["no_results"] = false; - } + outputs.push_back(mstch::map { + {"out_pub_key" , REMOVE_HASH_BRAKETS(fmt::format("{:s}", outp.first.key))}, + {"amount" , fmt::format("{:0.12f}", XMR_AMOUNT(outp.second))}, + {"amount_idx" , out_amount_index_str}, + {"num_outputs" , fmt::format("{:d}", num_outputs_amount)}, + {"output_idx" , fmt::format("{:02d}", output_idx++)} + }); } - // read search_results.html - string search_results_html = xmreg::read(TMPL_SEARCH_RESULTS); + context["outputs_xmr_sum"] = fmt::format("{:0.12f}", XMR_AMOUNT(outputs_xmr_sum)); - // add header and footer - string full_page = get_full_page(search_results_html); + context["outputs"] = outputs; - // read partial for showing details of tx(s) found - map partials { - {"tx_table_head", xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_header.html")}, - {"tx_table_row" , xmreg::read(string(TMPL_PARIALS_DIR) + "/tx_table_row.html")} - }; - // render the page - return mstch::render(full_page, context, partials); + return context; } - - private: - tx_details get_tx_details(const transaction& tx, bool coinbase = false) {