Merge remote-tracking branch 'upstream/master' into develop

This commit is contained in:
Ilya Kitaev 2016-04-26 11:08:40 +03:00
commit dce5374660
29 changed files with 1044 additions and 305 deletions

View file

@ -193,9 +193,9 @@ See README.i18n
## Using Tor
While Monero isn't made to integrate with Tor, it can be used wrapped with torsocks, if you add --p2p-bind-ip 127.0.0.1 to the bitmonerod command line. You also want to set DNS requests to go over TCP, so they'll be routed through Tor, by setting DNS_PUBLIC=tcp. You may also disable IGD (UPnP port forwarding negotiation), which is pointless with Tor. Example:
While Monero isn't made to integrate with Tor, it can be used wrapped with torsocks, if you add --p2p-bind-ip 127.0.0.1 to the bitmonerod command line. You also want to set DNS requests to go over TCP, so they'll be routed through Tor, by setting DNS_PUBLIC=tcp. You may also disable IGD (UPnP port forwarding negotiation), which is pointless with Tor. To allow local connections from the wallet, add TORSOCKS_ALLOW_INBOUND=1. Example:
DNS_PUBLIC=tcp torsocks bitmonerod --p2p-bind-ip 127.0.0.1 --no-igd
DNS_PUBLIC=tcp TORSOCKS_ALLOW_INBOUND=1 torsocks bitmonerod --p2p-bind-ip 127.0.0.1 --no-igd
## Using readline

View file

@ -424,6 +424,7 @@ namespace log_space
}
std::cout << buf;
std::cout << std::flush;
#endif
reset_console_color();
return true;

View file

@ -53,11 +53,13 @@ namespace misc_utils
#if defined(_MSC_VER)
return ::GetTickCount64();
#elif defined(WIN32)
# if defined(WIN64)
return GetTickCount64();
# else
return GetTickCount();
# endif
static LARGE_INTEGER pcfreq = {0};
LARGE_INTEGER ticks;
if (!pcfreq.QuadPart)
QueryPerformanceFrequency(&pcfreq);
QueryPerformanceCounter(&ticks);
ticks.QuadPart *= 1000; /* we want msec */
return ticks.QuadPart / pcfreq.QuadPart;
#elif defined(__MACH__)
clock_serv_t cclock;
mach_timespec_t mts;

View file

@ -252,11 +252,24 @@ PRAGMA_WARNING_DISABLE_VS(4355)
template<class t_protocol_handler>
void connection<t_protocol_handler>::save_dbg_log()
{
std::string address, port;
boost::system::error_code e;
boost::asio::ip::tcp::endpoint endpoint = socket_.remote_endpoint(e);
if (e)
{
address = "<not connected>";
port = "<not connected>";
}
else
{
address = endpoint.address().to_string();
port = boost::lexical_cast<std::string>(endpoint.port());
}
_mark_c("net/kind" ,
" connection type " << to_string( m_connection_type ) << " "
<< socket_.local_endpoint().address().to_string() << ":" << socket_.local_endpoint().port()
<< " <--> " << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port()
);
" connection type " << to_string( m_connection_type ) << " "
<< socket_.local_endpoint().address().to_string() << ":" << socket_.local_endpoint().port()
<< " <--> " << address << ":" << port);
}
//---------------------------------------------------------------------------------
template<class t_protocol_handler>

View file

@ -377,7 +377,9 @@ void BlockchainLMDB::do_resize(uint64_t increase_size)
mdb_txn_safe::wait_no_active_txns();
mdb_env_set_mapsize(m_env, new_mapsize);
int result = mdb_env_set_mapsize(m_env, new_mapsize);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to set new mapsize: ", result).c_str()));
LOG_PRINT_GREEN("LMDB Mapsize increased." << " Old: " << mei.me_mapsize / (1024 * 1024) << "MiB" << ", New: " << new_mapsize / (1024 * 1024) << "MiB", LOG_LEVEL_0);

53
src/common/json_util.h Normal file
View file

