// Copyright (c) 2014-2015, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "include_base_utils.h" using namespace epee; #include "wallet_rpc_server.h" #include "common/command_line.h" #include "cryptonote_core/cryptonote_format_utils.h" #include "cryptonote_core/account.h" #include "wallet_rpc_server_commands_defs.h" #include "misc_language.h" #include "string_tools.h" #include "crypto/hash.h" namespace tools { //----------------------------------------------------------------------------------- const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port = {"rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server", "", true}; const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip = {"rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"}; void wallet_rpc_server::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_rpc_bind_ip); command_line::add_arg(desc, arg_rpc_bind_port); } //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::wallet_rpc_server(wallet2& w):m_wallet(w) {} //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::run() { m_net_server.add_idle_handler([this](){ try { m_wallet.refresh(); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } return true; }, 20000); //DO NOT START THIS SERVER IN MORE THEN 1 THREADS WITHOUT REFACTORING return epee::http_server_impl_base::run(1, true); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::handle_command_line(const boost::program_options::variables_map& vm) { m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); m_port = command_line::get_arg(vm, arg_rpc_bind_port); return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::init(const boost::program_options::variables_map& vm) { m_net_server.set_threads_prefix("RPC"); bool r = handle_command_line(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to process command line in core_rpc_server"); return epee::http_server_impl_base::init(m_port, m_bind_ip); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er) { try { res.balance = m_wallet.balance(); res.unlocked_balance = m_wallet.unlocked_balance(); } catch (std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er) { try { res.address = m_wallet.get_account().get_public_address_str(m_wallet.testnet()); } catch (std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::validate_transfer(const std::list destinations, std::string payment_id, std::vector& dsts, std::vector& extra, epee::json_rpc::error& er) { crypto::hash8 integrated_payment_id = cryptonote::null_hash8; std::string extra_nonce; for (auto it = destinations.begin(); it != destinations.end(); it++) { cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; if(!get_account_integrated_address_from_str(de.addr, has_payment_id, new_payment_id, m_wallet.testnet(), it->address)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; return false; } de.amount = it->amount; dsts.push_back(de); if (has_payment_id) { if (!payment_id.empty() || integrated_payment_id != cryptonote::null_hash8) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "A single payment id is allowed per transaction"; return false; } integrated_payment_id = new_payment_id; cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, integrated_payment_id); } } if (!payment_id.empty()) { /* Just to clarify */ const std::string& payment_id_str = payment_id; crypto::hash long_payment_id; crypto::hash8 short_payment_id; /* Parse payment ID */ if (wallet2::parse_long_payment_id(payment_id_str, long_payment_id)) { cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, long_payment_id); } /* or short payment ID */ else if (!wallet2::parse_short_payment_id(payment_id_str, short_payment_id)) { cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, short_payment_id); } else { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment id has invalid format: \"" + payment_id_str + "\", expected 16 or 64 character string"; return false; } /* Append Payment ID data into extra */ if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Something went wront with payment_id. Please check its format: \"" + payment_id_str + "\", expected 64-character string"; return false; } } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er) { std::vector dsts; std::vector extra; if (m_wallet.restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } // validate the transfer requested and populate dsts & extra if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) { return false; } try { std::vector ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra); // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) { er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; er.message = "Transaction would be too large. try /transfer_split."; return false; } m_wallet.commit_tx(ptx_vector); // populate response with tx hash res.tx_hash = boost::lexical_cast(cryptonote::get_transaction_hash(ptx_vector.back().tx)); return true; } catch (const tools::error::daemon_busy& e) { er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; er.message = e.what(); return false; } catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; er.message = e.what(); return false; } catch (...) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er) { std::vector dsts; std::vector extra; if (m_wallet.restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) { return false; } try { std::vector ptx_vector; if (req.new_algorithm) ptx_vector = m_wallet.create_transactions_2(dsts, req.mixin, req.unlock_time, req.fee, extra); else ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra); m_wallet.commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) { res.tx_hash_list.push_back(boost::lexical_cast(cryptonote::get_transaction_hash(ptx.tx))); } return true; } catch (const tools::error::daemon_busy& e) { er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; er.message = e.what(); return false; } catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; er.message = e.what(); return false; } catch (...) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er) { if (m_wallet.restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } try { std::vector ptx_vector = m_wallet.create_dust_sweep_transactions(); m_wallet.commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) { res.tx_hash_list.push_back(boost::lexical_cast(cryptonote::get_transaction_hash(ptx.tx))); } return true; } catch (const tools::error::daemon_busy& e) { er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; er.message = e.what(); return false; } catch (const std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; er.message = e.what(); return false; } catch (...) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { try { crypto::hash8 payment_id; if (req.payment_id.empty()) { crypto::generate_random_bytes(8, payment_id.data); } else { if (!tools::wallet2::parse_short_payment_id(req.payment_id,payment_id)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Invalid payment ID"; return false; } } res.integrated_address = m_wallet.get_account().get_public_integrated_address_str(payment_id, m_wallet.testnet()); return true; } catch (std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { try { cryptonote::account_public_address address; crypto::hash8 payment_id; bool has_payment_id; if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet.testnet(), req.integrated_address)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = "Invalid address"; return false; } if(!has_payment_id) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = "Address is not an integrated address"; return false; } res.standard_address = get_account_address_as_str(m_wallet.testnet(),address); res.payment_id = boost::lexical_cast(payment_id); return true; } catch (std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er) { if (m_wallet.restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } try { m_wallet.store(); } catch (std::exception& e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = e.what(); return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er) { crypto::hash payment_id; cryptonote::blobdata payment_id_blob; if(!epee::string_tools::parse_hexstr_to_binbuff(req.payment_id, payment_id_blob)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment ID has invald format"; return false; } if(sizeof(payment_id) != payment_id_blob.size()) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment ID has invalid size"; return false; } payment_id = *reinterpret_cast(payment_id_blob.data()); res.payments.clear(); std::list payment_list; m_wallet.get_payments(payment_id, payment_list); for (auto & payment : payment_list) { wallet_rpc::payment_details rpc_payment; rpc_payment.payment_id = req.payment_id; rpc_payment.tx_hash = epee::string_tools::pod_to_hex(payment.m_tx_hash); rpc_payment.amount = payment.m_amount; rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; res.payments.push_back(rpc_payment); } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er) { res.payments.clear(); /* If the payment ID list is empty, we get payments to any payment ID (or lack thereof) */ if (req.payment_ids.empty()) { std::list> payment_list; m_wallet.get_payments(payment_list, req.min_block_height); for (auto & payment : payment_list) { wallet_rpc::payment_details rpc_payment; rpc_payment.payment_id = epee::string_tools::pod_to_hex(payment.first); rpc_payment.tx_hash = epee::string_tools::pod_to_hex(payment.second.m_tx_hash); rpc_payment.amount = payment.second.m_amount; rpc_payment.block_height = payment.second.m_block_height; rpc_payment.unlock_time = payment.second.m_unlock_time; res.payments.push_back(std::move(rpc_payment)); } return true; } for (auto & payment_id_str : req.payment_ids) { crypto::hash payment_id; crypto::hash8 payment_id8; cryptonote::blobdata payment_id_blob; // TODO - should the whole thing fail because of one bad id? if(!epee::string_tools::parse_hexstr_to_binbuff(payment_id_str, payment_id_blob)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment ID has invalid format: " + payment_id_str; return false; } if(sizeof(payment_id) == payment_id_blob.size()) { payment_id = *reinterpret_cast(payment_id_blob.data()); } else if(sizeof(payment_id8) == payment_id_blob.size()) { payment_id8 = *reinterpret_cast(payment_id_blob.data()); memcpy(payment_id.data, payment_id8.data, 8); memset(payment_id.data + 8, 0, 24); } else { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment ID has invalid size: " + payment_id_str; return false; } std::list payment_list; m_wallet.get_payments(payment_id, payment_list, req.min_block_height); for (auto & payment : payment_list) { wallet_rpc::payment_details rpc_payment; rpc_payment.payment_id = payment_id_str; rpc_payment.tx_hash = epee::string_tools::pod_to_hex(payment.m_tx_hash); rpc_payment.amount = payment.m_amount; rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; res.payments.push_back(std::move(rpc_payment)); } } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_incoming_transfers(const wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::response& res, epee::json_rpc::error& er) { if(req.transfer_type.compare("all") != 0 && req.transfer_type.compare("available") != 0 && req.transfer_type.compare("unavailable") != 0) { er.code = WALLET_RPC_ERROR_CODE_TRANSFER_TYPE; er.message = "Transfer type must be one of: all, available, or unavailable"; return false; } bool filter = false; bool available = false; if (req.transfer_type.compare("available") == 0) { filter = true; available = true; } else if (req.transfer_type.compare("unavailable") == 0) { filter = true; available = false; } wallet2::transfer_container transfers; m_wallet.get_transfers(transfers); bool transfers_found = false; for (const auto& td : transfers) { if (!filter || available != td.m_spent) { if (!transfers_found) { transfers_found = true; } auto txBlob = t_serializable_object_to_blob(td.m_tx); wallet_rpc::transfer_details rpc_transfers; rpc_transfers.amount = td.amount(); rpc_transfers.spent = td.m_spent; rpc_transfers.global_index = td.m_global_output_index; rpc_transfers.tx_hash = boost::lexical_cast(cryptonote::get_transaction_hash(td.m_tx)); rpc_transfers.tx_size = txBlob.size(); res.transfers.push_back(rpc_transfers); } } if (!transfers_found) { return false; } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er) { if (m_wallet.restricted()) { er.code = WALLET_RPC_ERROR_CODE_DENIED; er.message = "Command unavailable in restricted mode."; return false; } if (req.key_type.compare("mnemonic") == 0) { if (!m_wallet.get_seed(res.key)) { er.message = "The wallet is non-deterministic. Cannot display seed."; return false; } } else if(req.key_type.compare("view_key") == 0) { res.key = string_tools::pod_to_hex(m_wallet.get_account().get_keys().m_view_secret_key); } else { er.message = "key_type " + req.key_type + " not found"; return false; } return true; } }