diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ee76e1ac..6d45766e 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1301,6 +1301,110 @@ namespace currency return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& resp, epee::json_rpc::error& error_resp, connection_context& cntx) + { +#define LOCAL_CHECK(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; } +#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; } + + LOCAL_CHECK(req.address != account_public_address{}, "address is missing"); + LOCAL_CHECK(req.viewkey != null_skey, "viewkey is missing"); + LOCAL_CHECK(0 <= req.blocks_limit && req.blocks_limit <= 5, "blocks_limit is out of allowed bounds"); + + // verify addess keys + crypto::point_t view_pk, spend_pk; + LOCAL_CHECK(view_pk.from_public_key(req.address.view_public_key), "cannon load point from address.view_public_key"); + LOCAL_CHECK(view_pk.is_in_main_subgroup(), "address.view_public_key isn't in main subgroup"); + LOCAL_CHECK(spend_pk.from_public_key(req.address.spend_public_key), "cannon load point from address.spend_public_key"); + LOCAL_CHECK(spend_pk.is_in_main_subgroup(), "address.spend_public_key isn't in main subgroup"); + + // verify viewkey + crypto::scalar_t view_sc = req.viewkey; + LOCAL_CHECK(view_sc.is_reduced(), "viewkey is invalid"); + LOCAL_CHECK(view_sc * crypto::c_point_G == view_pk, "viewkey doesn't correspond to the given address"); + + const blockchain_storage& bcs = m_core.get_blockchain_storage(); + resp.blockchain_top_block_height = bcs.get_top_block_height(); + resp.blocks_limit = req.blocks_limit; + + // get blockchain transactions + std::unordered_map, std::list>> blockchain_txs; // block height -> (vector of tx_ids, list of txs) + if (req.blocks_limit > 0) + { + uint64_t start_offset = resp.blockchain_top_block_height - req.blocks_limit + 1; + std::list recent_blocks; + LOCAL_CHECK_INT_ERR(bcs.get_blocks(start_offset, req.blocks_limit, recent_blocks), "cannot get recent blocks"); + + std::vector blockchain_tx_ids, missed_tx; + for(auto& b : recent_blocks) + { + blockchain_tx_ids.insert(blockchain_tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end()); + uint64_t height = get_block_height(b); + auto& el = blockchain_txs[height]; + el.first = b.tx_hashes; + missed_tx.clear(); + LOCAL_CHECK_INT_ERR(bcs.get_transactions(b.tx_hashes, el.second, missed_tx), "bcs.get_transactions failed"); + LOCAL_CHECK_INT_ERR(missed_tx.empty(), "missed_tx is not empty"); + LOCAL_CHECK_INT_ERR(el.first.size() == el.second.size(), "el.first.size() != el.second.size()"); + } + } + + // get pool transactions + std::list pool_txs; + LOCAL_CHECK_INT_ERR(m_core.get_tx_pool().get_transactions(pool_txs), "cannot get txs from pool"); + + // processor lambda + auto process_tx = [&](const transaction& tx, const crypto::hash& tx_id, const int64_t height) { + crypto::key_derivation derivation{}; + LOCAL_CHECK(generate_key_derivation(get_tx_pub_key_from_extra(tx), req.viewkey, derivation), "generate_key_derivation failed"); + + for(size_t i = 0, sz = tx.vout.size(); i < sz; ++i) + { + const tx_out_v& outv = tx.vout[i]; + if (outv.type() != typeid(tx_out_zarcanum)) + continue; + + uint64_t decoded_amount = 0; + crypto::public_key decoded_asset_id{}; + crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{}; + if (!is_out_to_acc(req.address, boost::get(outv), derivation, i, decoded_amount, decoded_asset_id, amount_blinding_mask, asset_id_blinding_mask)) + continue; + + auto& el = resp.outputs.emplace_back(); + el.amount = decoded_amount; + el.asset_id = decoded_asset_id; + el.tx_id = tx_id; + el.tx_block_height = height; + el.output_tx_index = i; + } + return true; + }; + + // process blockchain txs + for(auto& [height, pair] : blockchain_txs) + { + LOCAL_CHECK(pair.first.size() == pair.second.size(), "container size inconsistency"); + auto tx_it = pair.second.begin(); + for(size_t i = 0, sz = pair.first.size(); i < sz; ++i, ++tx_it) + { + const crypto::hash& tx_id = pair.first[i]; + LOCAL_CHECK(process_tx(*tx_it, tx_id, height), "process blockchain tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + } + + // process pool txs + for(auto& tx : pool_txs) + { + crypto::hash tx_id = get_transaction_hash(tx); + LOCAL_CHECK(process_tx(tx, tx_id, -1), "process pool tx failed for tx " + crypto::pod_to_hex(tx_id)); + } + + resp.status = resp.outputs.empty() ? API_RETURN_CODE_NOT_FOUND : API_RETURN_CODE_OK; + return true; + +#undef LOCAL_CHECK_INT_ERR +#undef LOCAL_CHECK + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_aliases_by_address(const COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& req, COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx) { account_public_address addr = AUTO_VAL_INIT(addr); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 02e7b478..8e44a31a 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -94,6 +94,7 @@ namespace currency bool on_get_alt_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_get_alt_blocks_details(const COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::request& req, COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::response& res, connection_context& cntx); bool on_get_est_height_from_date(const COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request& req, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response& res, connection_context& cntx); + bool on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx); bool on_validate_signature(const COMMAND_VALIDATE_SIGNATURE::request& req, COMMAND_VALIDATE_SIGNATURE::response& res, epee::json_rpc::error& er, connection_context& cntx); @@ -133,6 +134,8 @@ namespace currency MAP_JON_RPC_WE("get_alias_by_address", on_aliases_by_address, COMMAND_RPC_GET_ALIASES_BY_ADDRESS) MAP_JON_RPC_WE("get_alias_reward", on_get_alias_reward, COMMAND_RPC_GET_ALIAS_REWARD) MAP_JON_RPC ("get_est_height_from_date", on_get_est_height_from_date, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE) + MAP_JON_RPC_WE("find_outs_in_recent_blocks", on_find_outs_in_recent_blocks, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS) + //block explorer api MAP_JON_RPC ("get_blocks_details", on_rpc_get_blocks_details, COMMAND_RPC_GET_BLOCKS_DETAILS) MAP_JON_RPC_WE("get_tx_details", on_get_tx_details, COMMAND_RPC_GET_TX_DETAILS) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 9a89d043..452a8316 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -23,6 +23,7 @@ #include #include #include +#include //#include "currency_core/basic_api_response_codes.h" namespace currency @@ -289,6 +290,58 @@ namespace currency }; }; //----------------------------------------------- + struct COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS + { + DOC_COMMAND("Retrieves information about outputs in recent blocks that are targeted for the given address with the corresponding secret view key.") + + static constexpr uint64_t blocks_limit_default = 5; + + struct request + { + account_public_address address; + crypto::secret_key viewkey; + uint64_t blocks_limit = blocks_limit_default; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_ADDRESS_AS_TEXT(address) DOC_DSCR("Target address for which outputs are being searched") DOC_EXMP("ZxCSpsGGeJsS8fwvQ4HktDU3qBeauoJTR6j73jAWWZxFXdF7XTbGm4YfS2kXJmAP4Rf5BVsSQ9iZ45XANXEYsrLN2L2W77dH7") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(viewkey) DOC_DSCR("Secret view key corresponding to the given address.") DOC_EXMP("5fa8eaaf231a305053260ff91d69c6ef1ecbd0f5") DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Block count limit. If 0, only the transaction pool will be searched. Maximum and default is " + epee::string_tools::num_to_string_fast(blocks_limit_default) + ".") DOC_EXMP(1711021795) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct out_entry + { + uint64_t amount; + crypto::public_key asset_id; + crypto::hash tx_id; + int64_t tx_block_height; + uint64_t output_tx_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) DOC_DSCR("The amount of the output.") DOC_EXMP(1000000000000) DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset ID of the output.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Transaction ID where the output is present, if found.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END + KV_SERIALIZE(tx_block_height) DOC_DSCR("Block height where the transaction is present.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(output_tx_index) DOC_DSCR("Index of the output in the transaction.") DOC_EXMP(2) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector outputs; + uint64_t blockchain_top_block_height; + uint64_t blocks_limit; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outputs) DOC_DSCR("List of found outputs.") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE(blockchain_top_block_height) DOC_DSCR("Height of the most recent block in the blockchain.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(blocks_limit) DOC_DSCR("Used limit for block count.") DOC_EXMP(5) DOC_END + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + //----------------------------------------------- struct COMMAND_RPC_GET_TX_POOL { DOC_COMMAND("Retreives transactions from tx pool (and other information).")