@ -0,0 +1,53 @@
// Copyright (c) 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.
#pragma once
#define GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, name, type, jtype, mandatory) \
type field_##name; \
bool field_##name##_found = false; \
(void)field_##name##_found; \
do if (json.HasMember(#name)) \
{ \
if (json[#name].Is##jtype()) \
{ \
field_##name = json[#name].Get##jtype(); \
field_##name##_found = true; \
} \
else \
{ \
LOG_ERROR("Field " << #name << " found in JSON, but not " << #jtype); \
return false; \
} \
} \
else if (mandatory) \
{ \
LOG_ERROR("Field " << #name << " not found in JSON"); \
return false; \
} while(0)

View file

@ -413,4 +413,13 @@ std::string get_nix_version_display_string()
}
return false;
}
void set_strict_default_file_permissions(bool strict)
{
#if defined(__MINGW32__) || defined(__MINGW__)
// no clue about the odd one out
#else
mode_t mode = strict ? 077 : 0;
umask(mode);
#endif
}
}

View file

@ -158,4 +158,6 @@ namespace tools
/*! \brief where the installed handler is stored */
static std::function<void(int)> m_handler;
};
void set_strict_default_file_permissions(bool strict);
}

View file

@ -1991,7 +1991,7 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u
// This function overloads its sister function with
// an extra value (hash of highest block that holds an output used as input)
// as a return-by-reference.
bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, bool kept_by_block)
bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
@ -2013,26 +2013,20 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block
#endif
TIME_MEASURE_START(a);
bool res = check_tx_inputs(tx, &max_used_block_height);
bool res = check_tx_inputs(tx, tvc, &max_used_block_height);
TIME_MEASURE_FINISH(a);
crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx);
if(m_show_time_stats)
LOG_PRINT_L0("HASH: " << "+" << " VIN/VOUT: " << tx.vin.size() << "/" << tx.vout.size() << " H: " << max_used_block_height << " chcktx: " << a + m_fake_scan_time);
if (!res)
return false;
// ND: Speedup:
// 1. keep a list of verified transactions, when the Blockchain tries to check a tx again,
// verify against list and skip if already verified to be correct.
m_check_tx_inputs_table.emplace(tx_prefix_hash, std::make_pair(res, max_used_block_height));
CHECK_AND_ASSERT_MES(max_used_block_height < m_db->height(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_db->height());
max_used_block_id = m_db->get_block_hash_from_height(max_used_block_height);
return true;
}
//------------------------------------------------------------------
bool Blockchain::check_tx_outputs(const transaction& tx)
bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
@ -2041,6 +2035,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx)
if (m_hardfork->get_current_version() >= 2) {
for (auto &o: tx.vout) {
if (!is_valid_decomposed_amount(o.amount)) {
tvc.m_invalid_output = true;
return false;
}
}
@ -2066,7 +2061,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const
// check_tx_input() rather than here, and use this function simply
// to iterate the inputs as necessary (splitting the task
// using threads, etc.)
bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height)
bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height)
{
LOG_PRINT_L3("Blockchain::" << __func__);
size_t sig_index = 0;
@ -2075,16 +2070,6 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_bloc
crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx);
auto its = m_check_tx_inputs_table.find(tx_prefix_hash);
if (its != m_check_tx_inputs_table.end())
{
if (!its->second.first)
return false;
if (pmax_used_block_height)
*pmax_used_block_height = its->second.second;
return true;
}
// from hard fork 2, we require mixin at least 2 unless one output cannot mix with 2 others
// if one output cannot mix with 2 others, we accept at most 1 output that can mix
if (m_hardfork->get_current_version() >= 2)
@ -2113,11 +2098,13 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_bloc
if (n_unmixable == 0)
{
LOG_PRINT_L1("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and no unmixable inputs");
tvc.m_low_mixin = true;
return false;
}
if (n_mixable > 1)
{
LOG_PRINT_L1("Tx " << get_transaction_hash(tx) << " has too low mixin (" << mixin << "), and more than one mixable input with unmixable inputs");
tvc.m_low_mixin = true;
return false;
}
}
@ -2156,6 +2143,7 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_bloc
if(ioservice_active) \
{ \
work.reset(); \
while (!ioservice.stopped()) ioservice.poll(); \
threadpool.join_all(); \
ioservice.stop(); \
ioservice_active = false; \
@ -2176,6 +2164,7 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_bloc
if(have_tx_keyimg_as_spent(in_to_key.k_image))
{
LOG_PRINT_L1("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image));
tvc.m_double_spend = true;
return false;
}
@ -2667,7 +2656,8 @@ leave:
#endif
{
// validate that transaction inputs and the keys spending them are correct.
if(!check_tx_inputs(tx))
tx_verification_context tvc;
if(!check_tx_inputs(tx, tvc))
{
LOG_PRINT_L1("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs.");
@ -2962,7 +2952,6 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
TIME_MEASURE_FINISH(t1);
m_blocks_longhash_table.clear();
m_scan_table.clear();
m_check_tx_inputs_table.clear();
m_blocks_txs_check.clear();
m_check_txin_table.clear();
@ -3110,7 +3099,6 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
m_fake_pow_calc_time = 0;
m_scan_table.clear();
m_check_tx_inputs_table.clear();
m_check_txin_table.clear();
TIME_MEASURE_FINISH(prepare);

View file

@ -472,11 +472,12 @@ namespace cryptonote
* @param tx the transaction to validate
* @param pmax_used_block_height return-by-reference block height of most recent input
* @param max_used_block_id return-by-reference block hash of most recent input
* @param tvc returned information about tx verification
* @param kept_by_block whether or not the transaction is from a previously-verified block
*
* @return false if any input is invalid, otherwise true
*/
bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, bool kept_by_block = false);
bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false);
/**
* @brief check that a transaction's outputs conform to current standards
@ -486,10 +487,11 @@ namespace cryptonote
* written out would have only one non-zero digit in base 10).
*
* @param tx the transaction to check the outputs of
* @param tvc returned info about tx verification
*
* @return false if any outputs do not conform, otherwise true
*/
bool check_tx_outputs(const transaction& tx);
bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc);
/**
* @brief gets the blocksize limit based on recent blocks
@ -788,7 +790,6 @@ namespace cryptonote
// metadata containers
std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, std::vector<output_data_t>>> m_scan_table;
std::unordered_map<crypto::hash, std::pair<bool, uint64_t>> m_check_tx_inputs_table;
std::unordered_map<crypto::hash, crypto::hash> m_blocks_longhash_table;
std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, bool>> m_check_txin_table;
@ -883,11 +884,12 @@ namespace cryptonote
* transaction.
*
* @param tx the transaction to validate
* @param tvc returned information about tx verification
* @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set
*
* @return false if any validation step fails, otherwise true
*/
bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL);
bool check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL);
/**
* @brief performs a blockchain reorganization according to the longest chain rule

View file

@ -489,6 +489,7 @@ namespace cryptonote
{
LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected");
tvc.m_verifivation_failed = true;
tvc.m_too_big = true;
return false;
}

View file

@ -145,10 +145,18 @@ namespace cryptonote
[&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); });
CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero");
while (max_outs < out_amounts.size())
if (height == 0)
{
out_amounts[out_amounts.size() - 2] += out_amounts.back();
out_amounts.resize(out_amounts.size() - 1);
// the genesis block was not decomposed, for unknown reasons
while (max_outs < out_amounts.size())
{
out_amounts[out_amounts.size() - 2] += out_amounts.back();
out_amounts.resize(out_amounts.size() - 1);
}
}
else
{
CHECK_AND_ASSERT_MES(max_outs >= out_amounts.size(), false, "max_out exceeded");
}
uint64_t summary_amounts = 0;

View file

@ -54,6 +54,11 @@ namespace cryptonote
{
namespace
{
//TODO: constants such as these should at least be in the header,
// but probably somewhere more accessible to the rest of the
// codebase. As it stands, it is at best nontrivial to test
// whether or not changing these parameters (or adding new)
// will work correctly.
size_t const TRANSACTION_SIZE_LIMIT_V1 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
size_t const TRANSACTION_SIZE_LIMIT_V2 = (((CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
time_t const MIN_RELAY_TIME = (60 * 5); // only start re-relaying transactions after that many seconds
@ -97,6 +102,7 @@ namespace cryptonote
if(!check_inputs_types_supported(tx))
{
tvc.m_verifivation_failed = true;
tvc.m_invalid_input = true;
return false;
}
@ -113,17 +119,20 @@ namespace cryptonote
{
LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount));
tvc.m_verifivation_failed = true;
tvc.m_overspend = true;
return false;
}
// fee per kilobyte, size rounded up.
uint64_t fee = inputs_amount - outputs_amount;
uint64_t needed_fee = blob_size / 1024;
needed_fee += (blob_size % 1024) ? 1 : 0;
needed_fee *= FEE_PER_KB;
if (!kept_by_block && fee < needed_fee /*&& fee < MINING_ALLOWED_LEGACY_FEE*/)
if (!kept_by_block && fee < needed_fee)
{
LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee));
tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true;
return false;
}
@ -132,40 +141,46 @@ namespace cryptonote
{
LOG_PRINT_L1("transaction is too big: " << blob_size << " bytes, maximum size: " << tx_size_limit);
tvc.m_verifivation_failed = true;
tvc.m_too_big = true;
return false;
}
//check key images for transaction if it is not kept by block
// if the transaction came from a block popped from the chain,
// don't check if we have its key images as spent.
// TODO: Investigate why not?
if(!kept_by_block)
{
if(have_tx_keyimges_as_spent(tx))
{
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
tvc.m_verifivation_failed = true;
tvc.m_double_spend = true;
return false;
}
}
if (!m_blockchain.check_tx_outputs(tx))
if (!m_blockchain.check_tx_outputs(tx, tvc))
{
LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid outout");
tvc.m_verifivation_failed = true;
tvc.m_invalid_output = true;
return false;
}
crypto::hash max_used_block_id = null_hash;
uint64_t max_used_block_height = 0;
#if BLOCKCHAIN_DB == DB_LMDB
bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, kept_by_block);
bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, tvc, kept_by_block);
#else
bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id);
#endif
CRITICAL_REGION_LOCAL(m_transactions_lock);
if(!ch_inp_res)
{
// if the transaction was valid before (kept_by_block), then it
// may become valid again, so ignore the failed inputs check.
if(kept_by_block)
{
//anyway add this transaction to pool, because it related to block
auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details()));
CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool");
txd_p.first->second.blob_size = blob_size;
@ -207,13 +222,14 @@ namespace cryptonote
tvc.m_should_be_relayed = true;
}
// assume failure during verification steps until success is certain
tvc.m_verifivation_failed = true;
//update image_keys container, here should everything goes ok.
BOOST_FOREACH(const auto& in, tx.vin)
{
CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false);
std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image];
CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: keeped_by_block=" << kept_by_block
CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block
<< ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL
<< "tx_id=" << id );
auto ins_res = kei_image_set.insert(id);
@ -223,7 +239,7 @@ namespace cryptonote
tvc.m_verifivation_failed = false;
m_txs_by_fee.emplace((double)blob_size / fee, id);
//succeed
return true;
}
//---------------------------------------------------------------------------------
@ -235,6 +251,9 @@ namespace cryptonote
return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, version);
}
//---------------------------------------------------------------------------------
//FIXME: Can return early before removal of all of the key images.
// At the least, need to make sure that a false return here
// is treated properly. Should probably not return early, however.
bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -301,7 +320,7 @@ namespace cryptonote
);
}
//---------------------------------------------------------------------------------
//proper tx_pool handling courtesy of CryptoZoidberg and Boolberry
//TODO: investigate whether boolean return is appropriate
bool tx_memory_pool::remove_stuck_transactions()
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -332,6 +351,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
bool tx_memory_pool::get_relayable_transactions(std::list<std::pair<crypto::hash, cryptonote::transaction>> &txs) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -380,6 +400,7 @@ namespace cryptonote
txs.push_back(tx_vt.second.tx);
}
//------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -480,7 +501,8 @@ namespace cryptonote
if(txd.last_failed_id != null_hash && m_blockchain.get_current_blockchain_height() > txd.last_failed_height && txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height))
return false;//we already sure that this tx is broken for this height
if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id))
tx_verification_context tvc;
if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id, tvc))
{
txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
@ -490,13 +512,18 @@ namespace cryptonote
{
if(txd.max_used_block_height >= m_blockchain.get_current_blockchain_height())
return false;
if(m_blockchain.get_block_id_by_height(txd.max_used_block_height) != txd.max_used_block_id)
if(true)
{
//if we already failed on this height and id, skip actual ring signature check
if(txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height))
return false;
//check ring signature again, it is possible (with very small chance) that this transaction become again valid
#if BLOCKCHAIN_DB == DB_LMDB
tx_verification_context tvc;
if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id, tvc))
#else
if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id))
#endif
{
txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
@ -556,6 +583,7 @@ namespace cryptonote
return ss.str();
}
//---------------------------------------------------------------------------------
//TODO: investigate whether boolean return is appropriate
bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee)
{
// Warning: This function takes already_generated_
@ -646,6 +674,7 @@ namespace cryptonote
return n_removed;
}
//---------------------------------------------------------------------------------
//TODO: investigate whether only ever returning true is correct
bool tx_memory_pool::init(const std::string& config_folder)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -679,6 +708,7 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
//TODO: investigate whether only ever returning true is correct
bool tx_memory_pool::deinit()
{
if (m_config_folder.empty())

View file

@ -57,7 +57,9 @@ namespace cryptonote
/* */
/************************************************************************/
//! pair of <transaction fee, transaction hash> for organization
typedef std::pair<double, crypto::hash> tx_by_fee_entry;
class txCompare
{
public:
@ -71,47 +73,256 @@ namespace cryptonote
}
};
//! container for sorting transactions by fee per unit size
typedef std::set<tx_by_fee_entry, txCompare> sorted_tx_container;
/**
* @brief Transaction pool, handles transactions which are not part of a block
*
* This class handles all transactions which have been received, but not as
* part of a block.
*
* This handling includes:
* storing the transactions
* organizing the transactions by fee per size
* taking/giving transactions to and from various other components
* saving the transactions to disk on shutdown
* helping create a new block template by choosing transactions for it
*
*/
class tx_memory_pool: boost::noncopyable
{
public:
#if BLOCKCHAIN_DB == DB_LMDB
/**
* @brief Constructor
*
* @param bchs a Blockchain class instance, for getting chain info
*/
tx_memory_pool(Blockchain& bchs);
#else
tx_memory_pool(blockchain_storage& bchs);
#endif
bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block, bool relayed, uint8_t version);
bool add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, uint8_t version);
//gets tx and remove it from pool
/**
* @copydoc add_tx(const transaction&, tx_verification_context&, bool, bool, uint8_t)
*
* @param id the transaction's hash
* @param blob_size the transaction's size
*/
bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version);
/**
* @brief add a transaction to the transaction pool
*
* Most likely the transaction will come from the network, but it is
* also possible for transactions to come from popped blocks during
* a reorg, or from local clients creating a transaction and
* submitting it to the network
*
* @param tx the transaction to be added
* @param tvc return-by-reference status about the transaction verification
* @param kept_by_block has this transaction been in a block?
* @param relayed was this transaction from the network or a local client?
* @param version the version used to create the transaction
*
* @return true if the transaction passes validations, otherwise false
*/
bool add_tx(const transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version);
/**
* @brief takes a transaction with the given hash from the pool
*
* @param id the hash of the transaction
* @param tx return-by-reference the transaction taken
* @param blob_size return-by-reference the transaction's size
* @param fee the transaction fee
* @param relayed return-by-reference was transaction relayed to us by the network?
*
* @return true unless the transaction cannot be found in the pool
*/
bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed);
/**
* @brief checks if the pool has a transaction with the given hash
*
* @param id the hash to look for
*
* @return true if the transaction is in the pool, otherwise false
*/
bool have_tx(const crypto::hash &id) const;
/**
* @brief action to take when notified of a block added to the blockchain
*
* Currently does nothing
*
* @param new_block_height the height of the blockchain after the change
* @param top_block_id the hash of the new top block
*
* @return true
*/
bool on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id);
/**
* @brief action to take when notified of a block removed from the blockchain
*
* Currently does nothing
*
* @param new_block_height the height of the blockchain after the change
* @param top_block_id the hash of the new top block
*
* @return true
*/
bool on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id);
/**
* @brief action to take periodically
*
* Currently checks transaction pool for stale ("stuck") transactions
*/
void on_idle();
/**
* @brief locks the transaction pool
*/
void lock() const;
/**
* @brief unlocks the transaction pool
*/
void unlock() const;
// load/store operations
/**
* @brief loads pool state (if any) from disk, and initializes pool
*
* @param config_folder folder name where pool state will be
*
* @return true
*/
bool init(const std::string& config_folder);
/**
* @brief attempts to save the transaction pool state to disk
*
* Currently fails (returns false) if the data directory from init()
* does not exist and cannot be created, but returns true even if
* saving to disk is unsuccessful.
*
* @return true in most cases (see above)
*/
bool deinit();
/**
* @brief Chooses transactions for a block to include
*
* @param bl return-by-reference the block to fill in with transactions
* @param median_size the current median block size
* @param already_generated_coins the current total number of coins "minted"
* @param total_size return-by-reference the total size of the new block
* @param fee return-by-reference the total of fees from the included transactions
*
* @return true
*/
bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee);
/**
* @brief get a list of all transactions in the pool
*
* @param txs return-by-reference the list of transactions
*/
void get_transactions(std::list<transaction>& txs) const;
/**
* @brief get information about all transactions and key images in the pool
*
* see documentation on tx_info and spent_key_image_info for more details
*
* @param tx_infos return-by-reference the transactions' information
* @param key_image_infos return-by-reference the spent key images' information
*
* @return true
*/
bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const;
/**
* @brief get a specific transaction from the pool
*
* @param h the hash of the transaction to get
* @param tx return-by-reference the transaction requested
*
* @return true if the transaction is found, otherwise false
*/
bool get_transaction(const crypto::hash& h, transaction& tx) const;
/**
* @brief get a list of all relayable transactions and their hashes
*
* "relayable" in this case means:
* nonzero fee
* hasn't been relayed too recently
* isn't old enough that relaying it is considered harmful
*
* @param txs return-by-reference the transactions and their hashes
*
* @return true
*/
bool get_relayable_transactions(std::list<std::pair<crypto::hash, cryptonote::transaction>>& txs) const;
/**
* @brief tell the pool that certain transactions were just relayed
*
* @param txs the list of transactions (and their hashes)
*/
void set_relayed(const std::list<std::pair<crypto::hash, cryptonote::transaction>>& txs);
/**
* @brief get the total number of transactions in the pool
*
* @return the number of transactions in the pool
*/
size_t get_transactions_count() const;
/**
* @brief get a string containing human-readable pool information
*
* @param short_format whether to use a shortened format for the info
*
* @return the string
*/
std::string print_pool(bool short_format) const;
/**
* @brief remove transactions from the pool which are no longer valid
*
* With new versions of the currency, what conditions render a transaction
* invalid may change. This function clears those which were received
* before a version change and no longer conform to requirements.
*
* @param version the version the transactions must conform to
*
* @return the number of transactions removed
*/
size_t validate(uint8_t version);
/*bool flush_pool(const std::strig& folder);
bool inflate_pool(const std::strig& folder);*/
#define CURRENT_MEMPOOL_ARCHIVE_VER 11
#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 11
/**
* @brief serialize the transaction pool to/from disk
*
* If the archive version passed is older than the version compiled
* in, this function does nothing, as it cannot deserialize after a
* format change.
*
* @tparam archive_t the archive class
* @param a the archive to serialize to/from
* @param version the archive version
*/
template<class archive_t>
void serialize(archive_t & a, const unsigned int version)
{
@ -123,97 +334,169 @@ namespace cryptonote
a & m_timed_out_transactions;
}
/**
* @brief information about a single transaction
*/
struct tx_details
{
transaction tx;
size_t blob_size;
uint64_t fee;
crypto::hash max_used_block_id;
uint64_t max_used_block_height;
bool kept_by_block;
//
uint64_t last_failed_height;
crypto::hash last_failed_id;
time_t receive_time;
transaction tx; //!< the transaction
size_t blob_size; //!< the transaction's size
uint64_t fee; //!< the transaction's fee amount
crypto::hash max_used_block_id; //!< the hash of the highest block referenced by an input
uint64_t max_used_block_height; //!< the height of the highest block referenced by an input
time_t last_relayed_time;
bool relayed;
//! whether or not the transaction has been in a block before
/*! if the transaction was returned to the pool from the blockchain
* due to a reorg, then this will be true
*/
bool kept_by_block;
//! the highest block the transaction referenced when last checking it failed
/*! if verifying a transaction's inputs fails, it's possible this is due
* to a reorg since it was created (if it used recently created outputs
* as inputs).
*/
uint64_t last_failed_height;
//! the hash of the highest block the transaction referenced when last checking it failed
/*! if verifying a transaction's inputs fails, it's possible this is due
* to a reorg since it was created (if it used recently created outputs
* as inputs).
*/
crypto::hash last_failed_id;
time_t receive_time; //!< the time when the transaction entered the pool
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
bool relayed; //!< whether or not the transaction has been relayed to the network
};
private:
/**
* @brief remove old transactions from the pool
*
* After a certain time, it is assumed that a transaction which has not
* yet been mined will likely not be mined. These transactions are removed
* from the pool to avoid buildup.
*
* @return true
*/
bool remove_stuck_transactions();
/**
* @brief check if a transaction in the pool has a given spent key image
*
* @param key_im the spent key image to look for
*
* @return true if the spent key image is present, otherwise false
*/
bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const;
/**
* @brief check if any spent key image in a transaction is in the pool
*
* Checks if any of the spent key images in a given transaction are present
* in any of the transactions in the transaction pool.
*
* @note see tx_pool::have_tx_keyimg_as_spent
*
* @param tx the transaction to check spent key images of
*
* @return true if any spent key images are present in the pool, otherwise false
*/
bool have_tx_keyimges_as_spent(const transaction& tx) const;
/**
* @brief forget a transaction's spent key images
*
* Spent key images are stored separately from transactions for
* convenience/speed, so this is part of the process of removing
* a transaction from the pool.
*
* @param tx the transaction
*
* @return false if any key images to be removed cannot be found, otherwise true
*/
bool remove_transaction_keyimages(const transaction& tx);
/**
* @brief check if any of a transaction's spent key images are present in a given set
*
* @param kic the set of key images to check against
* @param tx the transaction to check
*
* @return true if any key images present in the set, otherwise false
*/
static bool have_key_images(const std::unordered_set<crypto::key_image>& kic, const transaction& tx);
/**
* @brief append the key images from a transaction to the given set
*
* @param kic the set of key images to append to
* @param tx the transaction
*
* @return false if any append fails, otherwise true
*/
static bool append_key_images(std::unordered_set<crypto::key_image>& kic, const transaction& tx);
/**
* @brief check if a transaction is a valid candidate for inclusion in a block
*
* @param txd the transaction to check (and info about it)
*
* @return true if the transaction is good to go, otherwise false
*/
bool is_transaction_ready_to_go(tx_details& txd) const;
//! map transactions (and related info) by their hashes
typedef std::unordered_map<crypto::hash, tx_details > transactions_container;
//TODO: confirm the below comments and investigate whether or not this
// is the desired behavior
//! map key images to transactions which spent them
/*! this seems odd, but it seems that multiple transactions can exist
* in the pool which both have the same spent key. This would happen
* in the event of a reorg where someone creates a new/different
* transaction on the assumption that the original will not be in a
* block again.
*/
typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash> > key_images_container;
mutable epee::critical_section m_transactions_lock;
transactions_container m_transactions;
key_images_container m_spent_key_images;
mutable epee::critical_section m_transactions_lock; //!< lock for the pool
transactions_container m_transactions; //!< container for transactions in the pool
//! container for spent key images from the transactions in the pool
key_images_container m_spent_key_images;
//TODO: this time should be a named constant somewhere, not hard-coded
//! interval on which to check for stale/"stuck" transactions
epee::math_helper::once_a_time_seconds<30> m_remove_stuck_tx_interval;
//TODO: add fee_per_kb element to type tx_details and replace this
//functionality by just making m_transactions a std::set
sorted_tx_container m_txs_by_fee;
//TODO: look into doing this better
sorted_tx_container m_txs_by_fee; //!< container for transactions organized by fee per size
/**
* @brief get an iterator to a transaction in the sorted container
*
* @param id the hash of the transaction to look for
*
* @return an iterator, possibly to the end of the container if not found
*/
sorted_tx_container::iterator find_tx_in_sorted_container(const crypto::hash& id) const;
//! transactions which are unlikely to be included in blocks
/*! These transactions are kept in RAM in case they *are* included
* in a block eventually, but this container is not saved to disk.
*/
std::unordered_set<crypto::hash> m_timed_out_transactions;
//transactions_container m_alternative_transactions;
std::string m_config_folder;
std::string m_config_folder; //!< the folder to save state to
#if BLOCKCHAIN_DB == DB_LMDB
Blockchain& m_blockchain;
Blockchain& m_blockchain; //!< reference to the Blockchain object
#else
blockchain_storage& m_blockchain;
#endif
/************************************************************************/
/* */
/************************************************************************/
/*class inputs_visitor: public boost::static_visitor<bool>
{
key_images_container& m_spent_keys;
public:
inputs_visitor(key_images_container& spent_keys): m_spent_keys(spent_keys)
{}
bool operator()(const txin_to_key& tx) const
{
auto pr = m_spent_keys.insert(tx.k_image);
CHECK_AND_ASSERT_MES(pr.second, false, "Tried to insert transaction with input seems already spent, input: " << epee::string_tools::pod_to_hex(tx.k_image));
return true;
}
bool operator()(const txin_gen& tx) const
{
CHECK_AND_ASSERT_MES(false, false, "coinbase transaction in memory pool");
return false;
}
bool operator()(const txin_to_script& tx) const {return false;}
bool operator()(const txin_to_scripthash& tx) const {return false;}
}; */
/************************************************************************/
/* */
/************************************************************************/
class amount_visitor: public boost::static_visitor<uint64_t>
{
public:
uint64_t operator()(const txin_to_key& tx) const
{
return tx.amount;
}
uint64_t operator()(const txin_gen& tx) const
{
CHECK_AND_ASSERT_MES(false, false, "coinbase transaction in memory pool");
return 0;
}
uint64_t operator()(const txin_to_script& tx) const {return 0;}
uint64_t operator()(const txin_to_scripthash& tx) const {return 0;}
};
#if BLOCKCHAIN_DB == DB_LMDB
#else
@ -248,6 +531,7 @@ namespace boost
}
}
BOOST_CLASS_VERSION(cryptonote::tx_memory_pool, CURRENT_MEMPOOL_ARCHIVE_VER)
BOOST_CLASS_VERSION(cryptonote::tx_memory_pool::tx_details, CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER)

