diff options
Diffstat (limited to 'src/cryptonote_basic')
22 files changed, 5524 insertions, 0 deletions
diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt new file mode 100644 index 000000000..2b8ad365a --- /dev/null +++ b/src/cryptonote_basic/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (c) 2014-2017, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(cryptonote_basic_sources + account.cpp + checkpoints.cpp + cryptonote_basic_impl.cpp + cryptonote_format_utils.cpp + difficulty.cpp + hardfork.cpp + miner.cpp) + +set(cryptonote_basic_headers) + +set(cryptonote_basic_private_headers + account.h + account_boost_serialization.h + checkpoints.h + connection_context.h + cryptonote_basic.h + cryptonote_basic_impl.h + cryptonote_boost_serialization.h + cryptonote_format_utils.h + cryptonote_stat_info.h + difficulty.h + hardfork.h + miner.h + tx_extra.h + verification_context.h) + +monero_private_headers(cryptonote_basic + ${crypto_private_headers}) +monero_add_library(cryptonote_basic + ${cryptonote_basic_sources} + ${cryptonote_basic_headers} + ${cryptonote_basic_private_headers}) +target_link_libraries(cryptonote_basic + PUBLIC + common + crypto + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp new file mode 100644 index 000000000..dd875402f --- /dev/null +++ b/src/cryptonote_basic/account.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <fstream> + +#include "include_base_utils.h" +#include "account.h" +#include "warnings.h" +#include "crypto/crypto.h" +extern "C" +{ +#include "crypto/keccak.h" +} +#include "cryptonote_basic_impl.h" +#include "cryptonote_format_utils.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "account" + +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::forget_spend_key() + { + m_keys.m_spend_secret_key = crypto::secret_key(); + } + //----------------------------------------------------------------- + crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) + { + crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover); + + // rng for generating second set of keys is hash of first rng. means only one set of electrum-style words needed for recovery + crypto::secret_key second; + keccak((uint8_t *)&m_keys.m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key)); + + generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key, second, two_random ? false : true); + + struct tm timestamp = {0}; + timestamp.tm_year = 2014 - 1900; // year 2014 + timestamp.tm_mon = 6 - 1; // month june + timestamp.tm_mday = 8; // 8th of june + timestamp.tm_hour = 0; + timestamp.tm_min = 0; + timestamp.tm_sec = 0; + + if (recover) + { + m_creation_timestamp = mktime(×tamp); + if (m_creation_timestamp == (uint64_t)-1) // failure + m_creation_timestamp = 0; // lowest value + } + else + { + m_creation_timestamp = time(NULL); + } + return first; + } + //----------------------------------------------------------------- + void account_base::create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey) + { + m_keys.m_account_address = address; + m_keys.m_spend_secret_key = spendkey; + m_keys.m_view_secret_key = viewkey; + + struct tm timestamp = {0}; + timestamp.tm_year = 2014 - 1900; // year 2014 + timestamp.tm_mon = 4 - 1; // month april + timestamp.tm_mday = 15; // 15th of april + timestamp.tm_hour = 0; + timestamp.tm_min = 0; + timestamp.tm_sec = 0; + + m_creation_timestamp = mktime(×tamp); + if (m_creation_timestamp == (uint64_t)-1) // failure + m_creation_timestamp = 0; // lowest value + } + //----------------------------------------------------------------- + void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey) + { + crypto::secret_key fake; + memset(&fake, 0, sizeof(fake)); + create_from_keys(address, fake, viewkey); + } + //----------------------------------------------------------------- + const account_keys& account_base::get_keys() const + { + return m_keys; + } + //----------------------------------------------------------------- + std::string account_base::get_public_address_str(bool testnet) const + { + //TODO: change this code into base 58 + return get_account_address_as_str(testnet, m_keys.m_account_address); + } + //----------------------------------------------------------------- + std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const + { + //TODO: change this code into base 58 + return get_account_integrated_address_as_str(testnet, m_keys.m_account_address, payment_id); + } + //----------------------------------------------------------------- +} diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h new file mode 100644 index 000000000..e0d5447a2 --- /dev/null +++ b/src/cryptonote_basic/account.h @@ -0,0 +1,92 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "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(); + crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); + void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); + void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); + const account_keys& get_keys() const; + std::string get_public_address_str(bool testnet) const; + std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; + + 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); + + void forget_spend_key(); + + 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_basic/account_boost_serialization.h b/src/cryptonote_basic/account_boost_serialization.h new file mode 100644 index 000000000..d2f541638 --- /dev/null +++ b/src/cryptonote_basic/account_boost_serialization.h @@ -0,0 +1,57 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "account.h" +#include "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_basic/checkpoints.cpp b/src/cryptonote_basic/checkpoints.cpp new file mode 100644 index 000000000..103a4a33e --- /dev/null +++ b/src/cryptonote_basic/checkpoints.cpp @@ -0,0 +1,269 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "include_base_utils.h" + +using namespace epee; + +#include "checkpoints.h" + +#include "common/dns_utils.h" +#include "include_base_utils.h" +#include <sstream> +#include <random> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "checkpoints" + +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, "Failed to parse checkpoint hash string into binary representation!"); + + // return false if adding at a height we already have AND the hash is different + if (m_points.count(height)) + { + CHECK_AND_ASSERT_MES(h == m_points[height], false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); + } + 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, bool& is_a_checkpoint) const + { + auto it = m_points.find(height); + is_a_checkpoint = it != m_points.end(); + if(!is_a_checkpoint) + return true; + + if(it->second == h) + { + MINFO("CHECKPOINT PASSED FOR HEIGHT " << height << " " << h); + return true; + }else + { + MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH: " << it->second << ", FETCHED HASH: " << h); + return false; + } + } + //--------------------------------------------------------------------------- + bool checkpoints::check_block(uint64_t height, const crypto::hash& h) const + { + bool ignored; + return check_block(height, h, ignored); + } + //--------------------------------------------------------------------------- + //FIXME: is this the desired behavior? + bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const + { + if (0 == block_height) + return false; + + auto it = m_points.upper_bound(blockchain_height); + // Is blockchain_height before the first checkpoint? + if (it == m_points.begin()) + return true; + + --it; + uint64_t checkpoint_height = it->first; + return checkpoint_height < block_height; + } + //--------------------------------------------------------------------------- + uint64_t checkpoints::get_max_height() const + { + std::map< uint64_t, crypto::hash >::const_iterator highest = + std::max_element( m_points.begin(), m_points.end(), + ( boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _1) < + boost::bind(&std::map< uint64_t, crypto::hash >::value_type::first, _2 ) ) ); + return highest->first; + } + //--------------------------------------------------------------------------- + const std::map<uint64_t, crypto::hash>& checkpoints::get_points() const + { + return m_points; + } + + bool checkpoints::check_for_conflicts(const checkpoints& other) const + { + for (auto& pt : other.get_points()) + { + if (m_points.count(pt.first)) + { + CHECK_AND_ASSERT_MES(pt.second == m_points.at(pt.first), false, "Checkpoint at given height already exists, and hash for new checkpoint was different!"); + } + } + return true; + } + + bool checkpoints::init_default_checkpoints() + { + ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); + ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); + ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); + ADD_CHECKPOINT(1000, "5acfc45acffd2b2e7345caf42fa02308c5793f15ec33946e969e829f40b03876"); + ADD_CHECKPOINT(10000, "c758b7c81f928be3295d45e230646de8b852ec96a821eac3fea4daf3fcac0ca2"); + ADD_CHECKPOINT(22231, "7cb10e29d67e1c069e6e11b17d30b809724255fee2f6868dc14cfc6ed44dfb25"); + ADD_CHECKPOINT(29556, "53c484a8ed91e4da621bb2fa88106dbde426fe90d7ef07b9c1e5127fb6f3a7f6"); + ADD_CHECKPOINT(50000, "0fe8758ab06a8b9cb35b7328fd4f757af530a5d37759f9d3e421023231f7b31c"); + ADD_CHECKPOINT(80000, "a62dcd7b536f22e003ebae8726e9e7276f63d594e264b6f0cd7aab27b66e75e3"); + ADD_CHECKPOINT(202612, "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"); + ADD_CHECKPOINT(202613, "e2aa337e78df1f98f462b3b1e560c6b914dec47b610698b7b7d1e3e86b6197c2"); + ADD_CHECKPOINT(202614, "c29e3dc37d8da3e72e506e31a213a58771b24450144305bcba9e70fa4d6ea6fb"); + ADD_CHECKPOINT(205000, "5d3d7a26e6dc7535e34f03def711daa8c263785f73ec1fadef8a45880fde8063"); + ADD_CHECKPOINT(220000, "9613f455933c00e3e33ac315cc6b455ee8aa0c567163836858c2d9caff111553"); + ADD_CHECKPOINT(230300, "bae7a80c46859db355556e3a9204a337ae8f24309926a1312323fdecf1920e61"); + ADD_CHECKPOINT(230700, "93e631240ceac831da1aebfc5dac8f722c430463024763ebafa888796ceaeedf"); + ADD_CHECKPOINT(231350, "b5add137199b820e1ea26640e5c3e121fd85faa86a1e39cf7e6cc097bdeb1131"); + ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); + ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); + ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); + ADD_CHECKPOINT(500000, "2428f0dbe49796be05ed81b347f53e1f7f44aed0abf641446ec2b94cae066b02"); + ADD_CHECKPOINT(600000, "f5828ebf7d7d1cb61762c4dfe3ccf4ecab2e1aad23e8113668d981713b7a54c5"); + ADD_CHECKPOINT(700000, "12be9b3d210b93f574d2526abb9c1ab2a881b479131fd0d4f7dac93875f503cd"); + ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); + ADD_CHECKPOINT(900000, "d9958d0e7dcf91a5a7b11de225927bf7efc6eb26240315ce12372be902cc1337"); + ADD_CHECKPOINT(913193, "5292d5d56f6ba4de33a58d9a34d263e2cb3c6fee0aed2286fd4ac7f36d53c85f"); + ADD_CHECKPOINT(1000000, "a886ef5149902d8342475fee9bb296341b891ac67c4842f47a833f23c00ed721"); + ADD_CHECKPOINT(1100000, "3fd720c5c8b3072fc1ccda922dec1ef25f9ed88a1e6ad4103d0fe00b180a5903"); + ADD_CHECKPOINT(1150000, "1dd16f626d18e1e988490dfd06de5920e22629c972c58b4d8daddea0038627b2"); + ADD_CHECKPOINT(1200000, "fa7d13a90850882060479d100141ff84286599ae39c3277c8ea784393f882d1f"); + + + return true; + } + + bool checkpoints::load_checkpoints_from_json(const std::string json_hashfile_fullpath) + { + boost::system::error_code errcode; + if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) + { + LOG_PRINT_L1("Blockchain checkpoints file not found"); + return true; + } + + LOG_PRINT_L1("Adding checkpoints from blockchain hashfile"); + + uint64_t prev_max_height = get_max_height(); + LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); + t_hash_json hashes; + epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); + for (std::vector<t_hashline>::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) + { + uint64_t height; + height = it->height; + if (height <= prev_max_height) { + LOG_PRINT_L1("ignoring checkpoint height " << height); + } else { + std::string blockhash = it->hash; + LOG_PRINT_L1("Adding checkpoint height " << height << ", hash=" << blockhash); + ADD_CHECKPOINT(height, blockhash); + } + ++it; + } + + return true; + } + + bool checkpoints::load_checkpoints_from_dns(bool testnet) + { + std::vector<std::string> records; + + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector<std::string> dns_urls = { "checkpoints.moneropulse.se" + , "checkpoints.moneropulse.org" + , "checkpoints.moneropulse.net" + , "checkpoints.moneropulse.co" + }; + + static const std::vector<std::string> testnet_dns_urls = { "testpoints.moneropulse.se" + , "testpoints.moneropulse.org" + , "testpoints.moneropulse.net" + , "testpoints.moneropulse.co" + }; + + if (!tools::dns_utils::load_txt_records_from_dns(records, testnet ? testnet_dns_urls : dns_urls)) + return true; // why true ? + + for (const auto& record : records) + { + auto pos = record.find(":"); + if (pos != std::string::npos) + { + uint64_t height; + crypto::hash hash; + + // parse the first part as uint64_t, + // if this fails move on to the next record + std::stringstream ss(record.substr(0, pos)); + if (!(ss >> height)) + { + continue; + } + + // parse the second part as crypto::hash, + // if this fails move on to the next record + std::string hashStr = record.substr(pos + 1); + if (!epee::string_tools::parse_tpod_from_hex_string(hashStr, hash)) + { + continue; + } + + ADD_CHECKPOINT(height, hashStr); + } + } + return true; + } + + bool checkpoints::load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet, bool dns) + { + bool result; + + result = load_checkpoints_from_json(json_hashfile_fullpath); + if (dns) + { + result &= load_checkpoints_from_dns(testnet); + } + + return result; + } +} diff --git a/src/cryptonote_basic/checkpoints.h b/src/cryptonote_basic/checkpoints.h new file mode 100644 index 000000000..3a74d8a69 --- /dev/null +++ b/src/cryptonote_basic/checkpoints.h @@ -0,0 +1,217 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <map> +#include <vector> +#include "cryptonote_basic_impl.h" +#include "misc_log_ex.h" +#include "storages/portable_storage_template_helper.h" // epee json include + +#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); +#define JSON_HASH_FILE_NAME "checkpoints.json" + + +namespace cryptonote +{ + /** + * @brief A container for blockchain checkpoints + * + * A checkpoint is a pre-defined hash for the block at a given height. + * Some of these are compiled-in, while others can be loaded at runtime + * either from a json file or via DNS from a checkpoint-hosting server. + */ + class checkpoints + { + public: + + /** + * @brief default constructor + */ + checkpoints(); + + /** + * @brief adds a checkpoint to the container + * + * @param height the height of the block the checkpoint is for + * @param hash_str the hash of the block, as a string + * + * @return false if parsing the hash fails, or if the height is a duplicate + * AND the existing checkpoint hash does not match the new one, + * otherwise returns true + */ + bool add_checkpoint(uint64_t height, const std::string& hash_str); + + /** + * @brief checks if there is a checkpoint in the future + * + * This function checks if the height passed is lower than the highest + * checkpoint. + * + * @param height the height to check against + * + * @return false if no checkpoints, otherwise returns whether or not + * the height passed is lower than the highest checkpoint. + */ + bool is_in_checkpoint_zone(uint64_t height) const; + + /** + * @brief checks if the given height and hash agree with the checkpoints + * + * This function checks if the given height and hash exist in the + * checkpoints container. If so, it returns whether or not the passed + * parameters match the stored values. + * + * @param height the height to be checked + * @param h the hash to be checked + * @param is_a_checkpoint return-by-reference if there is a checkpoint at the given height + * + * @return true if there is no checkpoint at the given height, + * true if the passed parameters match the stored checkpoint, + * false otherwise + */ + bool check_block(uint64_t height, const crypto::hash& h, bool& is_a_checkpoint) const; + + /** + * @overload + */ + bool check_block(uint64_t height, const crypto::hash& h) const; + + /** + * @brief checks if alternate chain blocks should be kept for a given height + * + * this basically says if the blockchain is smaller than the first + * checkpoint then alternate blocks are allowed. Alternatively, if the + * last checkpoint *before* the end of the current chain is also before + * the block to be added, then this is fine. + * + * @param blockchain_height the current blockchain height + * @param block_height the height of the block to be added as alternate + * + * @return true if alternate blocks are allowed given the parameters, + * otherwise false + */ + bool is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height) const; + + /** + * @brief gets the highest checkpoint height + * + * @return the height of the highest checkpoint + */ + uint64_t get_max_height() const; + + /** + * @brief gets the checkpoints container + * + * @return a const reference to the checkpoints container + */ + const std::map<uint64_t, crypto::hash>& get_points() const; + + /** + * @brief checks if our checkpoints container conflicts with another + * + * A conflict refers to a case where both checkpoint sets have a checkpoint + * for a specific height but their hashes for that height do not match. + * + * @param other the other checkpoints instance to check against + * + * @return false if any conflict is found, otherwise true + */ + bool check_for_conflicts(const checkpoints& other) const; + + /** + * @brief loads the default main chain checkpoints + * + * @return true unless adding a checkpoint fails + */ + bool init_default_checkpoints(); + + /** + * @brief load new checkpoints + * + * Loads new checkpoints from the specified json file, as well as + * (optionally) from DNS. + * + * @param json_hashfile_fullpath path to the json checkpoints file + * @param testnet whether to load testnet checkpoints or mainnet + * @param dns whether or not to load DNS checkpoints + * + * @return true if loading successful and no conflicts + */ + bool load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet=false, bool dns=true); + + /** + * @brief load new checkpoints from json + * + * @param json_hashfile_fullpath path to the json checkpoints file + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_json(const std::string json_hashfile_fullpath); + + /** + * @brief load new checkpoints from DNS + * + * @param testnet whether to load testnet checkpoints or mainnet + * + * @return true if loading successful and no conflicts + */ + bool load_checkpoints_from_dns(bool testnet = false); + + private: + + + /** + * @brief struct for loading a checkpoint from json + */ + struct t_hashline + { + uint64_t height; //!< the height of the checkpoint + std::string hash; //!< the hash for the checkpoint + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(height) + KV_SERIALIZE(hash) + END_KV_SERIALIZE_MAP() + }; + + /** + * @brief struct for loading many checkpoints from json + */ + struct t_hash_json { + std::vector<t_hashline> hashlines; //!< the checkpoint lines from the file + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hashlines) + END_KV_SERIALIZE_MAP() + }; + + std::map<uint64_t, crypto::hash> m_points; //!< the checkpoints container + + }; +} diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h new file mode 100644 index 000000000..8d739900e --- /dev/null +++ b/src/cryptonote_basic/connection_context.h @@ -0,0 +1,77 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <unordered_set> +#include <atomic> +#include "net/net_utils_base.h" +#include "copyable_atomic.h" + +namespace cryptonote +{ + + struct cryptonote_connection_context: public epee::net_utils::connection_context_base + { + + enum state + { + state_befor_handshake = 0, //default state + state_synchronizing, + state_idle, + 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; + epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise + //size_t m_score; TODO: add score calculations + }; + + inline std::string get_protocol_state_string(cryptonote_connection_context::state s) + { + switch (s) + { + case cryptonote_connection_context::state_befor_handshake: + return "state_befor_handshake"; + case cryptonote_connection_context::state_synchronizing: + return "state_synchronizing"; + case cryptonote_connection_context::state_idle: + return "state_idle"; + case cryptonote_connection_context::state_normal: + return "state_normal"; + default: + return "unknown"; + } + } + +} diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h new file mode 100644 index 000000000..c4adf1fcb --- /dev/null +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -0,0 +1,468 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <boost/variant.hpp> +#include <boost/functional/hash/hash.hpp> +#include <vector> +#include <cstring> // memcmp +#include <sstream> +#include <atomic> +#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 "cryptonote_config.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "misc_language.h" +#include "tx_extra.h" +#include "ringct/rctTypes.h" + +namespace cryptonote +{ + + const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash); + const static crypto::hash8 null_hash8 = AUTO_VAL_INIT(null_hash8); + 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(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; + VARINT_FIELD(unlock_time) + FIELD(vin) + FIELD(vout) + FIELD(extra) + END_SERIALIZE() + + public: + transaction_prefix(){} + }; + + class transaction: public transaction_prefix + { + private: + // hash cash + mutable std::atomic<bool> hash_valid; + mutable std::atomic<bool> blob_size_valid; + + public: + std::vector<std::vector<crypto::signature> > signatures; //count signatures always the same as inputs count + rct::rctSig rct_signatures; + + // hash cash + mutable crypto::hash hash; + mutable size_t blob_size; + + transaction(); + transaction(const transaction &t): transaction_prefix(t), hash_valid(false), blob_size_valid(false), signatures(t.signatures), rct_signatures(t.rct_signatures) { if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } } + transaction &operator=(const transaction &t) { transaction_prefix::operator=(t); set_hash_valid(false); set_blob_size_valid(false); signatures = t.signatures; rct_signatures = t.rct_signatures; if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } return *this; } + virtual ~transaction(); + void set_null(); + void invalidate_hashes(); + bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } + void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + bool is_blob_size_valid() const { return blob_size_valid.load(std::memory_order_acquire); } + void set_blob_size_valid(bool v) const { blob_size_valid.store(v,std::memory_order_release); } + + BEGIN_SERIALIZE_OBJECT() + if (!typename Archive<W>::is_saving()) + { + set_hash_valid(false); + set_blob_size_valid(false); + } + + FIELDS(*static_cast<transaction_prefix *>(this)) + + if (version == 1) + { + 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(); + } + else + { + ar.tag("rct_signatures"); + if (!vin.empty()) + { + ar.begin_object(); + bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); + if (!r || !ar.stream().good()) return false; + ar.end_object(); + if (rct_signatures.type != rct::RCTTypeNull) + { + ar.tag("rctsig_prunable"); + ar.begin_object(); + r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin.size(), vout.size(), + vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(vin[0]).key_offsets.size() - 1 : 0); + if (!r || !ar.stream().good()) return false; + ar.end_object(); + } + } + } + END_SERIALIZE() + + template<bool W, template <bool> class Archive> + bool serialize_base(Archive<W> &ar) + { + FIELDS(*static_cast<transaction_prefix *>(this)) + + if (version == 1) + { + } + else + { + ar.tag("rct_signatures"); + if (!vin.empty()) + { + ar.begin_object(); + bool r = rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); + if (!r || !ar.stream().good()) return false; + ar.end_object(); + } + } + return true; + } + + 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 = 1; + unlock_time = 0; + vin.clear(); + vout.clear(); + extra.clear(); + signatures.clear(); + rct_signatures.type = rct::RCTTypeNull; + set_hash_valid(false); + set_blob_size_valid(false); + } + + inline + void transaction::invalidate_hashes() + { + set_hash_valid(false); + set_blob_size_valid(false); + } + + 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; // now used as a voting mechanism, rather than how this particular block is built + uint64_t timestamp; + crypto::hash prev_id; + uint32_t nonce; + + BEGIN_SERIALIZE() + VARINT_FIELD(major_version) + VARINT_FIELD(minor_version) + VARINT_FIELD(timestamp) + FIELD(prev_id) + FIELD(nonce) + END_SERIALIZE() + }; + + struct block: public block_header + { + private: + // hash cash + mutable std::atomic<bool> hash_valid; + + public: + block(): block_header(), hash_valid(false) {} + block(const block &b): block_header(b), hash_valid(false), miner_tx(b.miner_tx), tx_hashes(b.tx_hashes) { if (b.is_hash_valid()) { hash = b.hash; set_hash_valid(true); } } + block &operator=(const block &b) { block_header::operator=(b); hash_valid = false; miner_tx = b.miner_tx; tx_hashes = b.tx_hashes; if (b.is_hash_valid()) { hash = b.hash; set_hash_valid(true); } return *this; } + void invalidate_hashes() { set_hash_valid(false); } + bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } + void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + + transaction miner_tx; + std::vector<crypto::hash> tx_hashes; + + // hash cash + mutable crypto::hash hash; + + BEGIN_SERIALIZE_OBJECT() + if (!typename Archive<W>::is_saving()) + set_hash_valid(false); + + 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_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp new file mode 100644 index 000000000..edd67793f --- /dev/null +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -0,0 +1,358 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "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" +#include "common/dns_utils.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" + +namespace cryptonote { + + struct integrated_address { + account_public_address adr; + crypto::hash8 payment_id; + + BEGIN_SERIALIZE_OBJECT() + FIELD(adr) + FIELD(payment_id) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(adr) + KV_SERIALIZE(payment_id) + END_KV_SERIALIZE_MAP() + }; + + /************************************************************************/ + /* Cryptonote helper functions */ + /************************************************************************/ + //----------------------------------------------------------------------------------------------- + size_t get_min_block_size(uint8_t version) + { + if (version < 2) + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; + if (version < 5) + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2; + return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; + } + //----------------------------------------------------------------------------------------------- + size_t get_max_block_size() + { + return CRYPTONOTE_MAX_BLOCK_SIZE; + } + //----------------------------------------------------------------------------------------------- + size_t get_max_tx_size() + { + return CRYPTONOTE_MAX_TX_SIZE; + } + //----------------------------------------------------------------------------------------------- + bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward, uint8_t version) { + static_assert(DIFFICULTY_TARGET_V2%60==0&&DIFFICULTY_TARGET_V1%60==0,"difficulty targets must be a multiple of 60"); + const int target = version < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + const int target_minutes = target / 60; + const int emission_speed_factor = EMISSION_SPEED_FACTOR_PER_MINUTE - (target_minutes-1); + + uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor; + if (base_reward < FINAL_SUBSIDY_PER_MINUTE*target_minutes) + { + base_reward = FINAL_SUBSIDY_PER_MINUTE*target_minutes; + } + + uint64_t full_reward_zone = get_min_block_size(version); + + //make it soft + if (median_size < full_reward_zone) { + median_size = full_reward_zone; + } + + if (current_block_size <= median_size) { + reward = base_reward; + return true; + } + + if(current_block_size > 2 * median_size) { + MERROR("Block cumulative size is too big: " << current_block_size << ", expected less than " << 2 * median_size); + return false; + } + + assert(median_size < std::numeric_limits<uint32_t>::max()); + assert(current_block_size < std::numeric_limits<uint32_t>::max()); + + uint64_t product_hi; + // BUGFIX: 32-bit saturation bug (e.g. ARM7), the result was being + // treated as 32-bit by default. + uint64_t multiplicand = 2 * median_size - current_block_size; + multiplicand *= current_block_size; + uint64_t product_lo = mul128(base_reward, multiplicand, &product_hi); + + uint64_t reward_hi; + uint64_t reward_lo; + div128_32(product_hi, product_lo, static_cast<uint32_t>(median_size), &reward_hi, &reward_lo); + div128_32(reward_hi, reward_lo, static_cast<uint32_t>(median_size), &reward_hi, &reward_lo); + assert(0 == reward_hi); + assert(reward_lo < base_reward); + + reward = reward_lo; + return true; + } + //------------------------------------------------------------------------------------ + 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; + } + //------------------------------------------------------------------------------------ + uint8_t get_account_integrated_address_checksum(const public_integrated_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_integrated_address_outer_blob)-1; i++) + summ += pbuf[i]; + + return summ; + } + //----------------------------------------------------------------------- + std::string get_account_address_as_str( + bool testnet + , account_public_address const & adr + ) + { + uint64_t address_prefix = testnet ? + config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; + + return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr)); + } + //----------------------------------------------------------------------- + std::string get_account_integrated_address_as_str( + bool testnet + , account_public_address const & adr + , crypto::hash8 const & payment_id + ) + { + uint64_t integrated_address_prefix = testnet ? + config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + + integrated_address iadr = { + adr, payment_id + }; + return tools::base58::encode_addr(integrated_address_prefix, t_serializable_object_to_blob(iadr)); + } + //----------------------------------------------------------------------- + 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_integrated_address_from_str( + account_public_address& adr + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , std::string const & str + ) + { + uint64_t address_prefix = testnet ? + config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = testnet ? + config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + + if (2 * sizeof(public_address_outer_blob) != str.size()) + { + blobdata data; + uint64_t prefix; + if (!tools::base58::decode_addr(str, prefix, data)) + { + LOG_PRINT_L2("Invalid address format"); + return false; + } + + if (integrated_address_prefix == prefix) + { + has_payment_id = true; + } + else if (address_prefix == prefix) + { + has_payment_id = false; + } + else { + LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix << " or " << integrated_address_prefix); + return false; + } + + if (has_payment_id) + { + integrated_address iadr; + if (!::serialization::parse_binary(data, iadr)) + { + LOG_PRINT_L1("Account public address keys can't be parsed"); + return false; + } + adr = iadr.adr; + payment_id = iadr.payment_id; + } + else + { + if (!::serialization::parse_binary(data, adr)) + { + LOG_PRINT_L1("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_L1("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_L1("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_L1("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_L1("Wrong public address checksum"); + return false; + } + + //we success + adr = blob.m_address; + has_payment_id = false; + } + + return true; + } + //----------------------------------------------------------------------- + bool get_account_address_from_str( + account_public_address& adr + , bool testnet + , std::string const & str + ) + { + bool has_payment_id; + crypto::hash8 payment_id; + return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str); + } + //-------------------------------------------------------------------------------- + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + , bool cli_confirm + ) + { + if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) + return true; + bool dnssec_valid; + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, cli_confirm); + return !address_str.empty() && + get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); + } + //-------------------------------------------------------------------------------- + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool testnet + , const std::string& str_or_url + , bool cli_confirm + ) + { + bool has_payment_id; + crypto::hash8 payment_id; + return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, cli_confirm); + } + //-------------------------------------------------------------------------------- + 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_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h new file mode 100644 index 000000000..14c03ac4c --- /dev/null +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -0,0 +1,152 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "cryptonote_basic.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "hex.h" +#include "span.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; + }; + struct public_integrated_address_outer_blob + { + uint8_t m_ver; + account_public_address m_address; + crypto::hash8 payment_id; + uint8_t check_sum; + }; +#pragma pack (pop) + + + /************************************************************************/ + /* Cryptonote helper functions */ + /************************************************************************/ + size_t get_min_block_size(uint8_t version); + size_t get_max_block_size(); + size_t get_max_tx_size(); + bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward, uint8_t version); + uint8_t get_account_address_checksum(const public_address_outer_blob& bl); + uint8_t get_account_integrated_address_checksum(const public_integrated_address_outer_blob& bl); + + std::string get_account_address_as_str( + bool testnet + , const account_public_address& adr + ); + + std::string get_account_integrated_address_as_str( + bool testnet + , const account_public_address& adr + , const crypto::hash8& payment_id + ); + + bool get_account_integrated_address_from_str( + account_public_address& adr + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str + ); + + bool get_account_address_from_str( + account_public_address& adr + , bool testnet + , const std::string& str + ); + + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool& has_payment_id + , crypto::hash8& payment_id + , bool testnet + , const std::string& str_or_url + , bool cli_confirm = true + ); + + bool get_account_address_from_str_or_url( + cryptonote::account_public_address& address + , bool testnet + , const std::string& str_or_url + , bool cli_confirm = true + ); + + 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); +} + +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) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } +} diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h new file mode 100644 index 000000000..6e4ac9b72 --- /dev/null +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -0,0 +1,301 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#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/serialization/is_bitwise_serializable.hpp> +#include <boost/archive/binary_iarchive.hpp> +#include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> +#include "cryptonote_basic.h" +#include "common/unordered_containers_boost_serialization.h" +#include "crypto/crypto.h" +#include "ringct/rctTypes.h" +#include "ringct/rctOps.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_prefix &x, const boost::serialization::version_type ver) + { + a & x.version; + a & x.unlock_time; + a & x.vin; + a & x.vout; + a & x.extra; + } + + 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; + if (x.version == 1) + { + a & x.signatures; + } + else + { + a & (rct::rctSigBase&)x.rct_signatures; + if (x.rct_signatures.type != rct::RCTTypeNull) + a & x.rct_signatures.p; + } + } + + 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; + } + + template <class Archive> + inline void serialize(Archive &a, rct::key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(rct::key)]>(x); + } + + template <class Archive> + inline void serialize(Archive &a, rct::ctkey &x, const boost::serialization::version_type ver) + { + a & x.dest; + a & x.mask; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rangeSig &x, const boost::serialization::version_type ver) + { + a & x.asig; + a & x.Ci; + } + + template <class Archive> + inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) + { + a & x.s0; + a & x.s1; + a & x.ee; + } + + template <class Archive> + inline void serialize(Archive &a, rct::mgSig &x, const boost::serialization::version_type ver) + { + a & x.ss; + a & x.cc; + // a & x.II; // not serialized, we can recover it from the tx vin + } + + template <class Archive> + inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver) + { + a & x.mask; + a & x.amount; + // a & x.senderPk; // not serialized, as we do not use it in monero currently + } + + template <class Archive> + inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + { + rct::keyV outPk; + a & outPk; + outPk_.resize(outPk.size()); + for (size_t n = 0; n < outPk_.size(); ++n) + { + outPk_[n].dest = rct::identity(); + outPk_[n].mask = outPk[n]; + } + } + + template <class Archive> + inline typename std::enable_if<Archive::is_saving::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + { + rct::keyV outPk(outPk_.size()); + for (size_t n = 0; n < outPk_.size(); ++n) + outPk[n] = outPk_[n].mask; + a & outPk; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rctSigBase &x, const boost::serialization::version_type ver) + { + a & x.type; + if (x.type == rct::RCTTypeNull) + return; + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); + // a & x.message; message is not serialized, as it can be reconstructed from the tx data + // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets + if (x.type == rct::RCTTypeSimple) + a & x.pseudoOuts; + a & x.ecdhInfo; + serializeOutPk(a, x.outPk, ver); + a & x.txnFee; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rctSigPrunable &x, const boost::serialization::version_type ver) + { + a & x.rangeSigs; + a & x.MGs; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rctSig &x, const boost::serialization::version_type ver) + { + a & x.type; + if (x.type == rct::RCTTypeNull) + return; + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); + // a & x.message; message is not serialized, as it can be reconstructed from the tx data + // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets + if (x.type == rct::RCTTypeSimple) + a & x.pseudoOuts; + a & x.ecdhInfo; + serializeOutPk(a, x.outPk, ver); + a & x.txnFee; + //-------------- + a & x.p.rangeSigs; + a & x.p.MGs; + } +} +} + +//} diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp new file mode 100644 index 000000000..745dfb72e --- /dev/null +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -0,0 +1,872 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "include_base_utils.h" +using namespace epee; + +#include <atomic> +#include "cryptonote_format_utils.h" +#include "cryptonote_config.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "ringct/rctSigs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn" + +#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d + +// #define ENABLE_HASH_CASH_INTEGRITY_CHECK + +static const uint64_t valid_decomposed_outputs[] = { + (uint64_t)1, (uint64_t)2, (uint64_t)3, (uint64_t)4, (uint64_t)5, (uint64_t)6, (uint64_t)7, (uint64_t)8, (uint64_t)9, // 1 piconero + (uint64_t)10, (uint64_t)20, (uint64_t)30, (uint64_t)40, (uint64_t)50, (uint64_t)60, (uint64_t)70, (uint64_t)80, (uint64_t)90, + (uint64_t)100, (uint64_t)200, (uint64_t)300, (uint64_t)400, (uint64_t)500, (uint64_t)600, (uint64_t)700, (uint64_t)800, (uint64_t)900, + (uint64_t)1000, (uint64_t)2000, (uint64_t)3000, (uint64_t)4000, (uint64_t)5000, (uint64_t)6000, (uint64_t)7000, (uint64_t)8000, (uint64_t)9000, + (uint64_t)10000, (uint64_t)20000, (uint64_t)30000, (uint64_t)40000, (uint64_t)50000, (uint64_t)60000, (uint64_t)70000, (uint64_t)80000, (uint64_t)90000, + (uint64_t)100000, (uint64_t)200000, (uint64_t)300000, (uint64_t)400000, (uint64_t)500000, (uint64_t)600000, (uint64_t)700000, (uint64_t)800000, (uint64_t)900000, + (uint64_t)1000000, (uint64_t)2000000, (uint64_t)3000000, (uint64_t)4000000, (uint64_t)5000000, (uint64_t)6000000, (uint64_t)7000000, (uint64_t)8000000, (uint64_t)9000000, // 1 micronero + (uint64_t)10000000, (uint64_t)20000000, (uint64_t)30000000, (uint64_t)40000000, (uint64_t)50000000, (uint64_t)60000000, (uint64_t)70000000, (uint64_t)80000000, (uint64_t)90000000, + (uint64_t)100000000, (uint64_t)200000000, (uint64_t)300000000, (uint64_t)400000000, (uint64_t)500000000, (uint64_t)600000000, (uint64_t)700000000, (uint64_t)800000000, (uint64_t)900000000, + (uint64_t)1000000000, (uint64_t)2000000000, (uint64_t)3000000000, (uint64_t)4000000000, (uint64_t)5000000000, (uint64_t)6000000000, (uint64_t)7000000000, (uint64_t)8000000000, (uint64_t)9000000000, + (uint64_t)10000000000, (uint64_t)20000000000, (uint64_t)30000000000, (uint64_t)40000000000, (uint64_t)50000000000, (uint64_t)60000000000, (uint64_t)70000000000, (uint64_t)80000000000, (uint64_t)90000000000, + (uint64_t)100000000000, (uint64_t)200000000000, (uint64_t)300000000000, (uint64_t)400000000000, (uint64_t)500000000000, (uint64_t)600000000000, (uint64_t)700000000000, (uint64_t)800000000000, (uint64_t)900000000000, + (uint64_t)1000000000000, (uint64_t)2000000000000, (uint64_t)3000000000000, (uint64_t)4000000000000, (uint64_t)5000000000000, (uint64_t)6000000000000, (uint64_t)7000000000000, (uint64_t)8000000000000, (uint64_t)9000000000000, // 1 monero + (uint64_t)10000000000000, (uint64_t)20000000000000, (uint64_t)30000000000000, (uint64_t)40000000000000, (uint64_t)50000000000000, (uint64_t)60000000000000, (uint64_t)70000000000000, (uint64_t)80000000000000, (uint64_t)90000000000000, + (uint64_t)100000000000000, (uint64_t)200000000000000, (uint64_t)300000000000000, (uint64_t)400000000000000, (uint64_t)500000000000000, (uint64_t)600000000000000, (uint64_t)700000000000000, (uint64_t)800000000000000, (uint64_t)900000000000000, + (uint64_t)1000000000000000, (uint64_t)2000000000000000, (uint64_t)3000000000000000, (uint64_t)4000000000000000, (uint64_t)5000000000000000, (uint64_t)6000000000000000, (uint64_t)7000000000000000, (uint64_t)8000000000000000, (uint64_t)9000000000000000, + (uint64_t)10000000000000000, (uint64_t)20000000000000000, (uint64_t)30000000000000000, (uint64_t)40000000000000000, (uint64_t)50000000000000000, (uint64_t)60000000000000000, (uint64_t)70000000000000000, (uint64_t)80000000000000000, (uint64_t)90000000000000000, + (uint64_t)100000000000000000, (uint64_t)200000000000000000, (uint64_t)300000000000000000, (uint64_t)400000000000000000, (uint64_t)500000000000000000, (uint64_t)600000000000000000, (uint64_t)700000000000000000, (uint64_t)800000000000000000, (uint64_t)900000000000000000, + (uint64_t)1000000000000000000, (uint64_t)2000000000000000000, (uint64_t)3000000000000000000, (uint64_t)4000000000000000000, (uint64_t)5000000000000000000, (uint64_t)6000000000000000000, (uint64_t)7000000000000000000, (uint64_t)8000000000000000000, (uint64_t)9000000000000000000, // 1 meganero + (uint64_t)10000000000000000000ull +}; + +static std::atomic<unsigned int> default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); + +static std::atomic<uint64_t> tx_hashes_calculated_count(0); +static std::atomic<uint64_t> tx_hashes_cached_count(0); +static std::atomic<uint64_t> block_hashes_calculated_count(0); +static std::atomic<uint64_t> block_hashes_cached_count(0); + +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"); + tx.invalidate_hashes(); + return true; + } + //--------------------------------------------------------------- + bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx) + { + std::stringstream ss; + ss << tx_blob; + binary_archive<false> ba(ss); + bool r = tx.serialize_base(ba); + 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"); + tx.invalidate_hashes(); + //TODO: validate tx + + get_transaction_hash(tx, tx_hash); + get_transaction_prefix_hash(tx, tx_prefix_hash); + 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::string str_amount = str_amount_; + boost::algorithm::trim(str_amount); + + size_t point_index = str_amount.find_first_of('.'); + size_t fraction_size; + if (std::string::npos != point_index) + { + fraction_size = str_amount.size() - point_index - 1; + while (default_decimal_point < fraction_size && '0' == str_amount.back()) + { + str_amount.erase(str_amount.size() - 1, 1); + --fraction_size; + } + if (default_decimal_point < fraction_size) + return false; + str_amount.erase(point_index, 1); + } + else + { + fraction_size = 0; + } + + if (str_amount.empty()) + return false; + + if (fraction_size < default_decimal_point) + { + str_amount.append(default_decimal_point - fraction_size, '0'); + } + + return string_tools::get_xtype_from_string(amount, str_amount); + } + //--------------------------------------------------------------- + bool get_tx_fee(const transaction& tx, uint64_t & fee) + { + if (tx.version > 1) + { + fee = tx.rct_signatures.txnFee; + return true; + } + uint64_t amount_in = 0; + uint64_t amount_out = 0; + for(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; + } + for(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; + } + //--------------------------------------------------------------- + bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields) + { + tx_extra_fields.clear(); + + if(tx_extra.empty()) + return true; + + std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); + std::istringstream iss(extra_str); + binary_archive<false> ar(iss); + + bool eof = false; + while (!eof) + { + tx_extra_field field; + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + tx_extra_fields.push_back(field); + + std::ios_base::iostate state = iss.rdstate(); + eof = (EOF == iss.peek()); + iss.clear(state); + } + CHECK_AND_NO_ASSERT_MES_L1(::serialization::check_stream_state(ar), false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + + return true; + } + //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index) + { + std::vector<tx_extra_field> tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); + + tx_extra_pub_key pub_key_field; + if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index)) + return null_pkey; + + return pub_key_field.pub_key; + } + //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx_prefix, size_t pk_index) + { + return get_tx_pub_key_from_extra(tx_prefix.extra, pk_index); + } + //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index) + { + return get_tx_pub_key_from_extra(tx.extra, pk_index); + } + //--------------------------------------------------------------- + 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_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce) + { + CHECK_AND_ASSERT_MES(extra_nonce.size() <= TX_EXTRA_NONCE_MAX_COUNT, 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 remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type) + { + if (tx_extra.empty()) + return true; + std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); + std::istringstream iss(extra_str); + binary_archive<false> ar(iss); + std::ostringstream oss; + binary_archive<true> newar(oss); + + bool eof = false; + while (!eof) + { + tx_extra_field field; + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + if (field.type() != type) + ::do_serialize(newar, field); + + std::ios_base::iostate state = iss.rdstate(); + eof = (EOF == iss.peek()); + iss.clear(state); + } + CHECK_AND_NO_ASSERT_MES_L1(::serialization::check_stream_state(ar), false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + tx_extra.clear(); + std::string s = oss.str(); + tx_extra.reserve(s.size()); + std::copy(s.begin(), s.end(), std::back_inserter(tx_extra)); + return true; + } + //--------------------------------------------------------------- + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id) + { + extra_nonce.clear(); + extra_nonce.push_back(TX_EXTRA_NONCE_PAYMENT_ID); + const uint8_t* payment_id_ptr = reinterpret_cast<const uint8_t*>(&payment_id); + std::copy(payment_id_ptr, payment_id_ptr + sizeof(payment_id), std::back_inserter(extra_nonce)); + } + //--------------------------------------------------------------- + void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id) + { + extra_nonce.clear(); + extra_nonce.push_back(TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID); + const uint8_t* payment_id_ptr = reinterpret_cast<const uint8_t*>(&payment_id); + std::copy(payment_id_ptr, payment_id_ptr + sizeof(payment_id), std::back_inserter(extra_nonce)); + } + //--------------------------------------------------------------- + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id) + { + if(sizeof(crypto::hash) + 1 != extra_nonce.size()) + return false; + if(TX_EXTRA_NONCE_PAYMENT_ID != extra_nonce[0]) + return false; + payment_id = *reinterpret_cast<const crypto::hash*>(extra_nonce.data() + 1); + return true; + } + //--------------------------------------------------------------- + bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id) + { + if(sizeof(crypto::hash8) + 1 != extra_nonce.size()) + return false; + if (TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID != extra_nonce[0]) + return false; + payment_id = *reinterpret_cast<const crypto::hash8*>(extra_nonce.data() + 1); + return true; + } + //--------------------------------------------------------------- + bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) + { + crypto::key_derivation derivation; + crypto::hash hash; + char data[33]; /* A hash, and an extra byte */ + + if (!generate_key_derivation(public_key, secret_key, derivation)) + return false; + + memcpy(data, &derivation, 32); + data[32] = ENCRYPTED_PAYMENT_ID_TAIL; + cn_fast_hash(data, 33, hash); + + for (size_t b = 0; b < 8; ++b) + payment_id.data[b] ^= hash.data[b]; + + return true; + } + bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) + { + // Encryption and decryption are the same operation (xor with a key) + return encrypt_payment_id(payment_id, public_key, secret_key); + } + //--------------------------------------------------------------- + bool get_inputs_money_amount(const transaction& tx, uint64_t& money) + { + money = 0; + for(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) + { + for(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) + { + for(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)); + + if (tx.version == 1) + { + CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output 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; + for(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; + for(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; + for(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 is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) + { + crypto::public_key pk; + derive_public_key(derivation, output_index, 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) + { + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if(null_pkey == tx_pub_key) + return false; + return lookup_acc_outs(acc, tx, tx_pub_key, 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; + for(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); + } + //--------------------------------------------------------------- + void set_default_decimal_point(unsigned int decimal_point) + { + switch (decimal_point) + { + case 12: + case 9: + case 6: + case 3: + case 0: + default_decimal_point = decimal_point; + break; + default: + ASSERT_MES_AND_THROW("Invalid decimal point specification: " << decimal_point); + } + } + //--------------------------------------------------------------- + unsigned int get_default_decimal_point() + { + return default_decimal_point; + } + //--------------------------------------------------------------- + std::string get_unit(unsigned int decimal_point) + { + if (decimal_point == (unsigned int)-1) + decimal_point = default_decimal_point; + switch (std::atomic_load(&default_decimal_point)) + { + case 12: + return "monero"; + case 9: + return "millinero"; + case 6: + return "micronero"; + case 3: + return "nanonero"; + case 0: + return "piconero"; + default: + ASSERT_MES_AND_THROW("Invalid decimal point specification: " << default_decimal_point); + } + } + //--------------------------------------------------------------- + std::string print_money(uint64_t amount, unsigned int decimal_point) + { + if (decimal_point == (unsigned int)-1) + decimal_point = default_decimal_point; + std::string s = std::to_string(amount); + if(s.size() < decimal_point+1) + { + s.insert(0, decimal_point+1 - s.size(), '0'); + } + if (decimal_point > 0) + s.insert(s.size() - 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; + get_transaction_hash(t, h, NULL); + return h; + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res) + { + return get_transaction_hash(t, res, NULL); + } + //--------------------------------------------------------------- + bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) + { + // v1 transactions hash the entire blob + if (t.version == 1) + { + size_t ignored_blob_size, &blob_size_ref = blob_size ? *blob_size : ignored_blob_size; + return get_object_hash(t, res, blob_size_ref); + } + + // v2 transactions hash different parts together, than hash the set of those hashes + crypto::hash hashes[3]; + + // prefix + get_transaction_prefix_hash(t, hashes[0]); + + transaction &tt = const_cast<transaction&>(t); + + // base rct + { + std::stringstream ss; + binary_archive<true> ba(ss); + const size_t inputs = t.vin.size(); + const size_t outputs = t.vout.size(); + bool r = tt.rct_signatures.serialize_rctsig_base(ba, inputs, outputs); + CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures base"); + cryptonote::get_blob_hash(ss.str(), hashes[1]); + } + + // prunable rct + if (t.rct_signatures.type == rct::RCTTypeNull) + { + hashes[2] = cryptonote::null_hash; + } + else + { + std::stringstream ss; + binary_archive<true> ba(ss); + const size_t inputs = t.vin.size(); + const size_t outputs = t.vout.size(); + const size_t mixin = t.vin.empty() ? 0 : t.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(t.vin[0]).key_offsets.size() - 1 : 0; + bool r = tt.rct_signatures.p.serialize_rctsig_prunable(ba, t.rct_signatures.type, inputs, outputs, mixin); + CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures prunable"); + cryptonote::get_blob_hash(ss.str(), hashes[2]); + } + + // the tx hash is the hash of the 3 hashes + res = cn_fast_hash(hashes, sizeof(hashes)); + + // we still need the size + if (blob_size) + *blob_size = get_object_blobsize(t); + + return true; + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) + { + if (t.is_hash_valid()) + { +#ifdef ENABLE_HASH_CASH_INTEGRITY_CHECK + CHECK_AND_ASSERT_THROW_MES(!calculate_transaction_hash(t, res, blob_size) || t.hash == res, "tx hash cash integrity failure"); +#endif + res = t.hash; + if (blob_size) + { + if (!t.is_blob_size_valid()) + { + t.blob_size = get_object_blobsize(t); + t.set_blob_size_valid(true); + } + *blob_size = t.blob_size; + } + ++tx_hashes_cached_count; + return true; + } + ++tx_hashes_calculated_count; + bool ret = calculate_transaction_hash(t, res, blob_size); + if (!ret) + return false; + t.hash = res; + t.set_hash_valid(true); + if (blob_size) + { + t.blob_size = *blob_size; + t.set_blob_size_valid(true); + } + return true; + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size) + { + return get_transaction_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(reinterpret_cast<const char*>(&tree_root_hash), sizeof(tree_root_hash)); + blob.append(tools::get_varint_data(b.tx_hashes.size()+1)); + return blob; + } + //--------------------------------------------------------------- + bool calculate_block_hash(const block& b, crypto::hash& res) + { + // EXCEPTION FOR BLOCK 202612 + const std::string correct_blob_hash_202612 = "3a8a2b3a29b50fc86ff73dd087ea43c6f0d6b8f936c849194d5c84c737903966"; + const std::string existing_block_id_202612 = "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"; + crypto::hash block_blob_hash = get_blob_hash(block_to_blob(b)); + + if (string_tools::pod_to_hex(block_blob_hash) == correct_blob_hash_202612) + { + string_tools::hex_to_pod(existing_block_id_202612, res); + return true; + } + bool hash_result = get_object_hash(get_block_hashing_blob(b), res); + + if (hash_result) + { + // make sure that we aren't looking at a block with the 202612 block id but not the correct blobdata + if (string_tools::pod_to_hex(res) == existing_block_id_202612) + { + LOG_ERROR("Block with block id for 202612 but incorrect block blob hash found!"); + res = null_hash; + return false; + } + } + return hash_result; + } + //--------------------------------------------------------------- + bool get_block_hash(const block& b, crypto::hash& res) + { + if (b.is_hash_valid()) + { +#ifdef ENABLE_HASH_CASH_INTEGRITY_CHECK + CHECK_AND_ASSERT_THROW_MES(!calculate_block_hash(b, res) || b.hash == res, "block hash cash integrity failure"); +#endif + res = b.hash; + ++block_hashes_cached_count; + return true; + } + ++block_hashes_calculated_count; + bool ret = calculate_block_hash(b, res); + if (!ret) + return false; + b.hash = res; + b.set_hash_valid(true); + return true; + } + //--------------------------------------------------------------- + crypto::hash get_block_hash(const block& b) + { + crypto::hash p = null_hash; + get_block_hash(b, p); + return p; + } + //--------------------------------------------------------------- + bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) + { + // block 202612 bug workaround + const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; + if (height == 202612) + { + string_tools::hex_to_pod(longhash_202612, res); + return true; + } + 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"); + b.invalidate_hashes(); + b.miner_tx.invalidate_hashes(); + 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); + for(auto& th: b.tx_hashes) + txs_ids.push_back(th); + return get_tx_tree_hash(txs_ids); + } + //--------------------------------------------------------------- + bool is_valid_decomposed_amount(uint64_t amount) + { + const uint64_t *begin = valid_decomposed_outputs; + const uint64_t *end = valid_decomposed_outputs + sizeof(valid_decomposed_outputs) / sizeof(valid_decomposed_outputs[0]); + return std::binary_search(begin, end, amount); + } + //--------------------------------------------------------------- + void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached) + { + tx_hashes_calculated = tx_hashes_calculated_count; + tx_hashes_cached = tx_hashes_cached_count; + block_hashes_calculated = block_hashes_calculated_count; + block_hashes_cached = block_hashes_cached_count; + } +} diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h new file mode 100644 index 000000000..5c10907fd --- /dev/null +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -0,0 +1,220 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "cryptonote_basic_impl.h" +#include "account.h" +#include "include_base_utils.h" +#include "crypto/crypto.h" +#include "crypto/hash.h" +#include "ringct/rctOps.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 parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx); + bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); + bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); + + template<typename T> + bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field, size_t index = 0) + { + auto it = std::find_if(tx_extra_fields.begin(), tx_extra_fields.end(), [&index](const tx_extra_field& f) { return typeid(T) == f.type() && !index--; }); + if(tx_extra_fields.end() == it) + return false; + + field = boost::get<T>(*it); + return true; + } + + bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields); + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0); + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0); + bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); + bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); + bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); + void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); + bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); + 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 is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, 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); + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); + bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); + blobdata get_block_hashing_blob(const block& b); + bool calculate_block_hash(const block& b, crypto::hash& res); + 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 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); + 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); + void set_default_decimal_point(unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT); + unsigned int get_default_decimal_point(); + std::string get_unit(unsigned int decimal_point = -1); + std::string print_money(uint64_t amount, unsigned int decimal_point = -1); + //--------------------------------------------------------------- + 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) + { + 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); + bool is_valid_decomposed_amount(uint64_t amount); + void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); + +#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_basic/cryptonote_stat_info.h b/src/cryptonote_basic/cryptonote_stat_info.h new file mode 100644 index 000000000..7ebf86878 --- /dev/null +++ b/src/cryptonote_basic/cryptonote_stat_info.h @@ -0,0 +1,53 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#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_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp new file mode 100644 index 000000000..863aa4359 --- /dev/null +++ b/src/cryptonote_basic/difficulty.cpp @@ -0,0 +1,165 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <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" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "difficulty" + +namespace cryptonote { + + using std::size_t; + using std::uint64_t; + using std::vector; + +#if defined(__x86_64__) + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + low = mul128(a, b, &high); + } + +#else + + static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { + // __int128 isn't part of the standard, so the previous function wasn't portable. mul128() in Windows is fine, + // but this portable function should be used elsewhere. Credit for this function goes to latexi95. + + uint64_t aLow = a & 0xFFFFFFFF; + uint64_t aHigh = a >> 32; + uint64_t bLow = b & 0xFFFFFFFF; + uint64_t bHigh = b >> 32; + + uint64_t res = aLow * bLow; + uint64_t lowRes1 = res & 0xFFFFFFFF; + uint64_t carry = res >> 32; + + res = aHigh * bLow + carry; + uint64_t highResHigh1 = res >> 32; + uint64_t highResLow1 = res & 0xFFFFFFFF; + + res = aLow * bHigh; + uint64_t lowRes2 = res & 0xFFFFFFFF; + carry = res >> 32; + + res = aHigh * bHigh + carry; + uint64_t highResHigh2 = res >> 32; + uint64_t highResLow2 = res & 0xFFFFFFFF; + + //Addition + + uint64_t r = highResLow1 + lowRes2; + carry = r >> 32; + low = (r << 32) | lowRes1; + r = highResHigh1 + highResLow2 + carry; + uint64_t d3 = r & 0xFFFFFFFF; + carry = r >> 32; + r = highResHigh2 + carry; + high = d3 | (r << 32); + } + +#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(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) { + + 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); + // blockchain errors "difficulty overhead" if this function returns zero. + // TODO: consider throwing an exception instead + if (high != 0 || low + time_span - 1 < low) { + return 0; + } + return (low + time_span - 1) / time_span; + } + +} diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h new file mode 100644 index 000000000..aed6cb289 --- /dev/null +++ b/src/cryptonote_basic/difficulty.h @@ -0,0 +1,56 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <cstdint> +#include <vector> + +#include "crypto/hash.h" + +namespace cryptonote +{ + typedef std::uint64_t difficulty_type; + + /** + * @brief checks if a hash fits the given difficulty + * + * The hash passes if (hash * difficulty) < 2^192. + * Phrased differently, if (hash * difficulty) fits without overflow into + * the least significant 192 bits of the 256 bit multiplication result. + * + * @param hash the hash to check + * @param difficulty the difficulty to check against + * + * @return true if valid, else false + */ + 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, size_t target_seconds); +} diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp new file mode 100644 index 000000000..546af2076 --- /dev/null +++ b/src/cryptonote_basic/hardfork.cpp @@ -0,0 +1,417 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <algorithm> +#include <cstdio> + +#include "cryptonote_basic/cryptonote_basic.h" +#include "blockchain_db/blockchain_db.h" +#include "hardfork.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "hardfork" + +using namespace cryptonote; + +static uint8_t get_block_vote(const cryptonote::block &b) +{ + // Pre-hardfork blocks have a minor version hardcoded to 0. + // For the purposes of voting, we consider 0 to refer to + // version number 1, which is what all blocks from the genesis + // block are. It makes things simpler. + if (b.minor_version == 0) + return 1; + return b.minor_version; +} + +static uint8_t get_block_version(const cryptonote::block &b) +{ + return b.major_version; +} + +HardFork::HardFork(cryptonote::BlockchainDB &db, uint8_t original_version, uint64_t original_version_till_height, time_t forked_time, time_t update_time, uint64_t window_size, uint8_t default_threshold_percent): + db(db), + original_version(original_version), + original_version_till_height(original_version_till_height), + forked_time(forked_time), + update_time(update_time), + window_size(window_size), + default_threshold_percent(default_threshold_percent) +{ + if (window_size == 0) + throw "window_size needs to be strictly positive"; + if (default_threshold_percent > 100) + throw "default_threshold_percent needs to be between 0 and 100"; +} + +bool HardFork::add_fork(uint8_t version, uint64_t height, uint8_t threshold, time_t time) +{ + CRITICAL_REGION_LOCAL(lock); + + // add in order + if (version == 0) + return false; + if (!heights.empty()) { + if (version <= heights.back().version) + return false; + if (height <= heights.back().height) + return false; + if (time <= heights.back().time) + return false; + } + if (threshold > 100) + return false; + heights.push_back(Params(version, height, threshold, time)); + return true; +} + +bool HardFork::add_fork(uint8_t version, uint64_t height, time_t time) +{ + return add_fork(version, height, default_threshold_percent, time); +} + +uint8_t HardFork::get_effective_version(uint8_t voting_version) const +{ + if (!heights.empty()) { + uint8_t max_version = heights.back().version; + if (voting_version > max_version) + voting_version = max_version; + } + return voting_version; +} + +bool HardFork::do_check(uint8_t block_version, uint8_t voting_version) const +{ + return block_version == heights[current_fork_index].version + && voting_version >= heights[current_fork_index].version; +} + +bool HardFork::check(const cryptonote::block &block) const +{ + CRITICAL_REGION_LOCAL(lock); + return do_check(::get_block_version(block), ::get_block_vote(block)); +} + +bool HardFork::do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const +{ + int fork_index = get_voted_fork_index(height); + return block_version == heights[fork_index].version + && voting_version >= heights[fork_index].version; +} + +bool HardFork::check_for_height(const cryptonote::block &block, uint64_t height) const +{ + CRITICAL_REGION_LOCAL(lock); + return do_check_for_height(::get_block_version(block), ::get_block_vote(block), height); +} + +bool HardFork::add(uint8_t block_version, uint8_t voting_version, uint64_t height) +{ + CRITICAL_REGION_LOCAL(lock); + + if (!do_check(block_version, voting_version)) + return false; + + db.set_hard_fork_version(height, heights[current_fork_index].version); + + voting_version = get_effective_version(voting_version); + + while (versions.size() >= window_size) { + const uint8_t old_version = versions.front(); + assert(last_versions[old_version] >= 1); + last_versions[old_version]--; + versions.pop_front(); + } + + last_versions[voting_version]++; + versions.push_back(voting_version); + + uint8_t voted = get_voted_fork_index(height + 1); + if (voted > current_fork_index) { + current_fork_index = voted; + } + + return true; +} + +bool HardFork::add(const cryptonote::block &block, uint64_t height) +{ + return add(::get_block_version(block), ::get_block_vote(block), height); +} + +void HardFork::init() +{ + CRITICAL_REGION_LOCAL(lock); + + // add a placeholder for the default version, to avoid special cases + if (heights.empty()) + heights.push_back(Params(original_version, 0, 0, 0)); + + versions.clear(); + for (size_t n = 0; n < 256; ++n) + last_versions[n] = 0; + current_fork_index = 0; + + // restore state from DB + uint64_t height = db.height(); + if (height > window_size) + height -= window_size - 1; + else + height = 1; + + bool populate = false; + try + { + db.get_hard_fork_version(0); + } + catch (...) { populate = true; } + if (populate) { + MINFO("The DB has no hard fork info, reparsing from start"); + height = 1; + } + MDEBUG("reorganizing from " << height); + if (populate) { + reorganize_from_chain_height(height); + // reorg will not touch the genesis block, use this as a flag for populating done + db.set_hard_fork_version(0, original_version); + } + else { + rescan_from_chain_height(height); + } + MDEBUG("reorganization done"); +} + +uint8_t HardFork::get_block_version(uint64_t height) const +{ + if (height <= original_version_till_height) + return original_version; + + const cryptonote::block &block = db.get_block_from_height(height); + return ::get_block_version(block); +} + +bool HardFork::reorganize_from_block_height(uint64_t height) +{ + CRITICAL_REGION_LOCAL(lock); + if (height >= db.height()) + return false; + + db.set_batch_transactions(true); + bool stop_batch = db.batch_start(); + + versions.clear(); + + for (size_t n = 0; n < 256; ++n) + last_versions[n] = 0; + const uint64_t rescan_height = height >= (window_size - 1) ? height - (window_size -1) : 0; + const uint8_t start_version = height == 0 ? original_version : db.get_hard_fork_version(height); + while (current_fork_index > 0 && heights[current_fork_index].version > start_version) { + --current_fork_index; + } + for (uint64_t h = rescan_height; h <= height; ++h) { + cryptonote::block b = db.get_block_from_height(h); + const uint8_t v = get_effective_version(get_block_vote(b)); + last_versions[v]++; + versions.push_back(v); + } + + uint8_t voted = get_voted_fork_index(height + 1); + if (voted > current_fork_index) { + current_fork_index = voted; + } + + const uint64_t bc_height = db.height(); + for (uint64_t h = height + 1; h < bc_height; ++h) { + add(db.get_block_from_height(h), h); + } + + if (stop_batch) + db.batch_stop(); + + return true; +} + +bool HardFork::reorganize_from_chain_height(uint64_t height) +{ + if (height == 0) + return false; + return reorganize_from_block_height(height - 1); +} + +bool HardFork::rescan_from_block_height(uint64_t height) +{ + CRITICAL_REGION_LOCAL(lock); + db.block_txn_start(true); + if (height >= db.height()) { + db.block_txn_stop(); + return false; + } + + versions.clear(); + + for (size_t n = 0; n < 256; ++n) + last_versions[n] = 0; + for (uint64_t h = height; h < db.height(); ++h) { + cryptonote::block b = db.get_block_from_height(h); + const uint8_t v = get_effective_version(get_block_vote(b)); + last_versions[v]++; + versions.push_back(v); + } + + uint8_t lastv = db.get_hard_fork_version(db.height() - 1); + current_fork_index = 0; + while (current_fork_index + 1 < heights.size() && heights[current_fork_index].version != lastv) + ++current_fork_index; + + uint8_t voted = get_voted_fork_index(db.height()); + if (voted > current_fork_index) { + current_fork_index = voted; + } + + db.block_txn_stop(); + + return true; +} + +bool HardFork::rescan_from_chain_height(uint64_t height) +{ + if (height == 0) + return false; + return rescan_from_block_height(height - 1); +} + +int HardFork::get_voted_fork_index(uint64_t height) const +{ + CRITICAL_REGION_LOCAL(lock); + uint32_t accumulated_votes = 0; + for (unsigned int n = heights.size() - 1; n > current_fork_index; --n) { + uint8_t v = heights[n].version; + accumulated_votes += last_versions[v]; + uint32_t threshold = (window_size * heights[n].threshold + 99) / 100; + if (height >= heights[n].height && accumulated_votes >= threshold) { + return n; + } + } + return current_fork_index; +} + +HardFork::State HardFork::get_state(time_t t) const +{ + CRITICAL_REGION_LOCAL(lock); + + // no hard forks setup yet + if (heights.size() <= 1) + return Ready; + + time_t t_last_fork = heights.back().time; + if (t >= t_last_fork + forked_time) + return LikelyForked; + if (t >= t_last_fork + update_time) + return UpdateNeeded; + return Ready; +} + +HardFork::State HardFork::get_state() const +{ + return get_state(time(NULL)); +} + +uint8_t HardFork::get(uint64_t height) const +{ + CRITICAL_REGION_LOCAL(lock); + if (height > db.height()) { + assert(false); + return 255; + } + if (height == db.height()) { + return get_current_version(); + } + return db.get_hard_fork_version(height); +} + +uint8_t HardFork::get_current_version() const +{ + CRITICAL_REGION_LOCAL(lock); + return heights[current_fork_index].version; +} + +uint8_t HardFork::get_ideal_version() const +{ + CRITICAL_REGION_LOCAL(lock); + return heights.back().version; +} + +uint8_t HardFork::get_ideal_version(uint64_t height) const +{ + CRITICAL_REGION_LOCAL(lock); + for (unsigned int n = heights.size() - 1; n > 0; --n) { + if (height >= heights[n].height) { + return heights[n].version; + } + } + return original_version; +} + +uint64_t HardFork::get_earliest_ideal_height_for_version(uint8_t version) const +{ + for (unsigned int n = heights.size() - 1; n > 0; --n) { + if (heights[n].version <= version) + return heights[n].height; + } + return 0; +} + +uint8_t HardFork::get_next_version() const +{ + CRITICAL_REGION_LOCAL(lock); + uint64_t height = db.height(); + for (unsigned int n = heights.size() - 1; n > 0; --n) { + if (height >= heights[n].height) { + return heights[n < heights.size() - 1 ? n + 1 : n].version; + } + } + return original_version; +} + +bool HardFork::get_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const +{ + CRITICAL_REGION_LOCAL(lock); + + const uint8_t current_version = heights[current_fork_index].version; + const bool enabled = current_version >= version; + window = versions.size(); + votes = 0; + for (size_t n = version; n < 256; ++n) + votes += last_versions[n]; + threshold = (window * heights[current_fork_index].threshold + 99) / 100; + //assert((votes >= threshold) == enabled); + earliest_height = get_earliest_ideal_height_for_version(version); + voting = heights.back().version; + return enabled; +} + diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h new file mode 100644 index 000000000..6c6fbcb84 --- /dev/null +++ b/src/cryptonote_basic/hardfork.h @@ -0,0 +1,265 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "syncobj.h" +#include "cryptonote_basic/cryptonote_basic.h" + +namespace cryptonote +{ + class BlockchainDB; + + class HardFork + { + public: + typedef enum { + LikelyForked, + UpdateNeeded, + Ready, + } State; + + static const uint64_t DEFAULT_ORIGINAL_VERSION_TILL_HEIGHT = 0; // <= actual height + static const time_t DEFAULT_FORKED_TIME = 31557600; // a year in seconds + static const time_t DEFAULT_UPDATE_TIME = 31557600 / 2; + static const uint64_t DEFAULT_WINDOW_SIZE = 10080; // supermajority window check length - a week + static const uint8_t DEFAULT_THRESHOLD_PERCENT = 80; + + /** + * @brief creates a new HardFork object + * + * @param original_version the block version for blocks 0 through to the first fork + * @param forked_time the time in seconds before thinking we're forked + * @param update_time the time in seconds before thinking we need to update + * @param window_size the size of the window in blocks to consider for version voting + * @param default_threshold_percent the size of the majority in percents + */ + HardFork(cryptonote::BlockchainDB &db, uint8_t original_version = 1, uint64_t original_version_till_height = DEFAULT_ORIGINAL_VERSION_TILL_HEIGHT, time_t forked_time = DEFAULT_FORKED_TIME, time_t update_time = DEFAULT_UPDATE_TIME, uint64_t window_size = DEFAULT_WINDOW_SIZE, uint8_t default_threshold_percent = DEFAULT_THRESHOLD_PERCENT); + + /** + * @brief add a new hardfork height + * + * returns true if no error, false otherwise + * + * @param version the major block version for the fork + * @param height The height the hardfork takes effect + * @param threshold The threshold of votes needed for this fork (0-100) + * @param time Approximate time of the hardfork (seconds since epoch) + */ + bool add_fork(uint8_t version, uint64_t height, uint8_t threshold, time_t time); + + /** + * @brief add a new hardfork height + * + * returns true if no error, false otherwise + * + * @param version the major block version for the fork + * @param voting_version the minor block version for the fork, used for voting + * @param height The height the hardfork takes effect + * @param time Approximate time of the hardfork (seconds since epoch) + */ + bool add_fork(uint8_t version, uint64_t height, time_t time); + + /** + * @brief initialize the object + * + * Must be done after adding all the required hardforks via add above + */ + void init(); + + /** + * @brief check whether a new block would be accepted + * + * returns true if the block is accepted, false otherwise + * + * @param block the new block + * + * This check is made by add. It is exposed publicly to allow + * the caller to inexpensively check whether a block would be + * accepted or rejected by its version number. Indeed, if this + * check could only be done as part of add, the caller would + * either have to add the block to the blockchain first, then + * call add, then have to pop the block from the blockchain if + * its version did not satisfy the hard fork requirements, or + * call add first, then, if the hard fork requirements are met, + * add the block to the blockchain, upon which a failure (the + * block being invalid, double spending, etc) would cause the + * hardfork object to reorganize. + */ + bool check(const cryptonote::block &block) const; + + /** + * @brief same as check, but for a particular height, rather than the top + * + * NOTE: this does not play well with voting, and relies on voting to be + * disabled (that is, forks happen on the scheduled date, whether or not + * enough blocks have voted for the fork). + * + * returns true if no error, false otherwise + * + * @param block the new block + * @param height which height to check for + */ + bool check_for_height(const cryptonote::block &block, uint64_t height) const; + + /** + * @brief add a new block + * + * returns true if no error, false otherwise + * + * @param block the new block + */ + bool add(const cryptonote::block &block, uint64_t height); + + /** + * @brief called when the blockchain is reorganized + * + * This will rescan the blockchain to determine which hard forks + * have been triggered + * + * returns true if no error, false otherwise + * + * @param blockchain the blockchain + * @param height of the last block kept from the previous blockchain + */ + bool reorganize_from_block_height(uint64_t height); + bool reorganize_from_chain_height(uint64_t height); + + /** + * @brief returns current state at the given time + * + * Based on the approximate time of the last known hard fork, + * estimate whether we need to update, or if we're way behind + * + * @param t the time to consider + */ + State get_state(time_t t) const; + State get_state() const; + + /** + * @brief returns the hard fork version for the given block height + * + * @param height height of the block to check + */ + uint8_t get(uint64_t height) const; + + /** + * @brief returns the latest "ideal" version + * + * This is the latest version that's been scheduled + */ + uint8_t get_ideal_version() const; + + /** + * @brief returns the "ideal" version for a given height + * + * @param height height of the block to check + */ + uint8_t get_ideal_version(uint64_t height) const; + + /** + * @brief returns the next version + * + * This is the version which will we fork to next + */ + uint8_t get_next_version() const; + + /** + * @brief returns the current version + * + * This is the latest version that's past its trigger date and had enough votes + * at one point in the past. + */ + uint8_t get_current_version() const; + + /** + * @brief returns the earliest block a given version may activate + */ + uint64_t get_earliest_ideal_height_for_version(uint8_t version) const; + + /** + * @brief returns information about current voting state + * + * returns true if the given version is enabled (ie, the current version + * is at least the passed version), false otherwise + * + * @param version the version to check voting for + * @param window the number of blocks considered in voting + * @param votes number of votes for next version + * @param threshold number of votes needed to switch to next version + * @param earliest_height earliest height at which the version can take effect + */ + bool get_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; + + /** + * @brief returns the size of the voting window in blocks + */ + uint64_t get_window_size() const { return window_size; } + + private: + + uint8_t get_block_version(uint64_t height) const; + bool do_check(uint8_t block_version, uint8_t voting_version) const; + bool do_check_for_height(uint8_t block_version, uint8_t voting_version, uint64_t height) const; + int get_voted_fork_index(uint64_t height) const; + uint8_t get_effective_version(uint8_t voting_version) const; + bool add(uint8_t block_version, uint8_t voting_version, uint64_t height); + + bool rescan_from_block_height(uint64_t height); + bool rescan_from_chain_height(uint64_t height); + + private: + + BlockchainDB &db; + + time_t forked_time; + time_t update_time; + uint64_t window_size; + uint8_t default_threshold_percent; + + uint8_t original_version; + uint64_t original_version_till_height; + + struct Params { + uint8_t version; + uint8_t threshold; + uint64_t height; + time_t time; + Params(uint8_t version, uint64_t height, uint8_t threshold, time_t time): version(version), threshold(threshold), height(height), time(time) {} + }; + std::vector<Params> heights; + + std::deque<uint8_t> versions; /* rolling window of the last N blocks' versions */ + unsigned int last_versions[256]; /* count of the block versions in the last N blocks */ + uint32_t current_fork_index; + + mutable epee::critical_section lock; + }; + +} // namespace cryptonote + diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp new file mode 100644 index 000000000..eeb7b6094 --- /dev/null +++ b/src/cryptonote_basic/miner.cpp @@ -0,0 +1,853 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <sstream> +#include <numeric> +#include <boost/utility/value_init.hpp> +#include <boost/interprocess/detail/atomic.hpp> +#include <boost/limits.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" +#include "boost/logic/tribool.hpp" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "miner" + +using namespace epee; + +#include "miner.h" + + +extern "C" void slow_hash_allocate_state(); +extern "C" void slow_hash_free_state(); +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}; + const command_line::arg_descriptor<bool> arg_bg_mining_enable = {"bg-mining-enable", "enable/disable background mining", true, true}; + const command_line::arg_descriptor<bool> arg_bg_mining_ignore_battery = {"bg-mining-ignore-battery", "if true, assumes plugged in when unable to query system power status", false, true}; + const command_line::arg_descriptor<uint64_t> arg_bg_mining_min_idle_interval_seconds = {"bg-mining-min-idle-interval", "Specify min lookback interval in seconds for determining idle state", miner::BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS, true}; + const command_line::arg_descriptor<uint8_t> arg_bg_mining_idle_threshold_percentage = {"bg-mining-idle-threshold", "Specify minimum avg idle percentage over lookback interval", miner::BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE, true}; + const command_line::arg_descriptor<uint8_t> arg_bg_mining_miner_target_percentage = {"bg-mining-miner-target", "Specificy maximum percentage cpu use by miner(s)", miner::BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE, 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), + m_current_hash_rate(0), + m_is_background_mining_enabled(false), + m_min_idle_seconds(BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS), + m_idle_threshold(BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE), + m_mining_target(BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE), + m_miner_extra_sleep(BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS) + { + + } + //----------------------------------------------------------------------------------------------------- + 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); + uint64_t expected_reward; //only used for RPC calls - could possibly be useful here too? + + 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, expected_reward, 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); + command_line::add_arg(desc, arg_bg_mining_enable); + command_line::add_arg(desc, arg_bg_mining_ignore_battery); + command_line::add_arg(desc, arg_bg_mining_min_idle_interval_seconds); + command_line::add_arg(desc, arg_bg_mining_idle_threshold_percentage); + command_line::add_arg(desc, arg_bg_mining_miner_target_percentage); + } + //----------------------------------------------------------------------------------------------------- + bool miner::init(const boost::program_options::variables_map& vm, bool testnet) + { + 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); + MINFO("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, testnet, 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); + } + } + + // Background mining parameters + // Let init set all parameters even if background mining is not enabled, they can start later with params set + if(command_line::has_arg(vm, arg_bg_mining_enable)) + set_is_background_mining_enabled( command_line::get_arg(vm, arg_bg_mining_enable) ); + if(command_line::has_arg(vm, arg_bg_mining_ignore_battery)) + set_ignore_battery( command_line::get_arg(vm, arg_bg_mining_ignore_battery) ); + if(command_line::has_arg(vm, arg_bg_mining_min_idle_interval_seconds)) + set_min_idle_seconds( command_line::get_arg(vm, arg_bg_mining_min_idle_interval_seconds) ); + if(command_line::has_arg(vm, arg_bg_mining_idle_threshold_percentage)) + set_idle_threshold( command_line::get_arg(vm, arg_bg_mining_idle_threshold_percentage) ); + if(command_line::has_arg(vm, arg_bg_mining_miner_target_percentage)) + set_mining_target( command_line::get_arg(vm, arg_bg_mining_miner_target_percentage) ); + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::is_mining() const + { + return !m_stop; + } + //----------------------------------------------------------------------------------------------------- + const account_public_address& miner::get_mining_address() const + { + return m_mine_address; + } + //----------------------------------------------------------------------------------------------------- + uint32_t miner::get_threads_count() const { + return m_threads_total; + } + //----------------------------------------------------------------------------------------------------- + bool miner::start(const account_public_address& adr, size_t threads_count, const boost::thread::attributes& attrs, bool do_background, bool ignore_battery) + { + 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); + set_is_background_mining_enabled(do_background); + set_ignore_battery(ignore_battery); + + for(size_t i = 0; i != threads_count; i++) + { + m_threads.push_back(boost::thread(attrs, boost::bind(&miner::worker_thread, this))); + } + + LOG_PRINT_L0("Mining has started with " << threads_count << " threads, good luck!" ); + + if( get_is_background_mining_enabled() ) + { + m_background_mining_thread = boost::thread(attrs, boost::bind(&miner::background_worker_thread, this)); + LOG_PRINT_L0("Background mining controller thread started" ); + } + + return true; + } + //----------------------------------------------------------------------------------------------------- + uint64_t miner::get_speed() const + { + if(is_mining()) { + return m_current_hash_rate; + } + else { + return 0; + } + } + //----------------------------------------------------------------------------------------------------- + void miner::send_stop_signal() + { + boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); + } + //----------------------------------------------------------------------------------------------------- + bool miner::stop() + { + MTRACE("Miner has received stop signal"); + + if (!is_mining()) + { + MDEBUG("Not mining - nothing to stop" ); + return true; + } + + send_stop_signal(); + CRITICAL_REGION_LOCAL(m_threads_lock); + + // In case background mining was active and the miner threads are waiting + // on the background miner to signal start. + m_is_background_mining_started_cond.notify_all(); + + for(boost::thread& th: m_threads) + th.join(); + + // The background mining thread could be sleeping for a long time, so we + // interrupt it just in case + m_background_mining_thread.interrupt(); + m_background_mining_thread.join(); + + MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); + m_threads.clear(); + 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)) + { + bl.invalidate_hashes(); + return true; + } + } + bl.invalidate_hashes(); + return false; + } + //----------------------------------------------------------------------------------------------------- + void miner::on_synchronized() + { + if(m_do_mining) + { + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + + start(m_mine_address, m_threads_total, attrs, get_is_background_mining_enabled()); + } + } + //----------------------------------------------------------------------------------------------------- + void miner::pause() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + MDEBUG("miner::pause: " << m_pausers_count << " -> " << (m_pausers_count + 1)); + ++m_pausers_count; + if(m_pausers_count == 1 && is_mining()) + MDEBUG("MINING PAUSED"); + } + //----------------------------------------------------------------------------------------------------- + void miner::resume() + { + CRITICAL_REGION_LOCAL(m_miners_count_lock); + MDEBUG("miner::resume: " << m_pausers_count << " -> " << (m_pausers_count - 1)); + --m_pausers_count; + if(m_pausers_count < 0) + { + m_pausers_count = 0; + MERROR("Unexpected miner::resume() called"); + } + if(!m_pausers_count && is_mining()) + MDEBUG("MINING RESUMED"); + } + //----------------------------------------------------------------------------------------------------- + bool miner::worker_thread() + { + uint32_t th_local_index = boost::interprocess::ipcdetail::atomic_inc32(&m_thread_index); + MGINFO("Miner thread was started ["<< th_local_index << "]"); + MLOG_SET_THREAD_NAME(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; + slow_hash_allocate_state(); + while(!m_stop) + { + if(m_pausers_count)//anti split workaround + { + misc_utils::sleep_no_w(100); + continue; + } + else if( m_is_background_mining_enabled ) + { + misc_utils::sleep_no_w(m_miner_extra_sleep); + while( !m_is_background_mining_started ) + { + MGINFO("background mining is enabled, but not started, waiting until start triggers"); + boost::unique_lock<boost::mutex> started_lock( m_is_background_mining_started_mutex ); + m_is_background_mining_started_cond.wait( started_lock ); + if( m_stop ) break; + } + + if( m_stop ) 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; + MGINFO_GREEN("Found block for difficulty: " << local_diff); + if(!m_phandler->handle_block_found(b)) + { + --m_config.current_extra_message_index; + }else + { + //success update, lets update config + if (!m_config_folder_path.empty()) + epee::serialization::store_t_to_json_file(m_config, m_config_folder_path + "/" + MINER_CONFIG_FILE_NAME); + } + } + nonce+=m_threads_total; + ++m_hashes; + } + slow_hash_free_state(); + MGINFO("Miner thread stopped ["<< th_local_index << "]"); + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_is_background_mining_enabled() const + { + return m_is_background_mining_enabled; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_ignore_battery() const + { + return m_ignore_battery; + } + //----------------------------------------------------------------------------------------------------- + /** + * This has differing behaviour depending on if mining has been started/etc. + * Note: add documentation + */ + bool miner::set_is_background_mining_enabled(bool is_background_mining_enabled) + { + m_is_background_mining_enabled = is_background_mining_enabled; + // Extra logic will be required if we make this function public in the future + // and allow toggling smart mining without start/stop + //m_is_background_mining_enabled_cond.notify_one(); + return true; + } + //----------------------------------------------------------------------------------------------------- + void miner::set_ignore_battery(bool ignore_battery) + { + m_ignore_battery = ignore_battery; + } + //----------------------------------------------------------------------------------------------------- + uint64_t miner::get_min_idle_seconds() const + { + return m_min_idle_seconds; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_min_idle_seconds(uint64_t min_idle_seconds) + { + if(min_idle_seconds > BACKGROUND_MINING_MAX_MIN_IDLE_INTERVAL_IN_SECONDS) return false; + if(min_idle_seconds < BACKGROUND_MINING_MIN_MIN_IDLE_INTERVAL_IN_SECONDS) return false; + m_min_idle_seconds = min_idle_seconds; + return true; + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_idle_threshold() const + { + return m_idle_threshold; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_idle_threshold(uint8_t idle_threshold) + { + if(idle_threshold > BACKGROUND_MINING_MAX_IDLE_THRESHOLD_PERCENTAGE) return false; + if(idle_threshold < BACKGROUND_MINING_MIN_IDLE_THRESHOLD_PERCENTAGE) return false; + m_idle_threshold = idle_threshold; + return true; + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_mining_target() const + { + return m_mining_target; + } + //----------------------------------------------------------------------------------------------------- + bool miner::set_mining_target(uint8_t mining_target) + { + if(mining_target > BACKGROUND_MINING_MAX_MINING_TARGET_PERCENTAGE) return false; + if(mining_target < BACKGROUND_MINING_MIN_MINING_TARGET_PERCENTAGE) return false; + m_mining_target = mining_target; + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::background_worker_thread() + { + uint64_t prev_total_time, current_total_time; + uint64_t prev_idle_time, current_idle_time; + uint64_t previous_process_time = 0, current_process_time = 0; + m_is_background_mining_started = false; + + if(!get_system_times(prev_total_time, prev_idle_time)) + { + LOG_ERROR("get_system_times call failed, background mining will NOT work!"); + return false; + } + + while(!m_stop) + { + + try + { + // Commenting out the below since we're going with privatizing the bg mining enabled + // function, but I'll leave the code/comments here for anyone that wants to modify the + // patch in the future + // ------------------------------------------------------------------------------------- + // All of this might be overkill if we just enforced some simple requirements + // about changing this variable before/after the miner starts, but I envision + // in the future a checkbox that you can tick on/off for background mining after + // you've clicked "start mining". There's still an issue here where if background + // mining is disabled when start is called, this thread is never created, and so + // enabling after does nothing, something I have to fix in the future. However, + // this should take care of the case where mining is started with bg-enabled, + // and then the user decides to un-check background mining, and just do + // regular full-speed mining. I might just be over-doing it and thinking up + // non-existant use-cases, so if the concensus is to simplify, we can remove all this fluff. + /* + while( !m_is_background_mining_enabled ) + { + MGINFO("background mining is disabled, waiting until enabled!"); + boost::unique_lock<boost::mutex> enabled_lock( m_is_background_mining_enabled_mutex ); + m_is_background_mining_enabled_cond.wait( enabled_lock ); + } + */ + + // If we're already mining, then sleep for the miner monitor interval. + // If we're NOT mining, then sleep for the idle monitor interval + uint64_t sleep_for_seconds = BACKGROUND_MINING_MINER_MONITOR_INVERVAL_IN_SECONDS; + if( !m_is_background_mining_started ) sleep_for_seconds = get_min_idle_seconds(); + boost::this_thread::sleep_for(boost::chrono::seconds(sleep_for_seconds)); + } + catch(const boost::thread_interrupted&) + { + MDEBUG("background miner thread interrupted "); + continue; // if interrupted because stop called, loop should end .. + } + + boost::tribool battery_powered(on_battery_power()); + bool on_ac_power = false; + if(indeterminate( battery_powered )) + { + // name could be better, only ignores battery requirement if we failed + // to get the status of the system + if( m_ignore_battery ) + { + on_ac_power = true; + } + } + else + { + on_ac_power = !battery_powered; + } + + if( m_is_background_mining_started ) + { + // figure out if we need to stop, and monitor mining usage + + // If we get here, then previous values are initialized. + // Let's get some current data for comparison. + + if(!get_system_times(current_total_time, current_idle_time)) + { + MERROR("get_system_times call failed"); + continue; + } + + if(!get_process_time(current_process_time)) + { + MERROR("get_process_time call failed!"); + continue; + } + + uint64_t total_diff = (current_total_time - prev_total_time); + uint64_t idle_diff = (current_idle_time - prev_idle_time); + uint64_t process_diff = (current_process_time - previous_process_time); + uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); + uint8_t process_percentage = get_percent_of_total(process_diff, total_diff); + + MGINFO("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); + if( idle_percentage + process_percentage < get_idle_threshold() || !on_ac_power ) + { + MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); + m_is_background_mining_started = false; + + // reset process times + previous_process_time = 0; + current_process_time = 0; + } + else + { + previous_process_time = current_process_time; + + // adjust the miner extra sleep variable + int64_t miner_extra_sleep_change = (-1 * (get_mining_target() - process_percentage) ); + int64_t new_miner_extra_sleep = m_miner_extra_sleep + miner_extra_sleep_change; + // if you start the miner with few threads on a multicore system, this could + // fall below zero because all the time functions aggregate across all processors. + // I'm just hard limiting to 5 millis min sleep here, other options? + m_miner_extra_sleep = std::max( new_miner_extra_sleep , (int64_t)5 ); + MDEBUG("m_miner_extra_sleep " << m_miner_extra_sleep); + } + + prev_total_time = current_total_time; + prev_idle_time = current_idle_time; + } + else if( on_ac_power ) + { + // figure out if we need to start + + if(!get_system_times(current_total_time, current_idle_time)) + { + MERROR("get_system_times call failed"); + continue; + } + + uint64_t total_diff = (current_total_time - prev_total_time); + uint64_t idle_diff = (current_idle_time - prev_idle_time); + uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); + + MGINFO("idle percentage is " << unsigned(idle_percentage)); + if( idle_percentage >= get_idle_threshold() && on_ac_power ) + { + MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); + m_is_background_mining_started = true; + m_is_background_mining_started_cond.notify_all(); + + // Wait for a little mining to happen .. + boost::this_thread::sleep_for(boost::chrono::seconds( 1 )); + + // Starting data ... + if(!get_process_time(previous_process_time)) + { + m_is_background_mining_started = false; + MERROR("get_process_time call failed!"); + } + } + + prev_total_time = current_total_time; + prev_idle_time = current_idle_time; + } + } + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_system_times(uint64_t& total_time, uint64_t& idle_time) + { + #ifdef _WIN32 + + FILETIME idleTime; + FILETIME kernelTime; + FILETIME userTime; + if ( GetSystemTimes( &idleTime, &kernelTime, &userTime ) != -1 ) + { + total_time = + ( (((uint64_t)(kernelTime.dwHighDateTime)) << 32) | ((uint64_t)kernelTime.dwLowDateTime) ) + + ( (((uint64_t)(userTime.dwHighDateTime)) << 32) | ((uint64_t)userTime.dwLowDateTime) ); + + idle_time = ( (((uint64_t)(idleTime.dwHighDateTime)) << 32) | ((uint64_t)idleTime.dwLowDateTime) ); + + return true; + } + + #elif defined(__linux__) + + const std::string STR_CPU("cpu"); + const std::size_t STR_CPU_LEN = STR_CPU.size(); + const std::string STAT_FILE_PATH = "/proc/stat"; + + if( !epee::file_io_utils::is_file_exist(STAT_FILE_PATH) ) + { + LOG_ERROR("'" << STAT_FILE_PATH << "' file does not exist"); + return false; + } + + std::ifstream stat_file_stream(STAT_FILE_PATH); + if( stat_file_stream.fail() ) + { + LOG_ERROR("failed to open '" << STAT_FILE_PATH << "'"); + return false; + } + + std::string line; + std::getline(stat_file_stream, line); + std::istringstream stat_file_iss(line); + stat_file_iss.ignore(65536, ' '); // skip cpu label ... + uint64_t utime, ntime, stime, itime; + if( !(stat_file_iss >> utime && stat_file_iss >> ntime && stat_file_iss >> stime && stat_file_iss >> itime) ) + { + LOG_ERROR("failed to read '" << STAT_FILE_PATH << "'"); + return false; + } + + idle_time = itime; + total_time = utime + ntime + stime + itime; + + return true; + + #endif + + return false; // unsupported systemm.. + } + //----------------------------------------------------------------------------------------------------- + bool miner::get_process_time(uint64_t& total_time) + { + #ifdef _WIN32 + + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + if ( GetProcessTimes( GetCurrentProcess(), &createTime, &exitTime, &kernelTime, &userTime ) != -1 ) + { + total_time = + ( (((uint64_t)(kernelTime.dwHighDateTime)) << 32) | ((uint64_t)kernelTime.dwLowDateTime) ) + + ( (((uint64_t)(userTime.dwHighDateTime)) << 32) | ((uint64_t)userTime.dwLowDateTime) ); + + return true; + } + + #elif defined(__linux__) && defined(_SC_CLK_TCK) + + struct tms tms; + if ( times(&tms) != (clock_t)-1 ) + { + total_time = tms.tms_utime + tms.tms_stime; + return true; + } + + #endif + + return false; // unsupported system.. + } + //----------------------------------------------------------------------------------------------------- + uint8_t miner::get_percent_of_total(uint64_t other, uint64_t total) + { + return (uint8_t)( ceil( (other * 1.f / total * 1.f) * 100) ); + } + //----------------------------------------------------------------------------------------------------- + boost::logic::tribool miner::on_battery_power() + { + #ifdef _WIN32 + + SYSTEM_POWER_STATUS power_status; + if ( GetSystemPowerStatus( &power_status ) != 0 ) + { + return boost::logic::tribool(power_status.ACLineStatus != 1); + } + + #elif defined(__linux__) + + // i've only tested on UBUNTU, these paths might be different on other systems + // need to figure out a way to make this more flexible + std::string power_supply_path = ""; + const std::string POWER_SUPPLY_STATUS_PATHS[] = + { + "/sys/class/power_supply/ACAD/online", + "/sys/class/power_supply/AC/online", + "/sys/class/power_supply/AC0/online", + "/sys/class/power_supply/ADP0/online" + }; + + for(const std::string& path : POWER_SUPPLY_STATUS_PATHS) + { + if( epee::file_io_utils::is_file_exist(path) ) + { + power_supply_path = path; + break; + } + } + + if( power_supply_path.empty() ) + { + LOG_ERROR("Couldn't find battery/power status file, can't determine if plugged in!"); + return boost::logic::tribool(boost::logic::indeterminate);; + } + + std::ifstream power_stream(power_supply_path); + if( power_stream.fail() ) + { + LOG_ERROR("failed to open '" << power_supply_path << "'"); + return boost::logic::tribool(boost::logic::indeterminate);; + } + + return boost::logic::tribool( (power_stream.get() != '1') ); + + #endif + + LOG_ERROR("couldn't query power status"); + return boost::logic::tribool(boost::logic::indeterminate); + } +} diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h new file mode 100644 index 000000000..964ee6a36 --- /dev/null +++ b/src/cryptonote_basic/miner.h @@ -0,0 +1,173 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <boost/program_options.hpp> +#include <boost/logic/tribool_fwd.hpp> +#include <atomic> +#include "cryptonote_basic.h" +#include "difficulty.h" +#include "math_helper.h" +#ifdef _WIN32 +#include <windows.h> +#elif defined(__linux__) +#include <unistd.h> +#include <sys/resource.h> +#include <sys/times.h> +#include <time.h> +#endif + +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, uint64_t& expected_reward, 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, bool testnet); + 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, const boost::thread::attributes& attrs, bool do_background = false, bool ignore_battery = false); + uint64_t get_speed() const; + uint32_t get_threads_count() const; + void send_stop_signal(); + bool stop(); + bool is_mining() const; + const account_public_address& get_mining_address() const; + 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); + bool get_is_background_mining_enabled() const; + bool get_ignore_battery() const; + uint64_t get_min_idle_seconds() const; + bool set_min_idle_seconds(uint64_t min_idle_seconds); + uint8_t get_idle_threshold() const; + bool set_idle_threshold(uint8_t idle_threshold); + uint8_t get_mining_target() const; + bool set_mining_target(uint8_t mining_target); + + static constexpr uint8_t BACKGROUND_MINING_DEFAULT_IDLE_THRESHOLD_PERCENTAGE = 90; + static constexpr uint8_t BACKGROUND_MINING_MIN_IDLE_THRESHOLD_PERCENTAGE = 50; + static constexpr uint8_t BACKGROUND_MINING_MAX_IDLE_THRESHOLD_PERCENTAGE = 99; + static constexpr uint16_t BACKGROUND_MINING_DEFAULT_MIN_IDLE_INTERVAL_IN_SECONDS = 10; + static constexpr uint16_t BACKGROUND_MINING_MIN_MIN_IDLE_INTERVAL_IN_SECONDS = 10; + static constexpr uint16_t BACKGROUND_MINING_MAX_MIN_IDLE_INTERVAL_IN_SECONDS = 3600; + static constexpr uint8_t BACKGROUND_MINING_DEFAULT_MINING_TARGET_PERCENTAGE = 40; + static constexpr uint8_t BACKGROUND_MINING_MIN_MINING_TARGET_PERCENTAGE = 5; + static constexpr uint8_t BACKGROUND_MINING_MAX_MINING_TARGET_PERCENTAGE = 50; + static constexpr uint8_t BACKGROUND_MINING_MINER_MONITOR_INVERVAL_IN_SECONDS = 10; + static constexpr uint64_t BACKGROUND_MINING_DEFAULT_MINER_EXTRA_SLEEP_MILLIS = 400; // ramp up + static constexpr uint64_t BACKGROUND_MINING_MIN_MINER_EXTRA_SLEEP_MILLIS = 5; + + 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; + epee::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; + epee::critical_section m_miners_count_lock; + + std::list<boost::thread> m_threads; + epee::critical_section m_threads_lock; + i_miner_handler* m_phandler; + account_public_address m_mine_address; + epee::math_helper::once_a_time_seconds<5> m_update_block_template_interval; + epee::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; + epee::critical_section m_last_hash_rates_lock; + std::list<uint64_t> m_last_hash_rates; + bool m_do_print_hashrate; + bool m_do_mining; + + // background mining stuffs .. + + bool set_is_background_mining_enabled(bool is_background_mining_enabled); + void set_ignore_battery(bool ignore_battery); + bool background_worker_thread(); + std::atomic<bool> m_is_background_mining_enabled; + bool m_ignore_battery; + boost::mutex m_is_background_mining_enabled_mutex; + boost::condition_variable m_is_background_mining_enabled_cond; + std::atomic<bool> m_is_background_mining_started; + boost::mutex m_is_background_mining_started_mutex; + boost::condition_variable m_is_background_mining_started_cond; + boost::thread m_background_mining_thread; + uint64_t m_min_idle_seconds; + uint8_t m_idle_threshold; + uint8_t m_mining_target; + std::atomic<uint64_t> m_miner_extra_sleep; + static bool get_system_times(uint64_t& total_time, uint64_t& idle_time); + static bool get_process_time(uint64_t& total_time); + static uint8_t get_percent_of_total(uint64_t some_time, uint64_t total_time); + static boost::logic::tribool on_battery_power(); + }; +} diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h new file mode 100644 index 000000000..5a6c3176d --- /dev/null +++ b/src/cryptonote_basic/tx_extra.h @@ -0,0 +1,182 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + + +#define TX_EXTRA_PADDING_MAX_COUNT 255 +#define TX_EXTRA_NONCE_MAX_COUNT 255 + +#define TX_EXTRA_TAG_PADDING 0x00 +#define TX_EXTRA_TAG_PUBKEY 0x01 +#define TX_EXTRA_NONCE 0x02 +#define TX_EXTRA_MERGE_MINING_TAG 0x03 +#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE + +#define TX_EXTRA_NONCE_PAYMENT_ID 0x00 +#define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01 + +namespace cryptonote +{ + struct tx_extra_padding + { + size_t size; + + // load + template <template <bool> class Archive> + bool do_serialize(Archive<false>& ar) + { + // size - 1 - because of variant tag + for (size = 1; size <= TX_EXTRA_PADDING_MAX_COUNT; ++size) + { + std::ios_base::iostate state = ar.stream().rdstate(); + bool eof = EOF == ar.stream().peek(); + ar.stream().clear(state); + + if (eof) + break; + + uint8_t zero; + if (!::do_serialize(ar, zero)) + return false; + + if (0 != zero) + return false; + } + + return size <= TX_EXTRA_PADDING_MAX_COUNT; + } + + // store + template <template <bool> class Archive> + bool do_serialize(Archive<true>& ar) + { + if(TX_EXTRA_PADDING_MAX_COUNT < size) + return false; + + // i = 1 - because of variant tag + for (size_t i = 1; i < size; ++i) + { + uint8_t zero = 0; + if (!::do_serialize(ar, zero)) + return false; + } + return true; + } + }; + + struct tx_extra_pub_key + { + crypto::public_key pub_key; + + BEGIN_SERIALIZE() + FIELD(pub_key) + END_SERIALIZE() + }; + + struct tx_extra_nonce + { + std::string nonce; + + BEGIN_SERIALIZE() + FIELD(nonce) + if(TX_EXTRA_NONCE_MAX_COUNT < nonce.size()) return false; + END_SERIALIZE() + }; + + struct tx_extra_merge_mining_tag + { + struct serialize_helper + { + tx_extra_merge_mining_tag& mm_tag; + + serialize_helper(tx_extra_merge_mining_tag& mm_tag_) : mm_tag(mm_tag_) + { + } + + BEGIN_SERIALIZE() + VARINT_FIELD_N("depth", mm_tag.depth) + FIELD_N("merkle_root", mm_tag.merkle_root) + END_SERIALIZE() + }; + + size_t depth; + crypto::hash merkle_root; + + // load + template <template <bool> class Archive> + bool do_serialize(Archive<false>& ar) + { + std::string field; + if(!::do_serialize(ar, field)) + return false; + + std::istringstream iss(field); + binary_archive<false> iar(iss); + serialize_helper helper(*this); + return ::serialization::serialize(iar, helper); + } + + // store + template <template <bool> class Archive> + bool do_serialize(Archive<true>& ar) + { + std::ostringstream oss; + binary_archive<true> oar(oss); + serialize_helper helper(*this); + if(!::do_serialize(oar, helper)) + return false; + + std::string field = oss.str(); + return ::serialization::serialize(ar, field); + } + }; + + struct tx_extra_mysterious_minergate + { + std::string data; + + BEGIN_SERIALIZE() + FIELD(data) + END_SERIALIZE() + }; + + // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: + // varint tag; + // varint size; + // varint data[]; + typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate> tx_extra_field; +} + +VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG); diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h new file mode 100644 index 000000000..ce885ec1d --- /dev/null +++ b/src/cryptonote_basic/verification_context.h @@ -0,0 +1,61 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#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; + bool m_low_mixin; + bool m_double_spend; + bool m_invalid_input; + bool m_invalid_output; + bool m_too_big; + bool m_overspend; + bool m_fee_too_low; + bool m_not_rct; + }; + + 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; + bool m_partial_block_reward; + }; +} |