diff --git a/README.md b/README.md index 67467d5..f61c92e 100644 --- a/README.md +++ b/README.md @@ -380,9 +380,9 @@ Partial results shown: } ``` - #### api/mempool +Return all txs in the mempool. ```bash curl -w "\n" -X GET "http://139.162.32.245:8081/api/mempool" @@ -392,28 +392,47 @@ Partial results shown: ```json { - "data": [ - { - "coinbase": false, - "extra": "02210001c32d313b74a859b904079c69dbc04ea6e37eddcf4aeb34e9400cc12831da5401b34082a9ff7476fe29a19fa6a1735a9c59db226b9ddcf715928aa71625b13062", - "mixin": 7, - "payment_id": "01c32d313b74a859b904079c69dbc04ea6e37eddcf4aeb34e9400cc12831da54", - "payment_id8": "", - "rct_type": 1, - "timestamp": 1492763220, - "timestamp_utc": "2017-04-21 08:27:00", - "tx_fee": 4083040000, - "tx_hash": "6751e0029558fdc4ab4528896529e32b2864c6ad43c5d8838c8ebe156ada0514", - "tx_size": 13224, - "tx_version": 2, - "xmr_inputs": 0, - "xmr_outputs": 0 - } - ], + "data": { + "limit": 100000000, + "page": 0, + "total_page_no": 0, + "txs": [ + { + "coinbase": false, + "extra": "022100325f677d96f94155a4840a84d8e0c905f7a4697a25744633bcb438feb1e51fb2012eda81bf552c53c2168f4130dbe0265c3a7898f3a7eee7c1fed955a778167b5d", + "mixin": 3, + "payment_id": "325f677d96f94155a4840a84d8e0c905f7a4697a25744633bcb438feb1e51fb2", + "payment_id8": "", + "rct_type": 2, + "timestamp": 1494470894, + "timestamp_utc": "2017-05-11 02:48:14", + "tx_fee": 15894840000, + "tx_hash": "9f3374f8ac67febaab153eab297937a3d0d2c706601e496bf5028146da0c9aef", + "tx_size": 13291, + "tx_version": 2, + "xmr_inputs": 0, + "xmr_outputs": 0 + } + ], + "txs_no": 7 + }, "status": "success" } ``` +Limit of 100000000 is just default value above to ensure that all mempool txs are fetched +if no specific limit given. + +#### api/mempool?limit= + +Return number of newest mempool txs, e.g., only 10. + +```bash +curl -w "\n" -X GET "http://139.162.32.245:8081/api/mempool?limit=10" +``` + +Result analogical to the one above. + #### api/search/ ```bash @@ -453,6 +472,7 @@ Partial results shown: } ``` + #### api/outputs?txhash=&address=
&viewkey=&txprove=<0|1> For `txprove=0` we check which outputs belong to given address and corresponding viewkey. @@ -529,6 +549,62 @@ curl -w "\n" -X GET "http://139.162.32.245:8082/api/outputs?txhash=94782a8c0aa8 } ``` + +Result analogical to the one above. + +#### api/networkinfo + +```bash +curl -w "\n" -X GET "http://139.162.32.245:8081/api/networkinfo" +``` + +```json +{ + "data": { + "alt_blocks_count": 0, + "block_size_limit": 600000, + "cumulative_difficulty": 2067724366624367, + "difficulty": 7530486740, + "grey_peerlist_size": 4987, + "hash_rate": 62754056, + "height": 1307537, + "incoming_connections_count": 0, + "outgoing_connections_count": 8, + "start_time": 1494473774, + "status": "OK", + "target": 120, + "target_height": 1307518, + "testnet": false, + "top_block_hash": "0726de5b86f431547fc64fc2c8e1c11d76843ada0561993ee540e4eee29d83c3", + "tx_count": 1210222, + "tx_pool_size": 5, + "white_peerlist_size": 1000 + }, + "status": "success" +} +``` + + +#### api/rawblock/ + +Return raw json block data, as represented in Monero. + +```bash +curl -w "\n" -X GET "http://139.162.32.245:8081/api/rawblock/1293257" +``` + +Example result not shown. + +#### api/rawtransaction/ + +Return raw json tx data, as represented in Monero. + +```bash +curl -w "\n" -X GET "http://139.162.32.245:8081/api/rawtransaction/6093260dbe79fd6277694d14789dc8718f1bd54457df8bab338c2efa3bb0f03d" +``` + +Example result not shown. + ## Other monero examples Other examples can be found on [github](https://github.com/moneroexamples?tab=repositories). diff --git a/main.cpp b/main.cpp index eec2dea..8c1827d 100644 --- a/main.cpp +++ b/main.cpp @@ -363,6 +363,14 @@ int main(int ac, const char* av[]) { return r; }); + CROW_ROUTE(app, "/api/rawtransaction/") + ([&](const crow::request &req, string tx_hash) { + + myxmr::jsonresponse r{xmrblocks.json_rawtransaction(tx_hash)}; + + return r; + }); + CROW_ROUTE(app, "/api/block/") ([&](const crow::request &req, string block_no_or_hash) { @@ -371,6 +379,13 @@ int main(int ac, const char* av[]) { return r; }); + CROW_ROUTE(app, "/api/rawblock/") + ([&](const crow::request &req, string block_no_or_hash) { + + myxmr::jsonresponse r{xmrblocks.json_rawblock(block_no_or_hash)}; + + return r; + }); CROW_ROUTE(app, "/api/transactions").methods("GET"_method) ([&](const crow::request &req) { @@ -386,10 +401,19 @@ int main(int ac, const char* av[]) { return r; }); - CROW_ROUTE(app, "/api/mempool") + CROW_ROUTE(app, "/api/mempool").methods("GET"_method) ([&](const crow::request &req) { - myxmr::jsonresponse r{xmrblocks.json_mempool()}; + string page = regex_search(req.raw_url, regex {"page=\\d+"}) ? + req.url_params.get("page") : "0"; + + // default value for limit is some large number, so that + // a call to api/mempool without any limit return all + // mempool txs + string limit = regex_search(req.raw_url, regex {"limit=\\d+"}) ? + req.url_params.get("limit") : "100000000"; + + myxmr::jsonresponse r{xmrblocks.json_mempool(page, limit)}; return r; }); @@ -402,6 +426,14 @@ int main(int ac, const char* av[]) { return r; }); + CROW_ROUTE(app, "/api/networkinfo") + ([&](const crow::request &req) { + + myxmr::jsonresponse r{xmrblocks.json_networkinfo()}; + + return r; + }); + CROW_ROUTE(app, "/api/outputs").methods("GET"_method) ([&](const crow::request &req) { diff --git a/src/page.h b/src/page.h index f7ef443..9ae021a 100644 --- a/src/page.h +++ b/src/page.h @@ -3677,9 +3677,9 @@ namespace xmreg /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ json json_transaction(string tx_hash_str) { @@ -3834,10 +3834,95 @@ namespace xmreg return j_response; } + + /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ + * 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 {}} + }; + + json& j_data = j_response["data"]; + + // 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}; + + // 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)) + { + 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"; + 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_block(string block_no_or_hash) { @@ -3978,9 +4063,105 @@ namespace xmreg /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ + * 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"]; + + uint64_t current_blockchain_height + = core_storage->get_current_blockchain_height(); + + uint64_t block_height {0}; + + crypto::hash blk_hash; + + 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); + } + catch (const boost::bad_lexical_cast& e) + { + j_data["title"] = fmt::format( + "Cant parse block number: {:s}", block_no_or_hash); + return j_response; + } + + 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; + } + + if (!mcore->get_block_by_height(block_height, blk)) + { + 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); + + } + 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; + } + + 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); + } + else + { + j_data["title"] = fmt::format("Cant find blk using search string: {:s}", block_no_or_hash); + return j_response; + } + + // get raw tx json as in monero + + 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; + } + + 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_transactions(string _page, string _limit) { @@ -4092,7 +4273,8 @@ namespace xmreg j_data["page"] = page; j_data["limit"] = limit; j_data["current_height"] = height; - j_data["total_page_no"] = (limit == 0 ? 0 : height / limit); + + j_data["total_page_no"] = limit > 0 ? (height / limit) : 0; j_response["status"] = "success"; @@ -4105,7 +4287,7 @@ namespace xmreg * https://labs.omniti.com/labs/jsend */ json - json_mempool() + json_mempool(string _page, string _limit) { json j_response { {"status", "fail"}, @@ -4114,6 +4296,23 @@ namespace xmreg json& j_data = j_response["data"]; + // parse page and limit into numbers + + uint64_t page {0}; + uint64_t limit {0}; + + 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; + } + //get current server timestamp server_timestamp = std::time(nullptr); @@ -4135,21 +4334,62 @@ namespace xmreg (void) tx_hash_dummy; - // for each transaction in the memory pool - for (const auto& a_pair: mempool_data) + uint64_t no_mempool_txs = mempool_data.size(); + + // calculate starting and ending block numbers to show + int64_t start_height = limit * page; + + int64_t end_height = start_height + limit; + + end_height = end_height > no_mempool_txs ? no_mempool_txs : end_height; + + // check if start height is not below range + start_height = start_height > end_height ? end_height - limit : start_height; + + start_height = start_height < 0 ? 0 : start_height; + + // loop index + int64_t i = start_height; + + json j_txs = json::array(); + + // for each transaction in the memory pool in current page + while (i < end_height) { - const tx_details& txd = get_tx_details(a_pair.second, false, 1, height); // 1 is dummy here + const pair* a_pair {nullptr}; + + try + { + a_pair = &(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"); + + return j_response; + } + + const tx_details& txd = get_tx_details(a_pair->second, false, 1, height); // 1 is dummy here // get basic tx info - json j_tx = get_tx_json(a_pair.second, txd); + json j_tx = get_tx_json(a_pair->second, txd); // we add some extra data, for mempool txs, such as recieve timestamp - j_tx["timestamp"] = a_pair.first.receive_time; - j_tx["timestamp_utc"] = xmreg::timestamp_to_str_gm(a_pair.first.receive_time); + j_tx["timestamp"] = a_pair->first.receive_time; + j_tx["timestamp_utc"] = xmreg::timestamp_to_str_gm(a_pair->first.receive_time); - j_data.push_back(j_tx); + j_txs.push_back(j_tx); + + ++i; } + 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; + j_response["status"] = "success"; return j_response; @@ -4157,9 +4397,9 @@ namespace xmreg /* - * Lets use this json api convention for success and error - * https://labs.omniti.com/labs/jsend - */ + * Lets use this json api convention for success and error + * https://labs.omniti.com/labs/jsend + */ json json_search(const string& search_text) { @@ -4420,6 +4660,38 @@ namespace xmreg return j_response; } + + /* + * 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 {}} + }; + + json& j_data = j_response["data"]; + + json j_info; + + if (!get_monero_network_info(j_info)) + { + j_response["status"] = "error"; + j_response["message"] = "Cant get monero network info"; + return j_response; + } + + j_data = j_info; + + j_response["status"] = "success"; + + return j_response; + } + + private: json @@ -5192,6 +5464,40 @@ namespace xmreg + template_file["footer"]; } + bool + get_monero_network_info(json& j_info) + { + COMMAND_RPC_GET_INFO::response network_info; + + if (!rpc.get_network_info(network_info)) + { + return false; + } + + j_info = json { + {"status" , network_info.status}, + {"height" , network_info.height}, + {"target_height" , network_info.target_height}, + {"difficulty" , network_info.difficulty}, + {"target" , network_info.target}, + {"hash_rate" , (network_info.difficulty/network_info.target)}, + {"tx_count" , network_info.tx_count}, + {"tx_pool_size" , network_info.tx_pool_size}, + {"alt_blocks_count" , network_info.alt_blocks_count}, + {"outgoing_connections_count", network_info.outgoing_connections_count}, + {"incoming_connections_count", network_info.incoming_connections_count}, + {"white_peerlist_size" , network_info.white_peerlist_size}, + {"grey_peerlist_size" , network_info.grey_peerlist_size}, + {"testnet" , network_info.testnet}, + {"top_block_hash" , network_info.top_block_hash}, + {"cumulative_difficulty" , network_info.cumulative_difficulty}, + {"block_size_limit" , network_info.block_size_limit}, + {"start_time" , network_info.start_time} + }; + + return true; + } + string get_footer() { diff --git a/src/rpccalls.cpp b/src/rpccalls.cpp index 61bcf93..7fcce07 100644 --- a/src/rpccalls.cpp +++ b/src/rpccalls.cpp @@ -147,6 +147,64 @@ rpccalls::commit_tx(tools::wallet2::pending_tx& ptx, string& error_msg) return true; } +bool +rpccalls::get_network_info(COMMAND_RPC_GET_INFO::response& response) +{ + + epee::json_rpc::request req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response resp_t = AUTO_VAL_INIT(resp_t); + + bool r {false}; + + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_info"; + + { + std::lock_guard guard(m_daemon_rpc_mutex); + + if (!connect_to_monero_deamon()) + { + cerr << "get_mempool: not connected to deamon" << endl; + return false; + } + + r = epee::net_utils::invoke_http_json("/json_rpc", + req_t, resp_t, + m_http_client); + } + + string err; + + if (r) + { + if (resp_t.result.status == CORE_RPC_STATUS_BUSY) + { + err = "daemon is busy. Please try again later."; + } + else if (resp_t.result.status != CORE_RPC_STATUS_OK) + { + err = resp_t.result.status; + } + + if (!err.empty()) + { + cerr << "Error connecting to Monero deamon due to " + << err << endl; + return false; + } + } + else + { + cerr << "Error connecting to Monero deamon at " + << deamon_url << endl; + return false; + } + + response = resp_t.result; + + return true; +} } diff --git a/src/rpccalls.h b/src/rpccalls.h index 95ac59f..ed21ea4 100644 --- a/src/rpccalls.h +++ b/src/rpccalls.h @@ -46,10 +46,12 @@ public: bool get_mempool(vector& mempool_txs); - bool commit_tx(tools::wallet2::pending_tx& ptx, string& error_msg); + bool + get_network_info(COMMAND_RPC_GET_INFO::response& info); + };