aboutsummaryrefslogtreecommitdiff
path: root/src/cryptonote_core/blockchain.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cryptonote_core/blockchain.cpp')
-rw-r--r--src/cryptonote_core/blockchain.cpp318
1 files changed, 186 insertions, 132 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 69d2edf65..7ee88e430 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -45,6 +45,7 @@
#include "profile_tools.h"
#include "file_io_utils.h"
#include "common/int-util.h"
+#include "common/threadpool.h"
#include "common/boost_serialization_helper.h"
#include "warnings.h"
#include "crypto/hash.h"
@@ -751,7 +752,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
m_timestamps = timestamps;
m_difficulties = difficulties;
}
- size_t target = get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2;
+ size_t target = get_difficulty_target();
return next_difficulty(timestamps, difficulties, target);
}
//------------------------------------------------------------------
@@ -1571,6 +1572,98 @@ void Blockchain::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_A
output_data_t data = m_db->get_output_key(amount, i);
oen.out_key = data.pubkey;
}
+
+uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const
+{
+ uint64_t num_outs = m_db->get_num_outputs(amount);
+ // ensure we don't include outputs that aren't yet eligible to be used
+ // outpouts are sorted by height
+ while (num_outs > 0)
+ {
+ const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1);
+ const uint64_t height = m_db->get_tx_block_height(toi.first);
+ if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height())
+ break;
+ --num_outs;
+ }
+
+ return num_outs;
+}
+
+std::vector<uint64_t> Blockchain::get_random_outputs(uint64_t amount, uint64_t count) const
+{
+ uint64_t num_outs = get_num_mature_outputs(amount);
+
+ std::vector<uint64_t> indices;
+
+ std::unordered_set<uint64_t> seen_indices;
+
+ // if there aren't enough outputs to mix with (or just enough),
+ // use all of them. Eventually this should become impossible.
+ if (num_outs <= count)
+ {
+ for (uint64_t i = 0; i < num_outs; i++)
+ {
+ // get tx_hash, tx_out_index from DB
+ tx_out_index toi = m_db->get_output_tx_and_index(amount, i);
+
+ // if tx is unlocked, add output to indices
+ if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)))
+ {
+ indices.push_back(i);
+ }
+ }
+ }
+ else
+ {
+ // while we still need more mixins
+ while (indices.size() < count)
+ {
+ // if we've gone through every possible output, we've gotten all we can
+ if (seen_indices.size() == num_outs)
+ {
+ break;
+ }
+
+ // get a random output index from the DB. If we've already seen it,
+ // return to the top of the loop and try again, otherwise add it to the
+ // list of output indices we've seen.
+
+ // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
+ uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53);
+ double frac = std::sqrt((double)r / ((uint64_t)1 << 53));
+ uint64_t i = (uint64_t)(frac*num_outs);
+ // just in case rounding up to 1 occurs after sqrt
+ if (i == num_outs)
+ --i;
+
+ if (seen_indices.count(i))
+ {
+ continue;
+ }
+ seen_indices.emplace(i);
+
+ // get tx_hash, tx_out_index from DB
+ tx_out_index toi = m_db->get_output_tx_and_index(amount, i);
+
+ // if the output's transaction is unlocked, add the output's index to
+ // our list.
+ if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)))
+ {
+ indices.push_back(i);
+ }
+ }
+ }
+
+ return indices;
+}
+
+crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_index) const
+{
+ output_data_t data = m_db->get_output_key(amount, global_index);
+ return data.pubkey;
+}
+
//------------------------------------------------------------------
// This function takes an RPC request for mixins and creates an RPC response
// with the requested mixins.
@@ -1585,80 +1678,18 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT
// from BlockchainDB where <n> is req.outs_count (number of mixins).
for (uint64_t amount : req.amounts)
{
- auto num_outs = m_db->get_num_outputs(amount);
- // ensure we don't include outputs that aren't yet eligible to be used
- // outpouts are sorted by height
- while (num_outs > 0)
- {
- const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1);
- const uint64_t height = m_db->get_tx_block_height(toi.first);
- if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height())
- break;
- --num_outs;
- }
-
// create outs_for_amount struct and populate amount field
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount());
result_outs.amount = amount;
- std::unordered_set<uint64_t> seen_indices;
+ std::vector<uint64_t> indices = get_random_outputs(amount, req.outs_count);
- // if there aren't enough outputs to mix with (or just enough),
- // use all of them. Eventually this should become impossible.
- if (num_outs <= req.outs_count)
+ for (auto i : indices)
{
- for (uint64_t i = 0; i < num_outs; i++)
- {
- // get tx_hash, tx_out_index from DB
- tx_out_index toi = m_db->get_output_tx_and_index(amount, i);
+ COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oe = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry());
- // if tx is unlocked, add output to result_outs
- if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)))
- {
- add_out_to_get_random_outs(result_outs, amount, i);
- }
-
- }
- }
- else
- {
- // while we still need more mixins
- while (result_outs.outs.size() < req.outs_count)
- {
- // if we've gone through every possible output, we've gotten all we can
- if (seen_indices.size() == num_outs)
- {
- break;
- }
-
- // get a random output index from the DB. If we've already seen it,
- // return to the top of the loop and try again, otherwise add it to the
- // list of output indices we've seen.
-
- // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
- uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53);
- double frac = std::sqrt((double)r / ((uint64_t)1 << 53));
- uint64_t i = (uint64_t)(frac*num_outs);
- // just in case rounding up to 1 occurs after sqrt
- if (i == num_outs)
- --i;
-
- if (seen_indices.count(i))
- {
- continue;
- }
- seen_indices.emplace(i);
-
- // get tx_hash, tx_out_index from DB
- tx_out_index toi = m_db->get_output_tx_and_index(amount, i);
-
- // if the output's transaction is unlocked, add the output's index to
- // our list.
- if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)))
- {
- add_out_to_get_random_outs(result_outs, amount, i);
- }
- }
+ oe.global_amount_index = i;
+ oe.out_key = get_output_key(amount, i);
}
}
return true;
@@ -1816,6 +1847,15 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA
return true;
}
//------------------------------------------------------------------
+void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const
+{
+ const auto o_data = m_db->get_output_key(amount, index);
+ key = o_data.pubkey;
+ mask = o_data.commitment;
+ tx_out_index toi = m_db->get_output_tx_and_index(amount, index);
+ unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
+}
+//------------------------------------------------------------------
// This function takes a list of block hashes from another node
// on the network to find where the split point is between us and them.
// This is used to see what to send another node that needs to sync.
@@ -2025,28 +2065,39 @@ void Blockchain::print_blockchain_outs(const std::string& file) const
// Find the split point between us and foreign blockchain and return
// (by reference) the most recent common block hash along with up to
// BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes.
-bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const
+bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
// if we can't find the split point, return false
- if(!find_blockchain_supplement(qblock_ids, resp.start_height))
+ if(!find_blockchain_supplement(qblock_ids, start_height))
{
return false;
}
m_db->block_txn_start(true);
- resp.total_height = get_current_blockchain_height();
+ current_height = get_current_blockchain_height();
size_t count = 0;
- for(size_t i = resp.start_height; i < resp.total_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++)
+ for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++)
{
- resp.m_block_ids.push_back(m_db->get_block_hash_from_height(i));
+ hashes.push_back(m_db->get_block_hash_from_height(i));
}
- resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1);
+
m_db->block_txn_stop();
return true;
}
+
+bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const
+{
+ LOG_PRINT_L3("Blockchain::" << __func__);
+ CRITICAL_REGION_LOCAL(m_blockchain_lock);
+
+ bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height);
+ resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1);
+
+ return result;
+}
//------------------------------------------------------------------
//FIXME: change argument to std::vector, low priority
// find split point between ours and foreign blockchain (or start at
@@ -2333,6 +2384,26 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
}
}
+ // from v7, sorted outs
+ if (m_hardfork->get_current_version() >= 7) {
+ const crypto::public_key *last_key = NULL;
+ for (size_t n = 0; n < tx.vout.size(); ++n)
+ {
+ const tx_out &o = tx.vout[n];
+ if (o.target.type() == typeid(txout_to_key))
+ {
+ const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target);
+ if (last_key && memcmp(&out_to_key.key, last_key, sizeof(*last_key)) >= 0)
+ {
+ MERROR_VER("transaction has unsorted outputs");
+ tvc.m_invalid_output = true;
+ return false;
+ }
+ last_key = &out_to_key.key;
+ }
+ }
+ }
+
return true;
}
//------------------------------------------------------------------
@@ -2501,6 +2572,25 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
+ // from v7, sorted ins
+ if (hf_version >= 7) {
+ const crypto::key_image *last_key_image = NULL;
+ for (size_t n = 0; n < tx.vin.size(); ++n)
+ {
+ const txin_v &txin = tx.vin[n];
+ if (txin.type() == typeid(txin_to_key))
+ {
+ const txin_to_key& in_to_key = boost::get<txin_to_key>(txin);
+ if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0)
+ {
+ MERROR_VER("transaction has unsorted inputs");
+ tvc.m_verifivation_failed = true;
+ return false;
+ }
+ last_key_image = &in_to_key.k_image;
+ }
+ }
+ }
auto it = m_check_txin_table.find(tx_prefix_hash);
if(it == m_check_txin_table.end())
{
@@ -2513,33 +2603,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
std::vector < uint64_t > results;
results.resize(tx.vin.size(), 0);
- int threads = tools::get_max_concurrency();
-
- boost::asio::io_service ioservice;
- boost::thread_group threadpool;
- bool ioservice_active = false;
-
- std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice));
- if(threads > 1)
- {
- for (int i = 0; i < threads; i++)
- {
- threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice));
- }
- ioservice_active = true;
- }
-
-#define KILL_IOSERVICE() \
- if(ioservice_active) \
- { \
- work.reset(); \
- while (!ioservice.stopped()) ioservice.poll(); \
- threadpool.join_all(); \
- ioservice.stop(); \
- ioservice_active = false; \
- }
-
- epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); });
+ tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool::waiter waiter;
+ int threads = tpool.get_max_concurrency();
for (const auto& txin : tx.vin)
{
@@ -2600,7 +2666,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
{
// ND: Speedup
// 1. Thread ring signature verification if possible.
- ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index])));
+ tpool.submit(&waiter, boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index])));
}
else
{
@@ -2623,8 +2689,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
sig_index++;
}
-
- KILL_IOSERVICE();
+ if (tx.version == 1 && threads > 1)
+ waiter.wait();
if (tx.version == 1)
{
@@ -3249,7 +3315,7 @@ leave:
// XXX old code adds miner tx here
- int tx_index = 0;
+ size_t tx_index = 0;
// Iterate over the block's transaction hashes, grabbing each
// from the tx_pool and validating them. Each is then added
// to txs. Keys spent in each are added to <keys> by the double spend check.
@@ -3331,7 +3397,7 @@ leave:
{
// ND: if fast_check is enabled for blocks, there is no need to check
// the transaction inputs, but do some sanity checks anyway.
- if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0)
+ if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0)
{
MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs.");
//TODO: why is this done? make sure that keeping invalid blocks makes sense.
@@ -3699,7 +3765,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
return true;
bool blocks_exist = false;
- uint64_t threads = tools::get_max_concurrency();
+ tools::threadpool& tpool = tools::threadpool::getInstance();
+ uint64_t threads = tpool.get_max_concurrency();
if (blocks_entry.size() > 1 && threads > 1 && m_max_prepare_blocks_threads > 1)
{
@@ -3708,15 +3775,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
threads = m_max_prepare_blocks_threads;
uint64_t height = m_db->height();
- std::vector<boost::thread *> thread_list;
int batches = blocks_entry.size() / threads;
int extra = blocks_entry.size() % threads;
MDEBUG("block_batches: " << batches);
std::vector<std::unordered_map<crypto::hash, crypto::hash>> maps(threads);
std::vector < std::vector < block >> blocks(threads);
auto it = blocks_entry.begin();
- boost::thread::attributes attrs;
- attrs.set_stack_size(THREAD_STACK_SIZE);
for (uint64_t i = 0; i < threads; i++)
{
@@ -3775,19 +3839,14 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
{
m_blocks_longhash_table.clear();
uint64_t thread_height = height;
+ tools::threadpool::waiter waiter;
for (uint64_t i = 0; i < threads; i++)
{
- thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i]))));
+ tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])));
thread_height += blocks[i].size();
}
- for (size_t j = 0; j < thread_list.size(); j++)
- {
- thread_list[j]->join();
- delete thread_list[j];
- }
-
- thread_list.clear();
+ waiter.wait();
if (m_cancel)
return false;
@@ -3911,30 +3970,20 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e
// [output] stores all transactions for each tx_out_index::hash found
std::vector<std::unordered_map<crypto::hash, cryptonote::transaction>> transactions(amounts.size());
- threads = tools::get_max_concurrency();
+ threads = tpool.get_max_concurrency();
if (!m_db->can_thread_bulk_indices())
threads = 1;
if (threads > 1)
{
- boost::asio::io_service ioservice;
- boost::thread_group threadpool;
- std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice));
-
- for (uint64_t i = 0; i < threads; i++)
- {
- threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice));
- }
+ tools::threadpool::waiter waiter;
for (size_t i = 0; i < amounts.size(); i++)
{
uint64_t amount = amounts[i];
- ioservice.dispatch(boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i])));
+ tpool.submit(&waiter, boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i])));
}
-
- work.reset();
- threadpool.join_all();
- ioservice.stop();
+ waiter.wait();
}
else
{
@@ -4086,6 +4135,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);
}
+uint64_t Blockchain::get_difficulty_target() const
+{
+ return get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2;
+}
+
std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const
{
return m_db->get_output_histogram(amounts, unlocked, recent_cutoff);