// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Boolberry developers
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
//     https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// The EUPL is a copyleft licence that is compatible with the MIT/X11
// licence used by the original projects; the MIT terms are therefore
// considered “grandfathered” under the EUPL for this code.
//
// SPDX‑License‑Identifier: EUPL-1.2
//

#include "include_base_utils.h"
using namespace epee;

#include "core_rpc_server.h"
#include "common/command_line.h"
#include "currency_core/currency_format_utils.h"
#include "currency_core/account.h"

#include "misc_language.h"
#include "crypto/hash.h"
#include "core_rpc_server_error_codes.h"

#define CHECK_RPC_LIMITS(var, limit)    if(var > limit)  {res.status = API_RETURN_CODE_ARG_OUT_OF_LIMITS; return true;}


#define RPC_LIMIT_COMMAND_RPC_GET_BLOCKS_DIRECT_BLOCK_IDS                                       4000
#define RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS                                    500
#define RPC_LIMIT_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_TXIDS                               100
#define RPC_LIMIT_COMMAND_RPC_CHECK_KEYIMAGES_IMAGES                                            1000
#define RPC_LIMIT_COMMAND_RPC_GET_TRANSACTIONS_TXS_HASHES                                       5000
#define RPC_LIMIT_COMMAND_RPC_GET_OFFERS_EX_LIMIT                                               1000
#define RPC_LIMIT_COMMAND_RPC_GET_BLOCKS_DETAILS_COUNT                                          4000
#define RPC_LIMIT_COMMAND_RPC_GET_POOL_TXS_DETAILS_IDS                                          4000
#define RPC_LIMIT_COMMAND_RPC_GET_VOTES                                                         4000
#define RPC_LIMIT_COMMAND_RPC_GET_ASSETS_LIST                                                   1000
#define RPC_LIMIT_COMMAND_RPC_GET_ALT_BLOCKS_DETAILS_COUNT                                      100
#define RPC_LIMIT_COMMAND_RPC_FORCE_RELAY_RAW_TXS                                               100
#define RPC_LIMIT_COMMAND_RPC_GET_ALIASES_COUNT                                                 200



namespace currency
{
  namespace
  {
    const command_line::arg_descriptor<std::string> arg_rpc_bind_ip         ("rpc-bind-ip",         "", "127.0.0.1");
    const command_line::arg_descriptor<std::string> arg_rpc_bind_port       ("rpc-bind-port",       "", std::to_string(RPC_DEFAULT_PORT));
    const command_line::arg_descriptor<bool> arg_rpc_ignore_offline_status  ("rpc-ignore-offline",  "Let rpc calls despite online/offline status");
    const command_line::arg_descriptor<bool> arg_rpc_enable_admin_api       ("rpc-enable-admin-api", "Enable API commands that can alter pool or daemon state (reset pool, remove txs, etc.)");
  }
  //-----------------------------------------------------------------------------------
  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_rpc_ignore_offline_status);
    command_line::add_arg(desc, arg_rpc_enable_admin_api);
    
  }
  //------------------------------------------------------------------------------------------------------------------------------
  core_rpc_server::core_rpc_server(core& cr, nodetool::node_server<currency::t_currency_protocol_handler<currency::core> >& p2p,
    bc_services::bc_offers_service& of)
    : m_core(cr)
    , m_p2p(p2p)
    , m_of(of)
    , m_ignore_offline_status(false)
  {}
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::handle_command_line(const boost::program_options::variables_map& vm)
  {
    m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip);
    if (m_bind_ip == "0.0.0.0")
    {
      LOG_PRINT_COLOR("!!!!!!IMPORTANT!!!!!!: We strongly advise against binding the server to 0.0.0.0 and exposing it directly" << ENDL
                << "to the public internet. The server is meant solely for internal functionality and lacks the security features" << ENDL
                << "required for publicly facing services. If you need to offer a public API, always place it behind secure" << ENDL
                << "proxies that are properly configured with security measures--such as rate limiting--to protect the service.", LOG_LEVEL_0, LOG_COLOR_RED);
    }
    m_port = command_line::get_arg(vm, arg_rpc_bind_port);
    if (command_line::has_arg(vm, arg_rpc_ignore_offline_status))
    {
      m_ignore_offline_status = command_line::get_arg(vm, arg_rpc_ignore_offline_status);
    }
    if (command_line::has_arg(vm, arg_rpc_enable_admin_api))
    {
      m_enabled_admin_api = command_line::get_arg(vm, arg_rpc_enable_admin_api);
    }
    
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_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<core_rpc_server, connection_context>::init(m_port, m_bind_ip);
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::check_core_ready_(const std::string& calling_method)
  {
#ifndef TESTNET
    if (m_ignore_offline_status)
      return true;
    if(!m_p2p.get_payload_object().is_synchronized())
    {
      LOG_PRINT_L0("[" << calling_method << "]Core busy cz is_synchronized");
      return false;
    }
 #endif
    return true;
  }
