diff options
author | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-03-03 22:07:58 +0000 |
---|---|---|
committer | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-03-03 22:07:58 +0000 |
commit | 296ae46ed8f8f6e5f986f978febad302e3df231a (patch) | |
tree | 1629164454a239308f33c9e12afb22e7f3cd8eeb /src/cryptonote_core | |
parent | changed name (diff) | |
download | monero-296ae46ed8f8f6e5f986f978febad302e3df231a.tar.xz |
moved all stuff to github
Diffstat (limited to 'src/cryptonote_core')
27 files changed, 5782 insertions, 0 deletions
diff --git a/src/cryptonote_core/account.cpp b/src/cryptonote_core/account.cpp new file mode 100644 index 000000000..ba39b9b77 --- /dev/null +++ b/src/cryptonote_core/account.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <boost/archive/binary_oarchive.hpp> +#include <boost/archive/binary_iarchive.hpp> +#include <fstream> + +#include "include_base_utils.h" +#include "account.h" +#include "warnings.h" +#include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_format_utils.h" +using namespace std; + +DISABLE_VS_WARNINGS(4244 4345) + + namespace cryptonote +{ + //----------------------------------------------------------------- + account_base::account_base() + { + set_null(); + } + //----------------------------------------------------------------- + void account_base::set_null() + { + m_keys = account_keys(); + } + //----------------------------------------------------------------- + void account_base::generate() + { + generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key); + generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key); + m_creation_timestamp = time(NULL); + } + //----------------------------------------------------------------- + const account_keys& account_base::get_keys() const + { + return m_keys; + } + //----------------------------------------------------------------- + std::string account_base::get_public_address_str() + { + //TODO: change this code into base 58 + return get_account_address_as_str(m_keys.m_account_address); + } + //----------------------------------------------------------------- +} diff --git a/src/cryptonote_core/account.h b/src/cryptonote_core/account.h new file mode 100644 index 000000000..8b525da97 --- /dev/null +++ b/src/cryptonote_core/account.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "cryptonote_core/cryptonote_basic.h" +#include "crypto/crypto.h" +#include "serialization/keyvalue_serialization.h" + +namespace cryptonote +{ + + struct account_keys + { + account_public_address m_account_address; + crypto::secret_key m_spend_secret_key; + crypto::secret_key m_view_secret_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_account_address) + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) + END_KV_SERIALIZE_MAP() + }; + + /************************************************************************/ + /* */ + /************************************************************************/ + class account_base + { + public: + account_base(); + void generate(); + const account_keys& get_keys() const; + std::string get_public_address_str(); + + uint64_t get_createtime() const { return m_creation_timestamp; } + void set_createtime(uint64_t val) { m_creation_timestamp = val; } + + bool load(const std::string& file_path); + bool store(const std::string& file_path); + + template <class t_archive> + inline void serialize(t_archive &a, const unsigned int /*ver*/) + { + a & m_keys; + a & m_creation_timestamp; + } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_keys) + KV_SERIALIZE(m_creation_timestamp) + END_KV_SERIALIZE_MAP() + + private: + void set_null(); + account_keys m_keys; + uint64_t m_creation_timestamp; + }; +} diff --git a/src/cryptonote_core/account_boost_serialization.h b/src/cryptonote_core/account_boost_serialization.h new file mode 100644 index 000000000..9cc36d14a --- /dev/null +++ b/src/cryptonote_core/account_boost_serialization.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "account.h" +#include "cryptonote_core/cryptonote_boost_serialization.h" + +//namespace cryptonote { +namespace boost +{ + namespace serialization + { + template <class Archive> + inline void serialize(Archive &a, cryptonote::account_keys &x, const boost::serialization::version_type ver) + { + a & x.m_account_address; + a & x.m_spend_secret_key; + a & x.m_view_secret_key; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::account_public_address &x, const boost::serialization::version_type ver) + { + a & x.m_spend_public_key; + a & x.m_view_public_key; + } + + } +} diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp new file mode 100644 index 000000000..1ec186652 --- /dev/null +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -0,0 +1,1633 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <algorithm> +#include <cstdio> +#include <boost/archive/binary_oarchive.hpp> +#include <boost/archive/binary_iarchive.hpp> + +#include "include_base_utils.h" +#include "cryptonote_basic_impl.h" +#include "blockchain_storage.h" +#include "cryptonote_format_utils.h" +#include "cryptonote_boost_serialization.h" +#include "blockchain_storage_boost_serialization.h" +#include "cryptonote_config.h" +#include "miner.h" +#include "misc_language.h" +#include "profile_tools.h" +#include "file_io_utils.h" +#include "common/boost_serialization_helper.h" +#include "warnings.h" +#include "crypto/hash.h" +//#include "serialization/json_archive.h" + +using namespace std; +using namespace epee; +using namespace cryptonote; + +DISABLE_VS_WARNINGS(4267) + +//------------------------------------------------------------------ +bool blockchain_storage::have_tx(const crypto::hash &id) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_transactions.find(id) != m_transactions.end(); +} +//------------------------------------------------------------------ +bool blockchain_storage::have_tx_keyimg_as_spent(const crypto::key_image &key_im) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_spent_keys.find(key_im) != m_spent_keys.end(); +} +//------------------------------------------------------------------ +transaction *blockchain_storage::get_tx(const crypto::hash &id) +{ + auto it = m_transactions.find(id); + if (it == m_transactions.end()) + return NULL; + + return &it->second.tx; +} +//------------------------------------------------------------------ +uint64_t blockchain_storage::get_current_blockchain_height() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_blocks.size(); +} +//------------------------------------------------------------------ +bool blockchain_storage::init(const std::string& config_folder) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_config_folder = config_folder; + LOG_PRINT_L0("Loading blockchain..."); + const std::string filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_FILENAME; + if(!tools::unserialize_obj_from_file(*this, filename)) + { + const std::string temp_filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME; + if(tools::unserialize_obj_from_file(*this, temp_filename)) + { + LOG_PRINT_L0("Blockchain storage loaded from temporary file"); + std::error_code ec = tools::replace_file(temp_filename, filename); + if (ec) + { + LOG_ERROR("Failed to rename blockchain data file " << temp_filename << " to " << filename << ": " << ec.message() << ':' << ec.value()); + return false; + } + } + else + { + LOG_PRINT_L0("Blockchain storage file not found, generating genesis block."); + block bl = boost::value_initialized<block>(); + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + generate_genesis_block(bl); + add_new_block(bl, bvc); + CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed && bvc.m_added_to_main_chain, false, "Failed to add genesis block to blockchain"); + } + } + if(!m_blocks.size()) + { + LOG_PRINT_L0("Blockchain not loaded, generating genesis block."); + block bl = boost::value_initialized<block>(); + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + generate_genesis_block(bl); + add_new_block(bl, bvc); + CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "Failed to add genesis block to blockchain"); + } + uint64_t timestamp_diff = time(NULL) - m_blocks.back().bl.timestamp; + if(!m_blocks.back().bl.timestamp) + timestamp_diff = time(NULL) - 1341378000; + LOG_PRINT_GREEN("Blockchain initialized. last block: " << m_blocks.size()-1 << ", " << misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block(), LOG_LEVEL_0); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::store_blockchain() +{ + LOG_PRINT_L0("Storing blockchain..."); + if (!tools::create_directories_if_necessary(m_config_folder)) + { + LOG_PRINT_L0("Failed to create data directory: " << m_config_folder); + return false; + } + + const std::string temp_filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME; + // There is a chance that temp_filename and filename are hardlinks to the same file + std::remove(temp_filename.c_str()); + if(!tools::serialize_obj_to_file(*this, temp_filename)) + { + //achtung! + LOG_ERROR("Failed to save blockchain data to file: " << temp_filename); + return false; + } + const std::string filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_FILENAME; + std::error_code ec = tools::replace_file(temp_filename, filename); + if (ec) + { + LOG_ERROR("Failed to rename blockchain data file " << temp_filename << " to " << filename << ": " << ec.message() << ':' << ec.value()); + return false; + } + LOG_PRINT_L0("Blockchain stored OK."); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::deinit() +{ + return store_blockchain(); +} +//------------------------------------------------------------------ +bool blockchain_storage::pop_block_from_blockchain() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + CHECK_AND_ASSERT_MES(m_blocks.size() > 1, false, "pop_block_from_blockchain: can't pop from blockchain with size = " << m_blocks.size()); + size_t h = m_blocks.size()-1; + block_extended_info& bei = m_blocks[h]; + //crypto::hash id = get_block_hash(bei.bl); + bool r = purge_block_data_from_blockchain(bei.bl, bei.bl.tx_hashes.size()); + CHECK_AND_ASSERT_MES(r, false, "Failed to purge_block_data_from_blockchain for block " << get_block_hash(bei.bl) << " on height " << h); + + //remove from index + auto bl_ind = m_blocks_index.find(get_block_hash(bei.bl)); + CHECK_AND_ASSERT_MES(bl_ind != m_blocks_index.end(), false, "pop_block_from_blockchain: blockchain id not found in index"); + m_blocks_index.erase(bl_ind); + //pop block from core + m_blocks.pop_back(); + m_tx_pool.on_blockchain_dec(m_blocks.size()-1, get_tail_id()); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::reset_and_set_genesis_block(const block& b) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_transactions.clear(); + m_spent_keys.clear(); + m_blocks.clear(); + m_blocks_index.clear(); + m_alternative_chains.clear(); + m_outputs.clear(); + + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + add_new_block(b, bvc); + return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; +} +//------------------------------------------------------------------ +bool blockchain_storage::purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check) +{ + struct purge_transaction_visitor: public boost::static_visitor<bool> + { + key_images_container& m_spent_keys; + bool m_strict_check; + purge_transaction_visitor(key_images_container& spent_keys, bool strict_check):m_spent_keys(spent_keys), m_strict_check(strict_check){} + + bool operator()(const txin_to_key& inp) const + { + //const crypto::key_image& ki = inp.k_image; + auto r = m_spent_keys.find(inp.k_image); + if(r != m_spent_keys.end()) + { + m_spent_keys.erase(r); + }else + { + CHECK_AND_ASSERT_MES(!m_strict_check, false, "purge_block_data_from_blockchain: key image in transaction not found"); + } + return true; + } + bool operator()(const txin_gen& inp) const + { + return true; + } + bool operator()(const txin_to_script& tx) const + { + return false; + } + + bool operator()(const txin_to_scripthash& tx) const + { + return false; + } + }; + + BOOST_FOREACH(const txin_v& in, tx.vin) + { + bool r = boost::apply_visitor(purge_transaction_visitor(m_spent_keys, strict_check), in); + CHECK_AND_ASSERT_MES(!strict_check || r, false, "failed to process purge_transaction_visitor"); + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& tx_id) +{ + auto tx_index_it = m_transactions.find(tx_id); + CHECK_AND_ASSERT_MES(tx_index_it != m_transactions.end(), false, "purge_block_data_from_blockchain: transaction not found in blockchain index!!"); + transaction& tx = tx_index_it->second.tx; + + purge_transaction_keyimages_from_blockchain(tx, true); + + if(!is_coinbase(tx)) + { + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + bool r = m_tx_pool.add_tx(tx, tvc, true); + CHECK_AND_ASSERT_MES(r, false, "purge_block_data_from_blockchain: failed to add transaction to transaction pool"); + } + + bool res = pop_transaction_from_global_index(tx, tx_id); + m_transactions.erase(tx_index_it); + LOG_PRINT_L1("Removed transaction from blockchain history:" << tx_id << ENDL); + return res; +} +//------------------------------------------------------------------ +bool blockchain_storage::purge_block_data_from_blockchain(const block& bl, size_t processed_tx_count) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + bool res = true; + CHECK_AND_ASSERT_MES(processed_tx_count <= bl.tx_hashes.size(), false, "wrong processed_tx_count in purge_block_data_from_blockchain"); + for(size_t count = 0; count != processed_tx_count; count++) + { + res = purge_transaction_from_blockchain(bl.tx_hashes[(processed_tx_count -1)- count]) && res; + } + + res = purge_transaction_from_blockchain(get_transaction_hash(bl.miner_tx)) && res; + + return res; +} +//------------------------------------------------------------------ +crypto::hash blockchain_storage::get_tail_id(uint64_t& height) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + height = get_current_blockchain_height()-1; + return get_tail_id(); +} +//------------------------------------------------------------------ +crypto::hash blockchain_storage::get_tail_id() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + crypto::hash id = null_hash; + if(m_blocks.size()) + { + get_block_hash(m_blocks.back().bl, id); + } + return id; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_short_chain_history(std::list<crypto::hash>& ids) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + size_t i = 0; + size_t current_multiplier = 1; + size_t sz = m_blocks.size(); + if(!sz) + return true; + size_t current_back_offset = 1; + bool genesis_included = false; + while(current_back_offset < sz) + { + ids.push_back(get_block_hash(m_blocks[sz-current_back_offset].bl)); + if(sz-current_back_offset == 0) + genesis_included = true; + if(i < 10) + { + ++current_back_offset; + }else + { + current_back_offset += current_multiplier *= 2; + } + ++i; + } + if(!genesis_included) + ids.push_back(get_block_hash(m_blocks[0].bl)); + + return true; +} +//------------------------------------------------------------------ +crypto::hash blockchain_storage::get_block_id_by_height(uint64_t height) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(height >= m_blocks.size()) + return null_hash; + + return get_block_hash(m_blocks[height].bl); +} +//------------------------------------------------------------------ +bool blockchain_storage::get_block_by_hash(const crypto::hash &h, block &blk) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + // try to find block in main chain + blocks_by_id_index::const_iterator it = m_blocks_index.find(h); + if (m_blocks_index.end() != it) { + blk = m_blocks[it->second].bl; + return true; + } + + // try to find block in alternative chain + blocks_ext_by_hash::const_iterator it_alt = m_alternative_chains.find(h); + if (m_alternative_chains.end() != it_alt) { + blk = it_alt->second.bl; + return true; + } + + return false; +} +//------------------------------------------------------------------ +void blockchain_storage::get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + BOOST_FOREACH(blocks_by_id_index::value_type &v, m_blocks_index) + main.push_back(v.first); + + BOOST_FOREACH(blocks_ext_by_hash::value_type &v, m_alternative_chains) + alt.push_back(v.first); + + BOOST_FOREACH(blocks_ext_by_hash::value_type &v, m_invalid_blocks) + invalid.push_back(v.first); +} +//------------------------------------------------------------------ +difficulty_type blockchain_storage::get_difficulty_for_next_block() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + std::vector<uint64_t> timestamps; + std::vector<difficulty_type> commulative_difficulties; + size_t offset = m_blocks.size() - std::min(m_blocks.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT)); + if(!offset) + ++offset;//skip genesis block + for(; offset < m_blocks.size(); offset++) + { + timestamps.push_back(m_blocks[offset].bl.timestamp); + commulative_difficulties.push_back(m_blocks[offset].cumulative_difficulty); + } + return next_difficulty(timestamps, commulative_difficulties); +} +//------------------------------------------------------------------ +bool blockchain_storage::rollback_blockchain_switching(std::list<block>& original_chain, size_t rollback_height) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + //remove failed subchain + for(size_t i = m_blocks.size()-1; i >=rollback_height; i--) + { + bool r = pop_block_from_blockchain(); + CHECK_AND_ASSERT_MES(r, false, "PANIC!!! failed to remove block while chain switching during the rollback!"); + } + //return back original chain + BOOST_FOREACH(auto& bl, original_chain) + { + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + bool r = handle_block_to_main_chain(bl, bvc); + CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC!!! failed to add (again) block while chain switching during the rollback!"); + } + + LOG_PRINT_L0("Rollback success."); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + CHECK_AND_ASSERT_MES(alt_chain.size(), false, "switch_to_alternative_blockchain: empty chain passed"); + + size_t split_height = alt_chain.front()->second.height; + CHECK_AND_ASSERT_MES(m_blocks.size() > split_height, false, "switch_to_alternative_blockchain: blockchain size is lower than split height"); + + //disconnecting old chain + std::list<block> disconnected_chain; + for(size_t i = m_blocks.size()-1; i >=split_height; i--) + { + block b = m_blocks[i].bl; + bool r = pop_block_from_blockchain(); + CHECK_AND_ASSERT_MES(r, false, "failed to remove block on chain switching"); + disconnected_chain.push_front(b); + } + + //connecting new alternative chain + for(auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++) + { + auto ch_ent = *alt_ch_iter; + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + bool r = handle_block_to_main_chain(ch_ent->second.bl, bvc); + if(!r || !bvc.m_added_to_main_chain) + { + LOG_PRINT_L0("Failed to switch to alternative blockchain"); + rollback_blockchain_switching(disconnected_chain, split_height); + add_block_as_invalid(ch_ent->second, get_block_hash(ch_ent->second.bl)); + LOG_PRINT_L0("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl)); + m_alternative_chains.erase(ch_ent); + + for(auto alt_ch_to_orph_iter = ++alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); alt_ch_to_orph_iter++) + { + //block_verification_context bvc = boost::value_initialized<block_verification_context>(); + add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first); + m_alternative_chains.erase(*alt_ch_to_orph_iter); + } + return false; + } + } + + //pushing old chain as alternative chain + BOOST_FOREACH(auto& old_ch_ent, disconnected_chain) + { + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc); + if(!r) + { + LOG_ERROR("Failed to push ex-main chain blocks to alternative chain "); + rollback_blockchain_switching(disconnected_chain, split_height); + return false; + } + } + + //removing all_chain entries from alternative chain + BOOST_FOREACH(auto ch_ent, alt_chain) + { + m_alternative_chains.erase(ch_ent); + } + + LOG_PRINT_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_blocks.size(), LOG_LEVEL_0); + return true; +} +//------------------------------------------------------------------ +difficulty_type blockchain_storage::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) +{ + std::vector<uint64_t> timestamps; + std::vector<difficulty_type> commulative_difficulties; + if(alt_chain.size()< DIFFICULTY_BLOCKS_COUNT) + { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; + size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); + main_chain_count = std::min(main_chain_count, main_chain_stop_offset); + size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; + + if(!main_chain_start_offset) + ++main_chain_start_offset; //skip genesis block + for(; main_chain_start_offset < main_chain_stop_offset; ++main_chain_start_offset) + { + timestamps.push_back(m_blocks[main_chain_start_offset].bl.timestamp); + commulative_difficulties.push_back(m_blocks[main_chain_start_offset].cumulative_difficulty); + } + + CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= DIFFICULTY_BLOCKS_COUNT, false, "Internal error, alt_chain.size()["<< alt_chain.size() + << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT ); + BOOST_FOREACH(auto it, alt_chain) + { + timestamps.push_back(it->second.bl.timestamp); + commulative_difficulties.push_back(it->second.cumulative_difficulty); + } + }else + { + timestamps.resize(std::min(alt_chain.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT))); + commulative_difficulties.resize(std::min(alt_chain.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT))); + size_t count = 0; + size_t max_i = timestamps.size()-1; + BOOST_REVERSE_FOREACH(auto it, alt_chain) + { + timestamps[max_i - count] = it->second.bl.timestamp; + commulative_difficulties[max_i - count] = it->second.cumulative_difficulty; + count++; + if(count >= DIFFICULTY_BLOCKS_COUNT) + break; + } + } + return next_difficulty(timestamps, commulative_difficulties); +} +//------------------------------------------------------------------ +bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t height) +{ + CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); + CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type"); + if(boost::get<txin_gen>(b.miner_tx.vin[0]).height != height) + { + LOG_PRINT_RED_L0("The miner transaction in block has invalid height: " << boost::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height); + return false; + } + CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, + false, + "coinbase transaction transaction have wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + + //check outs overflow + if(!check_outs_overflow(b.miner_tx)) + { + LOG_PRINT_RED_L0("miner transaction have money overflow in block " << get_block_hash(b)); + return false; + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins) +{ + //validate reward + uint64_t money_in_use = 0; + BOOST_FOREACH(auto& o, b.miner_tx.vout) + money_in_use += o.amount; + + std::vector<size_t> last_blocks_sizes; + get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + bool block_too_big = false; + base_reward = get_block_reward(last_blocks_sizes, cumulative_block_size, block_too_big, already_generated_coins); + if(block_too_big) + { + LOG_PRINT_L0("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); + return false; + } + if(base_reward + fee < money_in_use) + { + LOG_ERROR("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + return false; + } + if(base_reward + fee != money_in_use) + { + LOG_ERROR("coinbase transaction doesn't use full amount of block reward: spent: " + << print_money(money_in_use) << ", block reward " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + return false; + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_backward_blocks_sizes(size_t from_height, std::vector<size_t>& sz, size_t count) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + CHECK_AND_ASSERT_MES(from_height < m_blocks.size(), false, "Internal error: get_backward_blocks_sizes called with from_height=" << from_height << ", blockchain height = " << m_blocks.size()); + + size_t start_offset = (from_height+1) - std::min((from_height+1), count); + for(size_t i = start_offset; i != from_height+1; i++) + sz.push_back(m_blocks[i].block_cumulative_size); + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_last_n_blocks_sizes(std::vector<size_t>& sz, size_t count) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(!m_blocks.size()) + return true; + return get_backward_blocks_sizes(m_blocks.size() -1, sz, count); +} +//------------------------------------------------------------------ +uint64_t blockchain_storage::get_current_comulative_blocksize_limit() +{ + return m_current_block_comul_sz_limit; +} +//------------------------------------------------------------------ +bool blockchain_storage::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) +{ + size_t txs_cumulative_size = 0; + uint64_t fee = 0; + size_t comul_sz_limit = 0; + std::vector<size_t> sz; + + CRITICAL_REGION_BEGIN(m_blockchain_lock); + get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + b.major_version = CURRENT_BLOCK_MAJOR_VERSION; + b.minor_version = CURRENT_BLOCK_MINOR_VERSION; + b.prev_id = get_tail_id(); + b.timestamp = time(NULL); + height = m_blocks.size(); + diffic = get_difficulty_for_next_block(); + CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead."); + + comul_sz_limit = m_current_block_comul_sz_limit - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + + CRITICAL_REGION_END(); + + m_tx_pool.fill_block_template(b, txs_cumulative_size, comul_sz_limit, fee); + + /* + two-phase miner transaction generation: we don't know exact block size until we prepare block, but we don't know reward until we know + block size, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block size + */ + //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size + bool r = construct_miner_tx(height, m_blocks.back().already_generated_coins, miner_address, b.miner_tx, fee, sz, txs_cumulative_size, ex_nonce, 11); + CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); +#ifdef _DEBUG + std::list<size_t> try_val; + try_val.push_back(get_object_blobsize(b.miner_tx)); +#endif + + size_t cumulative_size = txs_cumulative_size + get_object_blobsize(b.miner_tx); + size_t try_count = 0; + for(; try_count != 10; ++try_count) + { + r = construct_miner_tx(height, m_blocks.back().already_generated_coins, miner_address, b.miner_tx, fee, sz, cumulative_size, ex_nonce, 11); +#ifdef _DEBUG + try_val.push_back(get_object_blobsize(b.miner_tx)); +#endif + + CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); + size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); + if(coinbase_blob_size > cumulative_size - txs_cumulative_size ) + { + cumulative_size = txs_cumulative_size + coinbase_blob_size; + continue; + }else + { + if(coinbase_blob_size < cumulative_size - txs_cumulative_size ) + { + size_t delta = cumulative_size - txs_cumulative_size - coinbase_blob_size; + b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); + //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. + if(cumulative_size != txs_cumulative_size + get_object_blobsize(b.miner_tx)) + { + CHECK_AND_ASSERT_MES(cumulative_size + 1== txs_cumulative_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_cumulative_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx) ); + b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1 ); + if(cumulative_size != txs_cumulative_size + get_object_blobsize(b.miner_tx)) + {//fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size + LOG_PRINT_RED("Miner tx creation have no luck with delta_extra size = " << delta << " and " << delta - 1 , LOG_LEVEL_2); + cumulative_size += delta + 1; + continue; + } + LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); + } + } + CHECK_AND_ASSERT_MES(cumulative_size == txs_cumulative_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_cumulative_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx) ); + return true; + } + } + LOG_ERROR("Failed to create_block_template with " << try_count << " tries"); + return false; +} +//------------------------------------------------------------------ +bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) +{ + if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) + return true; + + size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); + CHECK_AND_ASSERT_MES(start_top_height < m_blocks.size(), false, "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_blocks.size()); + size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements:0; + do + { + timestamps.push_back(m_blocks[start_top_height].bl.timestamp); + if(start_top_height == 0) + break; + --start_top_height; + }while(start_top_height != stop_offset); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + //block is not related with head of main chain + //first of all - look in alternative chains container + auto it_main_prev = m_blocks_index.find(b.prev_id); + auto it_prev = m_alternative_chains.find(b.prev_id); + if(it_prev != m_alternative_chains.end() || it_main_prev != m_blocks_index.end()) + { + //we have new block in alternative chain + + //build alternative subchain, front -> mainchain, back -> alternative head + blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() + std::list<blocks_ext_by_hash::iterator> alt_chain; + std::vector<uint64_t> timestamps; + while(alt_it != m_alternative_chains.end()) + { + alt_chain.push_front(alt_it); + timestamps.push_back(alt_it->second.bl.timestamp); + alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); + } + + if(alt_chain.size()) + { + //make sure that it has right connection to main chain + CHECK_AND_ASSERT_MES(m_blocks.size() > alt_chain.front()->second.height, false, "main blockchain wrong height"); + crypto::hash h = null_hash; + get_block_hash(m_blocks[alt_chain.front()->second.height - 1].bl, h); + CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain have wrong connection to main chain"); + complete_timestamps_vector(alt_chain.front()->second.height - 1, timestamps); + }else + { + CHECK_AND_ASSERT_MES(it_main_prev != m_blocks_index.end(), false, "internal error: broken imperative condition it_main_prev != m_blocks_index.end()"); + complete_timestamps_vector(it_main_prev->second, timestamps); + } + //check timestamp correct + if(!check_block_timestamp(timestamps, b)) + { + LOG_PRINT_RED_L0("Block with id: " << id + << ENDL << " for alternative chain, have invalid timestamp: " << b.timestamp); + //add_block_as_invalid(b, id);//do not add blocks to invalid storage before proof of work check was passed + bvc.m_verifivation_failed = true; + return false; + } + + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = b; + bei.height = alt_chain.size() ? it_prev->second.height + 1 : it_main_prev->second + 1; + difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); + CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); + crypto::hash proof_of_work = null_hash; + if(!m_checkpoints.is_in_checkpoint_zone(bei.height)) + { + m_is_in_checkpoint_zone = false; + get_block_longhash(bei.bl, proof_of_work, bei.height); + + if(!check_hash(proof_of_work, current_diff)) + { + LOG_PRINT_RED_L0("Block with id: " << id + << ENDL << " for alternative chain, have not enough proof of work: " << proof_of_work + << ENDL << " expected difficulty: " << current_diff); + bvc.m_verifivation_failed = true; + return false; + } + }else + { + m_is_in_checkpoint_zone = true; + if(!m_checkpoints.check_block(bei.height, id)) + { + LOG_ERROR("CHECKPOINT VALIDATION FAILED"); + bvc.m_verifivation_failed = true; + return false; + } + } + + if(!prevalidate_miner_transaction(b, bei.height)) + { + LOG_PRINT_RED_L0("Block with id: " << string_tools::pod_to_hex(id) + << " (as alternative) have wrong miner transaction."); + bvc.m_verifivation_failed = true; + return false; + + } + + bei.cumulative_difficulty = alt_chain.size() ? it_prev->second.cumulative_difficulty: m_blocks[it_main_prev->second].cumulative_difficulty; + bei.cumulative_difficulty += current_diff; + +#ifdef _DEBUG + auto i_dres = m_alternative_chains.find(id); + CHECK_AND_ASSERT_MES(i_dres == m_alternative_chains.end(), false, "insertion of new alternative block returned as it already exist"); +#endif + auto i_res = m_alternative_chains.insert(blocks_ext_by_hash::value_type(id, bei)); + CHECK_AND_ASSERT_MES(i_res.second, false, "insertion of new alternative block returned as it already exist"); + alt_chain.push_back(i_res.first); + //check if difficulty bigger then in main chain + if(m_blocks.back().cumulative_difficulty < bei.cumulative_difficulty) + { + //do reorganize! + LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_blocks.size() -1 << " with cum_difficulty " << m_blocks.back().cumulative_difficulty + << ENDL << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty, LOG_LEVEL_0); + bool r = switch_to_alternative_blockchain(alt_chain); + if(r) bvc.m_added_to_main_chain = true; + else bvc.m_verifivation_failed = true; + return r; + } + LOG_PRINT_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height + << ENDL << "id:\t" << id + << ENDL << "PoW:\t" << proof_of_work + << ENDL << "difficulty:\t" << current_diff, LOG_LEVEL_0); + return true; + }else + { + //block orphaned + bvc.m_marked_as_orphaned = true; + LOG_PRINT_RED_L0("Block recognized as orphaned and rejected, id = " << id); + } + + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(start_offset >= m_blocks.size()) + return false; + for(size_t i = start_offset; i < start_offset + count && i < m_blocks.size();i++) + { + blocks.push_back(m_blocks[i].bl); + std::list<crypto::hash> missed_ids; + get_transactions(m_blocks[i].bl.tx_hashes, txs, missed_ids); + CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "have missed transactions in own block in main blockchain"); + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(start_offset >= m_blocks.size()) + return false; + + for(size_t i = start_offset; i < start_offset + count && i < m_blocks.size();i++) + blocks.push_back(m_blocks[i].bl); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + rsp.current_blockchain_height = get_current_blockchain_height(); + std::list<block> blocks; + get_blocks(arg.blocks, blocks, rsp.missed_ids); + + BOOST_FOREACH(const auto& bl, blocks) + { + std::list<crypto::hash> missed_tx_id; + std::list<transaction> txs; + get_transactions(bl.tx_hashes, txs, rsp.missed_ids); + CHECK_AND_ASSERT_MES(!missed_tx_id.size(), false, "Internal error: have missed missed_tx_id.size()=" << missed_tx_id.size() + << ENDL << "for block id = " << get_block_hash(bl)); + rsp.blocks.push_back(block_complete_entry()); + block_complete_entry& e = rsp.blocks.back(); + //pack block + e.block = t_serializable_object_to_blob(bl); + //pack transactions + BOOST_FOREACH(transaction& tx, txs) + e.txs.push_back(t_serializable_object_to_blob(tx)); + + } + //get another transactions, if need + std::list<transaction> txs; + get_transactions(arg.txs, txs, rsp.missed_ids); + //pack aside transactions + BOOST_FOREACH(const auto& tx, txs) + rsp.txs.push_back(t_serializable_object_to_blob(tx)); + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_alternative_blocks(std::list<block>& blocks) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + BOOST_FOREACH(const auto& alt_bl, m_alternative_chains) + { + blocks.push_back(alt_bl.second.bl); + } + return true; +} +//------------------------------------------------------------------ +size_t blockchain_storage::get_alternative_blocks_count() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_alternative_chains.size(); +} +//------------------------------------------------------------------ +bool blockchain_storage::add_out_to_get_random_outs(std::vector<std::pair<crypto::hash, size_t> >& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + transactions_container::iterator tx_it = m_transactions.find(amount_outs[i].first); + CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "internal error: transaction with id " << amount_outs[i].first << ENDL << + ", used in mounts global index for amount=" << amount << ": i=" << i << "not found in transactions index"); + CHECK_AND_ASSERT_MES(tx_it->second.tx.vout.size() > amount_outs[i].second, false, "internal error: in global outs index, transaction out index=" + << amount_outs[i].second << " more than transaction outputs = " << tx_it->second.tx.vout.size() << ", for tx id = " << amount_outs[i].first); + transaction& tx = tx_it->second.tx; + CHECK_AND_ASSERT_MES(tx.vout[amount_outs[i].second].target.type() == typeid(txout_to_key), false, "unknown tx out type"); + + //check if transaction is unlocked + if(!is_tx_spendtime_unlocked(tx.unlock_time)) + return false; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); + oen.global_amount_index = i; + oen.out_key = boost::get<txout_to_key>(tx.vout[amount_outs[i].second].target).key; + return true; +} +//------------------------------------------------------------------ +size_t blockchain_storage::find_end_of_allowed_index(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(!amount_outs.size()) + return 0; + size_t i = amount_outs.size(); + do + { + --i; + transactions_container::iterator it = m_transactions.find(amount_outs[i].first); + CHECK_AND_ASSERT_MES(it != m_transactions.end(), 0, "internal error: failed to find transaction from outputs index with tx_id=" << amount_outs[i].first); + if(it->second.m_keeper_block_height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW <= get_current_blockchain_height() ) + return i+1; + } while (i != 0); + return 0; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) +{ + srand(static_cast<unsigned int>(time(NULL))); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + BOOST_FOREACH(uint64_t amount, req.amounts) + { + 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; + auto it = m_outputs.find(amount); + if(it == m_outputs.end()) + { + LOG_ERROR("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist"); + continue;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist + } + std::vector<std::pair<crypto::hash, size_t> >& amount_outs = it->second; + //it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split + //lets find upper bound of not fresh outs + size_t up_index_limit = find_end_of_allowed_index(amount_outs); + CHECK_AND_ASSERT_MES(up_index_limit <= amount_outs.size(), false, "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << amount_outs.size()); + if(amount_outs.size() > req.outs_count) + { + std::set<size_t> used; + size_t try_count = 0; + for(uint64_t j = 0; j != req.outs_count && try_count < up_index_limit;) + { + size_t i = rand()%up_index_limit; + if(used.count(i)) + continue; + bool added = add_out_to_get_random_outs(amount_outs, result_outs, amount, i); + used.insert(i); + if(added) + ++j; + ++try_count; + } + }else + { + for(size_t i = 0; i != up_index_limit; i++) + add_out_to_get_random_outs(amount_outs, result_outs, amount, i); + } + } + return true; +} +//------------------------------------------------------------------ +//bool blockchain_storage::get_outs_for_amounts(uint64_t amount, std::vector<std::pair<crypto::hash, size_t> >& keys, std::map<crypto::hash, transaction>& txs) +//{ +// auto it = m_outputs.find(amount); +// if(it == m_outputs.end()) +// return false; +// keys = it->second; +// typedef std::pair<crypto::hash, size_t> pair; +// BOOST_FOREACH(pair& pr, keys) +// { +// auto it = m_transactions.find(pr.first); +// CHECK_AND_ASSERT_MES(it != m_transactions.end(), false, "internal error: transaction with id " << pr.first << " not found in internal index, but have refference for amount " << amount); +// txs[pr.first] = it->second.tx; +// } +// return true; +//} +//------------------------------------------------------------------ +bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + if(!qblock_ids.size() /*|| !req.m_total_height*/) + { + LOG_ERROR("Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << /*", m_height=" << req.m_total_height <<*/ ", dropping connection"); + return false; + } + //check genesis match + if(qblock_ids.back() != get_block_hash(m_blocks[0].bl)) + { + LOG_ERROR("Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block missmatch: " << ENDL << "id: " + << qblock_ids.back() << ", " << ENDL << "expected: " << get_block_hash(m_blocks[0].bl) + << "," << ENDL << " dropping connection"); + return false; + } + + /* Figure out what blocks we should request to get state_normal */ + size_t i = 0; + auto bl_it = qblock_ids.begin(); + auto block_index_it = m_blocks_index.find(*bl_it); + for(; bl_it != qblock_ids.end(); bl_it++, i++) + { + block_index_it = m_blocks_index.find(*bl_it); + if(block_index_it != m_blocks_index.end()) + break; + } + + if(bl_it == qblock_ids.end()) + { + LOG_ERROR("Internal error handling connection, can't find split point"); + return false; + } + + if(block_index_it == m_blocks_index.end()) + { + //this should NEVER happen, but, dose of paranoia in such cases is not too bad + LOG_ERROR("Internal error handling connection, can't find split point"); + return false; + } + + //we start to put block ids INCLUDING last known id, just to make other side be sure + starter_offset = block_index_it->second; + return true; +} +//------------------------------------------------------------------ +uint64_t blockchain_storage::block_difficulty(size_t i) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + CHECK_AND_ASSERT_MES(i < m_blocks.size(), false, "wrong block index i = " << i << " at blockchain_storage::block_difficulty()"); + if(i == 0) + return m_blocks[i].cumulative_difficulty; + + return m_blocks[i].cumulative_difficulty - m_blocks[i-1].cumulative_difficulty; +} +//------------------------------------------------------------------ +void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_index) +{ + std::stringstream ss; + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(start_index >=m_blocks.size()) + { + LOG_PRINT_L0("Wrong starter index set: " << start_index << ", expected max index " << m_blocks.size()-1); + return; + } + + for(size_t i = start_index; i != m_blocks.size() && i != end_index; i++) + { + ss << "height " << i << ", timastamp " << m_blocks[i].bl.timestamp << ", cumul_dif " << m_blocks[i].cumulative_difficulty << ", cumul_size " << m_blocks[i].block_cumulative_size + << "\nid\t\t" << get_block_hash(m_blocks[i].bl) + << "\ndifficulty\t\t" << block_difficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.tx_hashes.size() << ENDL; + } + LOG_PRINT_L0("Current blockchain:" << ENDL << ss.str()); +} +//------------------------------------------------------------------ +void blockchain_storage::print_blockchain_index() +{ + std::stringstream ss; + CRITICAL_REGION_LOCAL(m_blockchain_lock); + BOOST_FOREACH(const blocks_by_id_index::value_type& v, m_blocks_index) + ss << "id\t\t" << v.first << " height" << v.second << ENDL << ""; + + LOG_PRINT_L0("Current blockchain index:" << ENDL << ss.str()); +} +//------------------------------------------------------------------ +void blockchain_storage::print_blockchain_outs(const std::string& file) +{ + std::stringstream ss; + CRITICAL_REGION_LOCAL(m_blockchain_lock); + BOOST_FOREACH(const outputs_container::value_type& v, m_outputs) + { + const std::vector<std::pair<crypto::hash, size_t> >& vals = v.second; + if(vals.size()) + { + ss << "amount: " << v.first << ENDL; + for(size_t i = 0; i != vals.size(); i++) + ss << "\t" << vals[i].first << ": " << vals[i].second << ENDL; + } + } + if(file_io_utils::save_string_to_file(file, ss.str())) + { + LOG_PRINT_L0("Current outputs index writen to file: " << file); + }else + { + LOG_PRINT_L0("Failed to write current outputs index to file: " << file); + } +} +//------------------------------------------------------------------ +bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(!find_blockchain_supplement(qblock_ids, resp.start_height)) + return false; + + resp.total_height = get_current_blockchain_height(); + size_t count = 0; + for(size_t i = resp.start_height; i != m_blocks.size() && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) + resp.m_block_ids.push_back(get_block_hash(m_blocks[i].bl)); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(!find_blockchain_supplement(qblock_ids, start_height)) + return false; + + total_height = get_current_blockchain_height(); + size_t count = 0; + for(size_t i = start_height; i != m_blocks.size() && count < max_count; i++, count++) + { + blocks.resize(blocks.size()+1); + blocks.back().first = m_blocks[i].bl; + std::list<crypto::hash> mis; + get_transactions(m_blocks[i].bl.tx_hashes, blocks.back().second, mis); + CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::add_block_as_invalid(const block& bl, const crypto::hash& h) +{ + block_extended_info bei = AUTO_VAL_INIT(bei); + bei.bl = bl; + return add_block_as_invalid(bei, h); +} +//------------------------------------------------------------------ +bool blockchain_storage::add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + auto i_res = m_invalid_blocks.insert(std::map<crypto::hash, block_extended_info>::value_type(h, bei)); + CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed"); + LOG_PRINT_L0("BLOCK ADDED AS INVALID: " << h << ENDL << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size()); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::have_block(const crypto::hash& id) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(m_blocks_index.count(id)) + return true; + if(m_alternative_chains.count(id)) + return true; + /*if(m_orphaned_blocks.get<by_id>().count(id)) + return true;*/ + + /*if(m_orphaned_by_tx.count(id)) + return true;*/ + if(m_invalid_blocks.count(id)) + return true; + + return false; +} +//------------------------------------------------------------------ +bool blockchain_storage::handle_block_to_main_chain(const block& bl, block_verification_context& bvc) +{ + crypto::hash id = get_block_hash(bl); + return handle_block_to_main_chain(bl, id, bvc); +} +//------------------------------------------------------------------ +bool blockchain_storage::push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + size_t i = 0; + BOOST_FOREACH(const auto& ot, tx.vout) + { + outputs_container::mapped_type& amount_index = m_outputs[ot.amount]; + amount_index.push_back(std::pair<crypto::hash, size_t>(tx_id, i)); + global_indexes.push_back(amount_index.size()-1); + ++i; + } + return true; +} +//------------------------------------------------------------------ +size_t blockchain_storage::get_total_transactions() +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return m_transactions.size(); +} +//------------------------------------------------------------------ +bool blockchain_storage::get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + auto it = m_outputs.find(amount); + if(it == m_outputs.end()) + return true; + + BOOST_FOREACH(const auto& out_entry, it->second) + { + auto tx_it = m_transactions.find(out_entry.first); + CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "transactions outs global index consistency broken: wrong tx id in index"); + CHECK_AND_ASSERT_MES(tx_it->second.tx.vout.size() > out_entry.second, false, "transactions outs global index consistency broken: index in tx_outx more then size"); + CHECK_AND_ASSERT_MES(tx_it->second.tx.vout[out_entry.second].target.type() == typeid(txout_to_key), false, "transactions outs global index consistency broken: index in tx_outx more then size"); + pkeys.push_back(boost::get<txout_to_key>(tx_it->second.tx.vout[out_entry.second].target).key); + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + size_t i = tx.vout.size()-1; + BOOST_REVERSE_FOREACH(const auto& ot, tx.vout) + { + auto it = m_outputs.find(ot.amount); + CHECK_AND_ASSERT_MES(it != m_outputs.end(), false, "transactions outs global index consistency broken"); + CHECK_AND_ASSERT_MES(it->second.size(), false, "transactions outs global index: empty index for amount: " << ot.amount); + CHECK_AND_ASSERT_MES(it->second.back().first == tx_id , false, "transactions outs global index consistency broken: tx id missmatch"); + CHECK_AND_ASSERT_MES(it->second.back().second == i, false, "transactions outs global index consistency broken: in transaction index missmatch"); + it->second.pop_back(); + --i; + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + struct add_transaction_input_visitor: public boost::static_visitor<bool> + { + key_images_container& m_spent_keys; + const crypto::hash& m_tx_id; + const crypto::hash& m_bl_id; + add_transaction_input_visitor(key_images_container& spent_keys, const crypto::hash& tx_id, const crypto::hash& bl_id):m_spent_keys(spent_keys), m_tx_id(tx_id), m_bl_id(bl_id) + {} + bool operator()(const txin_to_key& in) const + { + const crypto::key_image& ki = in.k_image; + auto r = m_spent_keys.insert(ki); + if(!r.second) + { + //double spend detected + LOG_PRINT_L0("tx with id: " << m_tx_id << " in block id: " << m_bl_id << " have input marked as spent with key image: " << ki << ", block declined"); + return false; + } + return true; + } + + bool operator()(const txin_gen& tx) const{return true;} + bool operator()(const txin_to_script& tx) const{return false;} + bool operator()(const txin_to_scripthash& tx) const{return false;} + }; + + BOOST_FOREACH(const txin_v& in, tx.vin) + { + if(!boost::apply_visitor(add_transaction_input_visitor(m_spent_keys, tx_id, bl_id), in)) + { + LOG_ERROR("critical internal error: add_transaction_input_visitor failed. but here key_images should be shecked"); + purge_transaction_keyimages_from_blockchain(tx, false); + return false; + } + } + transaction_chain_entry ch_e; + ch_e.m_keeper_block_height = bl_height; + ch_e.tx = tx; + auto i_r = m_transactions.insert(std::pair<crypto::hash, transaction_chain_entry>(tx_id, ch_e)); + if(!i_r.second) + { + LOG_PRINT_L0("tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain"); + return false; + } + bool r = push_transaction_to_global_outs_index(tx, tx_id, i_r.first->second.m_global_output_indexes); + CHECK_AND_ASSERT_MES(r, false, "failed to return push_transaction_to_global_outs_index tx id " << tx_id); + LOG_PRINT_L2("Added transaction to blockchain history:" << ENDL + << "tx_id: " << tx_id << ENDL + << "inputs: " << tx.vin.size() << ", outs: " << tx.vout.size() << ", spend money: " << print_money(get_outs_money_amount(tx)) << "(fee: " << (is_coinbase(tx) ? "0[coinbase]" : print_money(get_tx_fee(tx))) << ")"); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + auto it = m_transactions.find(tx_id); + if(it == m_transactions.end()) + { + LOG_PRINT_RED_L0("warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id); + return false; + } + + CHECK_AND_ASSERT_MES(it->second.m_global_output_indexes.size(), false, "internal error: global indexes for transaction " << tx_id << " is empty"); + indexs = it->second.m_global_output_indexes; + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + bool res = check_tx_inputs(tx, &max_used_block_height); + if(!res) return false; + CHECK_AND_ASSERT_MES(max_used_block_height < m_blocks.size(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_blocks.size()); + get_block_hash(m_blocks[max_used_block_height].bl, max_used_block_id); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::have_tx_keyimges_as_spent(const transaction &tx) +{ + BOOST_FOREACH(const txin_v& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, in_to_key, true); + if(have_tx_keyimg_as_spent(in_to_key.k_image)) + return true; + } + return false; +} +//------------------------------------------------------------------ +bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) +{ + crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + return check_tx_inputs(tx, tx_prefix_hash, pmax_used_block_height); +} +//------------------------------------------------------------------ +bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height) +{ + size_t sig_index = 0; + if(pmax_used_block_height) + *pmax_used_block_height = 0; + + BOOST_FOREACH(const auto& txin, tx.vin) + { + CHECK_AND_ASSERT_MES(txin.type() == typeid(txin_to_key), false, "wrong type id in tx input at blockchain_storage::check_tx_inputs"); + const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); + + CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx)); + + if(have_tx_keyimg_as_spent(in_to_key.k_image)) + { + LOG_PRINT_L1("Key image already spent in blockchain: " << string_tools::pod_to_hex(in_to_key.k_image)); + return false; + } + + CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); + if(!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[sig_index], pmax_used_block_height)) + { + LOG_PRINT_L0("Failed to check ring signature for tx " << get_transaction_hash(tx)); + return false; + } + + sig_index++; + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::is_tx_spendtime_unlocked(uint64_t unlock_time) +{ + if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + { + //interpret as block index + if(get_current_blockchain_height()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) + return true; + else + return false; + }else + { + //interpret as time + uint64_t current_time = static_cast<uint64_t>(time(NULL)); + if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS >= unlock_time) + return true; + else + return false; + } + return false; +} +//------------------------------------------------------------------ +bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t* pmax_related_block_height) +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + struct outputs_visitor + { + std::vector<const crypto::public_key *>& m_results_collector; + blockchain_storage& m_bch; + outputs_visitor(std::vector<const crypto::public_key *>& results_collector, blockchain_storage& bch):m_results_collector(results_collector), m_bch(bch) + {} + bool handle_output(const transaction& tx, const tx_out& out) + { + //check tx unlock time + if(!m_bch.is_tx_spendtime_unlocked(tx.unlock_time)) + { + LOG_PRINT_L0("One of outputs for one of inputs have wrong tx.unlock_time = " << tx.unlock_time); + return false; + } + + if(out.target.type() != typeid(txout_to_key)) + { + LOG_PRINT_L0("Output have wrong type id, which=" << out.target.which()); + return false; + } + + m_results_collector.push_back(&boost::get<txout_to_key>(out.target).key); + return true; + } + }; + + //check ring signature + std::vector<const crypto::public_key *> output_keys; + outputs_visitor vi(output_keys, *this); + if(!scan_outputkeys_for_indexes(txin, vi, pmax_related_block_height)) + { + LOG_PRINT_L0("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); + return false; + } + + if(txin.key_offsets.size() != output_keys.size()) + { + LOG_PRINT_L0("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); + return false; + } + CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + if(m_is_in_checkpoint_zone) + return true; + return crypto::check_ring_signature(tx_prefix_hash, txin.k_image, output_keys, sig.data()); +} +//------------------------------------------------------------------ +uint64_t blockchain_storage::get_adjusted_time() +{ + //TODO: add collecting median time + return time(NULL); +} +//------------------------------------------------------------------ +bool blockchain_storage::check_block_timestamp_main(const block& b) +{ + if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) + { + LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); + return false; + } + + std::vector<uint64_t> timestamps; + size_t offset = m_blocks.size() <= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW ? 0: m_blocks.size()- BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; + for(;offset!= m_blocks.size(); ++offset) + timestamps.push_back(m_blocks[offset].bl.timestamp); + + return check_block_timestamp(std::move(timestamps), b); +} +//------------------------------------------------------------------ +bool blockchain_storage::check_block_timestamp(std::vector<uint64_t> timestamps, const block& b) +{ + if(timestamps.size() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) + return true; + + uint64_t median_ts = epee::misc_utils::median(timestamps); + + if(b.timestamp < median_ts) + { + LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); + return false; + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) +{ + TIME_MEASURE_START(block_processing_time); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + if(bl.prev_id != get_tail_id()) + { + LOG_PRINT_L0("Block with id: " << id << ENDL + << "have wrong prev_id: " << bl.prev_id << ENDL + << "expected: " << get_tail_id()); + return false; + } + + if(!check_block_timestamp_main(bl)) + { + LOG_PRINT_L0("Block with id: " << id << ENDL + << "have invalid timestamp: " << bl.timestamp); + //add_block_as_invalid(bl, id);//do not add blocks to invalid storage befor proof of work check was passed + bvc.m_verifivation_failed = true; + return false; + } + + //check proof of work + TIME_MEASURE_START(target_calculating_time); + difficulty_type current_diffic = get_difficulty_for_next_block(); + CHECK_AND_ASSERT_MES(current_diffic, false, "!!!!!!!!! difficulty overhead !!!!!!!!!"); + TIME_MEASURE_FINISH(target_calculating_time); + TIME_MEASURE_START(longhash_calculating_time); + crypto::hash proof_of_work = null_hash; + if(!m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height())) + { + proof_of_work = get_block_longhash(bl, m_blocks.size()); + + if(!check_hash(proof_of_work, current_diffic)) + { + LOG_PRINT_L0("Block with id: " << id << ENDL + << "have not enough proof of work: " << proof_of_work << ENDL + << "nexpected difficulty: " << current_diffic ); + bvc.m_verifivation_failed = true; + return false; + } + }else + { + if(!m_checkpoints.check_block(get_current_blockchain_height(), id)) + { + LOG_ERROR("CHECKPOINT VALIDATION FAILED"); + bvc.m_verifivation_failed = true; + return false; + } + } + TIME_MEASURE_FINISH(longhash_calculating_time); + + if(!prevalidate_miner_transaction(bl, m_blocks.size())) + { + LOG_PRINT_L0("Block with id: " << id + << " failed to pass prevalidation"); + bvc.m_verifivation_failed = true; + return false; + } + size_t coinbase_blob_size = get_object_blobsize(bl.miner_tx); + size_t cumulative_block_size = coinbase_blob_size; + //process transactions + if(!add_transaction_from_block(bl.miner_tx, get_transaction_hash(bl.miner_tx), id, get_current_blockchain_height())) + { + LOG_PRINT_L0("Block with id: " << id << " failed to add transaction to blockchain storage"); + bvc.m_verifivation_failed = true; + return false; + } + size_t tx_processed_count = 0; + uint64_t fee_summary = 0; + BOOST_FOREACH(const crypto::hash& tx_id, bl.tx_hashes) + { + transaction tx; + size_t blob_size = 0; + uint64_t fee = 0; + if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee)) + { + LOG_PRINT_L0("Block with id: " << id << "have at least one unknown transaction with id: " << tx_id); + purge_block_data_from_blockchain(bl, tx_processed_count); + //add_block_as_invalid(bl, id); + bvc.m_verifivation_failed = true; + return false; + } + if(!check_tx_inputs(tx)) + { + LOG_PRINT_L0("Block with id: " << id << "have at least one transaction (id: " << tx_id << ") with wrong inputs."); + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + bool add_res = m_tx_pool.add_tx(tx, tvc, true); + CHECK_AND_ASSERT_MES2(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + purge_block_data_from_blockchain(bl, tx_processed_count); + add_block_as_invalid(bl, id); + LOG_PRINT_L0("Block with id " << id << " added as invalid becouse of wrong inputs in transactions"); + bvc.m_verifivation_failed = true; + return false; + } + + if(!add_transaction_from_block(tx, tx_id, id, get_current_blockchain_height())) + { + LOG_PRINT_L0("Block with id: " << id << " failed to add transaction to blockchain storage"); + cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + bool add_res = m_tx_pool.add_tx(tx, tvc, true); + CHECK_AND_ASSERT_MES2(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + purge_block_data_from_blockchain(bl, tx_processed_count); + bvc.m_verifivation_failed = true; + return false; + } + fee_summary += fee; + cumulative_block_size += blob_size; + ++tx_processed_count; + } + uint64_t base_reward = 0; + uint64_t already_generated_coins = m_blocks.size() ? m_blocks.back().already_generated_coins:0; + if(!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins)) + { + LOG_PRINT_L0("Block with id: " << id + << " have wrong miner transaction"); + purge_block_data_from_blockchain(bl, tx_processed_count); + bvc.m_verifivation_failed = true; + return false; + } + + + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = bl; + bei.block_cumulative_size = cumulative_block_size; + bei.cumulative_difficulty = current_diffic; + bei.already_generated_coins = already_generated_coins + base_reward; + if(m_blocks.size()) + bei.cumulative_difficulty += m_blocks.back().cumulative_difficulty; + + bei.height = m_blocks.size(); + + auto ind_res = m_blocks_index.insert(std::pair<crypto::hash, size_t>(id, bei.height)); + if(!ind_res.second) + { + LOG_ERROR("block with id: " << id << " already in block indexes"); + purge_block_data_from_blockchain(bl, tx_processed_count); + bvc.m_verifivation_failed = true; + return false; + } + + m_blocks.push_back(bei); + update_next_comulative_size_limit(); + TIME_MEASURE_FINISH(block_processing_time); + LOG_PRINT_L1("+++++ BLOCK SUCCESSFULLY ADDED" << ENDL << "id:\t" << id + << ENDL << "PoW:\t" << proof_of_work + << ENDL << "HEIGHT " << bei.height << ", difficulty:\t" << current_diffic + << ENDL << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) + << "), coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size + << ", " << block_processing_time << "("<< target_calculating_time << "/" << longhash_calculating_time << ")ms"); + + bvc.m_added_to_main_chain = true; + /*if(!m_orphanes_reorganize_in_work) + review_orphaned_blocks_with_new_block_id(id, true);*/ + + m_tx_pool.on_blockchain_inc(bei.height, id); + //LOG_PRINT_L0("BLOCK: " << ENDL << "" << dump_obj_as_json(bei.bl)); + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::update_next_comulative_size_limit() +{ + std::vector<size_t> sz; + get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + + uint64_t median = misc_utils::median(sz); + if(median <= CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) + median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + + m_current_block_comul_sz_limit = median*2; + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::add_new_block(const block& bl_, block_verification_context& bvc) +{ + //copy block here to let modify block.target + block bl = bl_; + crypto::hash id = get_block_hash(bl); + CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process + CRITICAL_REGION_LOCAL1(m_blockchain_lock); + if(have_block(id)) + { + LOG_PRINT_L3("block with id = " << id << " already exists"); + bvc.m_already_exists = true; + return false; + } + + //check that block refers to chain tail + if(!(bl.prev_id == get_tail_id())) + { + //chain switching or wrong block + bvc.m_added_to_main_chain = false; + return handle_alternative_block(bl, id, bvc); + //never relay alternative blocks + } + + return handle_block_to_main_chain(bl, id, bvc); +}
\ No newline at end of file diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h new file mode 100644 index 000000000..4ff5e83ff --- /dev/null +++ b/src/cryptonote_core/blockchain_storage.h @@ -0,0 +1,297 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include <boost/serialization/serialization.hpp> +#include <boost/serialization/version.hpp> +#include <boost/serialization/list.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/global_fun.hpp> +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/member.hpp> +#include <boost/foreach.hpp> +#include <atomic> + +#include "tx_pool.h" +#include "cryptonote_basic.h" +#include "common/util.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "difficulty.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "verification_context.h" +#include "crypto/hash.h" +#include "checkpoints.h" + +namespace cryptonote +{ + + /************************************************************************/ + /* */ + /************************************************************************/ + class blockchain_storage + { + public: + struct transaction_chain_entry + { + transaction tx; + uint64_t m_keeper_block_height; + size_t m_blob_size; + std::vector<uint64_t> m_global_output_indexes; + }; + + struct block_extended_info + { + block bl; + uint64_t height; + size_t block_cumulative_size; + difficulty_type cumulative_difficulty; + uint64_t already_generated_coins; + }; + + blockchain_storage(tx_memory_pool& tx_pool):m_tx_pool(tx_pool), m_current_block_comul_sz_limit(0), m_is_in_checkpoint_zone(false) + {}; + + bool init() { return init(tools::get_default_data_dir()); } + bool init(const std::string& config_folder); + bool deinit(); + + void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } + + //bool push_new_block(); + bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs); + bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks); + bool get_alternative_blocks(std::list<block>& blocks); + //bool get_orphaned_blocks(std::list<block>& orphaned_by_prev_id, std::list<block>& orphaned_by_tx); + //size_t get_orphaned_by_prev_blocks_count(); + //size_t get_orphaned_by_tx_blocks_count(); + size_t get_alternative_blocks_count(); + crypto::hash get_block_id_by_height(uint64_t height); + bool get_block_by_hash(const crypto::hash &h, block &blk); + void get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid); + + template<class archive_t> + void serialize(archive_t & ar, const unsigned int version); + + bool have_tx(const crypto::hash &id); + bool have_tx_keyimges_as_spent(const transaction &tx); + bool have_tx_keyimg_as_spent(const crypto::key_image &key_im); + transaction *get_tx(const crypto::hash &id); + + template<class visitor_t> + bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height = NULL); + + uint64_t get_current_blockchain_height(); + crypto::hash get_tail_id(); + crypto::hash get_tail_id(uint64_t& height); + difficulty_type get_difficulty_for_next_block(); + bool add_new_block(const block& bl_, block_verification_context& bvc); + + bool reset_and_set_genesis_block(const block& b); + bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce); + bool have_block(const crypto::hash& id); + size_t get_total_transactions(); + bool get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys); + bool get_short_chain_history(std::list<crypto::hash>& ids); + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp); + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset); + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); + //bool get_chain_entry(const NOTIFY_REQUEST_CHAIN_ENTRY::request& req, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp); + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); + bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + //bool get_outs_for_amounts(uint64_t amount, std::vector<std::pair<crypto::hash, size_t> >& keys, std::map<crypto::hash, transaction>& txs); + bool get_backward_blocks_sizes(size_t from_height, std::vector<size_t>& sz, size_t count); + bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs); + bool store_blockchain(); + bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t* pmax_related_block_height = NULL); + bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height = NULL); + bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL); + bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id); + uint64_t get_current_comulative_blocksize_limit(); + + template<class t_ids_container, class t_blocks_container, class t_missed_container> + bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) + { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + BOOST_FOREACH(const auto& bl_id, block_ids) + { + auto it = m_blocks_index.find(bl_id); + if(it == m_blocks_index.end()) + missed_bs.push_back(bl_id); + else + { + CHECK_AND_ASSERT_MES(it->second < m_blocks.size(), false, "Internal error: bl_id=" << string_tools::pod_to_hex(bl_id) + << " have index record with offset="<<it->second<< ", bigger then m_blocks.size()=" << m_blocks.size()); + blocks.push_back(m_blocks[it->second].bl); + } + } + return true; + } + + template<class t_ids_container, class t_tx_container, class t_missed_container> + bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) + { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + BOOST_FOREACH(const auto& tx_id, txs_ids) + { + auto it = m_transactions.find(tx_id); + if(it == m_transactions.end()) + { + transaction tx; + if(!m_tx_pool.get_transaction(tx_id, tx)) + missed_txs.push_back(tx_id); + else + txs.push_back(tx); + } + else + txs.push_back(it->second.tx); + } + return true; + } + //debug functions + void print_blockchain(uint64_t start_index, uint64_t end_index); + void print_blockchain_index(); + void print_blockchain_outs(const std::string& file); + + private: + typedef std::unordered_map<crypto::hash, size_t> blocks_by_id_index; + typedef std::unordered_map<crypto::hash, transaction_chain_entry> transactions_container; + typedef std::unordered_set<crypto::key_image> key_images_container; + typedef std::vector<block_extended_info> blocks_container; + typedef std::unordered_map<crypto::hash, block_extended_info> blocks_ext_by_hash; + typedef std::unordered_map<crypto::hash, block> blocks_by_hash; + typedef std::map<uint64_t, std::vector<std::pair<crypto::hash, size_t>>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction + + tx_memory_pool& m_tx_pool; + critical_section m_blockchain_lock; // TODO: add here reader/writer lock + + // main chain + blocks_container m_blocks; // height -> block_extended_info + blocks_by_id_index m_blocks_index; // crypto::hash -> height + transactions_container m_transactions; + key_images_container m_spent_keys; + size_t m_current_block_comul_sz_limit; + + + // all alternative chains + blocks_ext_by_hash m_alternative_chains; // crypto::hash -> block_extended_info + + // some invalid blocks + blocks_ext_by_hash m_invalid_blocks; // crypto::hash -> block_extended_info + outputs_container m_outputs; + + + std::string m_config_folder; + checkpoints m_checkpoints; + std::atomic<bool> m_is_in_checkpoint_zone; + + + + + bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain); + bool pop_block_from_blockchain(); + bool purge_block_data_from_blockchain(const block& b, size_t processed_tx_count); + bool purge_transaction_from_blockchain(const crypto::hash& tx_id); + bool purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check); + + bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); + bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); + bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); + //bool handle_block_as_orphaned(const block& b, const crypto::hash& id, block_verification_context& bvc); + difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei); + bool prevalidate_miner_transaction(const block& b, uint64_t height); + bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins); + bool validate_transaction(const block& b, uint64_t height, const transaction& tx); + //bool add_block_to_orphaned_by_tx(const block& bl, const crypto::hash& h); + //bool finish_forward_orphaned_chain(std::list< orphans_indexed_container::index<by_id>::type::iterator >& orph_chain); + bool rollback_blockchain_switching(std::list<block>& original_chain, size_t rollback_height); + bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height); + bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes); + bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); + //bool review_orphaned_blocks_with_new_block_id(const crypto::hash& id, bool main_chain); + //void review_orphaned_blocks_finisher(); + bool get_last_n_blocks_sizes(std::vector<size_t>& sz, size_t count); + bool add_out_to_get_random_outs(std::vector<std::pair<crypto::hash, size_t> >& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i); + bool is_tx_spendtime_unlocked(uint64_t unlock_time); + bool add_block_as_invalid(const block& bl, const crypto::hash& h); + bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); + size_t find_end_of_allowed_index(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs); + uint64_t block_difficulty(size_t i); + bool check_block_timestamp_main(const block& b); + bool check_block_timestamp(std::vector<uint64_t> timestamps, const block& b); + uint64_t get_adjusted_time(); + bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps); + bool update_next_comulative_size_limit(); + }; + + + /************************************************************************/ + /* */ + /************************************************************************/ + + #define CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER 11 + + template<class archive_t> + void blockchain_storage::serialize(archive_t & ar, const unsigned int version) + { + if(version < CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER) + return; + CRITICAL_REGION_LOCAL(m_blockchain_lock); + ar & m_blocks; + ar & m_blocks_index; + ar & m_transactions; + ar & m_spent_keys; + ar & m_alternative_chains; + ar & m_outputs; + ar & m_invalid_blocks; + ar & m_current_block_comul_sz_limit; + } + + //------------------------------------------------------------------ + template<class visitor_t> + bool blockchain_storage::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height) + { + + auto it = m_outputs.find(tx_in_to_key.amount); + if(it == m_outputs.end() || !tx_in_to_key.key_offsets.size()) + return false; + + std::vector<uint64_t> absolute_offsets = relative_output_offsets_to_absolute(tx_in_to_key.key_offsets); + + + std::vector<std::pair<crypto::hash, size_t> >& amount_outs_vec = it->second; + size_t count = 0; + BOOST_FOREACH(uint64_t i, absolute_offsets) + { + if(i >= amount_outs_vec.size() ) + { + LOG_PRINT_L0("Wrong index in transaction inputs: " << i << ", expected maximum " << amount_outs_vec.size() - 1); + return false; + } + transactions_container::iterator tx_it = m_transactions.find(amount_outs_vec[i].first); + CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "Wrong transaction id in output indexes: " <<string_tools::pod_to_hex(amount_outs_vec[i].first)); + CHECK_AND_ASSERT_MES(amount_outs_vec[i].second < tx_it->second.tx.vout.size(), false, + "Wrong index in transaction outputs: " << amount_outs_vec[i].second << ", expected less then " << tx_it->second.tx.vout.size()); + if(!vis.handle_output(tx_it->second.tx, tx_it->second.tx.vout[amount_outs_vec[i].second])) + { + LOG_PRINT_L0("Failed to handle_output for output no = " << count << ", with absolute offset " << i); + return false; + } + if(count++ == absolute_offsets.size()-1 && pmax_related_block_height) + { + if(*pmax_related_block_height < tx_it->second.m_keeper_block_height) + *pmax_related_block_height = tx_it->second.m_keeper_block_height; + } + } + + return true; + } +} + + + +BOOST_CLASS_VERSION(cryptonote::blockchain_storage, CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER) diff --git a/src/cryptonote_core/blockchain_storage_boost_serialization.h b/src/cryptonote_core/blockchain_storage_boost_serialization.h new file mode 100644 index 000000000..6aa06e87f --- /dev/null +++ b/src/cryptonote_core/blockchain_storage_boost_serialization.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +namespace boost +{ + namespace serialization + { + + + template<class archive_t> + void serialize(archive_t & ar, cryptonote::blockchain_storage::transaction_chain_entry& te, const unsigned int version) + { + ar & te.tx; + ar & te.m_keeper_block_height; + ar & te.m_blob_size; + ar & te.m_global_output_indexes; + } + + template<class archive_t> + void serialize(archive_t & ar, cryptonote::blockchain_storage::block_extended_info& ei, const unsigned int version) + { + ar & ei.bl; + ar & ei.height; + ar & ei.cumulative_difficulty; + ar & ei.block_cumulative_size; + ar & ei.already_generated_coins; + } + + } +} diff --git a/src/cryptonote_core/checkpoints.cpp b/src/cryptonote_core/checkpoints.cpp new file mode 100644 index 000000000..54c2f3a6d --- /dev/null +++ b/src/cryptonote_core/checkpoints.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "include_base_utils.h" +using namespace epee; + +#include "checkpoints.h" + +namespace cryptonote +{ + //--------------------------------------------------------------------------- + checkpoints::checkpoints() + { + } + //--------------------------------------------------------------------------- + bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str) + { + crypto::hash h = null_hash; + bool r = epee::string_tools::parse_tpod_from_hex_string(hash_str, h); + CHECK_AND_ASSERT_MES(r, false, "WRONG HASH IN CHECKPOINTS!!!"); + CHECK_AND_ASSERT_MES(0 == m_points.count(height), false, "WRONG HASH IN CHECKPOINTS!!!"); + m_points[height] = h; + return true; + } + //--------------------------------------------------------------------------- + bool checkpoints::is_in_checkpoint_zone(uint64_t height) const + { + return !m_points.empty() && (height <= (--m_points.end())->first); + } + //--------------------------------------------------------------------------- + bool checkpoints::check_block(uint64_t height, const crypto::hash& h) const + { + auto it = m_points.find(height); + if(it == m_points.end()) + return true; + + if(it->second == h) + { + LOG_PRINT_GREEN("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h, LOG_LEVEL_0); + return true; + }else + { + LOG_ERROR("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h); + return false; + } + } +} diff --git a/src/cryptonote_core/checkpoints.h b/src/cryptonote_core/checkpoints.h new file mode 100644 index 000000000..20014b1c8 --- /dev/null +++ b/src/cryptonote_core/checkpoints.h @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include <map> +#include "cryptonote_basic_impl.h" + + +namespace cryptonote +{ + class checkpoints + { + public: + checkpoints(); + bool add_checkpoint(uint64_t height, const std::string& hash_str); + bool is_in_checkpoint_zone(uint64_t height) const; + bool check_block(uint64_t height, const crypto::hash& h) const; + private: + std::map<uint64_t, crypto::hash> m_points; + }; +} diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h new file mode 100644 index 000000000..3d539de98 --- /dev/null +++ b/src/cryptonote_core/checkpoints_create.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "checkpoints.h" +#include "misc_log_ex.h" + +#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(checkpoints.add_checkpoint(h, hash), false); + +namespace cryptonote { + inline bool create_checkpoints(cryptonote::checkpoints& checkpoints) + { + ADD_CHECKPOINT(79000, "cae33204e624faeb64938d80073bb7bbacc27017dc63f36c5c0f313cad455a02"); + ADD_CHECKPOINT(140000, "993059fb6ab92db7d80d406c67a52d9c02d873ca34b6290a12b744c970208772"); + ADD_CHECKPOINT(200000, "a5f74c7542077df6859f48b5b1f9c3741f29df38f91a47e14c94b5696e6c3073"); + ADD_CHECKPOINT(230580, "32bd7cb6c68a599cf2861941f29002a5e203522b9af54f08dfced316f6459103"); + ADD_CHECKPOINT(260000, "f68e70b360ca194f48084da7a7fd8e0251bbb4b5587f787ca65a6f5baf3f5947"); + ADD_CHECKPOINT(300000, "8e80861713f68354760dc10ea6ea79f5f3ff28f39b3f0835a8637463b09d70ff"); + ADD_CHECKPOINT(390285, "e00bdc9bf407aeace2f3109de11889ed25894bf194231d075eddaec838097eb7"); + ADD_CHECKPOINT(417000, "2dc96f8fc4d4a4d76b3ed06722829a7ab09d310584b8ecedc9b578b2c458a69f"); + ADD_CHECKPOINT(427193, "00feabb08f2d5759ed04fd6b799a7513187478696bba2db2af10d4347134e311"); + + return true; + } +} diff --git a/src/cryptonote_core/connection_context.h b/src/cryptonote_core/connection_context.h new file mode 100644 index 000000000..bf13449bc --- /dev/null +++ b/src/cryptonote_core/connection_context.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include <unordered_set> +#include <atomic> +#include "net/net_utils_base.h" + + +namespace cryptonote +{ + class my_atomic: public std::atomic<uint32_t> + { + public: + my_atomic() + {}; + my_atomic(const my_atomic& a):std::atomic<uint32_t>(a.load()) + {} + my_atomic& operator= (const my_atomic& a) + { + store(a.load()); + return *this; + } + uint32_t operator++() + { + return std::atomic<uint32_t>::operator++(); + } + uint32_t operator++(int fake) + { + return std::atomic<uint32_t>::operator++(fake); + } + }; + + + struct cryptonote_connection_context: public epee::net_utils::connection_context_base + { + + enum state + { + state_befor_handshake = 0, //default state + state_synchronizing, + state_normal + }; + + state m_state; + std::list<crypto::hash> m_needed_objects; + std::unordered_set<crypto::hash> m_requested_objects; + uint64_t m_remote_blockchain_height; + uint64_t m_last_response_height; + my_atomic m_callback_request_count; //in debug purpose: problem with double callback rise + //size_t m_score; TODO: add score calculations + }; +} diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_core/cryptonote_basic.h new file mode 100644 index 000000000..007e62bbd --- /dev/null +++ b/src/cryptonote_core/cryptonote_basic.h @@ -0,0 +1,347 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include <boost/variant.hpp> +#include <boost/functional/hash/hash.hpp> +#include <vector> +#include <cstring> // memcmp +#include <sstream> +#include "serialization/serialization.h" +#include "serialization/variant.h" +#include "serialization/vector.h" +#include "serialization/binary_archive.h" +#include "serialization/json_archive.h" +#include "serialization/debug_archive.h" +#include "serialization/crypto.h" +#include "serialization/keyvalue_serialization.h" // eepe named serialization +#include "string_tools.h" +#include "cryptonote_config.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "misc_language.h" +#include "tx_extra.h" + + +namespace cryptonote +{ + + const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash); + const static crypto::public_key null_pkey = AUTO_VAL_INIT(null_pkey); + + typedef std::vector<crypto::signature> ring_signature; + + + /* outputs */ + + struct txout_to_script + { + std::vector<crypto::public_key> keys; + std::vector<uint8_t> script; + + BEGIN_SERIALIZE_OBJECT() + FIELD(keys) + FIELD(script) + END_SERIALIZE() + }; + + struct txout_to_scripthash + { + crypto::hash hash; + }; + + struct txout_to_key + { + txout_to_key() { } + txout_to_key(const crypto::public_key &_key) : key(_key) { } + crypto::public_key key; + }; + + + /* inputs */ + + struct txin_gen + { + size_t height; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(height) + END_SERIALIZE() + }; + + struct txin_to_script + { + crypto::hash prev; + size_t prevout; + std::vector<uint8_t> sigset; + + BEGIN_SERIALIZE_OBJECT() + FIELD(prev) + VARINT_FIELD(prevout) + FIELD(sigset) + END_SERIALIZE() + }; + + struct txin_to_scripthash + { + crypto::hash prev; + size_t prevout; + txout_to_script script; + std::vector<uint8_t> sigset; + + BEGIN_SERIALIZE_OBJECT() + FIELD(prev) + VARINT_FIELD(prevout) + FIELD(script) + FIELD(sigset) + END_SERIALIZE() + }; + + struct txin_to_key + { + uint64_t amount; + std::vector<uint64_t> key_offsets; + crypto::key_image k_image; // double spending protection + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(key_offsets) + FIELD(k_image) + END_SERIALIZE() + }; + + + typedef boost::variant<txin_gen, txin_to_script, txin_to_scripthash, txin_to_key> txin_v; + + typedef boost::variant<txout_to_script, txout_to_scripthash, txout_to_key> txout_target_v; + + //typedef std::pair<uint64_t, txout> out_t; + struct tx_out + { + uint64_t amount; + txout_target_v target; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(target) + END_SERIALIZE() + + + }; + + class transaction_prefix + { + + public: + // tx information + size_t version; + uint64_t unlock_time; //number of block (or time), used as a limitation like: spend this tx not early then block/time + + std::vector<txin_v> vin; + std::vector<tx_out> vout; + //extra + std::vector<uint8_t> extra; + + BEGIN_SERIALIZE() + VARINT_FIELD(version) + if(CURRENT_TRANSACTION_VERSION < version) return false; + VARINT_FIELD(unlock_time) + FIELD(vin) + FIELD(vout) + FIELD(extra) + END_SERIALIZE() + + + protected: + transaction_prefix(){} + }; + + class transaction: public transaction_prefix + { + public: + std::vector<std::vector<crypto::signature> > signatures; //count signatures always the same as inputs count + + transaction(); + virtual ~transaction(); + void set_null(); + + BEGIN_SERIALIZE_OBJECT() + FIELDS(*static_cast<transaction_prefix *>(this)) + + ar.tag("signatures"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); + bool signatures_not_expected = signatures.empty(); + if (!signatures_not_expected && vin.size() != signatures.size()) + return false; + + for (size_t i = 0; i < vin.size(); ++i) + { + size_t signature_size = get_signature_size(vin[i]); + if (signatures_not_expected) + { + if (0 == signature_size) + continue; + else + return false; + } + + PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); + if (signature_size != signatures[i].size()) + return false; + + FIELDS(signatures[i]); + + if (vin.size() - i > 1) + ar.delimit_array(); + } + ar.end_array(); + END_SERIALIZE() + + private: + static size_t get_signature_size(const txin_v& tx_in); + }; + + + inline + transaction::transaction() + { + set_null(); + } + + inline + transaction::~transaction() + { + //set_null(); + } + + inline + void transaction::set_null() + { + version = 0; + unlock_time = 0; + vin.clear(); + vout.clear(); + extra.clear(); + signatures.clear(); + } + + inline + size_t transaction::get_signature_size(const txin_v& tx_in) + { + struct txin_signature_size_visitor : public boost::static_visitor<size_t> + { + size_t operator()(const txin_gen& txin) const{return 0;} + size_t operator()(const txin_to_script& txin) const{return 0;} + size_t operator()(const txin_to_scripthash& txin) const{return 0;} + size_t operator()(const txin_to_key& txin) const {return txin.key_offsets.size();} + }; + + return boost::apply_visitor(txin_signature_size_visitor(), tx_in); + } + + + + /************************************************************************/ + /* */ + /************************************************************************/ + struct block_header + { + uint8_t major_version; + uint8_t minor_version; + uint64_t timestamp; + crypto::hash prev_id; + uint32_t nonce; + + BEGIN_SERIALIZE() + VARINT_FIELD(major_version) + if(major_version > CURRENT_BLOCK_MAJOR_VERSION) return false; + VARINT_FIELD(minor_version) + VARINT_FIELD(timestamp) + FIELD(prev_id) + FIELD(nonce) + END_SERIALIZE() + }; + + struct block: public block_header + { + transaction miner_tx; + std::vector<crypto::hash> tx_hashes; + + BEGIN_SERIALIZE_OBJECT() + FIELDS(*static_cast<block_header *>(this)) + FIELD(miner_tx) + FIELD(tx_hashes) + END_SERIALIZE() + }; + + + /************************************************************************/ + /* */ + /************************************************************************/ + struct account_public_address + { + crypto::public_key m_spend_public_key; + crypto::public_key m_view_public_key; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_spend_public_key) + FIELD(m_view_public_key) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key) + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key) + END_KV_SERIALIZE_MAP() + }; + + struct keypair + { + crypto::public_key pub; + crypto::secret_key sec; + + static inline keypair generate() + { + keypair k; + generate_keys(k.pub, k.sec); + return k; + } + }; + //--------------------------------------------------------------- + +} + +BLOB_SERIALIZER(cryptonote::txout_to_key); +BLOB_SERIALIZER(cryptonote::txout_to_scripthash); + +VARIANT_TAG(binary_archive, cryptonote::txin_gen, 0xff); +VARIANT_TAG(binary_archive, cryptonote::txin_to_script, 0x0); +VARIANT_TAG(binary_archive, cryptonote::txin_to_scripthash, 0x1); +VARIANT_TAG(binary_archive, cryptonote::txin_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::txout_to_script, 0x0); +VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); +VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc); +VARIANT_TAG(binary_archive, cryptonote::block, 0xbb); + +VARIANT_TAG(json_archive, cryptonote::txin_gen, "gen"); +VARIANT_TAG(json_archive, cryptonote::txin_to_script, "script"); +VARIANT_TAG(json_archive, cryptonote::txin_to_scripthash, "scripthash"); +VARIANT_TAG(json_archive, cryptonote::txin_to_key, "key"); +VARIANT_TAG(json_archive, cryptonote::txout_to_script, "script"); +VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash"); +VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(json_archive, cryptonote::transaction, "tx"); +VARIANT_TAG(json_archive, cryptonote::block, "block"); + +VARIANT_TAG(debug_archive, cryptonote::txin_gen, "gen"); +VARIANT_TAG(debug_archive, cryptonote::txin_to_script, "script"); +VARIANT_TAG(debug_archive, cryptonote::txin_to_scripthash, "scripthash"); +VARIANT_TAG(debug_archive, cryptonote::txin_to_key, "key"); +VARIANT_TAG(debug_archive, cryptonote::txout_to_script, "script"); +VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash"); +VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key"); +VARIANT_TAG(debug_archive, cryptonote::transaction, "tx"); +VARIANT_TAG(debug_archive, cryptonote::block, "block"); diff --git a/src/cryptonote_core/cryptonote_basic_impl.cpp b/src/cryptonote_core/cryptonote_basic_impl.cpp new file mode 100644 index 000000000..b320a3463 --- /dev/null +++ b/src/cryptonote_core/cryptonote_basic_impl.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include "include_base_utils.h" +using namespace epee; + +#include "cryptonote_basic_impl.h" +#include "string_tools.h" +#include "serialization/binary_utils.h" +#include "serialization/vector.h" +#include "cryptonote_format_utils.h" +#include "cryptonote_config.h" +#include "misc_language.h" +#include "common/base58.h" +#include "crypto/hash.h" +#include "common/int-util.h" + +namespace cryptonote { + + /************************************************************************/ + /* Cryptonote helper functions */ + /************************************************************************/ + //----------------------------------------------------------------------------------------------- + size_t get_max_block_size() + { + return CRYPTONOTE_MAX_BLOCK_SIZE; + } + //----------------------------------------------------------------------------------------------- + size_t get_max_tx_size() + { + return CRYPTONOTE_MAX_TX_SIZE; + } + //----------------------------------------------------------------------------------------------- + uint64_t get_block_reward(std::vector<size_t>& last_blocks_sizes, size_t current_block_size, bool& block_too_big, uint64_t already_generated_coins) + { + block_too_big = false; + + uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> 18; + + if(current_block_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) + return base_reward; + + size_t med_sz = misc_utils::median(last_blocks_sizes); + + //make it soft + if(med_sz < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) + med_sz = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + + if(current_block_size > med_sz) + { + if(current_block_size > med_sz*2) + { + LOG_PRINT_L0("Block cumulative size is too big: " << current_block_size << ", expected less than " << med_sz*2); + block_too_big = true; + return 0; + } + + assert(med_sz < std::numeric_limits<uint32_t>::max()); + assert(current_block_size < std::numeric_limits<uint32_t>::max()); + + uint64_t product_hi; + uint64_t product_lo = mul128(base_reward, current_block_size * (2 * med_sz - current_block_size), &product_hi); + + uint64_t reward_hi; + uint64_t reward_lo; + div128_32(product_hi, product_lo, static_cast<uint32_t>(med_sz), &reward_hi, &reward_lo); + div128_32(reward_hi, reward_lo, static_cast<uint32_t>(med_sz), &reward_hi, &reward_lo); + assert(0 == reward_hi); + assert(reward_lo < base_reward); + + return reward_lo; + }else + return base_reward; + } + //------------------------------------------------------------------------------------ + uint8_t get_account_address_checksum(const public_address_outer_blob& bl) + { + const unsigned char* pbuf = reinterpret_cast<const unsigned char*>(&bl); + uint8_t summ = 0; + for(size_t i = 0; i!= sizeof(public_address_outer_blob)-1; i++) + summ += pbuf[i]; + + return summ; + } + //----------------------------------------------------------------------- + std::string get_account_address_as_str(const account_public_address& adr) + { + return tools::base58::encode_addr(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(adr)); + } + //----------------------------------------------------------------------- + bool is_coinbase(const transaction& tx) + { + if(tx.vin.size() != 1) + return false; + + if(tx.vin[0].type() != typeid(txin_gen)) + return false; + + return true; + } + //----------------------------------------------------------------------- + bool get_account_address_from_str(account_public_address& adr, const std::string& str) + { + if (2 * sizeof(public_address_outer_blob) != str.size()) + { + blobdata data; + uint64_t prefix; + if (!tools::base58::decode_addr(str, prefix, data)) + { + LOG_PRINT_L0("Invalid address format"); + return false; + } + + if (CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX != prefix) + { + LOG_PRINT_L0("Wrong address prefix: " << prefix << ", expected " << CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); + return false; + } + + if (!::serialization::parse_binary(data, adr)) + { + LOG_PRINT_L0("Account public address keys can't be parsed"); + return false; + } + + if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key)) + { + LOG_PRINT_L0("Failed to validate address keys"); + return false; + } + } + else + { + // Old address format + std::string buff; + if(!string_tools::parse_hexstr_to_binbuff(str, buff)) + return false; + + if(buff.size()!=sizeof(public_address_outer_blob)) + { + LOG_PRINT_L0("Wrong public address size: " << buff.size() << ", expected size: " << sizeof(public_address_outer_blob)); + return false; + } + + public_address_outer_blob blob = *reinterpret_cast<const public_address_outer_blob*>(buff.data()); + + + if(blob.m_ver > CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER) + { + LOG_PRINT_L0("Unknown version of public address: " << blob.m_ver << ", expected " << CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER); + return false; + } + + if(blob.check_sum != get_account_address_checksum(blob)) + { + LOG_PRINT_L0("Wrong public address checksum"); + return false; + } + + //we success + adr = blob.m_address; + } + + return true; + } + + bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { + return cryptonote::get_transaction_hash(a) == cryptonote::get_transaction_hash(b); + } + + bool operator ==(const cryptonote::block& a, const cryptonote::block& b) { + return cryptonote::get_block_hash(a) == cryptonote::get_block_hash(b); + } +} + +//-------------------------------------------------------------------------------- +bool parse_hash256(const std::string str_hash, crypto::hash& hash) +{ + std::string buf; + bool res = epee::string_tools::parse_hexstr_to_binbuff(str_hash, buf); + if (!res || buf.size() != sizeof(crypto::hash)) + { + std::cout << "invalid hash format: <" << str_hash << '>' << std::endl; + return false; + } + else + { + buf.copy(reinterpret_cast<char *>(&hash), sizeof(crypto::hash)); + return true; + } +} diff --git a/src/cryptonote_core/cryptonote_basic_impl.h b/src/cryptonote_core/cryptonote_basic_impl.h new file mode 100644 index 000000000..f56d09a8e --- /dev/null +++ b/src/cryptonote_core/cryptonote_basic_impl.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "cryptonote_basic.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" + + +namespace cryptonote { + /************************************************************************/ + /* */ + /************************************************************************/ + template<class t_array> + struct array_hasher: std::unary_function<t_array&, std::size_t> + { + std::size_t operator()(const t_array& val) const + { + return boost::hash_range(&val.data[0], &val.data[sizeof(val.data)]); + } + }; + + +#pragma pack(push, 1) + struct public_address_outer_blob + { + uint8_t m_ver; + account_public_address m_address; + uint8_t check_sum; + }; +#pragma pack (pop) + + + /************************************************************************/ + /* Cryptonote helper functions */ + /************************************************************************/ + size_t get_max_block_size(); + size_t get_max_tx_size(); + uint64_t get_block_reward(std::vector<size_t>& last_blocks_sizes, size_t current_block_size, bool& block_too_big, uint64_t already_generated_coins); + uint8_t get_account_address_checksum(const public_address_outer_blob& bl); + std::string get_account_address_as_str(const account_public_address& adr); + bool get_account_address_from_str(account_public_address& adr, const std::string& str); + bool is_coinbase(const transaction& tx); + + bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b); + bool operator ==(const cryptonote::block& a, const cryptonote::block& b); +} + +template <class T> +std::ostream &print256(std::ostream &o, const T &v) { + return o << "<" << epee::string_tools::pod_to_hex(v) << ">"; +} + +bool parse_hash256(const std::string str_hash, crypto::hash& hash); + +namespace crypto { + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { return print256(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { return print256(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { return print256(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { return print256(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { return print256(o, v); } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { return print256(o, v); } +} diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_core/cryptonote_boost_serialization.h new file mode 100644 index 000000000..80c497844 --- /dev/null +++ b/src/cryptonote_core/cryptonote_boost_serialization.h @@ -0,0 +1,143 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include <boost/serialization/vector.hpp> +#include <boost/serialization/utility.hpp> +#include <boost/serialization/variant.hpp> +#include <boost/serialization/set.hpp> +#include <boost/serialization/map.hpp> +#include <boost/foreach.hpp> +#include <boost/serialization/is_bitwise_serializable.hpp> +#include "cryptonote_basic.h" +#include "common/unordered_containers_boost_serialization.h" +#include "crypto/crypto.h" + +//namespace cryptonote { +namespace boost +{ + namespace serialization + { + + //--------------------------------------------------- + template <class Archive> + inline void serialize(Archive &a, crypto::public_key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::public_key)]>(x); + } + template <class Archive> + inline void serialize(Archive &a, crypto::secret_key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::secret_key)]>(x); + } + template <class Archive> + inline void serialize(Archive &a, crypto::key_derivation &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::key_derivation)]>(x); + } + template <class Archive> + inline void serialize(Archive &a, crypto::key_image &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::key_image)]>(x); + } + + template <class Archive> + inline void serialize(Archive &a, crypto::signature &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::signature)]>(x); + } + template <class Archive> + inline void serialize(Archive &a, crypto::hash &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::hash)]>(x); + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver) + { + a & x.keys; + a & x.script; + } + + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txout_to_key &x, const boost::serialization::version_type ver) + { + a & x.key; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txout_to_scripthash &x, const boost::serialization::version_type ver) + { + a & x.hash; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txin_gen &x, const boost::serialization::version_type ver) + { + a & x.height; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txin_to_script &x, const boost::serialization::version_type ver) + { + a & x.prev; + a & x.prevout; + a & x.sigset; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txin_to_scripthash &x, const boost::serialization::version_type ver) + { + a & x.prev; + a & x.prevout; + a & x.script; + a & x.sigset; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::txin_to_key &x, const boost::serialization::version_type ver) + { + a & x.amount; + a & x.key_offsets; + a & x.k_image; + } + + template <class Archive> + inline void serialize(Archive &a, cryptonote::tx_out &x, const boost::serialization::version_type ver) + { + a & x.amount; + a & x.target; + } + + + template <class Archive> + inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver) + { + a & x.version; + a & x.unlock_time; + a & x.vin; + a & x.vout; + a & x.extra; + a & x.signatures; + } + + + template <class Archive> + inline void serialize(Archive &a, cryptonote::block &b, const boost::serialization::version_type ver) + { + a & b.major_version; + a & b.minor_version; + a & b.timestamp; + a & b.prev_id; + a & b.nonce; + //------------------ + a & b.miner_tx; + a & b.tx_hashes; + } +} +} + +//} diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp new file mode 100644 index 000000000..d5ab8d65a --- /dev/null +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -0,0 +1,519 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include "include_base_utils.h" +using namespace epee; + +#include <boost/foreach.hpp> +#include <unordered_set> +#include "cryptonote_core.h" +#include "common/command_line.h" +#include "common/util.h" +#include "warnings.h" +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "cryptonote_format_utils.h" +#include "misc_language.h" + +DISABLE_VS_WARNINGS(4355) + +namespace cryptonote +{ + + //----------------------------------------------------------------------------------------------- + core::core(i_cryptonote_protocol* pprotocol): + m_mempool(m_blockchain_storage), + m_blockchain_storage(m_mempool), + m_miner(this), + m_miner_address(boost::value_initialized<account_public_address>()), + m_starter_message_showed(false) + { + set_cryptonote_protocol(pprotocol); + } + void core::set_cryptonote_protocol(i_cryptonote_protocol* pprotocol) + { + if(pprotocol) + m_pprotocol = pprotocol; + else + m_pprotocol = &m_protocol_stub; + } + //----------------------------------------------------------------------------------- + void core::set_checkpoints(checkpoints&& chk_pts) + { + m_blockchain_storage.set_checkpoints(std::move(chk_pts)); + } + //----------------------------------------------------------------------------------- + void core::init_options(boost::program_options::options_description& /*desc*/) + { + } + //----------------------------------------------------------------------------------------------- + bool core::handle_command_line(const boost::program_options::variables_map& vm) + { + m_config_folder = command_line::get_arg(vm, command_line::arg_data_dir); + return true; + } + //----------------------------------------------------------------------------------------------- + uint64_t core::get_current_blockchain_height() + { + return m_blockchain_storage.get_current_blockchain_height(); + } + //----------------------------------------------------------------------------------------------- + bool core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) + { + top_id = m_blockchain_storage.get_tail_id(height); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) + { + return m_blockchain_storage.get_blocks(start_offset, count, blocks, txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) + { + return m_blockchain_storage.get_blocks(start_offset, count, blocks); + } //----------------------------------------------------------------------------------------------- + bool core::get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs) + { + return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_transaction(const crypto::hash &h, transaction &tx) + { + std::vector<crypto::hash> ids; + ids.push_back(h); + std::list<transaction> ltx; + std::list<crypto::hash> missing; + if (m_blockchain_storage.get_transactions(ids, ltx, missing)) + { + if (ltx.size() > 0) + { + tx = *ltx.begin(); + return true; + } + } + + return false; + } + //----------------------------------------------------------------------------------------------- + bool core::get_alternative_blocks(std::list<block>& blocks) + { + return m_blockchain_storage.get_alternative_blocks(blocks); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_alternative_blocks_count() + { + return m_blockchain_storage.get_alternative_blocks_count(); + } + //----------------------------------------------------------------------------------------------- + bool core::init(const boost::program_options::variables_map& vm) + { + bool r = handle_command_line(vm); + + r = m_mempool.init(m_config_folder); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); + + r = m_blockchain_storage.init(m_config_folder); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + + r = m_miner.init(vm); + CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); + + return load_state_data(); + } + //----------------------------------------------------------------------------------------------- + bool core::set_genesis_block(const block& b) + { + return m_blockchain_storage.reset_and_set_genesis_block(b); + } + //----------------------------------------------------------------------------------------------- + bool core::load_state_data() + { + // may be some code later + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::deinit() + { + m_miner.stop(); + m_mempool.deinit(); + m_blockchain_storage.deinit(); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block) + { + tvc = boost::value_initialized<tx_verification_context>(); + //want to process all transactions sequentially + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + + if(tx_blob.size() > get_max_tx_size()) + { + LOG_PRINT_L0("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected"); + tvc.m_verifivation_failed = true; + return false; + } + + crypto::hash tx_hash = null_hash; + crypto::hash tx_prefixt_hash = null_hash; + transaction tx; + + if(!parse_tx_from_blob(tx, tx_hash, tx_prefixt_hash, tx_blob)) + { + LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to parse, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + //std::cout << "!"<< tx.vin.size() << std::endl; + + if(!check_tx_syntax(tx)) + { + LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + + if(!check_tx_semantic(tx, keeped_by_block)) + { + LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + + bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block); + if(tvc.m_verifivation_failed) + {LOG_PRINT_RED_L0("Transaction verification failed: " << tx_hash);} + else if(tvc.m_verifivation_impossible) + {LOG_PRINT_RED_L0("Transaction verification impossible: " << tx_hash);} + + if(tvc.m_added_to_pool) + LOG_PRINT_L1("tx added: " << tx_hash); + return r; + } + //----------------------------------------------------------------------------------------------- + bool core::get_stat_info(core_stat_info& st_inf) + { + st_inf.mining_speed = m_miner.get_speed(); + st_inf.alternative_blocks = m_blockchain_storage.get_alternative_blocks_count(); + st_inf.blockchain_height = m_blockchain_storage.get_current_blockchain_height(); + st_inf.tx_pool_size = m_mempool.get_transactions_count(); + st_inf.top_block_id_str = epee::string_tools::pod_to_hex(m_blockchain_storage.get_tail_id()); + return true; + } + + //----------------------------------------------------------------------------------------------- + bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) + { + if(!tx.vin.size()) + { + LOG_PRINT_RED_L0("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!check_inputs_types_supported(tx)) + { + LOG_PRINT_RED_L0("unsupported input types for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!check_outs_valid(tx)) + { + LOG_PRINT_RED_L0("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!check_money_overflow(tx)) + { + LOG_PRINT_RED_L0("tx have money overflow, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + boost::uint64_t amount_in = 0; + get_inputs_money_amount(tx, amount_in); + boost::uint64_t amount_out = get_outs_money_amount(tx); + + if(amount_in <= amount_out) + { + LOG_PRINT_RED_L0("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) + { + LOG_PRINT_RED_L0("tx have to big size " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + return false; + } + + //check if tx use different key images + if(!check_tx_inputs_keyimages_diff(tx)) + { + LOG_PRINT_RED_L0("tx have to big size " << get_object_blobsize(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + return false; + } + + + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_keyimages_diff(const transaction& tx) + { + std::unordered_set<crypto::key_image> ki; + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if(!ki.insert(tokey_in.k_image).second) + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block) + { + crypto::hash tx_hash = get_transaction_hash(tx); + crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + blobdata bl; + t_serializable_object_to_blob(tx, bl); + return add_new_tx(tx, tx_hash, tx_prefix_hash, bl.size(), tvc, keeped_by_block); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_blockchain_total_transactions() + { + return m_blockchain_storage.get_total_transactions(); + } + //----------------------------------------------------------------------------------------------- + bool core::get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys) + { + return m_blockchain_storage.get_outs(amount, pkeys); + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block) + { + if(m_mempool.have_tx(tx_hash)) + { + LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); + return true; + } + + if(m_blockchain_storage.have_tx(tx_hash)) + { + LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); + return true; + } + + return m_mempool.add_tx(tx, tx_hash, blob_size, tvc, keeped_by_block); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) + { + return m_blockchain_storage.create_block_template(b, adr, diffic, height, ex_nonce); + } + //----------------------------------------------------------------------------------------------- + bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) + { + return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); + } + //----------------------------------------------------------------------------------------------- + bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) + { + return m_blockchain_storage.find_blockchain_supplement(qblock_ids, blocks, total_height, start_height, max_count); + } + //----------------------------------------------------------------------------------------------- + void core::print_blockchain(uint64_t start_index, uint64_t end_index) + { + m_blockchain_storage.print_blockchain(start_index, end_index); + } + //----------------------------------------------------------------------------------------------- + void core::print_blockchain_index() + { + m_blockchain_storage.print_blockchain_index(); + } + //----------------------------------------------------------------------------------------------- + void core::print_blockchain_outs(const std::string& file) + { + m_blockchain_storage.print_blockchain_outs(file); + } + //----------------------------------------------------------------------------------------------- + bool core::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) + { + return m_blockchain_storage.get_random_outs_for_amounts(req, res); + } + //----------------------------------------------------------------------------------------------- + bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) + { + return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs); + } + //----------------------------------------------------------------------------------------------- + void core::pause_mine() + { + m_miner.pause(); + } + //----------------------------------------------------------------------------------------------- + void core::resume_mine() + { + m_miner.resume(); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_block_found(block& b) + { + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + m_miner.pause(); + m_blockchain_storage.add_new_block(b, bvc); + //anyway - update miner template + update_miner_block_template(); + m_miner.resume(); + + + CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "mined block failed verification"); + if(bvc.m_added_to_main_chain) + { + cryptonote_connection_context exclude_context = boost::value_initialized<cryptonote_connection_context>(); + NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); + arg.hop = 0; + arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); + std::list<crypto::hash> missed_txs; + std::list<transaction> txs; + m_blockchain_storage.get_transactions(b.tx_hashes, txs, missed_txs); + if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) + { + LOG_PRINT_L0("Block found but, seems that reorganize just happened after that, do not relay this block"); + return true; + } + CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size() && !missed_txs.size(), false, "cant find some transactions in found block:" << get_block_hash(b) << " txs.size()=" << txs.size() + << ", b.tx_hashes.size()=" << b.tx_hashes.size() << ", missed_txs.size()" << missed_txs.size()); + + block_to_blob(b, arg.b.block); + //pack transactions + BOOST_FOREACH(auto& tx, txs) + arg.b.txs.push_back(t_serializable_object_to_blob(tx)); + + m_pprotocol->relay_block(arg, exclude_context); + } + return bvc.m_added_to_main_chain; + } + //----------------------------------------------------------------------------------------------- + void core::on_synchronized() + { + m_miner.on_synchronized(); + } + bool core::get_backward_blocks_sizes(uint64_t from_height, std::vector<size_t>& sizes, size_t count) + { + return m_blockchain_storage.get_backward_blocks_sizes(from_height, sizes, count); + } + //----------------------------------------------------------------------------------------------- + bool core::add_new_block(const block& b, block_verification_context& bvc) + { + return m_blockchain_storage.add_new_block(b, bvc); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate) + { + bvc = boost::value_initialized<block_verification_context>(); + if(block_blob.size() > get_max_block_size()) + { + LOG_PRINT_L0("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); + bvc.m_verifivation_failed = true; + return false; + } + + + block b = AUTO_VAL_INIT(b); + if(!parse_and_validate_block_from_blob(block_blob, b)) + { + LOG_PRINT_L0("Failed to parse and validate new block"); + bvc.m_verifivation_failed = true; + return false; + } + add_new_block(b, bvc); + if(update_miner_blocktemplate && bvc.m_added_to_main_chain) + update_miner_block_template(); + return true; + } + //----------------------------------------------------------------------------------------------- + crypto::hash core::get_tail_id() + { + return m_blockchain_storage.get_tail_id(); + } + //----------------------------------------------------------------------------------------------- + size_t core::get_pool_transactions_count() + { + return m_mempool.get_transactions_count(); + } + //----------------------------------------------------------------------------------------------- + bool core::have_block(const crypto::hash& id) + { + return m_blockchain_storage.have_block(id); + } + //----------------------------------------------------------------------------------------------- + bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob) + { + return parse_and_validate_tx_from_blob(blob, tx, tx_hash, tx_prefix_hash); + } + //----------------------------------------------------------------------------------------------- + bool core::check_tx_syntax(const transaction& tx) + { + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::get_pool_transactions(std::list<transaction>& txs) + { + return m_mempool.get_transactions(txs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_short_chain_history(std::list<crypto::hash>& ids) + { + return m_blockchain_storage.get_short_chain_history(ids); + } + //----------------------------------------------------------------------------------------------- + bool core::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context) + { + return m_blockchain_storage.handle_get_objects(arg, rsp); + } + //----------------------------------------------------------------------------------------------- + crypto::hash core::get_block_id_by_height(uint64_t height) + { + return m_blockchain_storage.get_block_id_by_height(height); + } + //----------------------------------------------------------------------------------------------- + bool core::get_block_by_hash(const crypto::hash &h, block &blk) { + return m_blockchain_storage.get_block_by_hash(h, blk); + } + //----------------------------------------------------------------------------------------------- + void core::get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid) { + m_blockchain_storage.get_all_known_block_ids(main, alt, invalid); + } + //----------------------------------------------------------------------------------------------- + std::string core::print_pool(bool short_format) + { + return m_mempool.print_pool(short_format); + } + //----------------------------------------------------------------------------------------------- + bool core::update_miner_block_template() + { + m_miner.on_block_chain_update(); + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::on_idle() + { + if(!m_starter_message_showed) + { + LOG_PRINT_L0(ENDL << "**********************************************************************" << ENDL + << "The daemon will start synchronizing with the network. It may take up to several hours." << ENDL + << ENDL + << "You can set the level of process detailization by using command \"set_log <level>\", where <level> is either 0 (no details), 1 (current block height synchronized), or 2 (all details)." << ENDL + << ENDL + << "Use \"help\" command to see the list of available commands." << ENDL + << ENDL + << "Note: in case you need to interrupt the process, use \"exit\" command. Otherwise, the current progress won't be saved." << ENDL + << "**********************************************************************"); + m_starter_message_showed = true; + } + + m_store_blockchain_interval.do_call(boost::bind(&blockchain_storage::store_blockchain, &m_blockchain_storage)); + m_miner.on_idle(); + return true; + } + //----------------------------------------------------------------------------------------------- +} diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h new file mode 100644 index 000000000..c298451e8 --- /dev/null +++ b/src/cryptonote_core/cryptonote_core.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> + +#include "p2p/net_node_common.h" +#include "cryptonote_protocol/cryptonote_protocol_handler_common.h" +#include "storages/portable_storage_template_helper.h" +#include "tx_pool.h" +#include "blockchain_storage.h" +#include "miner.h" +#include "connection_context.h" +#include "cryptonote_core/cryptonote_stat_info.h" +#include "warnings.h" +#include "crypto/hash.h" + +PUSH_WARNINGS +DISABLE_VS_WARNINGS(4355) + +namespace cryptonote +{ + /************************************************************************/ + /* */ + /************************************************************************/ + class core: public i_miner_handler + { + public: + core(i_cryptonote_protocol* pprotocol); + bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote_connection_context& context); + bool on_idle(); + bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block); + bool handle_incoming_block(const blobdata& block_blob, block_verification_context& bvc, bool update_miner_blocktemplate = true); + i_cryptonote_protocol* get_protocol(){return m_pprotocol;} + + //-------------------- i_miner_handler ----------------------- + virtual bool handle_block_found( block& b); + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce); + + + miner& get_miner(){return m_miner;} + static void init_options(boost::program_options::options_description& desc); + bool init(const boost::program_options::variables_map& vm); + bool set_genesis_block(const block& b); + bool deinit(); + uint64_t get_current_blockchain_height(); + bool get_blockchain_top(uint64_t& heeight, crypto::hash& top_id); + bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs); + bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks); + template<class t_ids_container, class t_blocks_container, class t_missed_container> + bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) + { + return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); + } + crypto::hash get_block_id_by_height(uint64_t height); + bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::list<transaction>& txs, std::list<crypto::hash>& missed_txs); + bool get_transaction(const crypto::hash &h, transaction &tx); + bool get_block_by_hash(const crypto::hash &h, block &blk); + void get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid); + + bool get_alternative_blocks(std::list<block>& blocks); + size_t get_alternative_blocks_count(); + + void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); + void set_checkpoints(checkpoints&& chk_pts); + + bool get_pool_transactions(std::list<transaction>& txs); + size_t get_pool_transactions_count(); + size_t get_blockchain_total_transactions(); + bool get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys); + bool have_block(const crypto::hash& id); + bool get_short_chain_history(std::list<crypto::hash>& ids); + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp); + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count); + bool get_stat_info(core_stat_info& st_inf); + bool get_backward_blocks_sizes(uint64_t from_height, std::vector<size_t>& sizes, size_t count); + bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs); + crypto::hash get_tail_id(); + bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); + void pause_mine(); + void resume_mine(); + blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} + //debug functions + void print_blockchain(uint64_t start_index, uint64_t end_index); + void print_blockchain_index(); + std::string print_pool(bool short_format); + void print_blockchain_outs(const std::string& file); + void on_synchronized(); + + private: + bool add_new_tx(const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prefix_hash, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block); + bool add_new_tx(const transaction& tx, tx_verification_context& tvc, bool keeped_by_block); + bool add_new_block(const block& b, block_verification_context& bvc); + bool load_state_data(); + bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash, const blobdata& blob); + + bool check_tx_syntax(const transaction& tx); + //check correct values, amounts and all lightweight checks not related with database + bool check_tx_semantic(const transaction& tx, bool keeped_by_block); + //check if tx already in memory pool or in main blockchain + + bool is_key_image_spent(const crypto::key_image& key_im); + + bool check_tx_ring_signature(const txin_to_key& tx, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig); + bool is_tx_spendtime_unlocked(uint64_t unlock_time); + bool update_miner_block_template(); + bool handle_command_line(const boost::program_options::variables_map& vm); + bool on_update_blocktemplate_interval(); + bool check_tx_inputs_keyimages_diff(const transaction& tx); + + + tx_memory_pool m_mempool; + blockchain_storage m_blockchain_storage; + i_cryptonote_protocol* m_pprotocol; + critical_section m_incoming_tx_lock; + //m_miner and m_miner_addres are probably temporary here + miner m_miner; + account_public_address m_miner_address; + std::string m_config_folder; + cryptonote_protocol_stub m_protocol_stub; + math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; + friend class tx_validate_inputs; + std::atomic<bool> m_starter_message_showed; + }; +} + +POP_WARNINGS diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp new file mode 100644 index 000000000..fbcadc081 --- /dev/null +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -0,0 +1,710 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "include_base_utils.h" +using namespace epee; + +#include "cryptonote_format_utils.h" +#include <boost/foreach.hpp> +#include "cryptonote_config.h" +#include "miner.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" + +namespace cryptonote +{ + //--------------------------------------------------------------- + void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h) + { + std::ostringstream s; + binary_archive<true> a(s); + ::serialization::serialize(a, const_cast<transaction_prefix&>(tx)); + crypto::cn_fast_hash(s.str().data(), s.str().size(), h); + } + //--------------------------------------------------------------- + crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx) + { + crypto::hash h = null_hash; + get_transaction_prefix_hash(tx, h); + return h; + } + //--------------------------------------------------------------- + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx) + { + std::stringstream ss; + ss << tx_blob; + binary_archive<false> ba(ss); + bool r = ::serialization::serialize(ba, tx); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + return true; + } + //--------------------------------------------------------------- + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash) + { + std::stringstream ss; + ss << tx_blob; + binary_archive<false> ba(ss); + bool r = ::serialization::serialize(ba, tx); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + //TODO: validate tx + + crypto::cn_fast_hash(tx_blob.data(), tx_blob.size(), tx_hash); + get_transaction_prefix_hash(tx, tx_prefix_hash); + return true; + } + //--------------------------------------------------------------- + bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector<size_t>& blocks_sizes, size_t current_block_size, size_t max_outs) + { + return construct_miner_tx(height, already_generated_coins, miner_address, tx, fee, blocks_sizes, current_block_size, blobdata(), max_outs); + } + //--------------------------------------------------------------- + bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector<size_t>& blocks_sizes, size_t current_block_size, const blobdata& extra_nonce, size_t max_outs) + { + tx.vin.clear(); + tx.vout.clear(); + tx.extra.clear(); + + keypair txkey = keypair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + if(extra_nonce.size()) + if(!add_tx_extra_nonce(tx, extra_nonce)) + return false; + + txin_gen in; + in.height = height; + + bool block_too_big = false; + uint64_t block_reward = get_block_reward(blocks_sizes, current_block_size, block_too_big, already_generated_coins) + fee; + if(block_too_big) + { + LOG_PRINT_L0("Block is too big"); + return false; + } + + std::vector<size_t> out_amounts; + decompose_amount_into_digits(block_reward, DEFAULT_FEE, + [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, + [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); + + CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); + while (max_outs < out_amounts.size()) + { + out_amounts[out_amounts.size() - 2] += out_amounts.back(); + out_amounts.resize(out_amounts.size() - 1); + } + + size_t summary_amounts = 0; + for (size_t no = 0; no < out_amounts.size(); no++) + { + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation, no, miner_address.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< miner_address.m_spend_public_key << ")"); + + txout_to_key tk; + tk.key = out_eph_public_key; + + tx_out out; + summary_amounts += out.amount = out_amounts[no]; + out.target = tk; + tx.vout.push_back(out); + } + + CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); + + tx.version = CURRENT_TRANSACTION_VERSION; + //lock + tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.vin.push_back(in); + //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) + // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); + return true; + } + //--------------------------------------------------------------- + bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + { + crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + return true; + } + //--------------------------------------------------------------- + uint64_t power_integral(uint64_t a, uint64_t b) + { + if(b == 0) + return 1; + uint64_t total = a; + for(uint64_t i = 1; i != b; i++) + total *= a; + return total; + } + //--------------------------------------------------------------- + bool parse_amount(uint64_t& amount, const std::string& str_amount_) + { + std::vector<std::string> pars; + std::string str_amount = str_amount_; + boost::algorithm::trim(str_amount); + if(!str_amount.size()) + return false; + if(str_amount[0] == '-') + return false; + + boost::split(pars, str_amount, boost::is_any_of("."), boost::token_compress_on ); + if(pars.size() > 2 || pars.size() < 1) + return false; + uint64_t left = 0; + if(!string_tools::get_xtype_from_string(left, pars[0])) + return false; + amount = left * power_integral(10, CRYPTONOTE_DISPLAY_DECIMAL_POINT); + if(pars.size() == 2) + { + uint64_t right = 0; + if(pars[1].size() > 8 ) + return false; + if(pars[1].size() < 8 ) + pars[1].append(8 - pars[1].size(), '0'); + if(!string_tools::get_xtype_from_string(right, pars[1])) + return false; + amount += right; + } + return true; + } + //--------------------------------------------------------------- + bool get_tx_fee(const transaction& tx, uint64_t & fee) + { + uint64_t amount_in = 0; + uint64_t amount_out = 0; + BOOST_FOREACH(auto& in, tx.vin) + { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), 0, "unexpected type id in transaction"); + amount_in += boost::get<txin_to_key>(in).amount; + } + BOOST_FOREACH(auto& o, tx.vout) + amount_out += o.amount; + + CHECK_AND_ASSERT_MES(amount_in >= amount_out, false, "transaction spend (" <<amount_in << ") more than it has (" << amount_out << ")"); + fee = amount_in - amount_out; + return true; + } + //--------------------------------------------------------------- + uint64_t get_tx_fee(const transaction& tx) + { + uint64_t r = 0; + if(!get_tx_fee(tx, r)) + return 0; + return r; + } + //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) + { + crypto::public_key pk = null_pkey; + parse_and_validate_tx_extra(tx, pk); + return pk; + } + //--------------------------------------------------------------- + bool parse_and_validate_tx_extra(const transaction& tx, crypto::public_key& tx_pub_key) + { + tx_pub_key = null_pkey; + bool padding_started = false; //let the padding goes only at the end + bool tx_extra_tag_pubkey_found = false; + bool tx_extra_extra_nonce_found = false; + for(size_t i = 0; i != tx.extra.size();) + { + if(padding_started) + { + CHECK_AND_ASSERT_MES(!tx.extra[i], false, "Failed to parse transaction extra (not 0 after padding) in tx " << get_transaction_hash(tx)); + } + else if(tx.extra[i] == TX_EXTRA_TAG_PUBKEY) + { + CHECK_AND_ASSERT_MES(sizeof(crypto::public_key) <= tx.extra.size()-1-i, false, "Failed to parse transaction extra (TX_EXTRA_TAG_PUBKEY have not enough bytes) in tx " << get_transaction_hash(tx)); + CHECK_AND_ASSERT_MES(!tx_extra_tag_pubkey_found, false, "Failed to parse transaction extra (duplicate TX_EXTRA_TAG_PUBKEY entry) in tx " << get_transaction_hash(tx)); + tx_pub_key = *reinterpret_cast<const crypto::public_key*>(&tx.extra[i+1]); + i += 1 + sizeof(crypto::public_key); + tx_extra_tag_pubkey_found = true; + continue; + }else if(tx.extra[i] == TX_EXTRA_NONCE) + { + //CHECK_AND_ASSERT_MES(is_coinbase(tx), false, "Failed to parse transaction extra (TX_EXTRA_NONCE can be only in coinbase) in tx " << get_transaction_hash(tx)); + CHECK_AND_ASSERT_MES(!tx_extra_extra_nonce_found, false, "Failed to parse transaction extra (duplicate TX_EXTRA_NONCE entry) in tx " << get_transaction_hash(tx)); + CHECK_AND_ASSERT_MES(tx.extra.size()-1-i >= 1, false, "Failed to parse transaction extra (TX_EXTRA_NONCE have not enough bytes) in tx " << get_transaction_hash(tx)); + ++i; + CHECK_AND_ASSERT_MES(tx.extra.size()-1-i >= tx.extra[i], false, "Failed to parse transaction extra (TX_EXTRA_NONCE have wrong bytes counter) in tx " << get_transaction_hash(tx)); + tx_extra_extra_nonce_found = true; + i += tx.extra[i];//actually don't need to extract it now, just skip + } + else if(!tx.extra[i]) + { + padding_started = true; + continue; + } + ++i; + } + return true; + } + //--------------------------------------------------------------- + bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key) + { + tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key)); + tx.extra[tx.extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY; + *reinterpret_cast<crypto::public_key*>(&tx.extra[tx.extra.size() - sizeof(crypto::public_key)]) = tx_pub_key; + return true; + } + //--------------------------------------------------------------- + bool add_tx_extra_nonce(transaction& tx, const blobdata& extra_nonce) + { + CHECK_AND_ASSERT_MES(extra_nonce.size() <=255, false, "extra nonce could be 255 bytes max"); + size_t start_pos = tx.extra.size(); + tx.extra.resize(tx.extra.size() + 2 + extra_nonce.size()); + //write tag + tx.extra[start_pos] = TX_EXTRA_NONCE; + //write len + ++start_pos; + tx.extra[start_pos] = static_cast<uint8_t>(extra_nonce.size()); + //write data + ++start_pos; + memcpy(&tx.extra[start_pos], extra_nonce.data(), extra_nonce.size()); + return true; + } + //--------------------------------------------------------------- + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, transaction& tx, uint64_t unlock_time) + { + tx.vin.clear(); + tx.vout.clear(); + tx.signatures.clear(); + tx.extra.clear(); + + tx.version = CURRENT_TRANSACTION_VERSION; + tx.unlock_time = unlock_time; + + keypair txkey = keypair::generate(); + add_tx_pub_key_to_extra(tx, txkey.pub); + + struct input_generation_context_data + { + keypair in_ephemeral; + }; + std::vector<input_generation_context_data> in_contexts; + + + uint64_t summary_inputs_money = 0; + //fill inputs + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + if(src_entr.real_output >= src_entr.outputs.size()) + { + LOG_ERROR("real_output index (" << src_entr.real_output << ")bigger than output_keys.size()=" << src_entr.outputs.size()); + return false; + } + summary_inputs_money += src_entr.amount; + + //key_derivation recv_derivation; + in_contexts.push_back(input_generation_context_data()); + keypair& in_ephemeral = in_contexts.back().in_ephemeral; + crypto::key_image img; + if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img)) + return false; + + //check that derivated key is equal with real output key + if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second) ) + { + LOG_ERROR("derived public key missmatch with output public key! "<< ENDL << "derived_key:" + << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" + << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second) ); + return false; + } + + //put key image into tx input + txin_to_key input_to_key; + input_to_key.amount = src_entr.amount; + input_to_key.k_image = img; + + //fill outputs array and use relative offsets + BOOST_FOREACH(const tx_source_entry::output_entry& out_entry, src_entr.outputs) + input_to_key.key_offsets.push_back(out_entry.first); + + input_to_key.key_offsets = absolute_output_offsets_to_relative(input_to_key.key_offsets); + tx.vin.push_back(input_to_key); + } + + // "Shuffle" outs + std::vector<tx_destination_entry> shuffled_dsts(destinations); + std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; } ); + + uint64_t summary_outs_money = 0; + //fill outputs + size_t output_index = 0; + BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) + { + CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); + crypto::key_derivation derivation; + crypto::public_key out_eph_public_key; + bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")"); + + r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); + + tx_out out; + out.amount = dst_entr.amount; + txout_to_key tk; + tk.key = out_eph_public_key; + out.target = tk; + tx.vout.push_back(out); + output_index++; + summary_outs_money += dst_entr.amount; + } + + //check money + if(summary_outs_money > summary_inputs_money ) + { + LOG_ERROR("Transaction inputs money ("<< summary_inputs_money << ") less than outputs money (" << summary_outs_money << ")"); + return false; + } + + + //generate ring signatures + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + + std::stringstream ss_ring_s; + size_t i = 0; + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + ss_ring_s << "pub_keys:" << ENDL; + std::vector<const crypto::public_key*> keys_ptrs; + BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + { + keys_ptrs.push_back(&o.second); + ss_ring_s << o.second << ENDL; + } + + tx.signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + ss_ring_s << "signatures:" << ENDL; + std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; + i++; + } + + LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); + + return true; + } + //--------------------------------------------------------------- + bool get_inputs_money_amount(const transaction& tx, uint64_t& money) + { + money = 0; + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + money += tokey_in.amount; + } + return true; + } + //--------------------------------------------------------------- + uint64_t get_block_height(const block& b) + { + CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1"); + CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], const txin_gen, coinbase_in, 0); + return coinbase_in.height; + } + //--------------------------------------------------------------- + bool check_inputs_types_supported(const transaction& tx) + { + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECK_AND_ASSERT_MES(in.type() == typeid(txin_to_key), false, "wrong variant type: " + << in.type().name() << ", expected " << typeid(txin_to_key).name() + << ", in transaction id=" << get_transaction_hash(tx)); + + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool check_outs_valid(const transaction& tx) + { + BOOST_FOREACH(const tx_out& out, tx.vout) + { + CHECK_AND_ASSERT_MES(out.target.type() == typeid(txout_to_key), false, "wrong variant type: " + << out.target.type().name() << ", expected " << typeid(txout_to_key).name() + << ", in transaction id=" << get_transaction_hash(tx)); + + CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount ouput in transaction id=" << get_transaction_hash(tx)); + + if(!check_key(boost::get<txout_to_key>(out.target).key)) + return false; + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool check_money_overflow(const transaction& tx) + { + return check_inputs_overflow(tx) && check_outs_overflow(tx); + } + //--------------------------------------------------------------- + bool check_inputs_overflow(const transaction& tx) + { + uint64_t money = 0; + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if(money > tokey_in.amount + money) + return false; + money += tokey_in.amount; + } + return true; + } + //--------------------------------------------------------------- + bool check_outs_overflow(const transaction& tx) + { + uint64_t money = 0; + BOOST_FOREACH(const auto& o, tx.vout) + { + if(money > o.amount + money) + return false; + money += o.amount; + } + return true; + } + //--------------------------------------------------------------- + uint64_t get_outs_money_amount(const transaction& tx) + { + uint64_t outputs_amount = 0; + BOOST_FOREACH(const auto& o, tx.vout) + outputs_amount += o.amount; + return outputs_amount; + } + //--------------------------------------------------------------- + std::string short_hash_str(const crypto::hash& h) + { + std::string res = string_tools::pod_to_hex(h); + CHECK_AND_ASSERT_MES(res.size() == 64, res, "wrong hash256 with string_tools::pod_to_hex conversion"); + auto erased_pos = res.erase(8, 48); + res.insert(8, "...."); + return res; + } + //--------------------------------------------------------------- + bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index) + { + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); + crypto::public_key pk; + derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + return pk == out_key.key; + } + //--------------------------------------------------------------- + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered) + { + return lookup_acc_outs(acc, tx, get_tx_pub_key_from_extra(tx), outs, money_transfered); + } + //--------------------------------------------------------------- + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered) + { + money_transfered = 0; + size_t i = 0; + BOOST_FOREACH(const tx_out& o, tx.vout) + { + CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); + if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) + { + outs.push_back(i); + money_transfered += o.amount; + } + i++; + } + return true; + } + //--------------------------------------------------------------- + void get_blob_hash(const blobdata& blob, crypto::hash& res) + { + cn_fast_hash(blob.data(), blob.size(), res); + } + //--------------------------------------------------------------- + std::string print_money(uint64_t amount) + { + std::string s = std::to_string(amount); + if(s.size() < CRYPTONOTE_DISPLAY_DECIMAL_POINT+1) + { + s.insert(0, CRYPTONOTE_DISPLAY_DECIMAL_POINT+1 - s.size(), '0'); + } + s.insert(s.size() - CRYPTONOTE_DISPLAY_DECIMAL_POINT, "."); + return s; + } + //--------------------------------------------------------------- + crypto::hash get_blob_hash(const blobdata& blob) + { + crypto::hash h = null_hash; + get_blob_hash(blob, h); + return h; + } + //--------------------------------------------------------------- + crypto::hash get_transaction_hash(const transaction& t) + { + crypto::hash h = null_hash; + size_t blob_size = 0; + get_object_hash(t, h, blob_size); + return h; + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res) + { + size_t blob_size = 0; + return get_object_hash(t, res, blob_size); + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size) + { + return get_object_hash(t, res, blob_size); + } + //--------------------------------------------------------------- + blobdata get_block_hashing_blob(const block& b) + { + blobdata blob = t_serializable_object_to_blob(static_cast<block_header>(b)); + crypto::hash tree_root_hash = get_tx_tree_hash(b); + blob.append((const char*)&tree_root_hash, sizeof(tree_root_hash )); + blob.append(tools::get_varint_data(b.tx_hashes.size()+1)); + return blob; + } + //--------------------------------------------------------------- + bool get_block_hash(const block& b, crypto::hash& res) + { + return get_object_hash(get_block_hashing_blob(b), res); + } + //--------------------------------------------------------------- + crypto::hash get_block_hash(const block& b) + { + crypto::hash p = null_hash; + get_block_hash(b, p); + return p; + } + //--------------------------------------------------------------- + bool generate_genesis_block(block& bl) + { + //genesis block + bl = boost::value_initialized<block>(); + + + account_public_address ac = boost::value_initialized<account_public_address>(); + std::vector<size_t> sz; + construct_miner_tx(0, 0, ac, bl.miner_tx, 0, sz, 0, 1); // zero fee in genesis + blobdata txb = tx_to_blob(bl.miner_tx); + std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); + + //hard code coinbase tx in genesis block, because "tru" generating tx use random, but genesis should be always the same + std::string genesis_coinbase_tx_hex = "010a01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121013c086a48c15fb637a96991bc6d53caf77068b5ba6eeb3c82357228c49790584a"; + + blobdata tx_bl; + string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); + bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; + bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; + bl.timestamp = 0; + bl.nonce = 70; + miner::find_nonce_for_given_block(bl, 1, 0); + return true; + } + //--------------------------------------------------------------- + bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) + { + block b_local = b; //workaround to avoid const errors with do_serialize + blobdata bd = get_block_hashing_blob(b); + crypto::cn_slow_hash(bd.data(), bd.size(), res); + return true; + } + //--------------------------------------------------------------- + std::vector<uint64_t> relative_output_offsets_to_absolute(const std::vector<uint64_t>& off) + { + std::vector<uint64_t> res = off; + for(size_t i = 1; i < res.size(); i++) + res[i] += res[i-1]; + return res; + } + //--------------------------------------------------------------- + std::vector<uint64_t> absolute_output_offsets_to_relative(const std::vector<uint64_t>& off) + { + std::vector<uint64_t> res = off; + if(!off.size()) + return res; + std::sort(res.begin(), res.end());//just to be sure, actually it is already should be sorted + for(size_t i = res.size()-1; i != 0; i--) + res[i] -= res[i-1]; + + return res; + } + //--------------------------------------------------------------- + crypto::hash get_block_longhash(const block& b, uint64_t height) + { + crypto::hash p = null_hash; + get_block_longhash(b, p, height); + return p; + } + //--------------------------------------------------------------- + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + { + std::stringstream ss; + ss << b_blob; + binary_archive<false> ba(ss); + bool r = ::serialization::serialize(ba, b); + CHECK_AND_ASSERT_MES(r, false, "Failed to parse block from blob"); + return true; + } + //--------------------------------------------------------------- + blobdata block_to_blob(const block& b) + { + return t_serializable_object_to_blob(b); + } + //--------------------------------------------------------------- + bool block_to_blob(const block& b, blobdata& b_blob) + { + return t_serializable_object_to_blob(b, b_blob); + } + //--------------------------------------------------------------- + blobdata tx_to_blob(const transaction& tx) + { + return t_serializable_object_to_blob(tx); + } + //--------------------------------------------------------------- + bool tx_to_blob(const transaction& tx, blobdata& b_blob) + { + return t_serializable_object_to_blob(tx, b_blob); + } + //--------------------------------------------------------------- + void get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes, crypto::hash& h) + { + tree_hash(tx_hashes.data(), tx_hashes.size(), h); + } + //--------------------------------------------------------------- + crypto::hash get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes) + { + crypto::hash h = null_hash; + get_tx_tree_hash(tx_hashes, h); + return h; + } + //--------------------------------------------------------------- + crypto::hash get_tx_tree_hash(const block& b) + { + std::vector<crypto::hash> txs_ids; + crypto::hash h = null_hash; + size_t bl_sz = 0; + get_transaction_hash(b.miner_tx, h, bl_sz); + txs_ids.push_back(h); + BOOST_FOREACH(auto& th, b.tx_hashes) + txs_ids.push_back(th); + return get_tx_tree_hash(txs_ids); + } + //--------------------------------------------------------------- +} diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h new file mode 100644 index 000000000..1c50832b6 --- /dev/null +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -0,0 +1,191 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "cryptonote_core/cryptonote_basic_impl.h" +#include "account.h" +#include "include_base_utils.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" + + +namespace cryptonote +{ + //--------------------------------------------------------------- + void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h); + crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx); + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); + bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector<size_t>& blocks_sizes, size_t current_block_size, const blobdata& extra_nonce, size_t max_outs = 1); + bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector<size_t>& blocks_sizes, size_t current_block_size, size_t max_outs = 1); + + struct tx_source_entry + { + typedef std::pair<uint64_t, crypto::public_key> output_entry; + + std::vector<output_entry> outputs; //index + key + uint64_t real_output; //index in outputs vector of real output_entry + crypto::public_key real_out_tx_key; //incoming real tx public key + size_t real_output_in_tx_index; //index in transaction outputs vector + uint64_t amount; //money + }; + + struct tx_destination_entry + { + uint64_t amount; //money + account_public_address addr; //destination address + + tx_destination_entry() { } + tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } + }; + + //--------------------------------------------------------------- + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, transaction& tx, uint64_t unlock_time); + bool parse_and_validate_tx_extra(const transaction& tx, crypto::public_key& tx_pub_key); + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); + bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); + bool add_tx_extra_nonce(transaction& tx, const blobdata& extra_nonce); + bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); + bool get_tx_fee(const transaction& tx, uint64_t & fee); + uint64_t get_tx_fee(const transaction& tx); + bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + void get_blob_hash(const blobdata& blob, crypto::hash& res); + crypto::hash get_blob_hash(const blobdata& blob); + std::string short_hash_str(const crypto::hash& h); + + crypto::hash get_transaction_hash(const transaction& t); + bool get_transaction_hash(const transaction& t, crypto::hash& res); + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size); + blobdata get_block_hashing_blob(const block& b); + bool get_block_hash(const block& b, crypto::hash& res); + crypto::hash get_block_hash(const block& b); + bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height); + crypto::hash get_block_longhash(const block& b, uint64_t height); + bool generate_genesis_block(block& bl); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b); + bool get_inputs_money_amount(const transaction& tx, uint64_t& money); + uint64_t get_outs_money_amount(const transaction& tx); + bool check_inputs_types_supported(const transaction& tx); + bool check_outs_valid(const transaction& tx); + blobdata get_block_hashing_blob(const block& b); + bool parse_amount(uint64_t& amount, const std::string& str_amount); + + bool check_money_overflow(const transaction& tx); + bool check_outs_overflow(const transaction& tx); + bool check_inputs_overflow(const transaction& tx); + uint64_t get_block_height(const block& b); + std::vector<uint64_t> relative_output_offsets_to_absolute(const std::vector<uint64_t>& off); + std::vector<uint64_t> absolute_output_offsets_to_relative(const std::vector<uint64_t>& off); + std::string print_money(uint64_t amount); + //--------------------------------------------------------------- + template<class t_object> + bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) + { + std::stringstream ss; + binary_archive<true> ba(ss); + bool r = ::serialization::serialize(ba, const_cast<t_object&>(to)); + b_blob = ss.str(); + return r; + } + //--------------------------------------------------------------- + template<class t_object> + blobdata t_serializable_object_to_blob(const t_object& to) + { + blobdata b; + t_serializable_object_to_blob(to, b); + return b; + } + //--------------------------------------------------------------- + template<class t_object> + bool get_object_hash(const t_object& o, crypto::hash& res) + { + get_blob_hash(t_serializable_object_to_blob(o), res); + return true; + } + //--------------------------------------------------------------- + template<class t_object> + size_t get_object_blobsize(const t_object& o) + { + blobdata b = t_serializable_object_to_blob(o); + return b.size(); + } + //--------------------------------------------------------------- + template<class t_object> + bool get_object_hash(const t_object& o, crypto::hash& res, size_t& blob_size) + { + blobdata bl = t_serializable_object_to_blob(o); + blob_size = bl.size(); + get_blob_hash(bl, res); + return true; + } + //--------------------------------------------------------------- + template <typename T> + std::string obj_to_json_str(T& obj) + { + std::stringstream ss; + json_archive<true> ar(ss, true); + bool r = ::serialization::serialize(ar, obj); + CHECK_AND_ASSERT_MES(r, "", "obj_to_json_str failed: serialization::serialize returned false"); + return ss.str(); + } + //--------------------------------------------------------------- + // 62387455827 -> 455827 + 7000000 + 80000000 + 300000000 + 2000000000 + 60000000000, where 455827 <= dust_threshold + template<typename chunk_handler_t, typename dust_handler_t> + void decompose_amount_into_digits(uint64_t amount, uint64_t dust_threshold, const chunk_handler_t& chunk_handler, const dust_handler_t& dust_handler) + { + if (0 == amount) + { + chunk_handler(0); + return; + } + + bool is_dust_handled = false; + uint64_t dust = 0; + uint64_t order = 1; + while (0 != amount) + { + uint64_t chunk = (amount % 10) * order; + amount /= 10; + order *= 10; + + if (dust + chunk <= dust_threshold) + { + dust += chunk; + } + else + { + if (!is_dust_handled && 0 != dust) + { + dust_handler(dust); + is_dust_handled = true; + } + if (0 != chunk) + { + chunk_handler(chunk); + } + } + } + + if (!is_dust_handled && 0 != dust) + { + dust_handler(dust); + } + } + //--------------------------------------------------------------- + blobdata block_to_blob(const block& b); + bool block_to_blob(const block& b, blobdata& b_blob); + blobdata tx_to_blob(const transaction& b); + bool tx_to_blob(const transaction& b, blobdata& b_blob); + void get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes, crypto::hash& h); + crypto::hash get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes); + crypto::hash get_tx_tree_hash(const block& b); + +#define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ + CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ + specific_type& variable_name = boost::get<specific_type>(variant_var); + +} diff --git a/src/cryptonote_core/cryptonote_stat_info.h b/src/cryptonote_core/cryptonote_stat_info.h new file mode 100644 index 000000000..9d406748c --- /dev/null +++ b/src/cryptonote_core/cryptonote_stat_info.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include "serialization/keyvalue_serialization.h" + + +namespace cryptonote +{ + struct core_stat_info + { + uint64_t tx_pool_size; + uint64_t blockchain_height; + uint64_t mining_speed; + uint64_t alternative_blocks; + std::string top_block_id_str; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_pool_size) + KV_SERIALIZE(blockchain_height) + KV_SERIALIZE(mining_speed) + KV_SERIALIZE(alternative_blocks) + KV_SERIALIZE(top_block_id_str) + END_KV_SERIALIZE_MAP() + }; +} diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp new file mode 100644 index 000000000..052f46662 --- /dev/null +++ b/src/cryptonote_core/difficulty.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <vector> + +#include "common/int-util.h" +#include "crypto/hash.h" +#include "cryptonote_config.h" +#include "difficulty.h" + +namespace cryptonote { + + using std::size_t; + using std::uint64_t; + using std::vector; + +#if defined(_MSC_VER) +#include <windows.h> +#include <winnt.h> + + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + low = UnsignedMultiply128(a, b, &high); + } + +#else + + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + typedef unsigned __int128 uint128_t; + uint128_t res = (uint128_t) a * (uint128_t) b; + low = (uint64_t) res; + high = (uint64_t) (res >> 64); + } + +#endif + + static inline bool cadd(uint64_t a, uint64_t b) { + return a + b < a; + } + + static inline bool cadc(uint64_t a, uint64_t b, bool c) { + return a + b < a || (c && a + b == (uint64_t) -1); + } + + bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { + uint64_t low, high, top, cur; + // First check the highest word, this will most likely fail for a random hash. + mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high); + if (high != 0) { + return false; + } + mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur); + mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high); + bool carry = cadd(cur, low); + cur = high; + mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high); + carry = cadc(cur, low, carry); + carry = cadc(high, top, carry); + return !carry; + } + + difficulty_type next_difficulty(vector<uint64_t> timestamps, vector<difficulty_type> cumulative_difficulties, size_t target_seconds) { + //cutoff DIFFICULTY_LAG + if(timestamps.size() > DIFFICULTY_WINDOW) + { + timestamps.resize(DIFFICULTY_WINDOW); + cumulative_difficulties.resize(DIFFICULTY_WINDOW); + } + + + size_t length = timestamps.size(); + assert(length == cumulative_difficulties.size()); + if (length <= 1) { + return 1; + } + static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small"); + assert(length <= DIFFICULTY_WINDOW); + sort(timestamps.begin(), timestamps.end()); + size_t cut_begin, cut_end; + static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large"); + if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) { + cut_begin = 0; + cut_end = length; + } else { + cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2; + cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT); + } + assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length); + uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin]; + if (time_span == 0) { + time_span = 1; + } + difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin]; + assert(total_work > 0); + uint64_t low, high; + mul(total_work, target_seconds, low, high); + if (high != 0 || low + time_span - 1 < low) { + return 0; + } + return (low + time_span - 1) / time_span; + } + + difficulty_type next_difficulty(vector<uint64_t> timestamps, vector<difficulty_type> cumulative_difficulties) + { + return next_difficulty(std::move(timestamps), std::move(cumulative_difficulties), DIFFICULTY_TARGET); + } +} diff --git a/src/cryptonote_core/difficulty.h b/src/cryptonote_core/difficulty.h new file mode 100644 index 000000000..aad1e27ca --- /dev/null +++ b/src/cryptonote_core/difficulty.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include <cstdint> +#include <vector> + +#include "crypto/hash.h" + +namespace cryptonote +{ + typedef std::uint64_t difficulty_type; + + bool check_hash(const crypto::hash &hash, difficulty_type difficulty); + difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties); + difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds); +} diff --git a/src/cryptonote_core/miner.cpp b/src/cryptonote_core/miner.cpp new file mode 100644 index 000000000..a882e1899 --- /dev/null +++ b/src/cryptonote_core/miner.cpp @@ -0,0 +1,356 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include <sstream> +#include <numeric> +#include <boost/utility/value_init.hpp> +#include <boost/interprocess/detail/atomic.hpp> +#include <boost/limits.hpp> +#include <boost/foreach.hpp> +#include "misc_language.h" +#include "include_base_utils.h" +#include "cryptonote_basic_impl.h" +#include "cryptonote_format_utils.h" +#include "file_io_utils.h" +#include "common/command_line.h" +#include "string_coding.h" +#include "storages/portable_storage_template_helper.h" + +using namespace epee; + +#include "miner.h" + + + +namespace cryptonote +{ + + namespace + { + const command_line::arg_descriptor<std::string> arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; + const command_line::arg_descriptor<std::string> arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; + const command_line::arg_descriptor<uint32_t> arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; + } + + + miner::miner(i_miner_handler* phandler):m_stop(1), + m_template(boost::value_initialized<block>()), + m_template_no(0), + m_diffic(0), + m_thread_index(0), + m_phandler(phandler), + m_height(0), + m_pausers_count(0), + m_threads_total(0), + m_starter_nonce(0), + m_last_hr_merge_time(0), + m_hashes(0), + m_do_print_hashrate(false), + m_do_mining(false) + { + + } + //----------------------------------------------------------------------------------------------------- + miner::~miner() + { + stop(); + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_block_template(const block& bl, const difficulty_type& di, uint64_t height) + { + CRITICAL_REGION_LOCAL(m_template_lock); + m_template = bl; + m_diffic = di; + m_height = height; + ++m_template_no; + m_starter_nonce = crypto::rand<uint32_t>(); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::on_block_chain_update() + { + if(!is_mining()) + return true; + + return request_block_template(); + } + //----------------------------------------------------------------------------------------------------- + bool miner::request_block_template() + { + block bl = AUTO_VAL_INIT(bl); + difficulty_type di = AUTO_VAL_INIT(di); + uint64_t height = AUTO_VAL_INIT(height); + cryptonote::blobdata extra_nonce; + if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) + { + extra_nonce = m_extra_messages[m_config.current_extra_message_index]; + } + + if(!m_phandler->get_block_template(bl, m_mine_address, di, height, extra_nonce)) + { + LOG_ERROR("Failed to get_block_template(), stopping mining"); + return false; + } + set_block_template(bl, di, height); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::on_idle() + { + m_update_block_template_interval.do_call([&](){ + if(is_mining())request_block_template(); + return true; + }); + + m_update_merge_hr_interval.do_call([&](){ + merge_hr(); + return true; + }); + + return true; + } + //----------------------------------------------------------------------------------------------------- + void miner::do_print_hashrate(bool do_hr) + { + m_do_print_hashrate = do_hr; + } + //----------------------------------------------------------------------------------------------------- + void miner::merge_hr() + { + if(m_last_hr_merge_time && is_mining()) + { + m_current_hash_rate = m_hashes * 1000 / ((misc_utils::get_tick_count() - m_last_hr_merge_time + 1)); + CRITICAL_REGION_LOCAL(m_last_hash_rates_lock); + m_last_hash_rates.push_back(m_current_hash_rate); + if(m_last_hash_rates.size() > 19) + m_last_hash_rates.pop_front(); + if(m_do_print_hashrate) + { + uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0); + float hr = static_cast<float>(total_hr)/static_cast<float>(m_last_hash_rates.size()); + std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << ENDL; + } + } + m_last_hr_merge_time = misc_utils::get_tick_count(); + m_hashes = 0; + } + //----------------------------------------------------------------------------------------------------- + void miner::init_options(boost::program_options::options_description& desc) + { + command_line::add_arg(desc, arg_extra_messages); + command_line::add_arg(desc, arg_start_mining); + command_line::add_arg(desc, arg_mining_threads); + } + //----------------------------------------------------------------------------------------------------- + bool miner::init(const boost::program_options::variables_map& vm) + { + if(command_line::has_arg(vm, arg_extra_messages)) + { + std::string buff; + bool r = file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_extra_messages), buff); + CHECK_AND_ASSERT_MES(r, false, "Failed to load file with extra messages: " << command_line::get_arg(vm, arg_extra_messages)); + std::vector<std::string> extra_vec; + boost::split(extra_vec, buff, boost::is_any_of("\n"), boost::token_compress_on ); + m_extra_messages.resize(extra_vec.size()); + for(size_t i = 0; i != extra_vec.size(); i++) + { + string_tools::trim(extra_vec[i]); + if(!extra_vec[i].size()) + continue; + std::string buff = string_encoding::base64_decode(extra_vec[i]); + if(buff != "0") + m_extra_messages[i] = buff; + } + m_config_folder_path = boost::filesystem::path(command_line::get_arg(vm, arg_extra_messages)).parent_path().string(); + m_config = AUTO_VAL_INIT(m_config); + epee::serialization::load_t_from_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + LOG_PRINT_L0("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); + } + + if(command_line::has_arg(vm, arg_start_mining)) + { + if(!cryptonote::get_account_address_from_str(m_mine_address, command_line::get_arg(vm, arg_start_mining))) + { + LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); + return false; + } + m_threads_total = 1; + m_do_mining = true; + if(command_line::has_arg(vm, arg_mining_threads)) + { + m_threads_total = command_line::get_arg(vm, arg_mining_threads); + } + } + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::is_mining() + { + return !m_stop; + } + //----------------------------------------------------------------------------------------------------- + bool miner::start(const account_public_address& adr, size_t threads_count) + { + m_mine_address = adr; + m_threads_total = static_cast<uint32_t>(threads_count); + m_starter_nonce = crypto::rand<uint32_t>(); + CRITICAL_REGION_LOCAL(m_threads_lock); + if(is_mining()) + { + LOG_ERROR("Starting miner but it's already started"); + return false; + } + + if(!m_threads.empty()) + { + LOG_ERROR("Unable to start miner because there are active mining threads"); + return false; + } + + if(!m_template_no) + request_block_template();//lets update block template + + boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); + boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); + + for(size_t i = 0; i != threads_count; i++) + m_threads.push_back(boost::thread(boost::bind(&miner::worker_thread, this))); + + LOG_PRINT_L0("Mining has started with " << threads_count << " threads, good luck!" ) + return true; + } + //----------------------------------------------------------------------------------------------------- + uint64_t miner::get_speed() + { + return m_current_hash_rate; + } + //----------------------------------------------------------------------------------------------------- + void miner::send_stop_signal() + { + boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); + } + //----------------------------------------------------------------------------------------------------- + bool miner::stop() + { + send_stop_signal(); + CRITICAL_REGION_LOCAL(m_threads_lock); + + BOOST_FOREACH(boost::thread& th, m_threads) + th.join(); + + m_threads.clear(); + LOG_PRINT_L0("Mining has been stopped, " << m_threads.size() << " finished" ); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height) + { + for(; bl.nonce != std::numeric_limits<uint32_t>::max(); bl.nonce++) + { + crypto::hash h; + get_block_longhash(bl, h, height); + + if(check_hash(h, diffic)) + { + return true; + } + } + return false; + } + //----------------------------------------------------------------------------------------------------- + void miner::on_synchronized() + { + if(m_do_mining) + { + start(m_mine_address, m_threads_total); + } + } + //----------------------------------------------------------------------------------------------------- + void miner::pause() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + ++m_pausers_count; + if(m_pausers_count == 1 && is_mining()) + LOG_PRINT_L2("MINING PAUSED"); + } + //----------------------------------------------------------------------------------------------------- + void miner::resume() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + --m_pausers_count; + if(m_pausers_count < 0) + { + m_pausers_count = 0; + LOG_PRINT_RED_L0("Unexpected miner::resume() called"); + } + if(!m_pausers_count && is_mining()) + LOG_PRINT_L2("MINING RESUMED"); + } + //----------------------------------------------------------------------------------------------------- + bool miner::worker_thread() + { + uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); + LOG_PRINT_L0("Miner thread was started ["<< th_local_index << "]"); + log_space::log_singletone::set_thread_log_prefix(std::string("[miner ") + std::to_string(th_local_index) + "]"); + uint32_t nonce = m_starter_nonce + th_local_index; + uint64_t height = 0; + difficulty_type local_diff = 0; + uint32_t local_template_ver = 0; + block b; + while(!m_stop) + { + if(m_pausers_count)//anti split workaround + { + misc_utils::sleep_no_w(100); + continue; + } + + if(local_template_ver != m_template_no) + { + + CRITICAL_REGION_BEGIN(m_template_lock); + b = m_template; + local_diff = m_diffic; + height = m_height; + CRITICAL_REGION_END(); + local_template_ver = m_template_no; + nonce = m_starter_nonce + th_local_index; + } + + if(!local_template_ver)//no any set_block_template call + { + LOG_PRINT_L2("Block template not set yet"); + epee::misc_utils::sleep_no_w(1000); + continue; + } + + b.nonce = nonce; + crypto::hash h; + get_block_longhash(b, h, height); + + if(check_hash(h, local_diff)) + { + //we lucky! + ++m_config.current_extra_message_index; + LOG_PRINT_GREEN("Found block for difficulty: " << local_diff, LOG_LEVEL_0); + if(!m_phandler->handle_block_found(b)) + { + --m_config.current_extra_message_index; + }else + { + //success update, lets update config + epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + } + } + nonce+=m_threads_total; + ++m_hashes; + } + LOG_PRINT_L0("Miner thread stopped ["<< th_local_index << "]"); + return true; + } + //----------------------------------------------------------------------------------------------------- +} + diff --git a/src/cryptonote_core/miner.h b/src/cryptonote_core/miner.h new file mode 100644 index 000000000..03f09509e --- /dev/null +++ b/src/cryptonote_core/miner.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include <boost/atomic.hpp> +#include <boost/program_options.hpp> +#include <atomic> +#include "cryptonote_basic.h" +#include "difficulty.h" +#include "math_helper.h" + + +namespace cryptonote +{ + + struct i_miner_handler + { + virtual bool handle_block_found(block& b) = 0; + virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) = 0; + protected: + ~i_miner_handler(){}; + }; + + /************************************************************************/ + /* */ + /************************************************************************/ + class miner + { + public: + miner(i_miner_handler* phandler); + ~miner(); + bool init(const boost::program_options::variables_map& vm); + static void init_options(boost::program_options::options_description& desc); + bool set_block_template(const block& bl, const difficulty_type& diffic, uint64_t height); + bool on_block_chain_update(); + bool start(const account_public_address& adr, size_t threads_count); + uint64_t get_speed(); + void send_stop_signal(); + bool stop(); + bool is_mining(); + bool on_idle(); + void on_synchronized(); + //synchronous analog (for fast calls) + static bool find_nonce_for_given_block(block& bl, const difficulty_type& diffic, uint64_t height); + void pause(); + void resume(); + void do_print_hashrate(bool do_hr); + + private: + bool worker_thread(); + bool request_block_template(); + void merge_hr(); + + struct miner_config + { + uint64_t current_extra_message_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(current_extra_message_index) + END_KV_SERIALIZE_MAP() + }; + + + volatile uint32_t m_stop; + ::critical_section m_template_lock; + block m_template; + std::atomic<uint32_t> m_template_no; + std::atomic<uint32_t> m_starter_nonce; + difficulty_type m_diffic; + uint64_t m_height; + volatile uint32_t m_thread_index; + volatile uint32_t m_threads_total; + std::atomic<int32_t> m_pausers_count; + ::critical_section m_miners_count_lock; + + std::list<boost::thread> m_threads; + ::critical_section m_threads_lock; + i_miner_handler* m_phandler; + account_public_address m_mine_address; + math_helper::once_a_time_seconds<5> m_update_block_template_interval; + math_helper::once_a_time_seconds<2> m_update_merge_hr_interval; + std::vector<blobdata> m_extra_messages; + miner_config m_config; + std::string m_config_folder_path; + std::atomic<uint64_t> m_last_hr_merge_time; + std::atomic<uint64_t> m_hashes; + std::atomic<uint64_t> m_current_hash_rate; + critical_section m_last_hash_rates_lock; + std::list<uint64_t> m_last_hash_rates; + bool m_do_print_hashrate; + bool m_do_mining; + + }; +} + + + diff --git a/src/cryptonote_core/tx_extra.h b/src/cryptonote_core/tx_extra.h new file mode 100644 index 000000000..7e27cbc7f --- /dev/null +++ b/src/cryptonote_core/tx_extra.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + + + +#define TX_EXTRA_PADDING_MAX_COUNT 40 +#define TX_EXTRA_TAG_PUBKEY 0x01 +#define TX_EXTRA_NONCE 0x02 diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp new file mode 100644 index 000000000..3a1799675 --- /dev/null +++ b/src/cryptonote_core/tx_pool.cpp @@ -0,0 +1,410 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <boost/filesystem.hpp> +#include <unordered_set> + +#include "tx_pool.h" +#include "cryptonote_format_utils.h" +#include "cryptonote_boost_serialization.h" +#include "cryptonote_config.h" +#include "blockchain_storage.h" +#include "common/boost_serialization_helper.h" +#include "misc_language.h" +#include "warnings.h" +#include "crypto/hash.h" + +DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated + +namespace cryptonote +{ + //--------------------------------------------------------------------------------- + tx_memory_pool::tx_memory_pool(blockchain_storage& bchs): m_blockchain(bchs) + { + + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block) + { + + + if(!check_inputs_types_supported(tx)) + { + tvc.m_verifivation_failed = true; + return false; + } + + uint64_t inputs_amount = 0; + if(!get_inputs_money_amount(tx, inputs_amount)) + { + tvc.m_verifivation_failed = true; + return false; + } + + uint64_t outputs_amount = get_outs_money_amount(tx); + + if(outputs_amount >= inputs_amount) + { + LOG_PRINT_L0("transaction use more money then it has: use " << outputs_amount << ", have " << inputs_amount); + tvc.m_verifivation_failed = true; + return false; + } + + //check key images for transaction if it is not kept by block + if(!kept_by_block) + { + if(have_tx_keyimges_as_spent(tx)) + { + LOG_ERROR("Transaction with id= "<< id << " used already spent key images"); + tvc.m_verifivation_failed = true; + return false; + } + } + + + crypto::hash max_used_block_id = null_hash; + uint64_t max_used_block_height = 0; + bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); + CRITICAL_REGION_LOCAL(m_transactions_lock); + if(!ch_inp_res) + { + 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; + txd_p.first->second.tx = tx; + txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.max_used_block_id = null_hash; + txd_p.first->second.max_used_block_height = 0; + txd_p.first->second.kept_by_block = kept_by_block; + tvc.m_verifivation_impossible = true; + tvc.m_added_to_pool = true; + }else + { + LOG_PRINT_L0("tx used wrong inputs, rejected"); + tvc.m_verifivation_failed = true; + return false; + } + }else + { + //update transactions container + auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); + CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); + txd_p.first->second.blob_size = blob_size; + txd_p.first->second.tx = tx; + txd_p.first->second.kept_by_block = kept_by_block; + txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.max_used_block_id = max_used_block_id; + txd_p.first->second.max_used_block_height = max_used_block_height; + txd_p.first->second.last_failed_height = 0; + txd_p.first->second.last_failed_id = null_hash; + tvc.m_added_to_pool = true; + + if(txd_p.first->second.fee > 0) + tvc.m_should_be_relayed = true; + } + + 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 + << ", 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); + CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); + } + + tvc.m_verifivation_failed = false; + //succeed + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block) + { + crypto::hash h = null_hash; + size_t blob_size = 0; + get_transaction_hash(tx, h, blob_size); + return add_tx(tx, h, blob_size, tvc, keeped_by_block); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::remove_transaction_keyimages(const transaction& tx) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + BOOST_FOREACH(const txin_v& vi, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(vi, const txin_to_key, txin, false); + auto it = m_spent_key_images.find(txin.k_image); + CHECK_AND_ASSERT_MES(it != m_spent_key_images.end(), false, "failed to find transaction input in key images. img=" << txin.k_image << ENDL + << "transaction id = " << get_transaction_hash(tx)); + std::unordered_set<crypto::hash>& key_image_set = it->second; + CHECK_AND_ASSERT_MES(key_image_set.size(), false, "empty key_image set, img=" << txin.k_image << ENDL + << "transaction id = " << get_transaction_hash(tx)); + + auto it_in_set = key_image_set.find(get_transaction_hash(tx)); + CHECK_AND_ASSERT_MES(key_image_set.size(), false, "transaction id not found in key_image set, img=" << txin.k_image << ENDL + << "transaction id = " << get_transaction_hash(tx)); + key_image_set.erase(it_in_set); + if(!key_image_set.size()) + { + //it is now empty hash container for this key_image + m_spent_key_images.erase(it); + } + + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + auto it = m_transactions.find(id); + if(it == m_transactions.end()) + return false; + + tx = it->second.tx; + blob_size = it->second.blob_size; + fee = it->second.fee; + remove_transaction_keyimages(it->second.tx); + m_transactions.erase(it); + return true; + } + //--------------------------------------------------------------------------------- + size_t tx_memory_pool::get_transactions_count() + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + return m_transactions.size(); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_transactions(std::list<transaction>& txs) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + BOOST_FOREACH(const auto& tx_vt, m_transactions) + txs.push_back(tx_vt.second.tx); + + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_transaction(const crypto::hash& id, transaction& tx) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + auto it = m_transactions.find(id); + if(it == m_transactions.end()) + return false; + tx = it->second.tx; + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id) + { + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id) + { + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx(const crypto::hash &id) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + if(m_transactions.count(id)) + return true; + return false; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + BOOST_FOREACH(const auto& in, tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail + if(have_tx_keyimg_as_spent(tokey_in.k_image)) + return true; + } + return false; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + return m_spent_key_images.end() != m_spent_key_images.find(key_im); + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::lock() + { + m_transactions_lock.lock(); + } + //--------------------------------------------------------------------------------- + void tx_memory_pool::unlock() + { + m_transactions_lock.unlock(); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::is_transaction_ready_to_go(tx_details& txd) + { + //not the best implementation at this time, sorry :( + //check is ring_signature already checked ? + if(txd.max_used_block_id == null_hash) + {//not checked, lets try to check + + 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)) + { + 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); + return false; + } + }else + { + if(txd.max_used_block_height >= m_blockchain.get_current_blockchain_height()) + return false; + if(m_blockchain.get_block_id_by_height(txd.max_used_block_height) != txd.max_used_block_id) + { + //if we already failed on this height and id, skip actual ring signature check + if(txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) + return false; + //check ring signature again, it is possible (with very small chance) that this transaction become again valid + if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id)) + { + 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); + return false; + } + } + } + //if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure + if(m_blockchain.have_tx_keyimges_as_spent(txd.tx)) + return false; + + //transaction is ok. + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::have_key_images(const std::unordered_set<crypto::key_image>& k_images, const transaction& tx) + { + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); + if(k_images.count(itk.k_image)) + return true; + } + return false; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::append_key_images(std::unordered_set<crypto::key_image>& k_images, const transaction& tx) + { + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, false); + auto i_res = k_images.insert(itk.k_image); + CHECK_AND_ASSERT_MES(i_res.second, false, "internal error: key images pool cache - inserted duplicate image in set: " << itk.k_image); + } + return true; + } + //--------------------------------------------------------------------------------- + std::string tx_memory_pool::print_pool(bool short_format) + { + std::stringstream ss; + CRITICAL_REGION_LOCAL(m_transactions_lock); + BOOST_FOREACH(transactions_container::value_type& txe, m_transactions) + { + if(short_format) + { + tx_details& txd = txe.second; + ss << "id: " << txe.first << ENDL + << "blob_size: " << txd.blob_size << ENDL + << "fee: " << txd.fee << ENDL + << "kept_by_block: " << txd.kept_by_block << ENDL + << "max_used_block_height: " << txd.max_used_block_height << ENDL + << "max_used_block_id: " << txd.max_used_block_id << ENDL + << "last_failed_height: " << txd.last_failed_height << ENDL + << "last_failed_id: " << txd.last_failed_id << ENDL; + }else + { + tx_details& txd = txe.second; + ss << "id: " << txe.first << ENDL + << obj_to_json_str(txd.tx) << ENDL + << "blob_size: " << txd.blob_size << ENDL + << "fee: " << txd.fee << ENDL + << "kept_by_block: " << txd.kept_by_block << ENDL + << "max_used_block_height: " << txd.max_used_block_height << ENDL + << "max_used_block_id: " << txd.max_used_block_id << ENDL + << "last_failed_height: " << txd.last_failed_height << ENDL + << "last_failed_id: " << txd.last_failed_id << ENDL; + } + + } + return ss.str(); + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::fill_block_template(block& bl, size_t& cumulative_sizes, size_t max_comulative_sz, uint64_t& fee) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + + fee = 0; + std::unordered_set<crypto::key_image> k_images; + + BOOST_FOREACH(transactions_container::value_type& tx, m_transactions) + { + if(cumulative_sizes + tx.second.blob_size > max_comulative_sz) + continue; + + if(!is_transaction_ready_to_go(tx.second)) + continue; + + if(have_key_images(k_images, tx.second.tx)) + continue; + + bl.tx_hashes.push_back(tx.first); + cumulative_sizes += tx.second.blob_size; + fee += tx.second.fee; + append_key_images(k_images, tx.second.tx); + + if(cumulative_sizes >= max_comulative_sz) + break; + } + + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::init(const std::string& config_folder) + { + m_config_folder = config_folder; + std::string state_file_path = config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; + boost::system::error_code ec; + if(!boost::filesystem::exists(state_file_path, ec)) + return true; + bool res = tools::unserialize_obj_from_file(*this, state_file_path); + if(!res) + { + LOG_PRINT_L0("Failed to load memory pool from file " << state_file_path); + } + return res; + } + + //--------------------------------------------------------------------------------- + bool tx_memory_pool::deinit() + { + if (!tools::create_directories_if_necessary(m_config_folder)) + { + LOG_PRINT_L0("Failed to create data directory: " << m_config_folder); + return false; + } + + std::string state_file_path = m_config_folder + "/" + CRYPTONOTE_POOLDATA_FILENAME; + bool res = tools::serialize_obj_to_file(*this, state_file_path); + if(!res) + { + LOG_PRINT_L0("Failed to serialize memory pool to file " << state_file_path); + } + return true; + } +} diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h new file mode 100644 index 000000000..1dff7ee1c --- /dev/null +++ b/src/cryptonote_core/tx_pool.h @@ -0,0 +1,168 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include "include_base_utils.h" +using namespace epee; + + +#include <set> +#include <unordered_map> +#include <unordered_set> +#include <boost/serialization/version.hpp> +#include <boost/utility.hpp> + +#include "string_tools.h" +#include "syncobj.h" +#include "cryptonote_basic_impl.h" +#include "verification_context.h" +#include "crypto/hash.h" + + +namespace cryptonote +{ + class blockchain_storage; + /************************************************************************/ + /* */ + /************************************************************************/ + + class tx_memory_pool: boost::noncopyable + { + public: + tx_memory_pool(blockchain_storage& bchs); + bool add_tx(const transaction &tx, const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool keeped_by_block); + bool add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block); + //gets tx and remove it from pool + bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee); + + bool have_tx(const crypto::hash &id); + bool have_tx_keyimg_as_spent(const crypto::key_image& key_im); + bool have_tx_keyimges_as_spent(const transaction& tx); + + bool on_blockchain_inc(uint64_t new_block_height, const crypto::hash& top_block_id); + bool on_blockchain_dec(uint64_t new_block_height, const crypto::hash& top_block_id); + + void lock(); + void unlock(); + + // load/store operations + bool init(const std::string& config_folder); + bool deinit(); + bool fill_block_template(block& bl, size_t& cumulative_sizes, size_t max_comulative_sz, uint64_t& fee); + bool get_transactions(std::list<transaction>& txs); + bool get_transaction(const crypto::hash& h, transaction& tx); + size_t get_transactions_count(); + bool remove_transaction_keyimages(const transaction& tx); + bool have_key_images(const std::unordered_set<crypto::key_image>& kic, const transaction& tx); + bool append_key_images(std::unordered_set<crypto::key_image>& kic, const transaction& tx); + std::string print_pool(bool short_format); + + /*bool flush_pool(const std::strig& folder); + bool inflate_pool(const std::strig& folder);*/ + +#define CURRENT_MEMPOOL_ARCHIVE_VER 7 + + template<class archive_t> + void serialize(archive_t & a, const unsigned int version) + { + if(version < CURRENT_MEMPOOL_ARCHIVE_VER ) + return; + CRITICAL_REGION_LOCAL(m_transactions_lock); + a & m_transactions; + a & m_spent_key_images; + } + + struct tx_details + { + transaction tx; + size_t blob_size; + uint64_t fee; + crypto::hash max_used_block_id; + uint64_t max_used_block_height; + bool kept_by_block; + // + uint64_t last_failed_height; + crypto::hash last_failed_id; + }; + + private: + bool is_transaction_ready_to_go(tx_details& txd); + typedef std::unordered_map<crypto::hash, tx_details > transactions_container; + typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash> > key_images_container; + + epee::critical_section m_transactions_lock; + transactions_container m_transactions; + key_images_container m_spent_key_images; + + //transactions_container m_alternative_transactions; + + std::string m_config_folder; + blockchain_storage& m_blockchain; + /************************************************************************/ + /* */ + /************************************************************************/ + /*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;} + }; + + }; +} + +namespace boost +{ + namespace serialization + { + template<class archive_t> + void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) + { + ar & td.blob_size; + ar & td.fee; + ar & td.tx; + ar & td.max_used_block_height; + ar & td.max_used_block_id; + ar & td.last_failed_height; + ar & td.last_failed_id; + + } + } +} +BOOST_CLASS_VERSION(cryptonote::tx_memory_pool, CURRENT_MEMPOOL_ARCHIVE_VER) + + + diff --git a/src/cryptonote_core/verification_context.h b/src/cryptonote_core/verification_context.h new file mode 100644 index 000000000..210cc2d5b --- /dev/null +++ b/src/cryptonote_core/verification_context.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#pragma once +namespace cryptonote +{ + /************************************************************************/ + /* */ + /************************************************************************/ + struct tx_verification_context + { + bool m_should_be_relayed; + 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; + }; + + struct block_verification_context + { + bool m_added_to_main_chain; + bool m_verifivation_failed; //bad block, should drop connection + bool m_marked_as_orphaned; + bool m_already_exists; + }; +} |