wownero/src/rpc/core_rpc_server.cpp
moneromooo-monero 600a3cf0c0
New RPC and daemon command to get output histogram
This is a list of existing output amounts along with the number
of outputs of that amount in the blockchain.

The daemon command takes:
- no parameters: all outputs with at least 3 instances
- one parameter: all outputs with at least that many instances
- two parameters: all outputs within that many instances

The default starts at 3 to avoid massive spamming of all dust
outputs in the blockchain, and is the current minimum mixin
requirement.

An optional vector of amounts may be passed, to request
histogram only for those outputs.
2016-03-26 21:10:43 +00:00

1141 lines
45 KiB
C++

// Copyright (c) 2014-2016, 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 <boost/foreach.hpp>
#include "include_base_utils.h"
using namespace epee;
#include "core_rpc_server.h"
#include "common/command_line.h"
#include "cryptonote_core/cryptonote_format_utils.h"
#include "cryptonote_core/account.h"
#include "cryptonote_core/cryptonote_basic_impl.h"
#include "misc_language.h"
#include "crypto/hash.h"
#include "core_rpc_server_error_codes.h"
namespace cryptonote
{
//-----------------------------------------------------------------------------------
void core_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);
command_line::add_arg(desc, arg_testnet_rpc_bind_port);
command_line::add_arg(desc, arg_restricted_rpc);
}
//------------------------------------------------------------------------------------------------------------------------------
core_rpc_server::core_rpc_server(
core& cr
, nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p
)
: m_core(cr)
, m_p2p(p2p)
{}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::handle_command_line(
const boost::program_options::variables_map& vm
)
{
auto p2p_bind_arg = m_testnet ? arg_testnet_rpc_bind_port : arg_rpc_bind_port;
m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip);
m_port = command_line::get_arg(vm, p2p_bind_arg);
m_restricted = command_line::get_arg(vm, arg_restricted_rpc);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::init(
const boost::program_options::variables_map& vm
)
{
m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on);
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<core_rpc_server, connection_context>::init(m_port, m_bind_ip);
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::check_core_busy()
{
if(m_p2p.get_payload_object().get_core().get_blockchain_storage().is_storing_blockchain())
{
return false;
}
return true;
}
#define CHECK_CORE_BUSY() do { if(!check_core_busy()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0)
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::check_core_ready()
{
if(!m_p2p.get_payload_object().is_synchronized())
{
return false;
}
return check_core_busy();
}
#define CHECK_CORE_READY() do { if(!check_core_ready()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0)
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res)
{
CHECK_CORE_BUSY();
res.height = m_core.get_current_blockchain_height();
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res)
{
CHECK_CORE_BUSY();
crypto::hash top_hash;
if (!m_core.get_blockchain_top(res.height, top_hash))
{
res.status = "Failed";
return false;
}
++res.height; // turn top block height into blockchain height
res.top_block_hash = string_tools::pod_to_hex(top_hash);
res.target_height = m_core.get_target_blockchain_height();
res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block();
res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2;
res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase
res.tx_pool_size = m_core.get_pool_transactions_count();
res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count();
uint64_t total_conn = m_p2p.get_connections_count();
res.outgoing_connections_count = m_p2p.get_outgoing_connections_count();
res.incoming_connections_count = total_conn - res.outgoing_connections_count;
res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count();
res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count();
res.testnet = m_testnet;
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res)
{
CHECK_CORE_BUSY();
std::list<std::pair<block, std::list<transaction> > > bs;
if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT))
{
res.status = "Failed";
return false;
}
BOOST_FOREACH(auto& b, bs)
{
res.blocks.resize(res.blocks.size()+1);
res.blocks.back().block = block_to_blob(b.first);
BOOST_FOREACH(auto& t, b.second)
{
res.blocks.back().txs.push_back(tx_to_blob(t));
}
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res)
{
CHECK_CORE_BUSY();
res.status = "Failed";
if(!m_core.get_random_outs_for_amounts(req, res))
{
return true;
}
res.status = CORE_RPC_STATUS_OK;
std::stringstream ss;
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount outs_for_amount;
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry;
std::for_each(res.outs.begin(), res.outs.end(), [&](outs_for_amount& ofa)
{
ss << "[" << ofa.amount << "]:";
CHECK_AND_ASSERT_MES(ofa.outs.size(), ;, "internal error: ofa.outs.size() is empty for amount " << ofa.amount);
std::for_each(ofa.outs.begin(), ofa.outs.end(), [&](out_entry& oe)
{
ss << oe.global_amount_index << " ";
});
ss << ENDL;
});
std::string s = ss.str();
LOG_PRINT_L2("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: " << ENDL << s);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res)
{
CHECK_CORE_BUSY();
bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes);
if(!r)
{
res.status = "Failed";
return true;
}
res.status = CORE_RPC_STATUS_OK;
LOG_PRINT_L2("COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES: [" << res.o_indexes.size() << "]");
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res)
{
CHECK_CORE_BUSY();
std::vector<crypto::hash> vh;
BOOST_FOREACH(const auto& tx_hex_str, req.txs_hashes)
{
blobdata b;
if(!string_tools::parse_hexstr_to_binbuff(tx_hex_str, b))
{
res.status = "Failed to parse hex representation of transaction hash";
return true;
}
if(b.size() != sizeof(crypto::hash))
{
res.status = "Failed, size of data mismatch";
return true;
}
vh.push_back(*reinterpret_cast<const crypto::hash*>(b.data()));
}
std::list<crypto::hash> missed_txs;
std::list<transaction> txs;
bool r = m_core.get_transactions(vh, txs, missed_txs);
if(!r)
{
res.status = "Failed";
return true;
}
LOG_PRINT_L2("Found " << txs.size() << "/" << vh.size() << " transactions on the blockchain");
// try the pool for any missing txes
size_t found_in_pool = 0;
if (!missed_txs.empty())
{
std::list<transaction> pool_txs;
bool r = m_core.get_pool_transactions(pool_txs);
if(r)
{
for (std::list<transaction>::const_iterator i = pool_txs.begin(); i != pool_txs.end(); ++i)
{
std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), get_transaction_hash(*i));
if (mi != missed_txs.end())
{
missed_txs.erase(mi);
txs.push_back(*i);
++found_in_pool;
}
}
}
LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool");
}
BOOST_FOREACH(auto& tx, txs)
{
blobdata blob = t_serializable_object_to_blob(tx);
res.txs_as_hex.push_back(string_tools::buff_to_hex_nodelimer(blob));
if (req.decode_as_json)
res.txs_as_json.push_back(obj_to_json_str(tx));
}
BOOST_FOREACH(const auto& miss_tx, missed_txs)
{
res.missed_tx.push_back(string_tools::pod_to_hex(miss_tx));
}
LOG_PRINT_L2(res.txs_as_hex.size() << " transactions found, " << res.missed_tx.size() << " not found");
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res)
{
CHECK_CORE_BUSY();
std::vector<crypto::key_image> key_images;
BOOST_FOREACH(const auto& ki_hex_str, req.key_images)
{
blobdata b;
if(!string_tools::parse_hexstr_to_binbuff(ki_hex_str, b))
{
res.status = "Failed to parse hex representation of key image";
return true;
}
if(b.size() != sizeof(crypto::key_image))
{
res.status = "Failed, size of data mismatch";
}
key_images.push_back(*reinterpret_cast<const crypto::key_image*>(b.data()));
}
std::vector<bool> spent_status;
bool r = m_core.are_key_images_spent(key_images, spent_status);
if(!r)
{
res.status = "Failed";
return true;
}
res.spent_status.clear();
for (size_t n = 0; n < spent_status.size(); ++n)
res.spent_status.push_back(spent_status[n] ? COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN : COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT);
// check the pool too
std::vector<cryptonote::tx_info> txs;
std::vector<cryptonote::spent_key_image_info> ki;
r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki);
if(!r)
{
res.status = "Failed";
return true;
}
for (std::vector<cryptonote::spent_key_image_info>::const_iterator i = ki.begin(); i != ki.end(); ++i)
{
crypto::hash hash;
crypto::key_image spent_key_image;
if (parse_hash256(i->id_hash, hash))
{
memcpy(&spent_key_image, &hash, sizeof(hash)); // a bit dodgy, should be other parse functions somewhere
for (size_t n = 0; n < res.spent_status.size(); ++n)
{
if (res.spent_status[n] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)
{
if (key_images[n] == spent_key_image)
{
res.spent_status[n] = COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL;
break;
}
}
}
}
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res)
{
CHECK_CORE_READY();
std::string tx_blob;
if(!string_tools::parse_hexstr_to_binbuff(req.tx_as_hex, tx_blob))
{
LOG_PRINT_L0("[on_send_raw_tx]: Failed to parse tx from hexbuff: " << req.tx_as_hex);
res.status = "Failed";
return true;
}
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
tx_verification_context tvc = AUTO_VAL_INIT(tvc);
if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false))
{
LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx");
res.status = "Failed";
return true;
}
if(tvc.m_verifivation_failed)
{
LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed");
res.status = "Failed";
return true;
}
if(!tvc.m_should_be_relayed)
{
LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed");
res.status = "Not relayed";
return true;
}
NOTIFY_NEW_TRANSACTIONS::request r;
r.txs.push_back(tx_blob);
m_core.get_protocol()->relay_transactions(r, fake_context);
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res)
{
CHECK_CORE_READY();
account_public_address adr;
if(!get_account_address_from_str(adr, m_testnet, req.miner_address))
{
res.status = "Failed, wrong address";
LOG_PRINT_L0(res.status);
return true;
}
boost::thread::attributes attrs;
attrs.set_stack_size(THREAD_STACK_SIZE);
if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs))
{
res.status = "Failed, mining not started";
LOG_PRINT_L0(res.status);
return true;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res)
{
if(!m_core.get_miner().stop())
{
res.status = "Failed, mining not stopped";
LOG_PRINT_L0(res.status);
return true;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res)
{
CHECK_CORE_READY();
const miner& lMiner = m_core.get_miner();
res.active = lMiner.is_mining();
if ( lMiner.is_mining() ) {
res.speed = lMiner.get_speed();
res.threads_count = lMiner.get_threads_count();
const account_public_address& lMiningAdr = lMiner.get_mining_address();
res.address = get_account_address_as_str(m_testnet, lMiningAdr);
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res)
{
CHECK_CORE_BUSY();
if( !m_core.get_blockchain_storage().store_blockchain() )
{
res.status = "Error while storing blockhain";
return true;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res)
{
std::list<nodetool::peerlist_entry> white_list;
std::list<nodetool::peerlist_entry> gray_list;
m_p2p.get_peerlist_manager().get_peerlist_full(white_list, gray_list);
for (auto & entry : white_list)
{
res.white_list.emplace_back(entry.id, entry.adr.ip, entry.adr.port, entry.last_seen);
}
for (auto & entry : gray_list)
{
res.gray_list.emplace_back(entry.id, entry.adr.ip, entry.adr.port, entry.last_seen);
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res)
{
if(m_core.get_miner().is_mining())
{
m_core.get_miner().do_print_hashrate(req.visible);
res.status = CORE_RPC_STATUS_OK;
}
else
{
res.status = CORE_RPC_STATUS_NOT_MINING;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res)
{
if (req.level < LOG_LEVEL_MIN || req.level > LOG_LEVEL_MAX)
{
res.status = "Error: log level not valid";
}
else
{
epee::log_space::log_singletone::get_set_log_detalisation_level(true, req.level);
int otshell_utils_log_level = 100 - (req.level * 20);
gCurrentLogger.setDebugLevel(otshell_utils_log_level);
res.status = CORE_RPC_STATUS_OK;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res)
{
CHECK_CORE_BUSY();
m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res)
{
// FIXME: replace back to original m_p2p.send_stop_signal() after
// investigating why that isn't working quite right.
m_p2p.send_stop_signal();
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res)
{
CHECK_CORE_BUSY();
res.count = m_core.get_current_blockchain_height();
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy";
return false;
}
if(req.size() != 1)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Wrong parameters, expected height";
return false;
}
uint64_t h = req[0];
if(m_core.get_current_blockchain_height() <= h)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Too big height: ") + std::to_string(h) + ", current blockchain height = " + std::to_string(m_core.get_current_blockchain_height());
}
res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h));
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
// equivalent of strstr, but with arbitrary bytes (ie, NULs)
// This does not differentiate between "not found" and "found at offset 0"
uint64_t slow_memmem(const void* start_buff, size_t buflen,const void* pat,size_t patlen)
{
const void* buf = start_buff;
const void* end=(const char*)buf+buflen;
if (patlen > buflen || patlen == 0) return 0;
while(buflen>0 && (buf=memchr(buf,((const char*)pat)[0],buflen-patlen+1)))
{
if(memcmp(buf,pat,patlen)==0)
return (const char*)buf - (const char*)start_buff;
buf=(const char*)buf+1;
buflen = (const char*)end - (const char*)buf;
}
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_ready())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy";
return false;
}
if(req.reserve_size > 255)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE;
error_resp.message = "To big reserved size, maximum 255";
return false;
}
cryptonote::account_public_address acc = AUTO_VAL_INIT(acc);
if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(acc, m_testnet, req.wallet_address))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS;
error_resp.message = "Failed to parse wallet address";
return false;
}
block b = AUTO_VAL_INIT(b);
cryptonote::blobdata blob_reserve;
blob_reserve.resize(req.reserve_size, 0);
if(!m_core.get_block_template(b, acc, res.difficulty, res.height, blob_reserve))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
LOG_ERROR("Failed to create block template");
return false;
}
blobdata block_blob = t_serializable_object_to_blob(b);
crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx);
if(tx_pub_key == null_pkey)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
LOG_ERROR("Failed to tx pub key in coinbase extra");
return false;
}
res.reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key));
if(!res.reserved_offset)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
LOG_ERROR("Failed to find tx pub key in blockblob");
return false;
}
res.reserved_offset += sizeof(tx_pub_key) + 3; //3 bytes: tag for TX_EXTRA_TAG_PUBKEY(1 byte), tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte)
if(res.reserved_offset + req.reserve_size > block_blob.size())
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
LOG_ERROR("Failed to calculate offset for ");
return false;
}
res.prev_hash = string_tools::pod_to_hex(b.prev_id);
res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp)
{
CHECK_CORE_READY();
if(req.size()!=1)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Wrong param";
return false;
}
blobdata blockblob;
if(!string_tools::parse_hexstr_to_binbuff(req[0], blockblob))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
error_resp.message = "Wrong block blob";
return false;
}
// Fixing of high orphan issue for most pools
// Thanks Boolberry!
block b = AUTO_VAL_INIT(b);
if(!parse_and_validate_block_from_blob(blockblob, b))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
error_resp.message = "Wrong block blob";
return false;
}
// Fix from Boolberry neglects to check block
// size, do that with the function below
if(!m_core.check_incoming_block_size(blockblob))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE;
error_resp.message = "Block bloc size is too big, rejecting block";
return false;
}
if(!m_core.handle_block_found(b))
{
error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED;
error_resp.message = "Block not accepted";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
uint64_t core_rpc_server::get_block_reward(const block& blk)
{
uint64_t reward = 0;
BOOST_FOREACH(const tx_out& out, blk.miner_tx.vout)
{
reward += out.amount;
}
return reward;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::fill_block_header_responce(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_responce& responce)
{
responce.major_version = blk.major_version;
responce.minor_version = blk.minor_version;
responce.timestamp = blk.timestamp;
responce.prev_hash = string_tools::pod_to_hex(blk.prev_id);
responce.nonce = blk.nonce;
responce.orphan_status = orphan_status;
responce.height = height;
responce.depth = m_core.get_current_blockchain_height() - height - 1;
responce.hash = string_tools::pod_to_hex(hash);
responce.difficulty = m_core.get_blockchain_storage().block_difficulty(height);
responce.reward = get_block_reward(blk);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
uint64_t last_block_height;
crypto::hash last_block_hash;
bool have_last_block_hash = m_core.get_blockchain_top(last_block_height, last_block_hash);
if (!have_last_block_hash)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't get last block hash.";
return false;
}
block last_block;
bool have_last_block = m_core.get_block_by_hash(last_block_hash, last_block);
if (!have_last_block)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't get last block.";
return false;
}
bool responce_filled = fill_block_header_responce(last_block, false, last_block_height, last_block_hash, res.block_header);
if (!responce_filled)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't produce valid response.";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp){
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
crypto::hash block_hash;
bool hash_parsed = parse_hash256(req.hash, block_hash);
if(!hash_parsed)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Failed to parse hex representation of block hash. Hex = " + req.hash + '.';
return false;
}
block blk;
bool have_block = m_core.get_block_by_hash(block_hash, blk);
if (!have_block)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.';
return false;
}
if (blk.miner_tx.vin.front().type() != typeid(txin_gen))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: coinbase transaction in the block has the wrong type";
return false;
}
uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height;
bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header);
if (!responce_filled)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't produce valid response.";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp){
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
if(m_core.get_current_blockchain_height() <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Too big height: ") + std::to_string(req.height) + ", current blockchain height = " + std::to_string(m_core.get_current_blockchain_height());
return false;
}
crypto::hash block_hash = m_core.get_block_id_by_height(req.height);
block blk;
bool have_block = m_core.get_block_by_hash(block_hash, blk);
if (!have_block)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.';
return false;
}
bool responce_filled = fill_block_header_responce(blk, false, req.height, block_hash, res.block_header);
if (!responce_filled)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't produce valid response.";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_block(const COMMAND_RPC_GET_BLOCK::request& req, COMMAND_RPC_GET_BLOCK::response& res, epee::json_rpc::error& error_resp){
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
crypto::hash block_hash;
if (!req.hash.empty())
{
bool hash_parsed = parse_hash256(req.hash, block_hash);
if(!hash_parsed)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
error_resp.message = "Failed to parse hex representation of block hash. Hex = " + req.hash + '.';
return false;
}
}
else
{
if(m_core.get_current_blockchain_height() <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Too big height: ") + std::to_string(req.height) + ", current blockchain height = " + std::to_string(m_core.get_current_blockchain_height());
return false;
}
block_hash = m_core.get_block_id_by_height(req.height);
}
block blk;
bool have_block = m_core.get_block_by_hash(block_hash, blk);
if (!have_block)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.';
return false;
}
if (blk.miner_tx.vin.front().type() != typeid(txin_gen))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: coinbase transaction in the block has the wrong type";
return false;
}
uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height;
bool responce_filled = fill_block_header_responce(blk, false, block_height, block_hash, res.block_header);
if (!responce_filled)
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: can't produce valid response.";
return false;
}
for (size_t n = 0; n < blk.tx_hashes.size(); ++n)
{
res.tx_hashes.push_back(epee::string_tools::pod_to_hex(blk.tx_hashes[n]));
}
res.blob = t_serializable_object_to_blob(blk);
res.json = obj_to_json_str(blk);
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
res.connections = m_p2p.get_payload_object().get_connections();
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
res.height = m_core.get_current_blockchain_height();
res.target_height = m_core.get_target_blockchain_height();
res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block();
res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2;
res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase
res.tx_pool_size = m_core.get_pool_transactions_count();
res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count();
uint64_t total_conn = m_p2p.get_connections_count();
res.outgoing_connections_count = m_p2p.get_outgoing_connections_count();
res.incoming_connections_count = total_conn - res.outgoing_connections_count;
res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count();
res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count();
res.testnet = m_testnet;
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
#if BLOCKCHAIN_DB == DB_LMDB
const Blockchain &blockchain = m_core.get_blockchain_storage();
uint8_t version = req.version > 0 ? req.version : blockchain.get_ideal_hard_fork_version();
res.version = blockchain.get_current_hard_fork_version();
res.enabled = blockchain.get_hard_fork_voting_info(version, res.window, res.votes, res.threshold, res.earliest_height, res.voting);
res.state = blockchain.get_hard_fork_state();
res.status = CORE_RPC_STATUS_OK;
return true;
#else
error_resp.code = CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC;
error_resp.message = "Hard fork inoperative in memory mode.";
return false;
#endif
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
auto now = time(nullptr);
std::map<uint32_t, time_t> blocked_ips = m_p2p.get_blocked_ips();
for (std::map<uint32_t, time_t>::const_iterator i = blocked_ips.begin(); i != blocked_ips.end(); ++i)
{
if (i->second > now) {
COMMAND_RPC_GETBANS::ban b;
b.ip = i->first;
b.seconds = i->second - now;
res.bans.push_back(b);
}
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
for (auto i = req.bans.begin(); i != req.bans.end(); ++i)
{
if (i->ban)
m_p2p.block_ip(i->ip, i->seconds);
else
m_p2p.unblock_ip(i->ip);
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
bool failed = false;
std::list<crypto::hash> txids;
if (req.txids.empty())
{
std::list<transaction> pool_txs;
bool r = m_core.get_pool_transactions(pool_txs);
if (!r)
{
res.status = "Failed to get txpool contents";
return true;
}
for (const auto &tx: pool_txs)
{
txids.push_back(cryptonote::get_transaction_hash(tx));
}
}
else
{
for (const auto &str: req.txids)
{
cryptonote::blobdata txid_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(str, txid_data))
{
failed = true;
}
crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
txids.push_back(txid);
}
}
if (!m_core.get_blockchain_storage().flush_txes_from_pool(txids))
{
res.status = "Failed to remove one more tx";
return false;
}
if (failed)
{
res.status = "Failed to parse txid";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp)
{
if(!check_core_busy())
{
error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
error_resp.message = "Core is busy.";
return false;
}
std::map<uint64_t, uint64_t> histogram;
try
{
histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts);
}
catch (const std::exception &e)
{
res.status = "Failed to get output histogram";
return true;
}
res.histogram.clear();
res.histogram.reserve(histogram.size());
for (const auto &i: histogram)
{
if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0))
res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second));
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res)
{
cryptonote::core::set_fast_exit();
m_p2p.deinit();
m_core.deinit();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res)
{
// TODO
/*if (m_p2p.get_outgoing_connections_count() > req.out_peers)
{
m_p2p.m_config.m_net_config.connections_count = req.out_peers;
if (m_p2p.get_outgoing_connections_count() > req.out_peers)
{
int count = m_p2p.get_outgoing_connections_count() - req.out_peers;
m_p2p.delete_connections(count);
}
}
else
m_p2p.m_config.m_net_config.connections_count = req.out_peers;
*/
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res)
{
m_p2p.set_save_graph(true);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res)
{
m_p2p.set_save_graph(false);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_bind_ip = {
"rpc-bind-ip"
, "IP for RPC server"
, "127.0.0.1"
};
const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_bind_port = {
"rpc-bind-port"
, "Port for RPC server"
, std::to_string(config::RPC_DEFAULT_PORT)
};
const command_line::arg_descriptor<std::string> core_rpc_server::arg_testnet_rpc_bind_port = {
"testnet-rpc-bind-port"
, "Port for testnet RPC server"
, std::to_string(config::testnet::RPC_DEFAULT_PORT)
};
const command_line::arg_descriptor<bool> core_rpc_server::arg_restricted_rpc = {
"restricted-rpc"
, "Restrict RPC to view only commands"
, false
};
} // namespace cryptonote