#define check_core_ready() check_core_ready_(LOCAL_FUNCTION_DEF__)
#define CHECK_CORE_READY()    if (!check_core_ready()) {res.status =  API_RETURN_CODE_BUSY; return true; }
#define CHECK_CORE_READY_WE() if (!check_core_ready()) {error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; error_resp.message = "Core is busy."; return false; }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    res.height = m_core.get_current_blockchain_size();
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, connection_context& cntx)
  {
    //unconditional values 
    res.height = m_core.get_current_blockchain_size();
    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.synchronized_connections_count = m_p2p.get_payload_object().get_synchronized_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.current_blocks_median = m_core.get_blockchain_storage().get_current_comulative_blocksize_limit() / 2;
    res.alias_count = m_core.get_blockchain_storage().get_aliases_count();
    res.current_max_allowed_block_size = m_core.get_blockchain_storage().get_current_comulative_blocksize_limit();
    if(m_ignore_offline_status)
    { 
      res.daemon_network_state = COMMAND_RPC_GET_INFO::daemon_network_state_online;
    }
    else
    {
      if (!res.outgoing_connections_count)
        res.daemon_network_state = COMMAND_RPC_GET_INFO::daemon_network_state_connecting;
      else if (m_p2p.get_payload_object().is_synchronized())
        res.daemon_network_state = COMMAND_RPC_GET_INFO::daemon_network_state_online;
      else
        res.daemon_network_state = COMMAND_RPC_GET_INFO::daemon_network_state_synchronizing;
    }
    res.synchronization_start_height = m_p2p.get_payload_object().get_core_inital_height();
    res.max_net_seen_height = m_p2p.get_payload_object().get_max_seen_height();
    m_p2p.get_maintainers_info(res.mi);
    res.pos_allowed = m_core.get_blockchain_storage().is_pos_allowed();
    wide_difficulty_type pos_diff = m_core.get_blockchain_storage().get_cached_next_difficulty(true);
    res.pos_difficulty = pos_diff.convert_to<std::string>();
    res.pow_difficulty = m_core.get_blockchain_storage().get_cached_next_difficulty(false).convert_to<uint64_t>();
    res.default_fee = m_core.get_blockchain_storage().get_core_runtime_config().tx_default_fee;
    res.minimum_fee = m_core.get_blockchain_storage().get_core_runtime_config().tx_pool_min_fee;

    auto & hf = m_core.get_blockchain_storage().get_core_runtime_config().hard_forks.m_height_the_hardfork_n_active_after;
    for (size_t i = 0; i != hf.size(); i++)
    {
      res.is_hardfok_active.push_back(m_core.get_blockchain_storage().is_hardfork_active(i));
    }

    //conditional values 
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_NET_TIME_DELTA_MEDIAN)
    {
      int64_t last_median2local_time_diff, last_ntp2local_time_diff;
      if (!m_p2p.get_payload_object().get_last_time_sync_difference(last_median2local_time_diff, last_ntp2local_time_diff))
        res.net_time_delta_median = 1;
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_CURRENT_NETWORK_HASHRATE_50)
      res.current_network_hashrate_50 = m_core.get_blockchain_storage().get_current_hashrate(50);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_CURRENT_NETWORK_HASHRATE_350)
      res.current_network_hashrate_350 = m_core.get_blockchain_storage().get_current_hashrate(350);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_SECONDS_FOR_10_BLOCKS)
      res.seconds_for_10_blocks = m_core.get_blockchain_storage().get_seconds_between_last_n_block(10);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_SECONDS_FOR_30_BLOCKS)
      res.seconds_for_30_blocks = m_core.get_blockchain_storage().get_seconds_between_last_n_block(30);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_TRANSACTIONS_DAILY_STAT)
      m_core.get_blockchain_storage().get_transactions_daily_stat(res.transactions_cnt_per_day, res.transactions_volume_per_day);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_LAST_POS_TIMESTAMP)
    {
      auto pos_bl_ptr = m_core.get_blockchain_storage().get_last_block_of_type(true);
      if (pos_bl_ptr)
        res.last_pos_timestamp = pos_bl_ptr->bl.timestamp;
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_LAST_POW_TIMESTAMP)
    {
      auto pow_bl_ptr = m_core.get_blockchain_storage().get_last_block_of_type(false);
      if (pow_bl_ptr)
        res.last_pow_timestamp = pow_bl_ptr->bl.timestamp;
    }
    boost::multiprecision::uint128_t total_coins = 0;
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_TOTAL_COINS)
    {
      total_coins = m_core.get_blockchain_storage().total_coins();
      res.total_coins = boost::lexical_cast<std::string>(total_coins);
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_LAST_BLOCK_SIZE)
    {
      std::vector<size_t> sz;
      m_core.get_blockchain_storage().get_last_n_blocks_sizes(sz, 1);
      res.last_block_size = sz.size() ? sz.back() : 0;
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_TX_COUNT_IN_LAST_BLOCK)
    {
      currency::block b = AUTO_VAL_INIT(b);
      m_core.get_blockchain_storage().get_top_block(b);
      res.tx_count_in_last_block = b.tx_hashes.size();
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_POS_SEQUENCE_FACTOR)
      res.pos_sequence_factor = m_core.get_blockchain_storage().get_current_sequence_factor(true);
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_POW_SEQUENCE_FACTOR)
      res.pow_sequence_factor = m_core.get_blockchain_storage().get_current_sequence_factor(false);
    if (req.flags&(COMMAND_RPC_GET_INFO_FLAG_POS_DIFFICULTY | COMMAND_RPC_GET_INFO_FLAG_TOTAL_COINS))
    {
      res.block_reward = currency::get_base_block_reward(res.height);
      currency::block b = AUTO_VAL_INIT(b);
      m_core.get_blockchain_storage().get_top_block(b);
      res.last_block_total_reward = currency::get_reward_from_miner_tx(b.miner_tx);
      res.pos_diff_total_coins_rate = (pos_diff / (total_coins - PREMINE_AMOUNT + 1)).convert_to<uint64_t>();
      res.last_block_timestamp = b.timestamp;
      res.last_block_hash = string_tools::pod_to_hex(get_block_hash(b));
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_POS_BLOCK_TS_SHIFT_VS_ACTUAL)
    {
      res.pos_block_ts_shift_vs_actual = 0;
      auto last_pos_block_ptr = m_core.get_blockchain_storage().get_last_block_of_type(true);
      if (last_pos_block_ptr)
        res.pos_block_ts_shift_vs_actual = last_pos_block_ptr->bl.timestamp - get_block_datetime(last_pos_block_ptr->bl);
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_OUTS_STAT)
      m_core.get_blockchain_storage().get_outs_index_stat(res.outs_stat);

    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_PERFORMANCE)
    {
      currency::blockchain_storage::performnce_data& pd = m_core.get_blockchain_storage().get_performnce_data();
      //block processing zone
      res.performance_data.block_processing_time_0 = pd.block_processing_time_0_ms.get_avg()*1000;
      res.performance_data.block_processing_time_1 = pd.block_processing_time_1.get_avg();
      res.performance_data.target_calculating_time_2 = pd.target_calculating_time_2.get_avg();
      res.performance_data.longhash_calculating_time_3 = pd.longhash_calculating_time_3.get_avg();
      res.performance_data.all_txs_insert_time_5 = pd.all_txs_insert_time_5.get_avg();
      res.performance_data.etc_stuff_6 = pd.etc_stuff_6.get_avg();
      res.performance_data.insert_time_4 = pd.insert_time_4.get_avg();
      res.performance_data.raise_block_core_event = pd.raise_block_core_event.get_avg();
      res.performance_data.target_calculating_enum_blocks = pd.target_calculating_enum_blocks.get_avg();
      res.performance_data.target_calculating_calc = pd.target_calculating_calc.get_avg();
      //tx processing zone
      res.performance_data.tx_check_inputs_time = pd.tx_check_inputs_time.get_avg();
      res.performance_data.tx_add_one_tx_time = pd.tx_add_one_tx_time.get_avg();
      res.performance_data.tx_process_extra = pd.tx_process_extra.get_avg();
      res.performance_data.tx_process_attachment = pd.tx_process_attachment.get_avg();
      res.performance_data.tx_process_inputs = pd.tx_process_inputs.get_avg();
      res.performance_data.tx_push_global_index = pd.tx_push_global_index.get_avg();
      res.performance_data.tx_check_exist = pd.tx_check_exist.get_avg();
      res.performance_data.tx_append_time = pd.tx_append_time.get_avg();
      res.performance_data.tx_append_rl_wait = pd.tx_append_rl_wait.get_avg();
      res.performance_data.tx_append_is_expired = pd.tx_append_is_expired.get_avg();
      res.performance_data.tx_mixin_count = pd.tx_mixin_count.get_avg();

      
      res.performance_data.tx_store_db = pd.tx_store_db.get_avg();
      //db performance count
      res.performance_data.map_size = pd.si.map_size;
      res.performance_data.tx_count = pd.si.tx_count;
      res.performance_data.writer_tx_count = pd.si.write_tx_count;
#define COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(field_name)   res.performance_data.field_name = pd.field_name.get_avg(); 
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_prefix_hash);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_attachment_check);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_kimage_check);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_ch_in_val_sig);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_get_item_size);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_relative_to_absolute);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_loop);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_loop_get_subitem);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_loop_find_tx);
      COPY_AVG_TO_BLOCK_CHAIN_PERF_DATA(tx_check_inputs_loop_scan_outputkeys_loop_handle_output);

      //tx pool perfromance stat
      const currency::tx_memory_pool::performnce_data& pool_pd = m_core.get_tx_pool().get_performnce_data();