View file

@ -40,6 +40,13 @@ namespace cryptonote
bool m_verifivation_failed; //bad tx, should drop connection
bool m_verifivation_impossible; //the transaction is related with an alternative blockchain
bool m_added_to_pool;
bool m_low_mixin;
bool m_double_spend;
bool m_invalid_input;
bool m_invalid_output;
bool m_too_big;
bool m_overspend;
bool m_fee_too_low;
};
struct block_verification_context

View file

@ -70,6 +70,23 @@ namespace {
<< "difficulty: " << boost::lexical_cast<std::string>(header.difficulty) << std::endl
<< "reward: " << boost::lexical_cast<std::string>(header.reward);
}
std::string get_human_time_ago(time_t t, time_t now)
{
if (t == now)
return "now";
time_t dt = t > now ? t - now : now - t;
std::string s;
if (dt < 90)
s = boost::lexical_cast<std::string>(dt) + " seconds";
else if (dt < 90 * 60)
s = boost::lexical_cast<std::string>(dt/60) + " minutes";
else if (dt < 36 * 3600)
s = boost::lexical_cast<std::string>(dt/3600) + " hours";
else
s = boost::lexical_cast<std::string>(dt/(3600*24)) + " days";
return s + " " + (t > now ? "in the future" : "ago");
}
}
t_rpc_command_executor::t_rpc_command_executor(
@ -575,16 +592,26 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) {
}
}
if (1 == res.txs_as_hex.size())
if (1 == res.txs.size() || 1 == res.txs_as_hex.size())
{
if (1 == res.txs.size())
{
// only available for new style answers
if (res.txs.front().in_pool)
tools::success_msg_writer() << "Found in pool";
else
tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height;
}
// first as hex
tools::success_msg_writer() << res.txs_as_hex.front();
const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front();
tools::success_msg_writer() << as_hex;
// then as json
crypto::hash tx_hash, tx_prefix_hash;
cryptonote::transaction tx;
cryptonote::blobdata blob;
if (!string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), blob))
if (!string_tools::parse_hexstr_to_binbuff(as_hex, blob))
{
tools::fail_msg_writer() << "Failed to parse tx";
}
@ -669,6 +696,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
}
if (! res.transactions.empty())
{
const time_t now = time(NULL);
tools::msg_writer() << "Transactions: ";
for (auto & tx_info : res.transactions)
{
@ -676,7 +704,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
<< tx_info.tx_json << std::endl
<< "blob_size: " << tx_info.blob_size << std::endl
<< "fee: " << cryptonote::print_money(tx_info.fee) << std::endl
<< "receive_time: " << tx_info.receive_time << std::endl
<< "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
@ -747,17 +775,21 @@ bool t_rpc_command_executor::print_transaction_pool_short() {
{
tools::msg_writer() << "Pool is empty" << std::endl;
}
for (auto & tx_info : res.transactions)
else
{
tools::msg_writer() << "id: " << tx_info.id_hash << std::endl
<< "blob_size: " << tx_info.blob_size << std::endl
<< "fee: " << cryptonote::print_money(tx_info.fee) << std::endl
<< "receive_time: " << tx_info.receive_time << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
<< "last_failed_id: " << tx_info.last_failed_id_hash << std::endl;
const time_t now = time(NULL);
for (auto & tx_info : res.transactions)
{
tools::msg_writer() << "id: " << tx_info.id_hash << std::endl
<< "blob_size: " << tx_info.blob_size << std::endl
<< "fee: " << cryptonote::print_money(tx_info.fee) << std::endl
<< "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")" << std::endl
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
<< "last_failed_id: " << tx_info.last_failed_id_hash << std::endl;
}
}
return true;

View file

@ -240,6 +240,7 @@ namespace cryptonote
// try the pool for any missing txes
size_t found_in_pool = 0;
std::unordered_set<crypto::hash> pool_tx_hashes;
if (!missed_txs.empty())
{
std::list<transaction> pool_txs;
@ -248,9 +249,11 @@ namespace cryptonote
{
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));
crypto::hash tx_hash = get_transaction_hash(*i);
std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), tx_hash);
if (mi != missed_txs.end())
{
pool_tx_hashes.insert(tx_hash);
missed_txs.erase(mi);
txs.push_back(*i);
++found_in_pool;
@ -260,12 +263,33 @@ namespace cryptonote
LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool");
}
std::list<std::string>::const_iterator txhi = req.txs_hashes.begin();
std::vector<crypto::hash>::const_iterator vhi = vh.begin();
BOOST_FOREACH(auto& tx, txs)
{
res.txs.push_back(COMMAND_RPC_GET_TRANSACTIONS::entry());
COMMAND_RPC_GET_TRANSACTIONS::entry &e = res.txs.back();
crypto::hash tx_hash = *vhi++;
e.tx_hash = *txhi++;
blobdata blob = t_serializable_object_to_blob(tx);
res.txs_as_hex.push_back(string_tools::buff_to_hex_nodelimer(blob));
e.as_hex = string_tools::buff_to_hex_nodelimer(blob);
if (req.decode_as_json)
res.txs_as_json.push_back(obj_to_json_str(tx));
e.as_json = obj_to_json_str(tx);
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool)
{
e.block_height = std::numeric_limits<uint64_t>::max();
}
else
{
e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash);
}
// fill up old style responses too, in case an old wallet asks
res.txs_as_hex.push_back(e.as_hex);
if (req.decode_as_json)
res.txs_as_json.push_back(e.as_json);
}
BOOST_FOREACH(const auto& miss_tx, missed_txs)
@ -273,7 +297,7 @@ namespace cryptonote
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");
LOG_PRINT_L2(res.txs.size() << " transactions found, " << res.missed_tx.size() << " not found");
res.status = CORE_RPC_STATUS_OK;
return true;
}
@ -355,24 +379,40 @@ namespace cryptonote
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))
if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false) || tvc.m_verifivation_failed)
{
LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx");
if (tvc.m_verifivation_failed)
{
LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed");
}
else
{
LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx");
}
res.status = "Failed";
if ((res.low_mixin = tvc.m_low_mixin))
res.reason = "mixin too low";
if ((res.double_spend = tvc.m_double_spend))
res.reason = "double spend";
if ((res.invalid_input = tvc.m_invalid_input))
res.reason = "invalid input";
if ((res.invalid_output = tvc.m_invalid_output))
res.reason = "invalid output";
if ((res.too_big = tvc.m_too_big))
res.reason = "too big";
if ((res.overspend = tvc.m_overspend))
res.reason = "overspend";
if ((res.fee_too_low = tvc.m_fee_too_low))
res.reason = "fee too low";
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)
if(!tvc.m_should_be_relayed || req.do_not_relay)
{
LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed");
res.status = "Not relayed";
res.reason = "Not relayed";
res.not_relayed = true;
res.status = CORE_RPC_STATUS_OK;
return true;
}
@ -627,8 +667,10 @@ namespace cryptonote
LOG_ERROR("Failed to calculate offset for ");
return false;
}
blobdata hashing_blob = get_block_hashing_blob(b);
res.prev_hash = string_tools::pod_to_hex(b.prev_id);
res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob);
res.blockhashing_blob = string_tools::buff_to_hex_nodelimer(hashing_blob);
res.status = CORE_RPC_STATUS_OK;
return true;
}

