aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/blockchain_db/berkeleydb/db_bdb.cpp6
-rw-r--r--src/blockchain_db/berkeleydb/db_bdb.h9
-rw-r--r--src/blockchain_db/blockchain_db.h9
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp57
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h10
-rw-r--r--src/cryptonote_core/blockchain.cpp20
-rw-r--r--src/cryptonote_core/blockchain.h18
-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/daemon/command_parser_executor.cpp18
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp5
-rw-r--r--src/daemon/rpc_command_executor.cpp36
-rw-r--r--src/daemon/rpc_command_executor.h2
-rw-r--r--src/rpc/core_rpc_server.cpp68
-rw-r--r--src/rpc/core_rpc_server.h2
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h59
-rw-r--r--src/simplewallet/simplewallet.cpp83
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/wallet/wallet2.cpp147
-rw-r--r--src/wallet/wallet2.h7
-rw-r--r--src/wallet/wallet_errors.h18
-rw-r--r--src/wallet/wallet_rpc_server.cpp2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h2
-rw-r--r--tests/unit_tests/hardfork.cpp1
27 files changed, 905 insertions, 152 deletions
diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp
index 1fef9e619..a7fa556bd 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.cpp
+++ b/src/blockchain_db/berkeleydb/db_bdb.cpp
@@ -2180,6 +2180,12 @@ void BlockchainBDB::get_output_tx_and_index(const uint64_t& amount, const std::v
LOG_PRINT_L3("db3: " << db3);
}
+std::map<uint64_t, uint64_t>::BlockchainBDB::get_output_histogram(const std::vector<uint64_t> &amounts) const
+{
+ LOG_PRINT_L3("BlockchainBDB::" << __func__);
+ throw1(DB_ERROR("Not implemented."));
+}
+
void BlockchainBDB::set_hard_fork_starting_height(uint8_t version, uint64_t height)
{
LOG_PRINT_L3("BlockchainBDB::" << __func__);
diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h
index d7cbd24e7..5c6bda4eb 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.h
+++ b/src/blockchain_db/berkeleydb/db_bdb.h
@@ -341,6 +341,15 @@ public:
virtual bool can_thread_bulk_indices() const { return false; }
#endif
+ /**
+ * @brief return a histogram of outputs on the blockchain
+ *
+ * @param amounts optional set of amounts to lookup
+ *
+ * @return a set of amount/instances
+ */
+ std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
+
private:
virtual void add_block( const block& blk
, const size_t& block_size
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 3e0ca141e..3585bd061 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1289,6 +1289,15 @@ public:
virtual void drop_hard_fork_info() = 0;
/**
+ * @brief return a histogram of outputs on the blockchain
+ *
+ * @param amounts optional set of amounts to lookup
+ *
+ * @return a set of amount/instances
+ */
+ virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const = 0;
+
+ /**
* @brief is BlockchainDB in read-only mode?
*
* @return true if in read-only mode, otherwise false
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index e928ab803..9b99520a1 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -2692,6 +2692,63 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std::
LOG_PRINT_L3("db3: " << db3);
}
+std::map<uint64_t, uint64_t> BlockchainLMDB::get_output_histogram(const std::vector<uint64_t> &amounts) const
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ check_open();
+
+ TXN_PREFIX_RDONLY();
+ RCURSOR(output_amounts);
+
+ std::map<uint64_t, uint64_t> histogram;
+ MDB_val k;
+ MDB_val v;
+
+ if (amounts.empty())
+ {
+ MDB_cursor_op op = MDB_FIRST;
+ while (1)
+ {
+ int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, op);
+ op = MDB_NEXT_NODUP;
+ if (ret == MDB_NOTFOUND)
+ break;
+ if (ret)
+ throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str()));
+ mdb_size_t num_elems = 0;
+ mdb_cursor_count(m_cur_output_amounts, &num_elems);
+ uint64_t amount = *(const uint64_t*)k.mv_data;
+ histogram[amount] = num_elems;
+ }
+ }
+ else
+ {
+ for (const auto &amount: amounts)
+ {
+ MDB_val_copy<uint64_t> k(amount);
+ int ret = mdb_cursor_get(m_cur_output_amounts, &k, &v, MDB_SET);
+ if (ret == MDB_NOTFOUND)
+ {
+ histogram[amount] = 0;
+ }
+ else if (ret == MDB_SUCCESS)
+ {
+ mdb_size_t num_elems = 0;
+ mdb_cursor_count(m_cur_output_amounts, &num_elems);
+ histogram[amount] = num_elems;
+ }
+ else
+ {
+ throw0(DB_ERROR(lmdb_error("Failed to enumerate outputs: ", ret).c_str()));
+ }
+ }
+ }
+
+ TXN_POSTFIX_RDONLY();
+
+ return histogram;
+}
+
void BlockchainLMDB::check_hard_fork_info()
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index a3f32ffaa..6cd3e0e8f 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -280,6 +280,16 @@ public:
virtual void pop_block(block& blk, std::vector<transaction>& txs);
virtual bool can_thread_bulk_indices() const { return true; }
+
+ /**
+ * @brief return a histogram of outputs on the blockchain
+ *
+ * @param amounts optional set of amounts to lookup
+ *
+ * @return a set of amount/instances
+ */
+ std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
+
private:
void do_resize(uint64_t size_increase=0);
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 0f6afe740..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.");
@@ -3322,6 +3327,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui
return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting);
}
+std::map<uint64_t, uint64_t> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts) const
+{
+ return m_db->get_output_histogram(amounts);
+}
+
void Blockchain::load_compiled_in_block_hashes()
{
if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr)
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 393faef2a..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
@@ -683,6 +685,15 @@ namespace cryptonote
bool flush_txes_from_pool(const std::list<crypto::hash> &txids);
/**
+ * @brief return a histogram of outputs on the blockchain
+ *
+ * @param amounts optional set of amounts to lookup
+ *
+ * @return a set of amount/instances
+ */
+ std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const;
+
+ /**
* @brief perform a check on all key images in the blockchain
*
* @param std::function the check to perform, pass/fail
@@ -874,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/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index 0b42257e7..166fe04ca 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -439,5 +439,23 @@ bool t_command_parser_executor::flush_txpool(const std::vector<std::string>& arg
return m_executor.flush_txpool(txid);
}
+bool t_command_parser_executor::output_histogram(const std::vector<std::string>& args)
+{
+ if (args.size() > 2) return false;
+
+ uint64_t min_count = 3;
+ uint64_t max_count = 0;
+
+ if (args.size() >= 1)
+ {
+ min_count = boost::lexical_cast<uint64_t>(args[0]);
+ }
+ if (args.size() >= 2)
+ {
+ max_count = boost::lexical_cast<uint64_t>(args[1]);
+ }
+ return m_executor.output_histogram(min_count, max_count);
+}
+
} // namespace daemonize
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index 51c55a8c0..11df92a5e 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -114,6 +114,8 @@ public:
bool unban(const std::vector<std::string>& args);
bool flush_txpool(const std::vector<std::string>& args);
+
+ bool output_histogram(const std::vector<std::string>& args);
};
} // namespace daemonize
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index 206de9d4a..aabc2f09a 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -214,6 +214,11 @@ t_command_server::t_command_server(
, std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1)
, "Flush a transaction from the tx pool by its txid, or the whole tx pool"
);
+ m_command_lookup.set_handler(
+ "output_histogram"
+ , std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1)
+ , "Print output histogram (amount, instances)"
+ );
}
bool t_command_server::process_command_str(const std::string& cmd)
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 15ddc081f..933c93ed7 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -1203,5 +1203,41 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid)
return true;
}
+bool t_rpc_command_executor::output_histogram(uint64_t min_count, uint64_t max_count)
+{
+ cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req;
+ cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response res;
+ std::string fail_message = "Unsuccessful";
+ epee::json_rpc::error error_resp;
+
+ req.min_count = min_count;
+ req.max_count = max_count;
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->json_rpc_request(req, res, "get_output_histogram", fail_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_output_histogram(req, res, error_resp))
+ {
+ tools::fail_msg_writer() << fail_message.c_str();
+ return true;
+ }
+ }
+
+ std::sort(res.histogram.begin(), res.histogram.end(),
+ [](const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e1, const cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry &e2)->bool { return e1.instances < e2.instances; });
+ for (const auto &e: res.histogram)
+ {
+ tools::msg_writer() << e.instances << " " << cryptonote::print_money(e.amount);
+ }
+
+ return true;
+}
+
}// namespace daemonize
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index bc3f2d5c5..7e73e7faf 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -132,6 +132,8 @@ public:
bool unban(const std::string &ip);
bool flush_txpool(const std::string &txid);
+
+ bool output_histogram(uint64_t min_count, uint64_t max_count);
};
} // namespace daemonize
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 6b625e77b..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;
}
@@ -1041,6 +1057,38 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp)
+ {
+ if(!check_core_busy())
+ {
+ error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY;
+ error_resp.message = "Core is busy.";
+ return false;
+ }
+
+ std::map<uint64_t, uint64_t> histogram;
+ try
+ {
+ histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts);
+ }
+ catch (const std::exception &e)
+ {
+ res.status = "Failed to get output histogram";
+ return true;
+ }
+
+ res.histogram.clear();
+ res.histogram.reserve(histogram.size());
+ for (const auto &i: histogram)
+ {
+ if (i.second >= req.min_count && (i.second <= req.max_count || req.max_count == 0))
+ res.histogram.push_back(COMMAND_RPC_GET_OUTPUT_HISTOGRAM::entry(i.first, i.second));
+ }
+
+ res.status = CORE_RPC_STATUS_OK;
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res)
{
cryptonote::core::set_fast_exit();
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index f79087030..5c3707209 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -109,6 +109,7 @@ namespace cryptonote
MAP_JON_RPC_WE("setbans", on_set_bans, COMMAND_RPC_SETBANS)
MAP_JON_RPC_WE("getbans", on_get_bans, COMMAND_RPC_GETBANS)
MAP_JON_RPC_WE("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL)
+ MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM)
END_JSON_RPC_MAP()
END_URI_MAP2()
@@ -149,6 +150,7 @@ namespace cryptonote
bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp);
bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp);
bool on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp);
+ bool on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp);
//-----------------------
private:
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 72e399ec4..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()
};
};
@@ -986,5 +1004,46 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
};
+
+ struct COMMAND_RPC_GET_OUTPUT_HISTOGRAM
+ {
+ struct request
+ {
+ std::vector<uint64_t> amounts;
+ uint64_t min_count;
+ uint64_t max_count;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amounts);
+ KV_SERIALIZE(min_count);
+ KV_SERIALIZE(max_count);
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct entry
+ {
+ uint64_t amount;
+ uint64_t instances;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount);
+ KV_SERIALIZE(instances);
+ END_KV_SERIALIZE_MAP()
+
+ entry(uint64_t amount, uint64_t instances): amount(amount), instances(instances) {}
+ entry() {}
+ };
+
+ struct response
+ {
+ std::string status;
+ std::vector<entry> histogram;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(histogram)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
}
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 1856118f7..d05b5ea4c 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -541,7 +541,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height"));
m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)"));
m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm"));
- m_cmd_binder.set_handler("sweep_dust", boost::bind(&simple_wallet::sweep_dust, this, _1), tr("Send all dust outputs to yourself with mixin 0"));
+ m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0"));
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>"));
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address"));
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
@@ -964,6 +964,37 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m
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 +1030,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;
@@ -1722,8 +1764,7 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/)
{
success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", "
- << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()) << ", "
- << tr("including unlocked dust: ") << print_money(m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD)));
+ << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance());
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -2160,6 +2201,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)
{
@@ -2208,7 +2252,7 @@ bool simple_wallet::transfer_new(const std::vector<std::string> &args_)
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
+bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
{
if (!try_connect_to_daemon())
return true;
@@ -2221,28 +2265,37 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
try
{
- uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD));
-
// figure out what tx will be necessary
- auto ptx_vector = m_wallet->create_dust_sweep_transactions();
+ auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
+
+ if (ptx_vector.empty())
+ {
+ fail_msg_writer() << tr("No unmixable outputs found");
+ return true;
+ }
// give user total and fee, and prompt to confirm
- uint64_t total_fee = 0;
+ uint64_t total_fee = 0, total_unmixable = 0;
for (size_t n = 0; n < ptx_vector.size(); ++n)
{
total_fee += ptx_vector[n].fee;
+ for (const auto &vin: ptx_vector[n].tx.vin)
+ {
+ if (vin.type() == typeid(txin_to_key))
+ total_unmixable += boost::get<txin_to_key>(vin).amount;
+ }
}
- std::string prompt_str = tr("Sweeping ") + print_money(total_dust);
+ std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable);
if (ptx_vector.size() > 1) {
prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
- print_money(total_dust) %
+ print_money(total_unmixable) %
((unsigned long long)ptx_vector.size()) %
print_money(total_fee)).str();
}
else {
prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
- print_money(total_dust) %
+ print_money(total_unmixable) %
print_money(total_fee)).str();
}
std::string accepted = command_line::input_line(prompt_str);
@@ -2285,11 +2338,12 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
}
catch (const tools::error::not_enough_money& e)
{
- fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) %
+ fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee).\n%s")) %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
- print_money(e.fee());
+ print_money(e.fee()) %
+ tr("This is usually due to dust which is so small it cannot pay for itself in fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
@@ -2307,6 +2361,9 @@ bool simple_wallet::sweep_dust(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/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 7dadb8536..21bbfa566 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -121,7 +121,7 @@ namespace cryptonote
bool transfer_main(bool new_algorithm, const std::vector<std::string> &args);
bool transfer(const std::vector<std::string> &args);
bool transfer_new(const std::vector<std::string> &args);
- bool sweep_dust(const std::vector<std::string> &args);
+ bool sweep_unmixable(const std::vector<std::string> &args);
std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 1c7187cf0..95a160434 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -92,6 +92,13 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
}
}
+uint64_t calculate_fee(const cryptonote::blobdata &blob)
+{
+ uint64_t bytes = blob.size();
+ uint64_t kB = (bytes + 1023) / 1024;
+ return kB * FEE_PER_KB;
+}
+
} //namespace
namespace tools
@@ -1207,7 +1214,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));
@@ -1958,7 +1965,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;
@@ -2030,13 +2037,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
{
transfer(dst_vector, fake_outs_count, unlock_time, needed_fee, extra, tx, ptx);
auto txBlob = t_serializable_object_to_blob(ptx.tx);
- uint64_t txSize = txBlob.size();
- uint64_t numKB = txSize / 1024;
- if (txSize % 1024)
- {
- numKB++;
- }
- needed_fee = numKB * FEE_PER_KB;
+ needed_fee = calculate_fee(txBlob);
} while (ptx.fee < needed_fee);
ptx_vector.push_back(ptx);
@@ -2408,15 +2409,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);
auto txBlob = t_serializable_object_to_blob(test_ptx.tx);
- uint64_t txSize = txBlob.size();
- uint64_t numKB = txSize / 1024;
- if (txSize % 1024)
- {
- numKB++;
- }
- needed_fee = numKB * FEE_PER_KB;
+ needed_fee = calculate_fee(txBlob);
available_for_fee = test_ptx.fee + test_ptx.change_dts.amount;
- LOG_PRINT_L2("Made a " << numKB << " kB tx, with " << print_money(available_for_fee) << " available for fee (" <<
+ LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" <<
print_money(needed_fee) << " needed)");
if (needed_fee > available_for_fee && dsts[0].amount > 0)
@@ -2513,7 +2508,7 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const
}
template<typename T>
-void wallet2::transfer_dust(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)
+void wallet2::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)
{
using namespace cryptonote;
@@ -2523,6 +2518,19 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n
// throw if there are none
uint64_t money = 0;
std::list<transfer_container::iterator> selected_transfers;
+#if 1
+ for (size_t n = 0; n < outs.size(); ++n)
+ {
+ const transfer_details& td = m_transfers[outs[n]];
+ if (!td.m_spent)
+ {
+ selected_transfers.push_back (m_transfers.begin() + outs[n]);
+ money += td.amount();
+ if (selected_transfers.size() >= num_outputs)
+ break;
+ }
+ }
+#else
for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
{
const transfer_details& td = *i;
@@ -2534,6 +2542,7 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n
break;
}
}
+#endif
// we don't allow no output to self, easier, but one may want to burn the dust if = fee
THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee);
@@ -2627,8 +2636,8 @@ bool wallet2::use_fork_rules(uint8_t version)
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();
CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status");
+ CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
+ CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status");
bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand
if (close_enough)
@@ -2646,20 +2655,85 @@ uint64_t wallet2::get_upper_tranaction_size_limit()
return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
}
//----------------------------------------------------------------------------------------------------
-std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions()
+std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(const transfer_details &td)> &f)
+{
+ std::vector<size_t> outputs;
+ size_t n = 0;
+ for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i, ++n)
+ {
+ if (i->m_spent)
+ continue;
+ if (!is_transfer_unlocked(*i))
+ continue;
+ if (f(*i))
+ outputs.push_back(n);
+ }
+ return outputs;
+}
+//----------------------------------------------------------------------------------------------------
+std::vector<uint64_t> wallet2::get_unspent_amounts_vector()
+{
+ std::set<uint64_t> set;
+ for (const auto &td: m_transfers)
+ {
+ if (!td.m_spent)
+ set.insert(td.amount());
+ }
+ std::vector<uint64_t> vector;
+ vector.reserve(set.size());
+ for (const auto &i: set)
+ {
+ vector.push_back(i);
+ }
+ return vector;
+}
+//----------------------------------------------------------------------------------------------------
+std::vector<size_t> wallet2::select_available_unmixable_outputs(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();
+ req_t.jsonrpc = "2.0";
+ req_t.id = epee::serialization::storage_entry(0);
+ 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.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();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_unmixable_outputs");
+ THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
+ THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status);
+
+ std::set<uint64_t> mixable;
+ for (const auto &i: resp_t.result.histogram)
+ {
+ mixable.insert(i.amount);
+ }
+
+ return select_available_outputs([mixable](const transfer_details &td) {
+ const uint64_t amount = td.amount();
+ if (mixable.find(amount) == mixable.end())
+ return true;
+ return false;
+ });
+}
+//----------------------------------------------------------------------------------------------------
+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
const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2
tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD);
- size_t num_dust_outputs = 0;
- for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
+ // may throw
+ std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon);
+ size_t num_dust_outputs = unmixable_outputs.size();
+
+ if (num_dust_outputs == 0)
{
- const transfer_details& td = *i;
- if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td))
- {
- num_dust_outputs++;
- }
+ return std::vector<wallet2::pending_tx>();
}
// failsafe split attempt counter
@@ -2682,23 +2756,18 @@ std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions()
// loop until fee is met without increasing tx size to next KB boundary.
uint64_t needed_fee = 0;
- if (1)
+ do
{
- transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx);
+ transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx);
auto txBlob = t_serializable_object_to_blob(ptx.tx);
- uint64_t txSize = txBlob.size();
- uint64_t numKB = txSize / 1024;
- if (txSize % 1024)
- {
- numKB++;
- }
- needed_fee = numKB * FEE_PER_KB;
+ needed_fee = calculate_fee(txBlob);
// reroll the tx with the actual amount minus the fee
// if there's not enough for the fee, it'll throw
- transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx);
+ transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx);
txBlob = t_serializable_object_to_blob(ptx.tx);
- }
+ needed_fee = calculate_fee(txBlob);
+ } while (ptx.fee < needed_fee);
ptx_vector.push_back(ptx);
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index e7b002925..fc700a3de 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -280,7 +280,7 @@ namespace tools
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);
template<typename T>
- void transfer_dust(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);
+ 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>
void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, 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);
@@ -289,7 +289,7 @@ namespace tools
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_dust_sweep_transactions();
+ std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
bool check_connection();
void get_transfers(wallet2::transfer_container& incoming_transfers) const;
void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const;
@@ -402,6 +402,9 @@ namespace tools
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
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(const std::function<bool(const transfer_details &td)> &f);
+ std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon);
cryptonote::account_base m_account;
std::string m_daemon_address;
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 6074e0858..3de97f49d 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -76,6 +76,7 @@ namespace tools
// daemon_busy
// no_connection_to_daemon
// is_key_image_spent_error
+ // get_histogram_error
// wallet_files_doesnt_correspond
//
// * - class with protected ctor
@@ -457,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
{
@@ -473,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
@@ -600,6 +608,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct get_histogram_error : public wallet_rpc_error
+ {
+ explicit get_histogram_error(std::string&& loc, const std::string& request)
+ : wallet_rpc_error(std::move(loc), "failed to get output histogram", request)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
struct wallet_files_doesnt_correspond : public wallet_logic_error
{
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 418de327c..d7d99c2ae 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -347,7 +347,7 @@ namespace tools
try
{
- std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_dust_sweep_transactions();
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_unmixable_sweep_transactions(req.trusted_daemon);
m_wallet.commit_tx(ptx_vector);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 40d6fd8f8..2c4e26406 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -178,9 +178,11 @@ namespace wallet_rpc
struct request
{
bool get_tx_keys;
+ bool trusted_daemon;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(get_tx_keys)
+ KV_SERIALIZE(trusted_daemon)
END_KV_SERIALIZE_MAP()
};
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index 50e0e5ae8..cc3eba8ea 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -108,6 +108,7 @@ public:
virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const { return true; }
virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const { return true; }
virtual bool is_read_only() const { return false; }
+ virtual std::map<uint64_t, uint64_t> get_output_histogram(const std::vector<uint64_t> &amounts) const { return std::map<uint64_t, uint64_t>(); }
virtual void add_block( const block& blk
, const size_t& block_size