// Copyright (c) 2011-2016 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "WalletRpcServer.h" #include #include "Common/CommandLine.h" #include "Common/StringTools.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/Account.h" #include "crypto/hash.h" #include "WalletLegacy/WalletHelper.h" // #include "wallet_errors.h" #include "Rpc/JsonRpc.h" using namespace Logging; using namespace CryptoNote; 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", 0, 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( System::Dispatcher& dispatcher, Logging::ILogger& log, CryptoNote::IWalletLegacy&w, CryptoNote::INode& n, CryptoNote::Currency& currency, const std::string& walletFile) : HttpServer(dispatcher, log), logger(log, "WalletRpc"), m_dispatcher(dispatcher), m_stopComplete(dispatcher), m_wallet(w), m_node(n), m_currency(currency), m_walletFilename(walletFile) { } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::run() { start(m_bind_ip, m_port); m_stopComplete.wait(); return true; } void wallet_rpc_server::send_stop_signal() { m_dispatcher.remoteSpawn([this] { std::cout << "wallet_rpc_server::send_stop_signal()" << std::endl; stop(); m_stopComplete.set(); }); } //------------------------------------------------------------------------------------------------------------------------------ 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) { if (!handle_command_line(vm)) { logger(ERROR) << "Failed to process command line in wallet_rpc_server"; return false; } return true; } void wallet_rpc_server::processRequest(const CryptoNote::HttpRequest& request, CryptoNote::HttpResponse& response) { using namespace CryptoNote::JsonRpc; JsonRpcRequest jsonRequest; JsonRpcResponse jsonResponse; try { jsonRequest.parseRequest(request.getBody()); jsonResponse.setId(jsonRequest.getId()); static std::unordered_map s_methods = { { "getbalance", makeMemberMethod(&wallet_rpc_server::on_getbalance) }, { "transfer", makeMemberMethod(&wallet_rpc_server::on_transfer) }, { "store", makeMemberMethod(&wallet_rpc_server::on_store) }, { "get_payments", makeMemberMethod(&wallet_rpc_server::on_get_payments) }, { "get_transfers", makeMemberMethod(&wallet_rpc_server::on_get_transfers) }, { "get_height", makeMemberMethod(&wallet_rpc_server::on_get_height) }, { "reset", makeMemberMethod(&wallet_rpc_server::on_reset) } }; auto it = s_methods.find(jsonRequest.getMethod()); if (it == s_methods.end()) { throw JsonRpcError(errMethodNotFound); } it->second(this, jsonRequest, jsonResponse); } catch (const JsonRpcError& err) { jsonResponse.setError(err); } catch (const std::exception& e) { jsonResponse.setError(JsonRpcError(WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR, e.what())); } response.setBody(jsonResponse.getBody()); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res) { res.locked_amount = m_wallet.pendingBalance(); res.available_balance = m_wallet.actualBalance(); return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res) { std::vector transfers; for (auto it = req.destinations.begin(); it != req.destinations.end(); it++) { CryptoNote::WalletLegacyTransfer transfer; transfer.address = it->address; transfer.amount = it->amount; transfers.push_back(transfer); } std::vector extra; if (!req.payment_id.empty()) { std::string payment_id_str = req.payment_id; Crypto::Hash payment_id; if (!CryptoNote::parsePaymentId(payment_id_str, payment_id)) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID, "Payment id has invalid format: \"" + payment_id_str + "\", expected 64-character string"); } BinaryArray extra_nonce; CryptoNote::setPaymentIdToTransactionExtraNonce(extra_nonce, payment_id); if (!CryptoNote::addExtraNonceToTransactionExtra(extra, extra_nonce)) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID, "Something went wrong with payment_id. Please check its format: \"" + payment_id_str + "\", expected 64-character string"); } } std::string extraString; std::copy(extra.begin(), extra.end(), std::back_inserter(extraString)); try { CryptoNote::WalletHelper::SendCompleteResultObserver sent; WalletHelper::IWalletRemoveObserverGuard removeGuard(m_wallet, sent); CryptoNote::TransactionId tx = m_wallet.sendTransaction(transfers, req.fee, extraString, req.mixin, req.unlock_time); if (tx == WALLET_LEGACY_INVALID_TRANSACTION_ID) { throw std::runtime_error("Couldn't send transaction"); } std::error_code sendError = sent.wait(tx); removeGuard.removeObserver(); if (sendError) { throw std::system_error(sendError); } CryptoNote::WalletLegacyTransaction txInfo; m_wallet.getTransaction(tx, txInfo); res.tx_hash = Common::podToHex(txInfo.hash); } catch (const std::exception& e) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR, e.what()); } return true; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res) { try { WalletHelper::storeWallet(m_wallet, m_walletFilename); } catch (std::exception& e) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR, std::string("Couldn't save wallet: ") + e.what()); } 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) { Crypto::Hash expectedPaymentId; CryptoNote::BinaryArray payment_id_blob; if (!Common::fromHex(req.payment_id, payment_id_blob)) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID, "Payment ID has invald format"); } if (sizeof(expectedPaymentId) != payment_id_blob.size()) { throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID, "Payment ID has invalid size"); } expectedPaymentId = *reinterpret_cast(payment_id_blob.data()); size_t transactionsCount = m_wallet.getTransactionCount(); for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) { WalletLegacyTransaction txInfo; m_wallet.getTransaction(trantransactionNumber, txInfo); if (txInfo.state != WalletLegacyTransactionState::Active || txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { continue; } if (txInfo.totalAmount < 0) continue; std::vector extraVec; extraVec.reserve(txInfo.extra.size()); std::for_each(txInfo.extra.begin(), txInfo.extra.end(), [&extraVec](const char el) { extraVec.push_back(el); }); Crypto::Hash paymentId; if (getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId == expectedPaymentId) { wallet_rpc::payment_details rpc_payment; rpc_payment.tx_hash = Common::podToHex(txInfo.hash); rpc_payment.amount = txInfo.totalAmount; rpc_payment.block_height = txInfo.blockHeight; rpc_payment.unlock_time = txInfo.unlockTime; res.payments.push_back(rpc_payment); } } return true; } bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res) { res.transfers.clear(); size_t transactionsCount = m_wallet.getTransactionCount(); for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) { WalletLegacyTransaction txInfo; m_wallet.getTransaction(trantransactionNumber, txInfo); if (txInfo.state != WalletLegacyTransactionState::Active || txInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { continue; } std::string address = ""; if (txInfo.totalAmount < 0) { if (txInfo.transferCount > 0) { WalletLegacyTransfer tr; m_wallet.getTransfer(txInfo.firstTransferId, tr); address = tr.address; } } wallet_rpc::Transfer transfer; transfer.time = txInfo.timestamp; transfer.output = txInfo.totalAmount < 0; transfer.transactionHash = Common::podToHex(txInfo.hash); transfer.amount = std::abs(txInfo.totalAmount); transfer.fee = txInfo.fee; transfer.address = address; transfer.blockIndex = txInfo.blockHeight; transfer.unlockTime = txInfo.unlockTime; transfer.paymentId = ""; std::vector extraVec; extraVec.reserve(txInfo.extra.size()); std::for_each(txInfo.extra.begin(), txInfo.extra.end(), [&extraVec](const char el) { extraVec.push_back(el); }); Crypto::Hash paymentId; transfer.paymentId = (getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId != NULL_HASH ? Common::podToHex(paymentId) : ""); res.transfers.push_back(transfer); } return true; } bool wallet_rpc_server::on_get_height(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res) { res.height = m_node.getLastLocalBlockHeight(); return true; } bool wallet_rpc_server::on_reset(const wallet_rpc::COMMAND_RPC_RESET::request& req, wallet_rpc::COMMAND_RPC_RESET::response& res) { m_wallet.reset(); return true; } }