View file

@ -103,18 +103,41 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
struct entry
{
std::string tx_hash;
std::string as_hex;
std::string as_json;
bool in_pool;
uint64_t block_height;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(as_hex)
KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool)
KV_SERIALIZE(block_height)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::list<std::string> txs_as_hex; //transactions blobs as hex
// older compatibility stuff
std::list<std::string> txs_as_hex; //transactions blobs as hex (old compat)
std::list<std::string> txs_as_json; //transactions decoded as json (old compat)
// in both old and new
std::list<std::string> missed_tx; //not found transactions
std::list<std::string> txs_as_json; //transactions decoded as json
// new style
std::vector<entry> txs;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txs_as_hex)
KV_SERIALIZE(missed_tx)
KV_SERIALIZE(txs_as_json)
KV_SERIALIZE(txs)
KV_SERIALIZE(missed_tx)
KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP()
};
@ -221,12 +244,14 @@ namespace cryptonote
struct request
{
std::string tx_as_hex;
bool do_not_relay;
request() {}
explicit request(const transaction &);
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_as_hex)
KV_SERIALIZE(do_not_relay)
END_KV_SERIALIZE_MAP()
};
@ -234,9 +259,27 @@ namespace cryptonote
struct response
{
std::string status;
std::string reason;
bool not_relayed;
bool low_mixin;
bool double_spend;
bool invalid_input;
bool invalid_output;
bool too_big;
bool overspend;
bool fee_too_low;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(reason)
KV_SERIALIZE(not_relayed)
KV_SERIALIZE(low_mixin)
KV_SERIALIZE(double_spend)
KV_SERIALIZE(invalid_input)
KV_SERIALIZE(invalid_output)
KV_SERIALIZE(too_big)
KV_SERIALIZE(overspend)
KV_SERIALIZE(fee_too_low)
END_KV_SERIALIZE_MAP()
};
};
@ -427,6 +470,7 @@ namespace cryptonote
uint64_t reserved_offset;
std::string prev_hash;
blobdata blocktemplate_blob;
blobdata blockhashing_blob;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
@ -435,6 +479,7 @@ namespace cryptonote
KV_SERIALIZE(reserved_offset)
KV_SERIALIZE(prev_hash)
KV_SERIALIZE(blocktemplate_blob)
KV_SERIALIZE(blockhashing_blob)
KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP()
};