#define COPY_AVG_TO_POOL_PERF_DATA(field_name)   res.tx_pool_performance_data.field_name = pool_pd.field_name.get_avg();
      COPY_AVG_TO_POOL_PERF_DATA(tx_processing_time);
      COPY_AVG_TO_POOL_PERF_DATA(check_inputs_types_supported_time);
      COPY_AVG_TO_POOL_PERF_DATA(expiration_validate_time);
      COPY_AVG_TO_POOL_PERF_DATA(validate_amount_time);
      COPY_AVG_TO_POOL_PERF_DATA(validate_alias_time);
      COPY_AVG_TO_POOL_PERF_DATA(check_keyimages_ws_ms_time);
      COPY_AVG_TO_POOL_PERF_DATA(check_inputs_time);
      COPY_AVG_TO_POOL_PERF_DATA(begin_tx_time);
      COPY_AVG_TO_POOL_PERF_DATA(update_db_time);
      COPY_AVG_TO_POOL_PERF_DATA(db_commit_time);

    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_PERFORMANCE)
    {
      res.offers_count = m_of.get_offers_container().size();
    }
    if (req.flags&COMMAND_RPC_GET_INFO_FLAG_EXPIRATIONS_MEDIAN)
    {
      res.expiration_median_timestamp = m_core.get_blockchain_storage().get_tx_expiration_median();
    }
      


    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_blocks_direct(const COMMAND_RPC_GET_BLOCKS_DIRECT::request& req, COMMAND_RPC_GET_BLOCKS_DIRECT::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.block_ids.size(), RPC_LIMIT_COMMAND_RPC_GET_BLOCKS_DIRECT_BLOCK_IDS);

    if (req.block_ids.back() != m_core.get_blockchain_storage().get_block_id_by_height(0))
    {
      //genesis mismatch, return specific
      res.status = API_RETURN_CODE_GENESIS_MISMATCH;
      return true;
    }

    if (req.minimum_height >= m_core.get_blockchain_storage().get_current_blockchain_size())
    {
      //wrong minimum_height
      res.status = API_RETURN_CODE_BAD_ARG;
      return true;
    }

    blockchain_storage::blocks_direct_container bs;
    if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT, req.minimum_height))
    {
      res.status = API_RETURN_CODE_FAIL;
      return false;
    }
    res.current_hardfork = m_core.get_blockchain_storage().get_core_runtime_config().hard_forks.get_the_most_recent_hardfork_id_for_height(res.current_height);

    for(auto& b: bs)
    {
      res.blocks.resize(res.blocks.size()+1);
      res.blocks.back().block_ptr = b.first;
      res.blocks.back().txs_ptr = std::move(b.second);
      res.blocks.back().coinbase_ptr = b.third;
    }

    res.status = API_RETURN_CODE_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, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.block_ids.size(), RPC_LIMIT_COMMAND_RPC_GET_BLOCKS_DIRECT_BLOCK_IDS);
    LOG_PRINT_L2("[on_get_blocks]: Prevalidating....");
    if (req.block_ids.back() != m_core.get_blockchain_storage().get_block_id_by_height(0))
    {
      //genesis mismatch, return specific
      res.status = API_RETURN_CODE_GENESIS_MISMATCH;
      return true;
    }

    if (req.minimum_height >= m_core.get_blockchain_storage().get_current_blockchain_size())
    {
      //wrong minimum_height
      res.status = API_RETURN_CODE_BAD_ARG;
      return true;
    }
    LOG_PRINT_L2("[on_get_blocks]: find_blockchain_supplement ....");
    blockchain_storage::blocks_direct_container bs;
    if (!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT, req.minimum_height))
    {
      res.status = API_RETURN_CODE_FAIL;
      return false;
    }
    res.current_hardfork = m_core.get_blockchain_storage().get_core_runtime_config().hard_forks.get_the_most_recent_hardfork_id_for_height(res.current_height);

    LOG_PRINT_L2("[on_get_blocks]: Enumerating over blocks ....");
    for (auto& b : bs)
    {
      res.blocks.resize(res.blocks.size() + 1);
      res.blocks.back().block = block_to_blob(b.first->bl);
      CHECK_AND_ASSERT_MES(b.third.get(), false, "Internal error on handling COMMAND_RPC_GET_BLOCKS_FAST: b.third is empty, ie coinbase info is not prepared");
      res.blocks.back().coinbase_global_outs = b.third->m_global_output_indexes;
      res.blocks.back().tx_global_outs.resize(b.second.size());
      size_t i = 0;

      BOOST_FOREACH(auto & t, b.second)
      {
        res.blocks.back().txs.push_back(tx_to_blob(t->tx));
        res.blocks.back().tx_global_outs[i].v = t->m_global_output_indexes;
        i++;
      }
    }


    LOG_PRINT_L2("[on_get_blocks]: Finished");
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_LEGACY::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_LEGACY::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.amounts.size(), RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS);
    
    res.status = API_RETURN_CODE_FAIL;

    COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req_native = AUTO_VAL_INIT(req_native);
    req_native.amounts = req.amounts;
    req_native.decoys_count = req.outs_count;
    req_native.use_forced_mix_outs = req.use_forced_mix_outs;
    COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response res_native = AUTO_VAL_INIT(res_native);

    if(!m_core.get_random_outs_for_amounts(req_native, res_native))
    {
      return true;
    }

    for (const auto& item : res_native.outs)
    {      
      res.outs.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_LEGACY::outs_for_amount());
      res.outs.back().amount = item.amount;
      for (const auto& subitem : item.outs)
      {
        res.outs.back().outs.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS_LEGACY::out_entry { subitem.global_amount_index, subitem.stealth_address });
      }
    }
    

    res.status = API_RETURN_CODE_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);
    */

    return true;
  }

    //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_random_outs1(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.amounts.size(), RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS);
    res.status = API_RETURN_CODE_FAIL;

    if(!m_core.get_random_outs_for_amounts(req, res))
    {
      return true;
    }

    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_random_outs3(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.amounts.size(), RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS);
    for (const auto& item : req.amounts)
    {
      CHECK_RPC_LIMITS(item.global_offsets.size(), RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS);      
    }

    res.status = API_RETURN_CODE_FAIL;
    if (!m_core.get_blockchain_storage().get_random_outs_for_amounts3(req, res))
    {
      return true;
    }

    res.status = API_RETURN_CODE_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, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.txids.size(), RPC_LIMIT_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_TXIDS);
    
    res.tx_global_outs.resize(req.txids.size());
    size_t i = 0;
    for (auto& txid : req.txids)
    {
      bool r = m_core.get_tx_outputs_gindexs(txid, res.tx_global_outs[i].v);
      if (!r)
      {
        res.status = API_RETURN_CODE_FAIL;
        return true;
      }
      i++;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_set_maintainers_info(const COMMAND_RPC_SET_MAINTAINERS_INFO::request& req, COMMAND_RPC_SET_MAINTAINERS_INFO::response& res, connection_context& cntx)
  {
    if(!m_p2p.handle_maintainers_entry(req))
    {
      res.status = "Failed to get call handle_maintainers_entry()";
      return true;
    }

    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_tx_pool(const COMMAND_RPC_GET_TX_POOL::request& req, COMMAND_RPC_GET_TX_POOL::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    std::list<transaction> txs;
    if (!m_core.get_pool_transactions(txs))
    {
      res.status = "Failed to call get_pool_transactions()";
      return true;
    }

    res.tx_expiration_ts_median = m_core.get_blockchain_storage().get_tx_expiration_median();


    for(auto& tx: txs)
    {
      res.txs.push_back(t_serializable_object_to_blob(tx));
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_check_keyimages(const COMMAND_RPC_CHECK_KEYIMAGES::request& req, COMMAND_RPC_CHECK_KEYIMAGES::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.images.size(), RPC_LIMIT_COMMAND_RPC_CHECK_KEYIMAGES_IMAGES);
    
    m_core.get_blockchain_storage().check_keyimages(req.images, res.images_stat);
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.txs_hashes.size(), RPC_LIMIT_COMMAND_RPC_GET_TRANSACTIONS_TXS_HASHES);

    std::vector<crypto::hash> vh;
    for(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;
    }

    for(auto& tx : txs)
    {
      blobdata blob = t_serializable_object_to_blob(tx);
      res.txs_as_hex.push_back(string_tools::buff_to_hex_nodelimer(blob));
    }

    for(const auto& miss_tx : missed_txs)
    {
      res.missed_tx.push_back(string_tools::pod_to_hex(miss_tx));
    }

    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_offers_ex(const COMMAND_RPC_GET_OFFERS_EX::request& req, COMMAND_RPC_GET_OFFERS_EX::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.filter.limit, RPC_LIMIT_COMMAND_RPC_GET_OFFERS_EX_LIMIT);
    
    m_of.get_offers_ex(req.filter, res.offers, res.total_offers, m_core.get_blockchain_storage().get_core_runtime_config().get_core_time());
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_validate_signature(const COMMAND_VALIDATE_SIGNATURE::request& req, COMMAND_VALIDATE_SIGNATURE::response& res, epee::json_rpc::error& er, connection_context& cntx)
  {
    if (!m_p2p.get_connections_count())
    {
      res.status = API_RETURN_CODE_DISCONNECTED;
      return true;
    }
    std::string buff = epee::string_encoding::base64_decode(req.buff);    
    crypto::public_key pkey = req.pkey;

    if(pkey == currency::null_pkey)
    {
      //need to load pkey from alias
      extra_alias_entry_base eaeb = AUTO_VAL_INIT(eaeb);
      if (!m_core.get_blockchain_storage().get_alias_info(req.alias, eaeb))
      {
        res.status = API_RETURN_CODE_NOT_FOUND;
        return true;
      }
      pkey = eaeb.m_address.spend_public_key;
    }

    crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size());
    bool sig_check_res = crypto::check_signature(h, pkey, req.sig);
    if (!sig_check_res)
    {
      res.status = API_RETURN_CODE_FAIL;
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_pos_mining_details(const COMMAND_RPC_GET_POS_MINING_DETAILS::request& req, COMMAND_RPC_GET_POS_MINING_DETAILS::response& res, connection_context& cntx)
  {
    if (!m_ignore_offline_status && !m_p2p.get_connections_count())
    {
      res.status = API_RETURN_CODE_DISCONNECTED;
      return true;
    }
    res.pos_mining_allowed = m_core.get_blockchain_storage().is_pos_allowed();
    if (!res.pos_mining_allowed)
    {
      res.status = API_RETURN_CODE_NOT_FOUND;
      return true;
    }

    res.pos_sequence_factor_is_good = true;
    uint64_t new_block_expected_height = m_core.get_blockchain_storage().get_top_block_height() + 1;
    size_t new_block_expected_sequence_factor = m_core.get_blockchain_storage().get_current_sequence_factor(true);
    if (new_block_expected_height > BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION && new_block_expected_sequence_factor > BLOCK_POS_STRICT_SEQUENCE_LIMIT)
      res.pos_sequence_factor_is_good = false;

    res.pos_basic_difficulty = m_core.get_blockchain_storage().get_next_diff_conditional(true).convert_to<std::string>();
    m_core.get_blockchain_storage().build_stake_modifier(res.sm, blockchain_storage::alt_chain_type(), 0, &res.last_block_hash);// , &res.height);
    
    //TODO: need atomic operation with  build_stake_modifier()
    res.starter_timestamp = m_core.get_blockchain_storage().get_last_timestamps_check_window_median();
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_current_core_tx_expiration_median(const COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request& req, COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response& res, connection_context& cntx)
  {
    res.expiration_median = m_core.get_blockchain_storage().get_tx_expiration_median();
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_rpc_get_blocks_details(const COMMAND_RPC_GET_BLOCKS_DETAILS::request& req, COMMAND_RPC_GET_BLOCKS_DETAILS::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.count, RPC_LIMIT_COMMAND_RPC_GET_BLOCKS_DETAILS_COUNT);
    
    m_core.get_blockchain_storage().get_main_blocks_rpc_details(req.height_start, req.count, req.ignore_transactions, res.blocks);
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------  
  bool core_rpc_server::on_get_tx_details(const COMMAND_RPC_GET_TX_DETAILS::request& req, COMMAND_RPC_GET_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    crypto::hash h = null_hash;
    if(!epee::string_tools::hex_to_pod(req.tx_hash, h))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
      error_resp.message = "Invalid tx hash given";
      return false;
    }
    if (!m_core.get_blockchain_storage().get_tx_rpc_details(h, res.tx_info, 0, false))
    {
      if (!m_core.get_tx_pool().get_transaction_details(h, res.tx_info))
      {
      error_resp.code = CORE_RPC_ERROR_CODE_NOT_FOUND;
      error_resp.message = "tx is not found";
      return false;
      }

    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_search_by_id(const COMMAND_RPC_SERARCH_BY_ID::request& req, COMMAND_RPC_SERARCH_BY_ID::response& res, connection_context& cntx)
  {
    crypto::hash id = null_hash;
    if (!epee::string_tools::hex_to_pod(req.id, id))
      return false;

    m_core.get_blockchain_storage().search_by_id(id, res.types_found);
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_out_info(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_BY_AMOUNT::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_BY_AMOUNT::response& res, connection_context& cntx)
  {
    if (!m_core.get_blockchain_storage().get_global_index_details(req, res))
      res.status = API_RETURN_CODE_NOT_FOUND;
    else
      res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_multisig_info(const COMMAND_RPC_GET_MULTISIG_INFO::request& req, COMMAND_RPC_GET_MULTISIG_INFO::response& res, connection_context& cntx)
  {
    if (!m_core.get_blockchain_storage().get_multisig_id_details(req, res))
      res.status = API_RETURN_CODE_NOT_FOUND;
    else
      res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_pool_txs_details(const COMMAND_RPC_GET_POOL_TXS_DETAILS::request& req, COMMAND_RPC_GET_POOL_TXS_DETAILS::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.ids.size(), RPC_LIMIT_COMMAND_RPC_GET_POOL_TXS_DETAILS_IDS);
    
    
    if (!req.ids.size())
    {
      m_core.get_tx_pool().get_all_transactions_details(res.txs);
    }
    else 
    {
      m_core.get_tx_pool().get_transactions_details(req.ids, res.txs);
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_pool_txs_brief_details(const COMMAND_RPC_GET_POOL_TXS_BRIEF_DETAILS::request& req, COMMAND_RPC_GET_POOL_TXS_BRIEF_DETAILS::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.ids.size(), RPC_LIMIT_COMMAND_RPC_GET_POOL_TXS_DETAILS_IDS);
    if (!req.ids.size())
    {
      m_core.get_tx_pool().get_all_transactions_brief_details(res.txs);
    }
    else
    {
      m_core.get_tx_pool().get_transactions_brief_details(req.ids, res.txs);
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_all_pool_tx_list(const COMMAND_RPC_GET_ALL_POOL_TX_LIST::request& req, COMMAND_RPC_GET_ALL_POOL_TX_LIST::response& res, connection_context& cntx)
  {
    m_core.get_tx_pool().get_all_transactions_list(res.ids);
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_pool_info(const COMMAND_RPC_GET_POOL_INFO::request& req, COMMAND_RPC_GET_POOL_INFO::response& res, connection_context& cntx)
  {
    std::list<currency::extra_alias_entry> al_list;
    m_core.get_tx_pool().get_aliases_from_tx_pool(al_list);
    for (const auto& a : al_list)
    {
      res.aliases_que.push_back(alias_info_to_rpc_alias_info(a));
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_votes(const COMMAND_RPC_GET_VOTES::request& req, COMMAND_RPC_GET_VOTES::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.h_end - req.h_start, RPC_LIMIT_COMMAND_RPC_GET_VOTES);

    if (!m_core.get_blockchain_storage().get_pos_votes(req.h_start, req.h_end, res.votes))
    {
      res.status = API_RETURN_CODE_INTERNAL_ERROR;
      res.error_code = "Internal error";
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_asset_info(const COMMAND_RPC_GET_ASSET_INFO::request& req, COMMAND_RPC_GET_ASSET_INFO::response& res, connection_context& cntx)
  {
    if (!m_core.get_blockchain_storage().get_asset_info(req.asset_id, res.asset_descriptor))
    {
      res.status = API_RETURN_CODE_NOT_FOUND;
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_assets_list(const COMMAND_RPC_GET_ASSETS_LIST::request& req, COMMAND_RPC_GET_ASSETS_LIST::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    CHECK_RPC_LIMITS(req.count, RPC_LIMIT_COMMAND_RPC_GET_ASSETS_LIST);

    if (!m_core.get_blockchain_storage().get_assets(req.offset, req.count, res.assets))
    {
      res.status = API_RETURN_CODE_NOT_FOUND;
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
#define LOCAL_CHECK(cond, msg)         if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;    error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; }
#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; }

    LOCAL_CHECK(req.tx_id.empty() != req.tx_blob.empty(), "One of either tx_id or tx_blob must be specified.");

    transaction tx{};
    if (!req.tx_id.empty())
    {
      CHECK_CORE_READY_WE();

      crypto::hash tx_id{};
      LOCAL_CHECK(crypto::parse_tpod_from_hex_string(req.tx_id, tx_id), "tx_id is given, but it's invalid");
      LOCAL_CHECK(m_core.get_transaction(tx_id, tx), "tx with the given tx_id could be found in the blockchain");
    }
    else
    {
      blobdata decoded_blob = string_encoding::base64_decode(req.tx_blob);
      if (!t_unserializable_object_from_blob(tx, decoded_blob))
      {
        // unable to decode tx_blob as base64, try once again as hex-encoding
        decoded_blob.clear();
        string_tools::parse_hexstr_to_binbuff(req.tx_blob, decoded_blob);
        LOCAL_CHECK(t_unserializable_object_from_blob(tx, decoded_blob), "tx_id is not given, and tx_blob is invalid");
      }
    }

    crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
    crypto::point_t R{};
    LOCAL_CHECK(tx_pub_key != null_pkey && R.from_public_key(tx_pub_key) && R.is_in_main_subgroup(), "unsigned_tx: tx public key is missing or invalid");

    LOCAL_CHECK(tx_pub_key == (crypto::scalar_t(req.tx_secret_key) * crypto::c_point_G).to_public_key(), "tx_secret_key doesn't match the transaction public key");

    LOCAL_CHECK(req.outputs_addresses.size() == tx.vout.size(), "outputs_addresses count (" + epee::string_tools::num_to_string_fast(req.outputs_addresses.size()) + " doesn't match tx.vout size (" + epee::string_tools::num_to_string_fast(tx.vout.size()) + ")");

    for(size_t i = 0; i < req.outputs_addresses.size(); ++i)
    {
      if (req.outputs_addresses[i].empty())
        continue; // skip this output if the given address is empty string

      account_public_address addr{};
      payment_id_t payment_id{};
      LOCAL_CHECK(currency::get_account_address_and_payment_id_from_str(addr, payment_id, req.outputs_addresses[i]) && payment_id.empty(), "output address #" + epee::string_tools::num_to_string_fast(i) + " couldn't be parsed or it is an integrated address (which is not supported)");

      tx_out_v& out_v = tx.vout[i];
      LOCAL_CHECK(out_v.type() == typeid(tx_out_zarcanum), "tx output #" + epee::string_tools::num_to_string_fast(i) + " has wrong type");
      const tx_out_zarcanum& zo = boost::get<tx_out_zarcanum>(out_v);

      crypto::key_derivation derivation{};
      LOCAL_CHECK_INT_ERR(crypto::generate_key_derivation(addr.view_public_key, req.tx_secret_key, derivation), "output #" + epee::string_tools::num_to_string_fast(i) + ": generate_key_derivation failed");
      
      auto& decoded_out = res.decoded_outputs.emplace_back();
      decoded_out.out_index = i;
      decoded_out.address = req.outputs_addresses[i];
      crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{};
      LOCAL_CHECK(currency::decode_output_amount_and_asset_id(zo, derivation, i, decoded_out.amount, decoded_out.asset_id, amount_blinding_mask, asset_id_blinding_mask), "output #" + epee::string_tools::num_to_string_fast(i) + ": cannot be decoded");
    }

    res.tx_in_json = currency::obj_to_json_str(tx);
    res.verified_tx_id = get_transaction_hash(tx);

    res.status = API_RETURN_CODE_OK;
    return true;
#undef LOCAL_CHECK
#undef LOCAL_CHECK_INT_ERR
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    if (!m_core.get_blockchain_storage().get_main_block_rpc_details(req.id, res.block_details))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_NOT_FOUND;
      error_resp.message = "the requested block has not been found";
      return false;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_alt_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    if (!m_core.get_blockchain_storage().get_alt_block_rpc_details(req.id, res.block_details))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_NOT_FOUND;
      error_resp.message = "the requested block has not been found";
      return false;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_alt_blocks_details(const COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::request& req, COMMAND_RPC_GET_ALT_BLOCKS_DETAILS::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.count, RPC_LIMIT_COMMAND_RPC_GET_ALT_BLOCKS_DETAILS_COUNT);
       
    m_core.get_blockchain_storage().get_alt_blocks_rpc_details(req.offset, req.count, res.blocks);
    res.status = API_RETURN_CODE_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, connection_context& cntx)
  {
    CHECK_CORE_READY();

    std::string tx_blob;
    if (req.tx_as_hex.size())
    {
      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;
      }
    }
    else
    {
      if (!req.tx_as_base64.size())
      {
        LOG_PRINT_L0("[on_send_raw_tx]: Failed to parse tx from hexbuff: " << req.tx_as_hex);
        res.status = API_RETURN_CODE_BAD_ARG;
        return true;
      }
      tx_blob = req.tx_as_base64;
    }

    if (!m_ignore_offline_status && !m_p2p.get_payload_object().get_synchronized_connections_count())
    {
      LOG_PRINT_L0("[on_send_raw_tx]: Failed to send, daemon not connected to net");
      res.status = API_RETURN_CODE_DISCONNECTED;
      return true;
    }

    if (m_p2p.get_payload_object().get_core().get_blockchain_storage().is_pre_hardfork_tx_freeze_period_active())
    {
      LOG_PRINT_L0("[on_send_raw_tx]: pre hardfork freeze period is in effect, sending transactions is not allowed till the next hardfork. Please, try again after the hardfork activation.");
      res.status = API_RETURN_CODE_BUSY;
      return true;
    }

    currency_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))
    {
      LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx");
      res.status = "Failed";
      return true;
    }

    if(tvc.m_verification_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, tx blob: " << req.tx_as_hex);
      res.status = "Not relayed";
      return true;
    }


    NOTIFY_OR_INVOKE_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 = API_RETURN_CODE_OK;
    return true;
  }
	//------------------------------------------------------------------------------------------------------------------------------
	bool core_rpc_server::on_force_relaey_raw_txs(const COMMAND_RPC_FORCE_RELAY_RAW_TXS::request& req, COMMAND_RPC_FORCE_RELAY_RAW_TXS::response& res, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.txs_as_hex.size(), RPC_LIMIT_COMMAND_RPC_FORCE_RELAY_RAW_TXS);
		CHECK_CORE_READY();
		NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r);

		for (const auto& t : req.txs_as_hex)
		{
			std::string tx_blob;
			if (!string_tools::parse_hexstr_to_binbuff(t, tx_blob))
			{
				LOG_PRINT_L0("[on_send_raw_tx]: Failed to parse tx from hexbuff: " << t);
				res.status = "Failed";
				return true;
			}
			r.txs.push_back(tx_blob);
		}

		currency_connection_context fake_context = AUTO_VAL_INIT(fake_context);
		bool call_res = m_core.get_protocol()->relay_transactions(r, fake_context);
		if (call_res)
			res.status = API_RETURN_CODE_OK;
		return call_res;
	}
  //------------------------------------------------------------------------------------------------------------------------------
#ifdef CPU_MINING_ENABLED
  bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    account_public_address adr;
    if(!get_account_address_from_str(adr, req.miner_address))
    {
      res.status = "Failed, wrong address";
      return true;
    }

    if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count)))
    {
      res.status = "Failed, mining not started";
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    if(!m_core.get_miner().stop())
    {
      res.status = "Failed, mining not stopped";
      return true;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
#endif // #ifdef CPU_MINING_ENABLED
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, connection_context& cntx)
  {
    CHECK_CORE_READY();
    res.count = m_core.get_current_blockchain_size();
    res.status = API_RETURN_CODE_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, connection_context& cntx)
  {
    if(!check_core_ready())
    {
      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_size() <= h)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
      error_resp.message = std::string("To big height: ") + std::to_string(h) + ", current blockchain size = " +  std::to_string(m_core.get_current_blockchain_size());
    }
    res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h));
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy";
      return false;
    }

    if(req.extra_text.size() > 255)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE;
      error_resp.message = "Extra text size is to big, maximum 255";
      return false;
    }

    currency::account_public_address miner_address = AUTO_VAL_INIT(miner_address);
    if(!req.wallet_address.size() || !currency::get_account_address_from_str(miner_address, req.wallet_address))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS;
      error_resp.message = "Failed to parse wallet address";
      return false;
    }
    currency::account_public_address stakeholder_address = AUTO_VAL_INIT(stakeholder_address);
    if(req.pos_block && (!req.stakeholder_address.size() || !currency::get_account_address_from_str(stakeholder_address, req.stakeholder_address)))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS;
      error_resp.message = "Failed to parse stakeholder address";
      return false;
    }


    create_block_template_params params = AUTO_VAL_INIT(params);
    params.miner_address = miner_address;
    params.stakeholder_address = stakeholder_address;
    params.ex_nonce = req.extra_text;
    params.pos = req.pos_block;
    params.pe = req.pe;
    //params.pe.keyimage key image will be set in the wallet
    //params.pe.wallet_index is not included in serialization map, TODO: refactoring here
    params.pcustom_fill_block_template_func = nullptr;
    if (req.explicit_transaction.size())
    {
      transaction tx = AUTO_VAL_INIT(tx);
      if (!parse_and_validate_tx_from_blob(req.explicit_transaction, tx))
      {
        error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
        error_resp.message = "Wrong parameters: explicit_transaction is invalid";
        LOG_ERROR("Failed to parse explicit_transaction blob");
        return false;
      }
      params.explicit_txs.push_back(tx);
    }

    create_block_template_response resp = AUTO_VAL_INIT(resp);
    if (!m_core.get_block_template(params, resp))
    {
      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;
    }

    res.difficulty = resp.diffic.convert_to<std::string>();
    blobdata block_blob = t_serializable_object_to_blob(resp.b);
    res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob);
    res.prev_hash = string_tools::pod_to_hex(resp.b.prev_id);
    res.miner_tx_tgc = resp.miner_tx_tgc;
    res.height = resp.height;
    res.block_reward_without_fee = resp.block_reward_without_fee;
    res.block_reward = resp.block_reward;
    res.txs_fee = resp.txs_fee;
    //calculate epoch seed
    res.seed = currency::ethash_epoch_to_seed(currency::ethash_height_to_epoch(res.height));

    res.status = API_RETURN_CODE_OK;
    LOG_PRINT_L1("COMMAND_RPC_GETBLOCKTEMPLATE OK, response block: " << ENDL << currency::obj_to_json_str(resp.b));
    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, connection_context& cntx)
  {
    CHECK_CORE_READY_WE();
    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;
    }
    
    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;
    }

    block_verification_context bvc = AUTO_VAL_INIT(bvc);
    if(!m_core.handle_block_found(b, &bvc))
    {
      if (bvc.m_added_to_altchain)
      {
        error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_ADDED_AS_ALTERNATIVE;
        error_resp.message = "Block added as alternative";
        return false;
      }
      error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED;
      error_resp.message = "Block not accepted";
      return false;
    }

    res.status = "OK";
    return true;
  }  
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    CHECK_CORE_READY_WE();

    block b = AUTO_VAL_INIT(b);
    if (!parse_and_validate_block_from_blob(req.b, b))
    {
      error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
      error_resp.message = "Wrong block blob";
      return false;
    }

    block_verification_context bvc = AUTO_VAL_INIT(bvc);
    for (const auto& txblob : req.explicit_txs)
    {

      crypto::hash tx_hash = AUTO_VAL_INIT(tx_hash);
      transaction tx = AUTO_VAL_INIT(tx);
      if (!parse_and_validate_tx_from_blob(txblob.blob, tx, tx_hash))
      {
        error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
        error_resp.message = "Wrong explicit tx blob";
        return false;
      }
      bvc.m_onboard_transactions[tx_hash] = tx;
    }


    if (!m_core.handle_block_found(b, &bvc))
    {
      if (bvc.m_added_to_altchain)
      {
        error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_ADDED_AS_ALTERNATIVE;
        error_resp.message = "Block added as alternative";
        return false;
      }
      error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED;
      error_resp.message = "Block not accepted";
      return false;
    }

    res.status = "OK";
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  uint64_t core_rpc_server::get_block_reward(const block& blk, const crypto::hash& h)
  {
    if (blk.miner_tx.version >= TRANSACTION_VERSION_POST_HF4)
    {
      uint64_t reward_with_fee = 0;
      m_core.get_blockchain_storage().get_block_reward_by_hash(h, reward_with_fee);
      return reward_with_fee;
    }

    // legacy version, pre HF4
    uint64_t reward = 0;
    BOOST_FOREACH(const auto& out, blk.miner_tx.vout)
    {
      VARIANT_SWITCH_BEGIN(out);
      VARIANT_CASE_CONST(tx_out_bare, out)
        reward += out.amount;
      VARIANT_SWITCH_END();
    }
    return reward;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, block_header_response& response)
  {
    crypto::hash block_hash = get_block_hash(blk);
    response.major_version = blk.major_version;
    response.minor_version = blk.minor_version;
    response.timestamp = blk.timestamp;
    response.prev_hash = string_tools::pod_to_hex(blk.prev_id);
    response.nonce = blk.nonce;
    response.orphan_status = orphan_status;
    response.height = get_block_height(blk);
    response.depth = m_core.get_current_blockchain_size() - response.height - 1;
    response.hash = string_tools::pod_to_hex(block_hash);
    response.difficulty = m_core.get_blockchain_storage().block_difficulty(response.height).convert_to<std::string>();
    response.reward = get_block_reward(blk, block_hash);
    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, connection_context& cntx)
  {
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy.";
      return false;
    }
    block last_block = AUTO_VAL_INIT(last_block);
    bool have_last_block_hash = m_core.get_blockchain_storage().get_top_block(last_block);
    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;
    }
    bool response_filled = fill_block_header_response(last_block, false, res.block_header);
    if (!response_filled)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
      error_resp.message = "Internal error: can't produce valid response.";
      return false;
    }
    res.status = API_RETURN_CODE_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, connection_context& cntx){
    if(!check_core_ready())
    {
      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 response_filled = fill_block_header_response(blk, false, res.block_header);
    if (!response_filled)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
      error_resp.message = "Internal error: can't produce valid response.";
      return false;
    }
    res.status = API_RETURN_CODE_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, connection_context& cntx){
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy.";
      return false;
    }
    if(m_core.get_current_blockchain_size() <= req.height)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
      error_resp.message = std::string("To big height: ") + std::to_string(req.height) + ", current blockchain size = " +  std::to_string(m_core.get_current_blockchain_size());
      return false;
    }
    block blk = AUTO_VAL_INIT(blk);
    m_core.get_blockchain_storage().get_block_by_height(req.height, blk);
    
    bool response_filled = fill_block_header_response(blk, false, res.block_header);
    if (!response_filled)
    {
      error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
      error_resp.message = "Internal error: can't produce valid response.";
      return false;
    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_alias_details(const COMMAND_RPC_GET_ALIAS_DETAILS::request& req, COMMAND_RPC_GET_ALIAS_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy.";
      return true;
    }
    extra_alias_entry_base aib = AUTO_VAL_INIT(aib);
    if(!validate_alias_name(req.alias))
    {      
      res.status = "Alias have wrong name";
      return true;
    }
    if(!m_core.get_blockchain_storage().get_alias_info(req.alias, aib))
    {
      res.status = API_RETURN_CODE_NOT_FOUND;
      return true;
    }
    res.alias_details.address = currency::get_account_address_as_str(aib.m_address);
    res.alias_details.comment = aib.m_text_comment;
    if (aib.m_view_key.size())
      res.alias_details.tracking_key = string_tools::pod_to_hex(aib.m_view_key.back());

    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------

  bool core_rpc_server::on_get_all_aliases(const COMMAND_RPC_GET_ALL_ALIASES::request& req, COMMAND_RPC_GET_ALL_ALIASES::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy.";
      return false;
    }

    m_core.get_blockchain_storage().enumerate_aliases([&](uint64_t i, const std::string& alias, const extra_alias_entry_base& alias_entry_base) -> bool
    {
      res.aliases.push_back(alias_rpc_details());
      alias_info_to_rpc_alias_info(alias, alias_entry_base, res.aliases.back());
      return true;
    });

    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_aliases(const COMMAND_RPC_GET_ALIASES::request& req, COMMAND_RPC_GET_ALIASES::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    CHECK_RPC_LIMITS(req.count, RPC_LIMIT_COMMAND_RPC_GET_ALIASES_COUNT);
    
    if(!check_core_ready())
    {
      error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
      error_resp.message = "Core is busy.";
      return false;
    }

    m_core.get_blockchain_storage().get_aliases([&res](const std::string& alias, const currency::extra_alias_entry_base& ai){
      res.aliases.push_back(alias_rpc_details());
      alias_info_to_rpc_alias_info(alias, ai, res.aliases.back());
    }, req.offset, req.count);

    res.status = API_RETURN_CODE_OK;
    return true;
  }

  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_alias_reward(const COMMAND_RPC_GET_ALIAS_REWARD::request& req, COMMAND_RPC_GET_ALIAS_REWARD::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    res.reward = m_core.get_blockchain_storage().get_alias_coast(req.alias);
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_est_height_from_date(const COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request& req, COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response& res, connection_context& cntx)
  {
    bool r = m_core.get_blockchain_storage().get_est_height_from_date(req.timestamp, res.h);

    if (r)
      res.status = API_RETURN_CODE_OK;
    else
      res.status = API_RETURN_CODE_NOT_FOUND;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_find_outs_in_recent_blocks(const COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::request& req, COMMAND_RPC_FIND_OUTS_IN_RECENT_BLOCKS::response& resp, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
#define LOCAL_CHECK(cond, msg)         if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;    error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; }
#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_find_outs_in_recent_blocks: " << msg); return false; }

    LOCAL_CHECK(req.address != account_public_address{}, "address is missing");
    LOCAL_CHECK(req.viewkey != null_skey, "viewkey is missing");
    LOCAL_CHECK(req.blocks_limit <= 5, "blocks_limit is out of allowed bounds");

    // verify addess keys
    crypto::point_t view_pk, spend_pk;
    LOCAL_CHECK(view_pk.from_public_key(req.address.view_public_key), "cannon load point from address.view_public_key");
    LOCAL_CHECK(view_pk.is_in_main_subgroup(), "address.view_public_key isn't in main subgroup");
    LOCAL_CHECK(spend_pk.from_public_key(req.address.spend_public_key), "cannon load point from address.spend_public_key");
    LOCAL_CHECK(spend_pk.is_in_main_subgroup(), "address.spend_public_key isn't in main subgroup");

    // verify viewkey
    crypto::scalar_t view_sc = req.viewkey;
    LOCAL_CHECK(view_sc.is_reduced(), "viewkey is invalid");
    LOCAL_CHECK(view_sc * crypto::c_point_G == view_pk, "viewkey doesn't correspond to the given address");

    const blockchain_storage& bcs = m_core.get_blockchain_storage();
    resp.blockchain_top_block_height = bcs.get_top_block_height();
    resp.blocks_limit = req.blocks_limit;

    // get blockchain transactions
    std::unordered_map<uint64_t, std::pair<std::vector<crypto::hash>, std::list<transaction>>> blockchain_txs; // block height -> (vector of tx_ids, list of txs)
    if (req.blocks_limit > 0)
    {
      uint64_t start_offset = resp.blockchain_top_block_height - req.blocks_limit + 1;
      std::list<block> recent_blocks;
      LOCAL_CHECK_INT_ERR(bcs.get_blocks(start_offset, static_cast<size_t>(req.blocks_limit), recent_blocks), "cannot get recent blocks");
      
      std::vector<crypto::hash> blockchain_tx_ids, missed_tx;
      for(auto& b : recent_blocks)
      {
        blockchain_tx_ids.insert(blockchain_tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end());
        uint64_t height = get_block_height(b);
        auto& el = blockchain_txs[height];
        el.first = b.tx_hashes;
        missed_tx.clear();
        LOCAL_CHECK_INT_ERR(bcs.get_transactions(b.tx_hashes, el.second, missed_tx), "bcs.get_transactions failed");
        LOCAL_CHECK_INT_ERR(missed_tx.empty(), "missed_tx is not empty");
        LOCAL_CHECK_INT_ERR(el.first.size() == el.second.size(), "el.first.size() != el.second.size()");
      }
    }

    // get pool transactions
    std::list<transaction> pool_txs;
    LOCAL_CHECK_INT_ERR(m_core.get_tx_pool().get_transactions(pool_txs), "cannot get txs from pool");

    // processor lambda
    auto process_tx = [&](const transaction& tx, const crypto::hash& tx_id, const int64_t height) {
      crypto::key_derivation derivation{};
      LOCAL_CHECK(generate_key_derivation(get_tx_pub_key_from_extra(tx), req.viewkey, derivation), "generate_key_derivation failed");

      for(size_t i = 0, sz = tx.vout.size(); i < sz; ++i)
      {
        const tx_out_v& outv = tx.vout[i];
        if (outv.type() != typeid(tx_out_zarcanum))
          continue;

        uint64_t decoded_amount = 0;
        crypto::public_key decoded_asset_id{};
        crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{};
        if (!is_out_to_acc(req.address, boost::get<tx_out_zarcanum>(outv), derivation, i, decoded_amount, decoded_asset_id, amount_blinding_mask, asset_id_blinding_mask))
          continue;

        auto& el = resp.outputs.emplace_back();
        el.amount           = decoded_amount;
        el.asset_id         = decoded_asset_id;
        el.tx_id            = tx_id;
        el.tx_block_height  = height;
        el.output_tx_index  = i;
      }
      return true;
    };

    // process blockchain txs
    for(auto& [height, pair] : blockchain_txs)
    {
      LOCAL_CHECK(pair.first.size() == pair.second.size(), "container size inconsistency");
      auto tx_it = pair.second.begin();
      for(size_t i = 0, sz = pair.first.size(); i < sz; ++i, ++tx_it)
      {
        const crypto::hash& tx_id = pair.first[i];
        LOCAL_CHECK(process_tx(*tx_it, tx_id, height), "process blockchain tx failed for tx " + crypto::pod_to_hex(tx_id));
      }
    }

    // process pool txs
    for(auto& tx : pool_txs)
    {
      crypto::hash tx_id = get_transaction_hash(tx);
      LOCAL_CHECK(process_tx(tx, tx_id, -1), "process pool tx failed for tx " + crypto::pod_to_hex(tx_id));
    }

    resp.status = resp.outputs.empty() ? API_RETURN_CODE_NOT_FOUND : API_RETURN_CODE_OK;
    return true;

#undef LOCAL_CHECK_INT_ERR
#undef LOCAL_CHECK
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_get_integrated_address(const COMMAND_RPC_GET_INTEGRATED_ADDRESS::request& req, COMMAND_RPC_GET_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    std::string payment_id;
    if (!epee::string_tools::parse_hexstr_to_binbuff(req.payment_id, payment_id))
    {
      error_resp.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
      error_resp.message = std::string("invalid payment id given: \'") + req.payment_id + "\', hex-encoded string was expected";
      return false;
    }

    if (!currency::is_payment_id_size_ok(payment_id))
    {
      error_resp.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
      error_resp.message = std::string("given payment id is too long: \'") + req.payment_id + "\'";
      return false;
    }

    if (payment_id.empty())
    {
      payment_id = std::string(8, ' ');
      crypto::generate_random_bytes(payment_id.size(), &payment_id.front());
    }


    std::string payment_id_from_provided_addr; // won't be used
    account_public_address addr = AUTO_VAL_INIT(addr);
    if (!get_account_address_and_payment_id_from_str(addr, payment_id_from_provided_addr, req.regular_address))
    {
      error_resp.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
      error_resp.message = std::string("invalid address provided: \'") + req.regular_address + "\', Lethean address expected";
      return false;
    }
    if (payment_id_from_provided_addr.size())
    {
      error_resp.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
      error_resp.message = std::string("invalid address provided: \'") + req.regular_address + "\', Lethean address expected be regular and NOT integrated address";
      return false;
    }

    res.integrated_address = currency::get_account_address_and_payment_id_as_str(addr, payment_id);
    res.payment_id = epee::string_tools::buff_to_hex_nodelimer(payment_id);
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_aliases_by_address(const COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& req, COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
  {
    account_public_address addr = AUTO_VAL_INIT(addr);
    if (!get_account_address_from_str(addr, req))
    {
      res.status = API_RETURN_CODE_FAIL;
      return true;
    }
    //res.alias = m_core.get_blockchain_storage().get_alias_by_address(addr);
    COMMAND_RPC_GET_ALIAS_DETAILS::request req2 = AUTO_VAL_INIT(req2);
    COMMAND_RPC_GET_ALIAS_DETAILS::response res2 = AUTO_VAL_INIT(res2);

    std::set<std::string> aliases = m_core.get_blockchain_storage().get_aliases_by_address(addr);

    if (!aliases.size())
    {
      res.status = API_RETURN_CODE_NOT_FOUND;
      return true;
    }

    for (auto it = aliases.begin(); it != aliases.end(); it++)
    {
      req2.alias = *it;
      bool r = this->on_get_alias_details(req2, res2, error_resp, cntx);
      if (!r || res2.status != API_RETURN_CODE_OK)
      {
        res.status = API_RETURN_CODE_FAIL;
        return true;
      }
      res.alias_info_list.push_back(alias_rpc_details());
      res.alias_info_list.back().details = res2.alias_details;
      res.alias_info_list.back().alias = req2.alias;

    }
    res.status = API_RETURN_CODE_OK;
    return true;
  }
  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_reset_transaction_pool(const COMMAND_RPC_RESET_TX_POOL::request& req, COMMAND_RPC_RESET_TX_POOL::response& res, connection_context& cntx)
  {
    m_core.get_tx_pool().purge_transactions();
    res.status = API_RETURN_CODE_OK;
    return true;
  }

  //------------------------------------------------------------------------------------------------------------------------------
  bool core_rpc_server::on_remove_tx_from_pool(const COMMAND_RPC_REMOVE_TX_FROM_POOL::request& req, COMMAND_RPC_REMOVE_TX_FROM_POOL::response& res, connection_context& cntx)
  {
    for (const auto& tx_id_str : req.tx_to_remove)
    {
      crypto::hash tx_id = epee::transform_str_to_t_pod<crypto::hash>(tx_id_str);
      currency::transaction tx; size_t dummy1 = 0; uint64_t dummy2 = 0;
      m_core.get_tx_pool().take_tx(tx_id, tx, dummy1, dummy2);
    }

    res.status = API_RETURN_CODE_OK;
    return true;
  }
}


#undef LOG_DEFAULT_CHANNEL 
#define LOG_DEFAULT_CHANNEL NULL
