// // Created by mwo on 8/12/16. // #ifndef RESTBED_XMR_YOURMONEROREQUESTS_H #define RESTBED_XMR_YOURMONEROREQUESTS_H #include #include #include "MySqlConnector.h" #include "CurrentBlockchainStatus.h" #include "tools.h" #include "../ext/restbed/source/restbed" namespace xmreg { using namespace std; using namespace restbed; using namespace nlohmann; string get_current_time(const char* format = "%a, %d %b %Y %H:%M:%S %Z") { auto current_time = date::make_zoned( date::current_zone(), date::floor(std::chrono::system_clock::now()) ); return date::format(format, current_time); } multimap make_headers(const multimap& extra_headers = multimap()) { multimap headers { {"Date", get_current_time()}, {"Access-Control-Allow-Origin", "http://127.0.0.1:81"}, {"access-control-allow-headers", "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie"}, {"access-control-max-age", "86400, 1728000"}, {"access-control-allow-methods", "GET, POST, OPTIONS"}, {"access-control-allow-credentials", "true"}, {"Content-Type", "application/json"} }; headers.insert(extra_headers.begin(), extra_headers.end()); return headers; }; struct handel_ { using fetch_func_t = function< void ( const shared_ptr< Session >, const Bytes& ) >; fetch_func_t request_callback; handel_(const fetch_func_t& callback): request_callback {callback} {} void operator()(const shared_ptr< Session > session) { const auto request = session->get_request( ); size_t content_length = request->get_header( "Content-Length", 0); session->fetch(content_length, this->request_callback); } }; class YourMoneroRequests { // this manages all mysql queries shared_ptr xmr_accounts; public: static bool show_logs; YourMoneroRequests(shared_ptr _acc): xmr_accounts {_acc} {} /** * A login request handler. * * It takes address and viewkey from the request * and check mysql if address/account exist. If yes, * it returns this account. If not, it creates new one. * * Once this complites, a thread is tarted that looks * for txs belonging to that account. * * @param session a Restbed session * @param body a POST body, i.e., json string */ void login(const shared_ptr session, const Bytes & body) { json j_request = body_to_json(body); if (show_logs) print_json_log("login request: ", j_request); string xmr_address = j_request["address"]; // a placeholder for exciting or new account data xmreg::XmrAccount acc; uint64_t acc_id {0}; json j_response; // select this account if its existing one if (xmr_accounts->select(xmr_address, acc)) { j_response = {{"new_address", false}}; } else { // account does not exist, so create new one // for this address // we will save current blockchain height // in mysql, so that we know from what block // to start searching txs of this new acount // make it 1 block lower than current, just in case. // this variable will be our using to initialize // `canned_block_height` in mysql Accounts table. uint64_t current_blkchain_height = get_current_blockchain_height() - 1; if ((acc_id = xmr_accounts->insert(xmr_address, current_blkchain_height)) != 0) { // select newly created account if (xmr_accounts->select(acc_id, acc)) { j_response = {{"new_address", true}}; } } } acc.viewkey = j_request["view_key"]; // so we have an account now. Either existing or // newly created. Thus, we can start a tread // which will scan for transactions belonging to // that account, using its address and view key. // the thread will scan the blockchain for txs belonging // to that account and updated mysql database whenever it // will find something. // // The other client (i.e., a webbrowser) will query other functions to retrieve // any belonging transactions in a loop. Thus the thread does not need // to do anything except looking for tx and updating mysql // with relative tx information if (CurrentBlockchainStatus::start_tx_search_thread(acc)) { cout << "Search thread started" << endl; } string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void get_address_txs(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); // if (show_logs) // print_json_log("get_address_txs request: ", j_request); string xmr_address = j_request["address"]; // initialize json response json j_response { { "total_received", "0"}, // taken from Accounts table { "scanned_height", 0}, // not used. it is here to match mymonero { "scanned_block_height", 0}, // taken from Accounts table { "start_height", 0}, // blockchain hieght when acc was created { "transaction_height", 0}, // not used. it is here to match mymonero { "blockchain_height", 0} // current blockchain height }; // a placeholder for exciting or new account data xmreg::XmrAccount acc; // select this account if its existing one if (xmr_accounts->select(xmr_address, acc)) { j_response["total_received"] = acc.total_received; j_response["start_height"] = acc.start_height; j_response["scanned_block_height"] = acc.scanned_block_height; j_response["blockchain_height"] = CurrentBlockchainStatus::get_current_blockchain_height(); vector txs; // retrieve txs from mysql associated with the given address if (xmr_accounts->select_txs(acc.id, txs)) { if (!txs.empty()) { // we found some txs. json j_txs = json::array(); for (XmrTransaction tx: txs) { // get inputs associated with a given // transaction, if any. json j_tx = tx.to_json(); vector inputs; if (xmr_accounts->select_inputs_for_tx(tx.id, inputs)) { json j_spent_outputs = json::array(); uint64_t total_spent {0}; for (XmrTransactionWithOutsAndIns input: inputs) { total_spent += input.amount; j_spent_outputs.push_back(input.spent_output()); } j_tx["total_sent"] = total_spent; j_tx["spent_outputs"] = j_spent_outputs; } j_txs.push_back(j_tx); } j_response["transactions"] = j_txs; } // if (!txs.empty()) } // if (xmr_accounts->select_txs(acc.id, txs)) } // if (xmr_accounts->select(xmr_address, acc)) string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void get_address_info(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); // if (show_logs) // print_json_log("get_address_info request: ", j_request); string xmr_address = j_request["address"]; json j_response { {"locked_funds", "0"}, // xmr in mempool transactions {"total_received", "0"}, // taken from Accounts table {"total_sent", "0"}, // sum of xmr in possible spent outputs {"scanned_height", 0}, // not used. it is here to match mymonero {"scanned_block_height", 0}, // taken from Accounts table {"start_height", 0}, // not used, but available in Accounts table. // it is here to match mymonero {"transaction_height", 0}, // not used. it is here to match mymonero {"blockchain_height", 0}, // current blockchain height {"spent_outputs", nullptr} // list of spent outputs that we think // user has spent. client side will // filter out false positives since // only client has spent key }; // a placeholder for exciting or new account data xmreg::XmrAccount acc; // select this account if its existing one if (xmr_accounts->select(xmr_address, acc)) { // ping the search thread that we still need it. // otherwise it will finish after some time. CurrentBlockchainStatus::ping_search_thread(xmr_address); j_response["total_received"] = acc.total_received; j_response["start_height"] = acc.start_height; j_response["scanned_block_height"] = acc.scanned_block_height; j_response["blockchain_height"] = CurrentBlockchainStatus::get_current_blockchain_height(); uint64_t total_sent {0}; vector txs; // retrieve txs from mysql associated with the given address if (xmr_accounts->select_txs_with_inputs_and_outputs(acc.id, txs)) { // we found some txs. if (!txs.empty()) { // json j_spent_outputs = json::array(); for (XmrTransactionWithOutsAndIns tx: txs) { if (tx.key_image.is_null) { continue; } j_spent_outputs.push_back(tx.spent_output()); total_sent += tx.amount; } j_response["spent_outputs"] = j_spent_outputs; j_response["total_sent"] = total_sent; } // if (!txs.empty()) } // if (xmr_accounts->select_txs_with_inputs_and_outputs(acc.id, txs)) } // if (xmr_accounts->select(xmr_address, acc)) string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void get_unspent_outs(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); // if (show_logs) // print_json_log("get_unspent_outs request: ", j_request); string xmr_address = j_request["address"]; uint64_t mixin = j_request["mixin"]; bool use_dust = j_request["use_dust"]; uint64_t amount = boost::lexical_cast(j_request["amount"].get()); json j_response { {"amount" , 0}, // total value of the outputs {"outputs", json::array()} // list of outputs // exclude those without require // no of confirmation }; // a placeholder for exciting or new account data xmreg::XmrAccount acc; // select this account if its existing one if (xmr_accounts->select(xmr_address, acc)) { uint64_t total_outputs_amount {0}; vector txs; // retrieve txs from mysql associated with the given address if (xmr_accounts->select_txs(acc.id, txs)) { // we found some txs. json& j_outputs = j_response["outputs"]; for (XmrTransaction& tx: txs) { vector outs; if (xmr_accounts->select_outputs_for_tx(tx.id, outs)) { for (XmrOutput &out: outs) { json j_out{ {"amount", out.amount}, {"public_key", out.out_pub_key}, {"index", out.out_index}, {"global_index", out.global_index}, {"tx_id", out.tx_id}, {"tx_hash", tx.hash}, {"tx_prefix_hash", tx.prefix_hash}, {"tx_pub_key" , out.tx_pub_key}, {"timestamp", out.timestamp}, {"height", tx.height}, {"spend_key_images", json::array()} }; vector ins; if (xmr_accounts->select_inputs_for_out(out.id, ins)) { json& j_ins = j_out["spend_key_images"]; for (XmrInput& in: ins) { j_ins.push_back(in.key_image); } } j_outputs.push_back(j_out); total_outputs_amount += out.amount; } //for (XmrOutput &out: outs) } // if (xmr_accounts->select_outputs_for_tx(tx.id, outs)) } // for (XmrTransaction& tx: txs) } // if (xmr_accounts->select_txs(acc.id, txs)) j_response["amount"] = total_outputs_amount; } // if (xmr_accounts->select(xmr_address, acc)) string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void get_random_outs(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); // if (show_logs) // print_json_log("get_unspent_outs request: ", j_request); uint64_t count = j_request["count"]; vector amounts; for (json amount: j_request["amounts"]) { amounts.push_back(boost::lexical_cast(amount.get())); } json j_response { {"amount_outs", json::array()} }; vector found_outputs; if (CurrentBlockchainStatus::get_random_outputs(amounts, count, found_outputs)) { json& j_amount_outs = j_response["amount_outs"]; for (const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs: found_outputs) { json j_outs {{"amount", outs.amount}, {"outputs", json::array()}}; json& j_outputs = j_outs["outputs"]; for (const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& out: outs.outs) { j_outputs.push_back(json { {"global_index", out.global_amount_index}, {"public_key" , pod_to_hex(out.out_key)} }); } j_amount_outs.push_back(j_outs); } } string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void submit_raw_tx(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); // if (show_logs) // print_json_log("get_unspent_outs request: ", j_request); string raw_tx_blob = j_request["tx"]; json j_response { {"status", "error"} }; if (CurrentBlockchainStatus::commit_tx(raw_tx_blob)) { j_response["status"] = "OK"; } string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } void import_wallet_request(const shared_ptr< Session > session, const Bytes & body) { json j_request = body_to_json(body); if (show_logs) print_json_log("import_wallet_request request: ", j_request); string xmr_address = j_request["address"]; // a placeholder for exciting or new account data xmreg::XmrPayment xmr_payment; json j_response; // select this payment if its existing one if (xmr_accounts->select_payment_by_address(xmr_address, xmr_payment)) { j_response["payment_id"] = xmr_payment.payment_id; j_response["import_fee"] = xmr_payment.import_fee; j_response["new_request"] = false; j_response["request_fulfilled"] = bool {xmr_payment.request_fulfilled}; j_response["payment_address"] = xmr_payment.payment_address; if (bool {xmr_payment.request_fulfilled} == false) { j_response["status"] = "Payment not yet received"; } else { j_response["status"] = "Payment received"; } } else { // payment request is now, so create its entry in // Payments table uint64_t payment_table_id {0}; xmr_payment.address = xmr_address; xmr_payment.payment_id = pod_to_hex(generated_payment_id()); xmr_payment.import_fee = 1000000000000; // xmr xmr_payment.request_fulfilled = false; xmr_payment.tx_hash = ""; // no tx_hash yet with the payment xmr_payment.payment_address = "49tyE1AZLzDHM1JPeLeG3vMjqXDGQRQPwWij3ARjZfQMhRLDNyH8PyJVX9AxF3jzabUqjQSecbzYm1JX3MtSib1NQvodSMQ"; if ((payment_table_id = xmr_accounts->insert_payment(xmr_payment)) != 0) { // payment entry created j_response["payment_id"] = xmr_payment.payment_id; j_response["import_fee"] = xmr_payment.import_fee; j_response["new_request"] = true; j_response["request_fulfilled"] = bool {xmr_payment.request_fulfilled}; j_response["payment_address"] = xmr_payment.payment_address; j_response["status"] = "Payment not yet received"; } } string response_body = j_response.dump(); auto response_headers = make_headers({{ "Content-Length", to_string(response_body.size())}}); session->close( OK, response_body, response_headers); } shared_ptr make_resource(function< void (YourMoneroRequests&, const shared_ptr< Session >, const Bytes& ) > handle_func, const string& path) { auto a_request = std::bind(handle_func, *this, std::placeholders::_1, std::placeholders::_2); shared_ptr resource_ptr = make_shared(); resource_ptr->set_path(path); resource_ptr->set_method_handler( "OPTIONS", generic_options_handler); resource_ptr->set_method_handler( "POST" , handel_(a_request) ); return resource_ptr; } static void generic_options_handler( const shared_ptr< Session > session ) { const auto request = session->get_request( ); size_t content_length = request->get_header( "Content-Length", 0); //cout << "generic_options_handler" << endl; session->fetch(content_length, [](const shared_ptr< Session > session, const Bytes & body) { session->close( OK, string{}, make_headers()); }); } static void print_json_log(const string& text, const json& j) { cout << text << '\n' << j.dump(4) << endl; } static inline string body_to_string(const Bytes & body) { return string(reinterpret_cast(body.data()), body.size()); } static inline json body_to_json(const Bytes & body) { json j = json::parse(body_to_string(body)); return j; } inline uint64_t get_current_blockchain_height() { return CurrentBlockchainStatus::get_current_blockchain_height(); } private: }; // define static variables bool YourMoneroRequests::show_logs = false; } #endif //RESTBED_XMR_YOURMONEROREQUESTS_H