View file

@ -58,6 +58,7 @@
#include "crypto/crypto.h" // for crypto::secret_key definition
#include "mnemonics/electrum-words.h"
#include "rapidjson/document.h"
#include "common/json_util.h"
#include <stdexcept>
#if defined(WIN32)
@ -257,6 +258,8 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st
if (m_wallet->get_seed_language().empty())
{
std::string mnemonic_language = get_mnemonic_language();
if (mnemonic_language.empty())
return true;
m_wallet->set_seed_language(mnemonic_language);
}
@ -304,6 +307,8 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s
}
std::string mnemonic_language = get_mnemonic_language();
if (mnemonic_language.empty())
return true;
m_wallet->set_seed_language(mnemonic_language);
m_wallet->rewrite(m_wallet_file, pwd_container.password());
return true;
@ -698,6 +703,10 @@ bool simple_wallet::ask_wallet_create_if_needed()
tr("Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n"
"Wallet file name: ")
);
if (std::cin.eof())
{
return false;
}
valid_path = tools::wallet2::wallet_valid_path_format(wallet_path);
if (!valid_path)
{
@ -820,6 +829,8 @@ static bool get_password(const boost::program_options::variables_map& vm, bool a
//----------------------------------------------------------------------------------------------------
bool simple_wallet::generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password)
{
bool testnet = command_line::get_arg(vm, arg_testnet);
std::string buf;
bool r = epee::file_io_utils::load_file_to_string(m_generate_from_json, buf);
if (!r) {
@ -833,84 +844,123 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
return false;
}
if (!json.HasMember("version")) {
fail_msg_writer() << tr("Version not found in JSON");
return false;
}
unsigned int version = json["version"].GetUint();
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true);
const int current_version = 1;
if (version > current_version) {
fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % version % current_version;
if (field_version > current_version) {
fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % field_version % current_version;
return false;
}
if (!json.HasMember("filename")) {
fail_msg_writer() << tr("Filename not found in JSON");
return false;
}
std::string filename = json["filename"].GetString();
bool recover = false;
uint64_t scan_from_height = 0;
if (json.HasMember("scan_from_height")) {
scan_from_height = json["scan_from_height"].GetUint64();
recover = true;
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true);
password = "";
if (json.HasMember("password")) {
password = json["password"].GetString();
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false);
bool recover = field_scan_from_height_found;
std::string viewkey_string("");
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false);
password = field_password;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false);
crypto::secret_key viewkey;
if (json.HasMember("viewkey")) {
viewkey_string = json["viewkey"].GetString();
if (field_viewkey_found)
{
cryptonote::blobdata viewkey_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data))
if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data))
{
fail_msg_writer() << tr("failed to parse view key secret key");
return false;
}
viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data());
crypto::public_key pkey;
if (!crypto::secret_key_to_public_key(viewkey, pkey)) {
fail_msg_writer() << tr("failed to verify view key secret key");
return false;
}
}
std::string spendkey_string("");
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false);
crypto::secret_key spendkey;
if (json.HasMember("spendkey")) {
spendkey_string = json["spendkey"].GetString();
if (field_spendkey_found)
{
cryptonote::blobdata spendkey_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data))
if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data))
{
fail_msg_writer() << tr("failed to parse spend key secret key");
return false;
}
spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data());
crypto::public_key pkey;
if (!crypto::secret_key_to_public_key(spendkey, pkey)) {
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
}
std::string seed("");
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false);
std::string old_language;
if (json.HasMember("seed")) {
seed = json["seed"].GetString();
if (!crypto::ElectrumWords::words_to_bytes(seed, m_recovery_key, old_language))
if (field_seed_found)
{
if (!crypto::ElectrumWords::words_to_bytes(field_seed, m_recovery_key, old_language))
{
fail_msg_writer() << tr("Electrum-style word list failed verification");
return false;
}
m_electrum_seed = seed;
m_electrum_seed = field_seed;
m_restore_deterministic_wallet = true;
}
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false);
// compatibility checks
if (seed.empty() && viewkey_string.empty()) {
if (!field_seed_found && !field_viewkey_found)
{
fail_msg_writer() << tr("At least one of Electrum-style word list and private view key must be specified");
return false;
}
if (!seed.empty() && (!viewkey_string.empty() || !spendkey_string.empty())) {
if (field_seed_found && (field_viewkey_found || field_spendkey_found))
{
fail_msg_writer() << tr("Both Electrum-style word list and private key(s) specified");
return false;
}
m_wallet_file = filename;
// if an address was given, we check keys against it, and deduce the spend
// public key if it was not given
if (field_address_found)
{
cryptonote::account_public_address address;
bool has_payment_id;
crypto::hash8 new_payment_id;
if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address))
{
fail_msg_writer() << tr("invalid address");
return false;
}
if (field_viewkey_found)
{
crypto::public_key pkey;
if (!crypto::secret_key_to_public_key(viewkey, pkey)) {
fail_msg_writer() << tr("failed to verify view key secret key");
return false;
}
if (address.m_view_public_key != pkey) {
fail_msg_writer() << tr("view key does not match standard address");
return false;
}
}
if (field_spendkey_found)
{
crypto::public_key pkey;
if (!crypto::secret_key_to_public_key(spendkey, pkey)) {
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
if (address.m_spend_public_key != pkey) {
fail_msg_writer() << tr("spend key does not match standard address");
return false;
}
}
}
m_wallet_file = field_filename;
bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) ||
crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed));
@ -919,13 +969,12 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
return false;
}
bool testnet = command_line::get_arg(vm, arg_testnet);
m_wallet.reset(new tools::wallet2(testnet));
m_wallet->callback(this);
try
{
if (!seed.empty())
if (!field_seed.empty())
{
m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false);
}
@ -936,17 +985,27 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
fail_msg_writer() << tr("failed to verify view key secret key");
return false;
}
if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) {
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
if (spendkey_string.empty())
if (field_spendkey.empty())
{
// if we have an addres but no spend key, we can deduce the spend public key
// from the address
if (field_address_found)
{
cryptonote::account_public_address address2;
bool has_payment_id;
crypto::hash8 new_payment_id;
get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address);
address.m_spend_public_key = address2.m_spend_public_key;
}
m_wallet->generate(m_wallet_file, password, address, viewkey);
}
else
{
if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) {
fail_msg_writer() << tr("failed to verify spend key secret key");
return false;
}
m_wallet->generate(m_wallet_file, password, address, spendkey, viewkey);
}
}
@ -957,13 +1016,44 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
return false;
}
m_wallet->set_refresh_from_block_height(scan_from_height);
m_wallet->set_refresh_from_block_height(field_scan_from_height);
wallet_file = m_wallet_file;
return r;
}
static bool is_local_daemon(const std::string &address)
{
// extract host
epee::net_utils::http::url_content u_c;
if (!epee::net_utils::parse_url(address, u_c))
{
LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not");
return false;
}
if (u_c.host.empty())
{
LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not");
return false;
}
// resolve to IP
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(u_c.host, "");
boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query);
while (i != boost::asio::ip::tcp::resolver::iterator())
{
const boost::asio::ip::tcp::endpoint &ep = *i;
if (ep.address().is_loopback())
return true;
++i;
}
return false;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::init(const boost::program_options::variables_map& vm)
{
@ -999,6 +1089,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if (m_daemon_address.empty())
m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
// set --trusted-daemon if local
try
{
if (is_local_daemon(m_daemon_address))
{
LOG_PRINT_L1(tr("Daemon is local, assuming trusted"));
m_trusted_daemon = true;
}
}
catch (const std::exception &e) { }
tools::password_container pwd_container;
if (!get_password(vm, true, pwd_container))
return false;
@ -1020,6 +1121,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if (m_electrum_seed.empty())
{
m_electrum_seed = command_line::input_line("Specify Electrum seed: ");
if (std::cin.eof())
return false;
if (m_electrum_seed.empty())
{
fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\"");
@ -1037,6 +1140,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
{
// parse address
std::string address_string = command_line::input_line("Standard address: ");
if (std::cin.eof())
return false;
if (address_string.empty()) {
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
@ -1052,6 +1157,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
// parse view secret key
std::string viewkey_string = command_line::input_line("View key: ");
if (std::cin.eof())
return false;
if (viewkey_string.empty()) {
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
@ -1084,6 +1191,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
{
// parse address
std::string address_string = command_line::input_line("Standard address: ");
if (std::cin.eof())
return false;
if (address_string.empty()) {
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
@ -1099,6 +1208,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
// parse spend secret key
std::string spendkey_string = command_line::input_line("Spend key: ");
if (std::cin.eof())
return false;
if (spendkey_string.empty()) {
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
@ -1113,6 +1224,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
// parse view secret key
std::string viewkey_string = command_line::input_line("View key: ");
if (std::cin.eof())
return false;
if (viewkey_string.empty()) {
fail_msg_writer() << tr("No data supplied, cancelled");
return false;
@ -1232,6 +1345,8 @@ std::string simple_wallet::get_mnemonic_language()
while (language_number < 0)
{
language_choice = command_line::input_line(tr("Enter the number corresponding to the language of your choice: "));
if (std::cin.eof())
return std::string();
try
{
language_number = std::stoi(language_choice);
@ -1270,6 +1385,8 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string
"a deprecated version of the wallet. Please use the new seed that we provide.\n");
}
mnemonic_language = get_mnemonic_language();
if (mnemonic_language.empty())
return false;
}
m_wallet_file = wallet_file;
@ -1397,6 +1514,8 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa
message_writer(epee::log_space::console_color_green, false) << "\n" << tr("You had been using "
"a deprecated version of the wallet. Please proceed to upgrade your wallet.\n");
std::string mnemonic_language = get_mnemonic_language();
if (mnemonic_language.empty())
return false;
m_wallet->set_seed_language(mnemonic_language);
m_wallet->rewrite(m_wallet_file, password);
@ -2013,6 +2132,10 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str
// prompt the user for confirmation given the dns query and dnssec status
std::string confirm_dns_ok = command_line::input_line(prompt.str());
if (std::cin.eof())
{
return true;
}
if (confirm_dns_ok != "Y" && confirm_dns_ok != "y" && confirm_dns_ok != "Yes" && confirm_dns_ok != "yes"
&& confirm_dns_ok != tr("yes") && confirm_dns_ok != tr("no"))
{
@ -2071,9 +2194,9 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str
// figure out what tx will be necessary
std::vector<tools::wallet2::pending_tx> ptx_vector;
if (new_algorithm)
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra);
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon);
else
ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra);
ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon);
// if more than one tx necessary, prompt user to confirm
if (m_wallet->always_confirm_transfers() || ptx_vector.size() > 1)
@ -2097,6 +2220,8 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str
print_money(total_fee)).str();
}
std::string accepted = command_line::input_line(prompt_str);
if (std::cin.eof())
return true;
if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
{
fail_msg_writer() << tr("transaction cancelled.");
@ -2159,6 +2284,9 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
std::string reason = e.reason();
if (!reason.empty())
fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
@ -2254,6 +2382,8 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
print_money(total_fee)).str();
}
std::string accepted = command_line::input_line(prompt_str);
if (std::cin.eof())
return true;
if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
{
fail_msg_writer() << tr("transaction cancelled.");
@ -2316,6 +2446,9 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
std::string reason = e.reason();
if (!reason.empty())
fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
@ -2425,13 +2558,18 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_)
COMMAND_RPC_GET_TRANSACTIONS::response res;
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
if (!net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client) ||
res.txs_as_hex.empty())
(res.txs.empty() && res.txs_as_hex.empty()))
{
fail_msg_writer() << tr("failed to get transaction from daemon");
return true;
}
cryptonote::blobdata tx_data;
if (!string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data))
bool ok;
if (!res.txs.empty())
ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data);
else
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
if (!ok)
{
fail_msg_writer() << tr("failed to parse transaction from daemon");
return true;
@ -2735,6 +2873,7 @@ int main(int argc, char* argv[])
std::string lang = i18n_get_language();
tools::sanitize_locale();
tools::set_strict_default_file_permissions(true);
string_tools::set_module_name_and_folder(argv[0]);
@ -2891,12 +3030,25 @@ int main(int argc, char* argv[])
}
tools::wallet2 wal(testnet,restricted);
bool quit = false;
tools::signal_handler::install([&wal, &quit](int) {
quit = true;
wal.stop();
});
try
{
LOG_PRINT_L0(sw::tr("Loading wallet..."));
wal.load(wallet_file, password);
wal.init(daemon_address);
wal.refresh();
// if we ^C during potentially length load/refresh, there's no server loop yet
if (quit)
{
LOG_PRINT_L0(sw::tr("Storing wallet..."));
wal.store();
LOG_PRINT_GREEN(sw::tr("Stored ok"), LOG_LEVEL_0);
return 1;
}
LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0);
}
catch (const std::exception& e)
@ -2907,10 +3059,8 @@ int main(int argc, char* argv[])
tools::wallet_rpc_server wrpc(wal);
bool r = wrpc.init(vm);
CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet rpc server"));
tools::signal_handler::install([&wrpc, &wal](int) {
wrpc.send_stop_signal();
wal.store();
});
LOG_PRINT_L0(sw::tr("Starting wallet rpc server"));
wrpc.run();

