aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.inl21
-rw-r--r--src/common/json_util.h53
-rw-r--r--src/cryptonote_core/blockchain.cpp15
-rw-r--r--src/cryptonote_core/blockchain.h9
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp1
-rw-r--r--src/cryptonote_core/tx_pool.cpp50
-rw-r--r--src/cryptonote_core/tx_pool.h416
-rw-r--r--src/cryptonote_core/verification_context.h7
-rw-r--r--src/rpc/core_rpc_server.cpp36
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h18
-rw-r--r--src/simplewallet/simplewallet.cpp154
-rw-r--r--src/wallet/wallet2.cpp63
-rw-r--r--src/wallet/wallet2.h2
-rw-r--r--src/wallet/wallet_errors.h9
14 files changed, 688 insertions, 166 deletions
diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl
index 1c854dfb7..b3d4e5fdb 100644
--- a/contrib/epee/include/net/abstract_tcp_server2.inl
+++ b/contrib/epee/include/net/abstract_tcp_server2.inl
@@ -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>
diff --git a/src/common/json_util.h b/src/common/json_util.h
new file mode 100644
index 000000000..6f8b4c18f
--- /dev/null
+++ b/src/common/json_util.h
@@ -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)
+
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index da14d7575..34810d983 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -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,7 +2013,7 @@ 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)
@@ -2032,7 +2032,7 @@ bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block
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 +2041,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 +2067,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;
@@ -2113,11 +2114,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;
}
}
@@ -2176,6 +2179,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 +2671,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.");
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 6bae0364d..a62487d1e 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -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
@@ -883,11 +885,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
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index c31be5acf..20b9f0b0b 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -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;
}
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 5d67acdd2..a06826163 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -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);
@@ -496,7 +518,12 @@ namespace cryptonote
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())
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 71febcab6..84e11eeff 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -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,255 @@ 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
+ /**
+ * @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 +333,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;
+ 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
+
+ //! 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;
- time_t last_relayed_time;
- bool relayed;
+ 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
diff --git a/src/cryptonote_core/verification_context.h b/src/cryptonote_core/verification_context.h
index fcfd2a3e2..e58291ea9 100644
--- a/src/cryptonote_core/verification_context.h
+++ b/src/cryptonote_core/verification_context.h
@@ -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
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 5350b6fe0..2d2727a29 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -355,24 +355,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");
- res.status = "Failed";
- return true;
- }
-
- if(tvc.m_verifivation_failed)
- {
- LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed");
+ 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_should_be_relayed)
{
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;
}
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 6d4dd1252..52885dd34 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -234,9 +234,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()
};
};
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 04170df62..aa571755f 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -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)
{
@@ -833,40 +842,27 @@ 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;
+
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false);
+ password = field_password;
- std::string viewkey_string("");
+ 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;
@@ -874,12 +870,12 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data());
}
- 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;
@@ -887,30 +883,32 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data());
}
- 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;
}
// 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;
+ 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));
@@ -925,7 +923,7 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
try
{
- if (!seed.empty())
+ if (!field_seed.empty())
{
m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false);
}
@@ -941,7 +939,7 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
return false;
}
- if (spendkey_string.empty())
+ if (field_spendkey.empty())
{
m_wallet->generate(m_wallet_file, password, address, viewkey);
}
@@ -957,13 +955,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 +1028,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 +1060,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 +1079,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 +1096,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 +1130,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 +1147,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 +1163,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 +1284,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 +1324,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 +1453,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 +2071,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"))
{
@@ -2097,6 +2159,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 +2223,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 +2321,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 +2385,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)
{
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 6fd77eead..a9a65535f 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -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"
{
@@ -1004,7 +1005,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 +1034,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;
- }
- 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);
+ set_seed_language(field_seed_language);
+ }
+ 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 +1081,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;
}
/*!
@@ -1214,7 +1226,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password,
m_account_public_address = account_public_address;
m_watch_only = false;
- bool r = store_keys(m_keys_file, password, true);
+ bool r = store_keys(m_keys_file, password, false);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
@@ -1358,7 +1370,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!
@@ -1965,7 +1980,7 @@ void wallet2::commit_tx(pending_tx& ptx)
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;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index fc700a3de..b6466d3f6 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -378,7 +378,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);
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 652b19499..3de97f49d 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -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