View file

@ -1,4 +1,4 @@
#define MONERO_VERSION_TAG "@VERSIONTAG@"
#define MONERO_VERSION "0.9.3.0"
#define MONERO_VERSION "0.9.4.0"
#define MONERO_RELEASE_NAME "Hydrogen Helix"
#define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG

View file

@ -52,6 +52,7 @@ using namespace epee;
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "common/json_util.h"
extern "C"
{
@ -72,6 +73,7 @@ using namespace cryptonote;
#define KILL_IOSERVICE() \
do { \
work.reset(); \
while (!ioservice.stopped()) ioservice.poll(); \
threadpool.join_all(); \
ioservice.stop(); \
} while(0)
@ -1004,7 +1006,7 @@ namespace
* \param keys_file_name Name of wallet file
* \param password Password of wallet file
*/
void wallet2::load_keys(const std::string& keys_file_name, const std::string& password)
bool wallet2::load_keys(const std::string& keys_file_name, const std::string& password)
{
wallet2::keys_file_data keys_file_data;
std::string buf;
@ -1033,34 +1035,44 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
}
else
{
account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() +
json["key_data"].GetStringLength());
if (json.HasMember("seed_language"))
if (!json.HasMember("key_data"))
{
set_seed_language(std::string(json["seed_language"].GetString(), json["seed_language"].GetString() +
json["seed_language"].GetStringLength()));
LOG_ERROR("Field key_data not found in JSON");
return false;
}
if (json.HasMember("watch_only"))
if (!json["key_data"].IsString())
{
m_watch_only = json["watch_only"].GetInt() != 0;
LOG_ERROR("Field key_data found in JSON, but not String");
return false;
}
else
const char *field_key_data = json["key_data"].GetString();
account_data = std::string(field_key_data, field_key_data + json["key_data"].GetStringLength());
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false);
if (field_seed_language_found)
{
m_watch_only = false;
set_seed_language(field_seed_language);
}
m_always_confirm_transfers = json.HasMember("always_confirm_transfers") && (json["always_confirm_transfers"].GetInt() != 0);
m_store_tx_info = (json.HasMember("store_tx_keys") && (json["store_tx_keys"].GetInt() != 0))
|| (json.HasMember("store_tx_info") && (json["store_tx_info"].GetInt() != 0));
m_default_mixin = json.HasMember("default_mixin") ? json["default_mixin"].GetUint() : 0;
m_auto_refresh = !json.HasMember("auto_refresh") || (json["auto_refresh"].GetInt() != 0);
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false);
m_watch_only = field_watch_only_found && field_watch_only;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false);
m_always_confirm_transfers = field_always_confirm_transfers_found && field_always_confirm_transfers;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false);
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false);
m_store_tx_info = (field_store_tx_keys_found && (field_store_tx_keys != 0))
|| (field_store_tx_info_found && (field_store_tx_info != 0));
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false);
m_default_mixin = field_default_mixin_found ? field_default_mixin : 0;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false);
m_auto_refresh = !field_auto_refresh_found || (field_auto_refresh != 0);
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false);
m_refresh_type = RefreshType::RefreshDefault;
if (json.HasMember("refresh_type"))
if (field_refresh_type_found)
{
int type = json["refresh_type"].GetInt();
if (type == RefreshFull || type == RefreshOptimizeCoinbase || type == RefreshNoCoinbase)
m_refresh_type = (RefreshType)type;
if (field_refresh_type == RefreshFull || field_refresh_type == RefreshOptimizeCoinbase || field_refresh_type == RefreshNoCoinbase)
m_refresh_type = (RefreshType)field_refresh_type;
else
LOG_PRINT_L0("Unknown refresh-type value (" << type << "), using default");
LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default");
}
}
@ -1070,6 +1082,7 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
if(!m_watch_only)
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
return true;
}
/*!
@ -1358,7 +1371,10 @@ void wallet2::load(const std::string& wallet_, const std::string& password)
bool exists = boost::filesystem::exists(m_keys_file, e);
THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file);
load_keys(m_keys_file, password);
if (!load_keys(m_keys_file, password))
{
THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file);
}
LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_testnet));
//keys loaded ok!
@ -1718,47 +1734,12 @@ namespace
// returns:
// direct return: amount of money found
// modified reference: selected_transfers, a list of iterators/indices of input sources
uint64_t wallet2::select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, bool hf2_rules, std::list<transfer_container::iterator>& selected_transfers)
uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
// aggregate sources available for transfers
// if dust needed, take dust from only one source (so require source has at least dust amount)
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
if (!td.m_spent && is_transfer_unlocked(td))
{
if (dust < td.amount() && is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
else
{
// for hf2 rules, we disregard dust, which will be spendable only
// via sweep_dust. If we're asked to add dust, though, we still
// consider them, as this will be a mixin 0 tx (and thus we may
// end up with a tx with one mixable output and N dusty ones).
// This should be made better at some point...
if (!hf2_rules || add_dust)
unused_dust_indices.push_back(i);
}
}
}
bool select_one_dust = add_dust && !unused_dust_indices.empty();
uint64_t found_money = 0;
while (found_money < needed_money && (!unused_transfers_indices.empty() || !unused_dust_indices.empty()))
while (found_money < needed_money && !unused_transfers_indices.empty())
{
size_t idx;
if (select_one_dust)
{
idx = pop_random_value(unused_dust_indices);
select_one_dust = false;
}
else
{
idx = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices);
}
size_t idx = pop_random_value(unused_transfers_indices);
transfer_container::iterator it = m_transfers.begin() + idx;
selected_transfers.push_back(it);
@ -1780,18 +1761,18 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, const std::v
}
//----------------------------------------------------------------------------------------------------
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx)
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon)
{
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), tx, ptx);
transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), tx, ptx, trusted_daemon);
}
//----------------------------------------------------------------------------------------------------
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra)
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
cryptonote::transaction tx;
pending_tx ptx;
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, tx, ptx);
transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, tx, ptx, trusted_daemon);
}
namespace {
@ -1943,13 +1924,14 @@ void wallet2::commit_tx(pending_tx& ptx)
COMMAND_RPC_SEND_RAW_TX::request req;
req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
req.do_not_relay = false;
COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status);
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
txid = get_transaction_hash(ptx.tx);
crypto::hash payment_id = cryptonote::null_hash;
@ -1988,8 +1970,9 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector)
//
// this function will make multiple calls to wallet2::transfer if multiple
// transactions will be required
std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra)
std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra, bool trusted_daemon)
{
const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, trusted_daemon);
// failsafe split attempt counter
size_t attempt_count = 0;
@ -2019,7 +2002,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
uint64_t needed_fee = 0;
do
{
transfer(dst_vector, fake_outs_count, unlock_time, needed_fee, extra, tx, ptx);
transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx, trusted_daemon);
auto txBlob = t_serializable_object_to_blob(ptx.tx);
needed_fee = calculate_fee(txBlob);
} while (ptx.fee < needed_fee);
@ -2252,7 +2235,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
// This system allows for sending (almost) the entire balance, since it does
// not generate spurious change in all txes, thus decreasing the instantaneous
// usable balance.
std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra)
std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
@ -2672,9 +2655,8 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector()
return vector;
}
//----------------------------------------------------------------------------------------------------
std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon)
std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool trusted_daemon)
{
// request all outputs with at least 3 instances, so we can use mixin 2 with
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
@ -2683,7 +2665,7 @@ std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_dae
req_t.method = "get_output_histogram";
if (trusted_daemon)
req_t.params.amounts = get_unspent_amounts_vector();
req_t.params.min_count = 3;
req_t.params.min_count = count;
req_t.params.max_count = 0;
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
m_daemon_rpc_mutex.unlock();
@ -2697,14 +2679,32 @@ std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_dae
mixable.insert(i.amount);
}
return select_available_outputs([mixable](const transfer_details &td) {
return select_available_outputs([mixable, atleast](const transfer_details &td) {
const uint64_t amount = td.amount();
if (mixable.find(amount) == mixable.end())
return true;
if (atleast) {
if (mixable.find(amount) != mixable.end())
return true;
}
else {
if (mixable.find(amount) == mixable.end())
return true;
}
return false;
});
}
//----------------------------------------------------------------------------------------------------
std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon)
{
// request all outputs with less than 3 instances
return select_available_outputs_from_histogram(3, false, trusted_daemon);
}
//----------------------------------------------------------------------------------------------------
std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon)
{
// request all outputs with at least 3 instances, so we can use mixin 2 with
return select_available_outputs_from_histogram(3, true, trusted_daemon);
}
//----------------------------------------------------------------------------------------------------
std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon)
{
// From hard fork 1, we don't consider small amounts to be dust anymore

View file

@ -274,11 +274,11 @@ namespace tools
uint64_t unlocked_balance() const;
uint64_t unlocked_dust_balance(const tx_dust_policy &dust_policy) const;
template<typename T>
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon);
template<typename T>
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon);
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon);
template<typename T>
void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx);
template<typename T>
@ -287,8 +287,8 @@ namespace tools
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra);
std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra);
std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra, bool trusted_daemon);
std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
bool check_connection();
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
@ -379,7 +379,7 @@ namespace tools
* \param keys_file_name Name of wallet file
* \param password Password of wallet file
*/
void load_keys(const std::string& keys_file_name, const std::string& password);
bool load_keys(const std::string& keys_file_name, const std::string& password);
void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, bool miner_tx);
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height);
void detach_blockchain(uint64_t height);
@ -390,7 +390,7 @@ namespace tools
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks);
void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, bool &error);
void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added);
uint64_t select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, bool hf2_rules, std::list<transfer_container::iterator>& selected_transfers);
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon);
bool prepare_file_names(const std::string& file_path);
void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height);
void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t spent, uint64_t received);
@ -404,8 +404,10 @@ namespace tools
uint64_t get_upper_tranaction_size_limit();
void check_pending_txes();
std::vector<uint64_t> get_unspent_amounts_vector();
std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool trusted_daemon);
std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f);
std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon);
std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon);
cryptonote::account_base m_account;
std::string m_daemon_address;
@ -563,17 +565,17 @@ namespace tools
}
//----------------------------------------------------------------------------------------------------
template<typename T>
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy)
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon)
{
pending_tx ptx;
cryptonote::transaction tx;
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx);
transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx, trusted_daemon);
}
template<typename T>
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx)
void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, bool trusted_daemon)
{
using namespace cryptonote;
// throw if attempting a transaction with no destinations
@ -594,9 +596,7 @@ namespace tools
// randomly select inputs for transaction
// throw if requested send amount is greater than amount available to send
std::list<transfer_container::iterator> selected_transfers;
bool hf2_rules = use_fork_rules(2); // first fork has version 2
const bool add_dust = (0 == fake_outputs_count) && hf2_rules;
uint64_t found_money = select_transfers(needed_money, add_dust, dust_policy.dust_threshold, hf2_rules, selected_transfers);
uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon);
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry;

View file

@ -458,15 +458,17 @@ namespace tools
//----------------------------------------------------------------------------------------------------
struct tx_rejected : public transfer_error
{
explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status)
explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status, const std::string& reason)
: transfer_error(std::move(loc), "transaction was rejected by daemon")
, m_tx(tx)
, m_status(status)
, m_reason(reason)
{
}
const cryptonote::transaction& tx() const { return m_tx; }
const std::string& status() const { return m_status; }
const std::string& reason() const { return m_reason; }
std::string to_string() const
{
@ -474,12 +476,17 @@ namespace tools
ss << transfer_error::to_string() << ", status = " << m_status << ", tx:\n";
cryptonote::transaction tx = m_tx;
ss << cryptonote::obj_to_json_str(tx);
if (!m_reason.empty())
{
ss << " (" << m_reason << ")";
}
return ss.str();
}
private:
cryptonote::transaction m_tx;
std::string m_status;
std::string m_reason;
};
//----------------------------------------------------------------------------------------------------
struct tx_sum_overflow : public transfer_error

View file

@ -232,7 +232,7 @@ namespace tools
LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2");
mixin = 2;
}
std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra, req.trusted_daemon);
// reject proposed transactions if there are more than one. see on_transfer_split below.
if (ptx_vector.size() != 1)
@ -299,9 +299,9 @@ namespace tools
}
std::vector<wallet2::pending_tx> ptx_vector;
if (req.new_algorithm)
ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee, extra);
ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee, extra, req.trusted_daemon);
else
ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra);
ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra, req.trusted_daemon);
m_wallet.commit_tx(ptx_vector);

View file

@ -115,6 +115,7 @@ namespace wallet_rpc
uint64_t unlock_time;
std::string payment_id;
bool get_tx_key;
bool trusted_daemon;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(destinations)
@ -123,6 +124,7 @@ namespace wallet_rpc
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
KV_SERIALIZE(get_tx_key)
KV_SERIALIZE(trusted_daemon)
END_KV_SERIALIZE_MAP()
};
@ -149,6 +151,7 @@ namespace wallet_rpc
std::string payment_id;
bool new_algorithm;
bool get_tx_keys;
bool trusted_daemon;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(destinations)
@ -158,6 +161,7 @@ namespace wallet_rpc
KV_SERIALIZE(payment_id)
KV_SERIALIZE(new_algorithm)
KV_SERIALIZE(get_tx_keys)
KV_SERIALIZE(trusted_daemon)
END_KV_SERIALIZE_MAP()
};

View file

@ -470,13 +470,15 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector<t
CATCH_ENTRY_L0("replay_events_through_core", false);
}
//--------------------------------------------------------------------------
template<class t_test_class>
template<typename t_test_class>
struct get_test_options {
const std::pair<uint8_t, uint64_t> hard_forks[1] = {std::make_pair((uint8_t)1, (uint64_t)0)};
const std::pair<uint8_t, uint64_t> hard_forks[1];
const cryptonote::test_options test_options = {
hard_forks
};
get_test_options():hard_forks{std::make_pair((uint8_t)1, (uint64_t)0)}{}
};
//--------------------------------------------------------------------------
template<class t_test_class>
inline bool do_replay_events(std::vector<test_event_entry>& events)

View file

@ -85,7 +85,7 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor,
try
{
tools::wallet2::pending_tx ptx;
w1.transfer(dsts, mix_in_factor, 0, TEST_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(TEST_DUST_THRESHOLD), tx, ptx);
w1.transfer(dsts, mix_in_factor, 0, TEST_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(TEST_DUST_THRESHOLD), tx, ptx, true);
w1.commit_tx(ptx);
return true;
}

View file

@ -49,7 +49,8 @@ set(unit_tests_sources
test_format_utils.cpp
test_peerlist.cpp
test_protocol_pack.cpp
hardfork.cpp)
hardfork.cpp
unbound.cpp)
set(unit_tests_headers
unit_tests_utils.h)
@ -69,6 +70,7 @@ target_link_libraries(unit_tests
${Boost_REGEX_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${UNBOUND_LIBRARIES}
${EXTRA_LIBRARIES})
set_property(TARGET unit_tests
PROPERTY

View file

@ -0,0 +1,53 @@
// Copyright (c) 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 "gtest/gtest.h"
#ifdef STATICLIB
extern "C" int dnskey_algo_id_is_supported(int);
TEST(unbound, supported_algorithms)
{
// Monero causes these to be tried, but we don't have access
// to this internal unbound header here, so we use raw numbers
// LDNS_RSASHA1 = 5,
// LDNS_RSASHA1_NSEC3 = 7,
// LDNS_RSASHA256 = 8, /* RFC 5702 */
// LDNS_ECDSAP256SHA256 = 13, /* RFC 6605 */
ASSERT_TRUE(dnskey_algo_id_is_supported(5));
ASSERT_TRUE(dnskey_algo_id_is_supported(7));
ASSERT_TRUE(dnskey_algo_id_is_supported(8));
ASSERT_TRUE(dnskey_algo_id_is_supported(13));
}
#endif