From 9af47e6085b5570ce4f980f93c8db93c7751043e Mon Sep 17 00:00:00 2001 From: kevacoin Date: Sun, 27 Sep 2020 14:44:23 -0700 Subject: [PATCH] Removed dead code that caused false Coverity check errors. --- .gitignore | 2 + src/cn_utils/crypto/crypto.cpp | 253 - src/cn_utils/crypto/crypto.h | 56 +- src/cn_utils/cryptonote_core/blockchain.cpp | 4698 ------------------- src/cn_utils/cryptonote_core/blockchain.h | 1423 ------ src/cn_utils/p2p/net_node.inl | 2116 --------- 6 files changed, 9 insertions(+), 8539 deletions(-) delete mode 100644 src/cn_utils/cryptonote_core/blockchain.cpp delete mode 100644 src/cn_utils/cryptonote_core/blockchain.h delete mode 100644 src/cn_utils/p2p/net_node.inl diff --git a/.gitignore b/.gitignore index 6515b5531..ed6af80dc 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,5 @@ contrib/devtools/split-debug.sh */.vscode/* .vscode + +cov-int diff --git a/src/cn_utils/crypto/crypto.cpp b/src/cn_utils/crypto/crypto.cpp index bd4bfdcca..350b1abeb 100644 --- a/src/cn_utils/crypto/crypto.cpp +++ b/src/cn_utils/crypto/crypto.cpp @@ -258,218 +258,6 @@ namespace crypto { ec_point Y; }; - void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) { - ge_p3 tmp3; - ec_scalar k; - s_comm buf; -#if !defined(NDEBUG) - { - ge_p3 t; - public_key t2; - assert(sc_check(&sec) == 0); - ge_scalarmult_base(&t, &sec); - ge_p3_tobytes(&t2, &t); - assert(pub == t2); - } -#endif - buf.h = prefix_hash; - buf.key = pub; - try_again: - random_scalar(k); - if (((const uint32_t*)(&k))[7] == 0) // we don't want tiny numbers here - goto try_again; - ge_scalarmult_base(&tmp3, &k); - ge_p3_tobytes(&buf.comm, &tmp3); - hash_to_scalar(&buf, sizeof(s_comm), sig.c); - if (!sc_isnonzero((const unsigned char*)sig.c.data)) - goto try_again; - sc_mulsub(&sig.r, &sig.c, &unwrap(sec), &k); - if (!sc_isnonzero((const unsigned char*)sig.r.data)) - goto try_again; - } - - bool crypto_ops::check_signature(const hash &prefix_hash, const public_key &pub, const signature &sig) { - ge_p2 tmp2; - ge_p3 tmp3; - ec_scalar c; - s_comm buf; - assert(check_key(pub)); - buf.h = prefix_hash; - buf.key = pub; - if (ge_frombytes_vartime(&tmp3, &pub) != 0) { - return false; - } - if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0 || !sc_isnonzero(&sig.c)) { - return false; - } - ge_double_scalarmult_base_vartime(&tmp2, &sig.c, &tmp3, &sig.r); - ge_tobytes(&buf.comm, &tmp2); - static const ec_point infinity = {{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; - if (memcmp(&buf.comm, &infinity, 32) == 0) - return false; - hash_to_scalar(&buf, sizeof(s_comm), c); - sc_sub(&c, &c, &sig.c); - return sc_isnonzero(&c) == 0; - } - - void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, signature &sig) { - // sanity check - ge_p3 R_p3; - ge_p3 A_p3; - ge_p3 B_p3; - ge_p3 D_p3; - if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid"); - if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid"); - if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid"); - if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid"); -#if !defined(NDEBUG) - { - assert(sc_check(&r) == 0); - // check R == r*G or R == r*B - public_key dbg_R; - if (B) - { - ge_p2 dbg_R_p2; - ge_scalarmult(&dbg_R_p2, &r, &B_p3); - ge_tobytes(&dbg_R, &dbg_R_p2); - } - else - { - ge_p3 dbg_R_p3; - ge_scalarmult_base(&dbg_R_p3, &r); - ge_p3_tobytes(&dbg_R, &dbg_R_p3); - } - assert(R == dbg_R); - // check D == r*A - ge_p2 dbg_D_p2; - ge_scalarmult(&dbg_D_p2, &r, &A_p3); - public_key dbg_D; - ge_tobytes(&dbg_D, &dbg_D_p2); - assert(D == dbg_D); - } -#endif - - // pick random k - ec_scalar k; - random_scalar(k); - - s_comm_2 buf; - buf.msg = prefix_hash; - buf.D = D; - - if (B) - { - // compute X = k*B - ge_p2 X_p2; - ge_scalarmult(&X_p2, &k, &B_p3); - ge_tobytes(&buf.X, &X_p2); - } - else - { - // compute X = k*G - ge_p3 X_p3; - ge_scalarmult_base(&X_p3, &k); - ge_p3_tobytes(&buf.X, &X_p3); - } - - // compute Y = k*A - ge_p2 Y_p2; - ge_scalarmult(&Y_p2, &k, &A_p3); - ge_tobytes(&buf.Y, &Y_p2); - - // sig.c = Hs(Msg || D || X || Y) - hash_to_scalar(&buf, sizeof(buf), sig.c); - - // sig.r = k - sig.c*r - sc_mulsub(&sig.r, &sig.c, &unwrap(r), &k); - } - - bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { - // sanity check - ge_p3 R_p3; - ge_p3 A_p3; - ge_p3 B_p3; - ge_p3 D_p3; - if (ge_frombytes_vartime(&R_p3, &R) != 0) return false; - if (ge_frombytes_vartime(&A_p3, &A) != 0) return false; - if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) return false; - if (ge_frombytes_vartime(&D_p3, &D) != 0) return false; - if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false; - - // compute sig.c*R - ge_p3 cR_p3; - { - ge_p2 cR_p2; - ge_scalarmult(&cR_p2, &sig.c, &R_p3); - public_key cR; - ge_tobytes(&cR, &cR_p2); - if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; - } - - ge_p1p1 X_p1p1; - if (B) - { - // compute X = sig.c*R + sig.r*B - ge_p2 rB_p2; - ge_scalarmult(&rB_p2, &sig.r, &B_p3); - public_key rB; - ge_tobytes(&rB, &rB_p2); - ge_p3 rB_p3; - if (ge_frombytes_vartime(&rB_p3, &rB) != 0) return false; - ge_cached rB_cached; - ge_p3_to_cached(&rB_cached, &rB_p3); - ge_add(&X_p1p1, &cR_p3, &rB_cached); - } - else - { - // compute X = sig.c*R + sig.r*G - ge_p3 rG_p3; - ge_scalarmult_base(&rG_p3, &sig.r); - ge_cached rG_cached; - ge_p3_to_cached(&rG_cached, &rG_p3); - ge_add(&X_p1p1, &cR_p3, &rG_cached); - } - ge_p2 X_p2; - ge_p1p1_to_p2(&X_p2, &X_p1p1); - - // compute sig.c*D - ge_p2 cD_p2; - ge_scalarmult(&cD_p2, &sig.c, &D_p3); - - // compute sig.r*A - ge_p2 rA_p2; - ge_scalarmult(&rA_p2, &sig.r, &A_p3); - - // compute Y = sig.c*D + sig.r*A - public_key cD; - public_key rA; - ge_tobytes(&cD, &cD_p2); - ge_tobytes(&rA, &rA_p2); - ge_p3 cD_p3; - ge_p3 rA_p3; - if (ge_frombytes_vartime(&cD_p3, &cD) != 0) return false; - if (ge_frombytes_vartime(&rA_p3, &rA) != 0) return false; - ge_cached rA_cached; - ge_p3_to_cached(&rA_cached, &rA_p3); - ge_p1p1 Y_p1p1; - ge_add(&Y_p1p1, &cD_p3, &rA_cached); - ge_p2 Y_p2; - ge_p1p1_to_p2(&Y_p2, &Y_p1p1); - - // compute c2 = Hs(Msg || D || X || Y) - s_comm_2 buf; - buf.msg = prefix_hash; - buf.D = D; - ge_tobytes(&buf.X, &X_p2); - ge_tobytes(&buf.Y, &Y_p2); - ec_scalar c2; - hash_to_scalar(&buf, sizeof(s_comm_2), c2); - - // test if c2 == sig.c - sc_sub(&c2, &c2, &sig.c); - return sc_isnonzero(&c2) == 0; - } - static void hash_to_ec(const public_key &key, ge_p3 &res) { hash h; ge_p2 point; @@ -567,45 +355,4 @@ POP_WARNINGS sc_mulsub(&sig[sec_index].r, &sig[sec_index].c, &unwrap(sec), &k); } - bool crypto_ops::check_ring_signature(const hash &prefix_hash, const key_image &image, - const public_key *const *pubs, size_t pubs_count, - const signature *sig) { - size_t i; - ge_p3 image_unp; - ge_dsmp image_pre; - ec_scalar sum, h; - boost::shared_ptr buf(reinterpret_cast(malloc(rs_comm_size(pubs_count))), free); - if (!buf) - return false; -#if !defined(NDEBUG) - for (i = 0; i < pubs_count; i++) { - assert(check_key(*pubs[i])); - } -#endif - if (ge_frombytes_vartime(&image_unp, &image) != 0) { - return false; - } - ge_dsm_precomp(image_pre, &image_unp); - sc_0(&sum); - buf->h = prefix_hash; - for (i = 0; i < pubs_count; i++) { - ge_p2 tmp2; - ge_p3 tmp3; - if (sc_check(&sig[i].c) != 0 || sc_check(&sig[i].r) != 0) { - return false; - } - if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) { - return false; - } - ge_double_scalarmult_base_vartime(&tmp2, &sig[i].c, &tmp3, &sig[i].r); - ge_tobytes(&buf->ab[i].a, &tmp2); - hash_to_ec(*pubs[i], tmp3); - ge_double_scalarmult_precomp_vartime(&tmp2, &sig[i].r, &tmp3, &sig[i].c, image_pre); - ge_tobytes(&buf->ab[i].b, &tmp2); - sc_add(&sum, &sum, &sig[i].c); - } - hash_to_scalar(buf.get(), rs_comm_size(pubs_count), h); - sc_sub(&h, &h, &sum); - return sc_isnonzero(&h) == 0; - } } diff --git a/src/cn_utils/crypto/crypto.h b/src/cn_utils/crypto/crypto.h index 33cc0a25a..ffb1ca983 100644 --- a/src/cn_utils/crypto/crypto.h +++ b/src/cn_utils/crypto/crypto.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-2018, 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 @@ -25,7 +25,7 @@ // 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 @@ -128,24 +128,12 @@ namespace crypto { friend void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &); static bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); friend bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); - static void generate_signature(const hash &, const public_key &, const secret_key &, signature &); - friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &); - static bool check_signature(const hash &, const public_key &, const signature &); - friend bool check_signature(const hash &, const public_key &, const signature &); - static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); - friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); - static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &); - friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &); static void generate_key_image(const public_key &, const secret_key &, key_image &); friend void generate_key_image(const public_key &, const secret_key &, key_image &); static void generate_ring_signature(const hash &, const key_image &, const public_key *const *, std::size_t, const secret_key &, std::size_t, signature *); friend void generate_ring_signature(const hash &, const key_image &, const public_key *const *, std::size_t, const secret_key &, std::size_t, signature *); - static bool check_ring_signature(const hash &, const key_image &, - const public_key *const *, std::size_t, const signature *); - friend bool check_ring_signature(const hash &, const key_image &, - const public_key *const *, std::size_t, const signature *); }; void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes); @@ -207,26 +195,6 @@ namespace crypto { return crypto_ops::derive_subaddress_public_key(out_key, derivation, output_index, result); } - /* Generation and checking of a standard signature. - */ - inline void generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) { - crypto_ops::generate_signature(prefix_hash, pub, sec, sig); - } - inline bool check_signature(const hash &prefix_hash, const public_key &pub, const signature &sig) { - return crypto_ops::check_signature(prefix_hash, pub, sig); - } - - /* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key - * derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A - * When the recipient's address is a subaddress, the tx pubkey R is defined as R=r*B where B is the recipient's spend pubkey - */ - inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, signature &sig) { - crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig); - } - inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { - return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig); - } - /* To send money to a key: * * The sender generates an ephemeral key and includes it in transaction output. * * To spend the money, the receiver generates a key image from it. @@ -242,11 +210,6 @@ namespace crypto { signature *sig) { crypto_ops::generate_ring_signature(prefix_hash, image, pubs, pubs_count, sec, sec_index, sig); } - inline bool check_ring_signature(const hash &prefix_hash, const key_image &image, - const public_key *const *pubs, std::size_t pubs_count, - const signature *sig) { - return crypto_ops::check_ring_signature(prefix_hash, image, pubs, pubs_count, sig); - } /* Variants with vector parameters. */ @@ -256,11 +219,6 @@ namespace crypto { signature *sig) { generate_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sec, sec_index, sig); } - inline bool check_ring_signature(const hash &prefix_hash, const key_image &image, - const std::vector &pubs, - const signature *sig) { - return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig); - } inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; diff --git a/src/cn_utils/cryptonote_core/blockchain.cpp b/src/cn_utils/cryptonote_core/blockchain.cpp deleted file mode 100644 index 3d211fef2..000000000 --- a/src/cn_utils/cryptonote_core/blockchain.cpp +++ /dev/null @@ -1,4698 +0,0 @@ -// Copyright (c) 2014-2018, 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 -#include -#include - -#include "include_base_utils.h" -#include "cryptonote_basic/cryptonote_basic_impl.h" -#include "tx_pool.h" -#include "blockchain.h" -#include "blockchain_db/blockchain_db.h" -#include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_config.h" -#include "cryptonote_basic/miner.h" -#include "misc_language.h" -#include "profile_tools.h" -#include "file_io_utils.h" -#include "common/int-util.h" -#include "common/threadpool.h" -#include "common/boost_serialization_helper.h" -#include "epee/include/warnings.h" -#include "crypto/hash.h" -#include "cryptonote_core.h" -#include "ringct/rctSigs.h" -#include "common/perf_timer.h" -#include "common/notify.h" -#if defined(PER_BLOCK_CHECKPOINT) -#include "blocks/blocks.h" -#endif - -#undef MONERO_DEFAULT_LOG_CATEGORY -#define MONERO_DEFAULT_LOG_CATEGORY "blockchain" - -#define FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE (100*1024*1024) // 100 MB - -using namespace crypto; - -//#include "serialization/json_archive.h" - -/* TODO: - * Clean up code: - * Possibly change how outputs are referred to/indexed in blockchain and wallets - * - */ - -using namespace cryptonote; -using epee::string_tools::pod_to_hex; -extern "C" void slow_hash_allocate_state(); -extern "C" void slow_hash_free_state(); - -DISABLE_VS_WARNINGS(4267) - -#define MERROR_VER(x) MCERROR("verify", x) - -// used to overestimate the block reward when estimating a per kB to use -#define BLOCK_REWARD_OVERESTIMATE (10 * 1000000000000) - -static const struct { - uint8_t version; - uint64_t height; - uint8_t threshold; - time_t time; -} mainnet_hard_forks[] = { - // version 1 from the start of the blockchain - { 1, 1, 0, 1341378000 }, - - // version 2 starts from block 1009827, which is on or around the 20th of March, 2016. Fork time finalised on 2015-09-20. No fork voting occurs for the v2 fork. - { 2, 1009827, 0, 1442763710 }, - - // version 3 starts from block 1141317, which is on or around the 24th of September, 2016. Fork time finalised on 2016-03-21. - { 3, 1141317, 0, 1458558528 }, - - // version 4 starts from block 1220516, which is on or around the 5th of January, 2017. Fork time finalised on 2016-09-18. - { 4, 1220516, 0, 1483574400 }, - - // version 5 starts from block 1288616, which is on or around the 15th of April, 2017. Fork time finalised on 2017-03-14. - { 5, 1288616, 0, 1489520158 }, - - // version 6 starts from block 1400000, which is on or around the 16th of September, 2017. Fork time finalised on 2017-08-18. - { 6, 1400000, 0, 1503046577 }, - - // version 7 starts from block 1546000, which is on or around the 6th of April, 2018. Fork time finalised on 2018-03-17. - { 7, 1546000, 0, 1521303150 }, - - // version 8 starts from block 1685555, which is on or around the 18th of October, 2018. Fork time finalised on 2018-09-02. - { 8, 1685555, 0, 1535889547 }, - - // version 9 starts from block 1686275, which is on or around the 19th of October, 2018. Fork time finalised on 2018-09-02. - { 9, 1686275, 0, 1535889548 }, - - // version 10 starts from block 1788000, which is on or around the 9th of March, 2019. Fork time finalised on 2019-02-10. - { 10, 1788000, 0, 1549792439 }, - - // version 11 starts from block 1788720, which is on or around the 10th of March, 2019. Fork time finalised on 2019-02-15. - { 11, 1788720, 0, 1550225678 }, -}; -static const uint64_t mainnet_hard_fork_version_1_till = 1009826; - -static const struct { - uint8_t version; - uint64_t height; - uint8_t threshold; - time_t time; -} testnet_hard_forks[] = { - // version 1 from the start of the blockchain - { 1, 1, 0, 1341378000 }, - - // version 2 starts from block 624634, which is on or around the 23rd of November, 2015. Fork time finalised on 2015-11-20. No fork voting occurs for the v2 fork. - { 2, 624634, 0, 1445355000 }, - - // versions 3-5 were passed in rapid succession from September 18th, 2016 - { 3, 800500, 0, 1472415034 }, - { 4, 801219, 0, 1472415035 }, - { 5, 802660, 0, 1472415036 + 86400*180 }, // add 5 months on testnet to shut the update warning up since there's a large gap to v6 - - { 6, 971400, 0, 1501709789 }, - { 7, 1057027, 0, 1512211236 }, - { 8, 1057058, 0, 1533211200 }, - { 9, 1057778, 0, 1533297600 }, - { 10, 1154318, 0, 1550153694 }, - { 11, 1155038, 0, 1550225678 }, -}; -static const uint64_t testnet_hard_fork_version_1_till = 624633; - -static const struct { - uint8_t version; - uint64_t height; - uint8_t threshold; - time_t time; -} stagenet_hard_forks[] = { - // version 1 from the start of the blockchain - { 1, 1, 0, 1341378000 }, - - // versions 2-7 in rapid succession from March 13th, 2018 - { 2, 32000, 0, 1521000000 }, - { 3, 33000, 0, 1521120000 }, - { 4, 34000, 0, 1521240000 }, - { 5, 35000, 0, 1521360000 }, - { 6, 36000, 0, 1521480000 }, - { 7, 37000, 0, 1521600000 }, - { 8, 176456, 0, 1537821770 }, - { 9, 177176, 0, 1537821771 }, - { 10, 269000, 0, 1550153694 }, - { 11, 269720, 0, 1550225678 }, -}; - -//------------------------------------------------------------------ -Blockchain::Blockchain(tx_memory_pool& tx_pool) : - m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_weight_limit(0), m_current_block_cumul_weight_median(0), - m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), - m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), - m_long_term_effective_median_block_weight(0), - m_difficulty_for_next_block_top_hash(crypto::null_hash), - m_difficulty_for_next_block(1), - m_btc_valid(false) -{ - LOG_PRINT_L3("Blockchain::" << __func__); -} -//------------------------------------------------------------------ -bool Blockchain::have_tx(const crypto::hash &id) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - return m_db->tx_exists(id); -} -//------------------------------------------------------------------ -bool Blockchain::have_tx_keyimg_as_spent(const crypto::key_image &key_im) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - return m_db->has_key_image(key_im); -} -//------------------------------------------------------------------ -// This function makes sure that each "input" in an input (mixins) exists -// and collects the public key for each from the transaction it was included in -// via the visitor passed to it. -template -bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - - // ND: Disable locking and make method private. - //CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // verify that the input has key offsets (that it exists properly, really) - if(!tx_in_to_key.key_offsets.size()) - return false; - - // cryptonote_format_utils uses relative offsets for indexing to the global - // outputs list. that is to say that absolute offset #2 is absolute offset - // #1 plus relative offset #2. - // TODO: Investigate if this is necessary / why this is done. - std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_in_to_key.key_offsets); - std::vector outputs; - - bool found = false; - auto it = m_scan_table.find(tx_prefix_hash); - if (it != m_scan_table.end()) - { - auto its = it->second.find(tx_in_to_key.k_image); - if (its != it->second.end()) - { - outputs = its->second; - found = true; - } - } - - if (!found) - { - try - { - m_db->get_output_key(tx_in_to_key.amount, absolute_offsets, outputs, true); - if (absolute_offsets.size() != outputs.size()) - { - MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); - return false; - } - } - catch (...) - { - MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); - return false; - } - } - else - { - // check for partial results and add the rest if needed; - if (outputs.size() < absolute_offsets.size() && outputs.size() > 0) - { - MDEBUG("Additional outputs needed: " << absolute_offsets.size() - outputs.size()); - std::vector < uint64_t > add_offsets; - std::vector add_outputs; - add_outputs.reserve(absolute_offsets.size() - outputs.size()); - for (size_t i = outputs.size(); i < absolute_offsets.size(); i++) - add_offsets.push_back(absolute_offsets[i]); - try - { - m_db->get_output_key(tx_in_to_key.amount, add_offsets, add_outputs, true); - if (add_offsets.size() != add_outputs.size()) - { - MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); - return false; - } - } - catch (...) - { - MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount); - return false; - } - outputs.insert(outputs.end(), add_outputs.begin(), add_outputs.end()); - } - } - - size_t count = 0; - for (const uint64_t& i : absolute_offsets) - { - try - { - output_data_t output_index; - try - { - // get tx hash and output index for output - if (count < outputs.size()) - output_index = outputs.at(count); - else - output_index = m_db->get_output_key(tx_in_to_key.amount, i); - - // call to the passed boost visitor to grab the public key for the output - if (!vis.handle_output(output_index.unlock_time, output_index.pubkey, output_index.commitment)) - { - MERROR_VER("Failed to handle_output for output no = " << count << ", with absolute offset " << i); - return false; - } - } - catch (...) - { - MERROR_VER("Output does not exist! amount = " << tx_in_to_key.amount << ", absolute_offset = " << i); - return false; - } - - // if on last output and pmax_related_block_height not null pointer - if(++count == absolute_offsets.size() && pmax_related_block_height) - { - // set *pmax_related_block_height to tx block height for this output - auto h = output_index.height; - if(*pmax_related_block_height < h) - { - *pmax_related_block_height = h; - } - } - - } - catch (const OUTPUT_DNE& e) - { - MERROR_VER("Output does not exist: " << e.what()); - return false; - } - catch (const TX_DNE& e) - { - MERROR_VER("Transaction does not exist: " << e.what()); - return false; - } - - } - - return true; -} -//------------------------------------------------------------------ -uint64_t Blockchain::get_current_blockchain_height() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - return m_db->height(); -} -//------------------------------------------------------------------ -//FIXME: possibly move this into the constructor, to avoid accidentally -// dereferencing a null BlockchainDB pointer -bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_tx_pool); - CRITICAL_REGION_LOCAL1(m_blockchain_lock); - - if (db == nullptr) - { - LOG_ERROR("Attempted to init Blockchain with null DB"); - return false; - } - if (!db->is_open()) - { - LOG_ERROR("Attempted to init Blockchain with unopened DB"); - delete db; - return false; - } - - m_db = db; - - m_nettype = test_options != NULL ? FAKECHAIN : nettype; - m_offline = offline; - m_fixed_difficulty = fixed_difficulty; - if (m_hardfork == nullptr) - { - if (m_nettype == FAKECHAIN || m_nettype == STAGENET) - m_hardfork = new HardFork(*db, 1, 0); - else if (m_nettype == TESTNET) - m_hardfork = new HardFork(*db, 1, testnet_hard_fork_version_1_till); - else - m_hardfork = new HardFork(*db, 1, mainnet_hard_fork_version_1_till); - } - if (m_nettype == FAKECHAIN) - { - for (size_t n = 0; test_options->hard_forks[n].first; ++n) - m_hardfork->add_fork(test_options->hard_forks[n].first, test_options->hard_forks[n].second, 0, n + 1); - } - else if (m_nettype == TESTNET) - { - for (size_t n = 0; n < sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); ++n) - m_hardfork->add_fork(testnet_hard_forks[n].version, testnet_hard_forks[n].height, testnet_hard_forks[n].threshold, testnet_hard_forks[n].time); - } - else if (m_nettype == STAGENET) - { - for (size_t n = 0; n < sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); ++n) - m_hardfork->add_fork(stagenet_hard_forks[n].version, stagenet_hard_forks[n].height, stagenet_hard_forks[n].threshold, stagenet_hard_forks[n].time); - } - else - { - for (size_t n = 0; n < sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); ++n) - m_hardfork->add_fork(mainnet_hard_forks[n].version, mainnet_hard_forks[n].height, mainnet_hard_forks[n].threshold, mainnet_hard_forks[n].time); - } - m_hardfork->init(); - - m_db->set_hard_fork(m_hardfork); - - // if the blockchain is new, add the genesis block - // this feels kinda kludgy to do it this way, but can be looked at later. - // TODO: add function to create and store genesis block, - // taking testnet into account - if(!m_db->height()) - { - MINFO("Blockchain not loaded, generating genesis block."); - block bl = boost::value_initialized(); - block_verification_context bvc = boost::value_initialized(); - generate_genesis_block(bl, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE); - add_new_block(bl, bvc); - CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "Failed to add genesis block to blockchain"); - } - // TODO: if blockchain load successful, verify blockchain against both - // hard-coded and runtime-loaded (and enforced) checkpoints. - else - { - } - - if (m_nettype != FAKECHAIN) - { - // ensure we fixup anything we found and fix in the future - m_db->fixup(); - } - - m_db->block_txn_start(true); - // check how far behind we are - uint64_t top_block_timestamp = m_db->get_top_block_timestamp(); - uint64_t timestamp_diff = time(NULL) - top_block_timestamp; - - // genesis block has no timestamp, could probably change it to have timestamp of 1341378000... - if(!top_block_timestamp) - timestamp_diff = time(NULL) - 1341378000; - - // create general purpose async service queue - - m_async_work_idle = std::unique_ptr < boost::asio::io_service::work > (new boost::asio::io_service::work(m_async_service)); - // we only need 1 - m_async_pool.create_thread(boost::bind(&boost::asio::io_service::run, &m_async_service)); - -#if defined(PER_BLOCK_CHECKPOINT) - if (m_nettype != FAKECHAIN) - load_compiled_in_block_hashes(); -#endif - - MINFO("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block()); - m_db->block_txn_stop(); - - uint64_t num_popped_blocks = 0; - while (!m_db->is_read_only()) - { - const uint64_t top_height = m_db->height() - 1; - const crypto::hash top_id = m_db->top_block_hash(); - const block top_block = m_db->get_top_block(); - const uint8_t ideal_hf_version = get_ideal_hard_fork_version(top_height); - if (ideal_hf_version <= 1 || ideal_hf_version == top_block.major_version) - { - if (num_popped_blocks > 0) - MGINFO("Initial popping done, top block: " << top_id << ", top height: " << top_height << ", block version: " << (uint64_t)top_block.major_version); - break; - } - else - { - if (num_popped_blocks == 0) - MGINFO("Current top block " << top_id << " at height " << top_height << " has version " << (uint64_t)top_block.major_version << " which disagrees with the ideal version " << (uint64_t)ideal_hf_version); - if (num_popped_blocks % 100 == 0) - MGINFO("Popping blocks... " << top_height); - ++num_popped_blocks; - block popped_block; - std::vector popped_txs; - try - { - m_db->pop_block(popped_block, popped_txs); - } - // anything that could cause this to throw is likely catastrophic, - // so we re-throw - catch (const std::exception& e) - { - MERROR("Error popping block from blockchain: " << e.what()); - throw; - } - catch (...) - { - MERROR("Error popping block from blockchain, throwing!"); - throw; - } - } - } - if (num_popped_blocks > 0) - { - m_timestamps_and_difficulties_height = 0; - m_hardfork->reorganize_from_chain_height(get_current_blockchain_height()); - m_tx_pool.on_blockchain_dec(m_db->height()-1, get_tail_id()); - } - - if (test_options && test_options->long_term_block_weight_window) - m_long_term_block_weights_window = test_options->long_term_block_weight_window; - - if (!update_next_cumulative_weight_limit()) - return false; - return true; -} -//------------------------------------------------------------------ -bool Blockchain::init(BlockchainDB* db, HardFork*& hf, const network_type nettype, bool offline) -{ - if (hf != nullptr) - m_hardfork = hf; - bool res = init(db, nettype, offline, NULL); - if (hf == nullptr) - hf = m_hardfork; - return res; -} -//------------------------------------------------------------------ -bool Blockchain::store_blockchain() -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // lock because the rpc_thread command handler also calls this - CRITICAL_REGION_LOCAL(m_db->m_synchronization_lock); - - TIME_MEASURE_START(save); - // TODO: make sure sync(if this throws that it is not simply ignored higher - // up the call stack - try - { - m_db->sync(); - } - catch (const std::exception& e) - { - MERROR(std::string("Error syncing blockchain db: ") + e.what() + "-- shutting down now to prevent issues!"); - throw; - } - catch (...) - { - MERROR("There was an issue storing the blockchain, shutting down now to prevent issues!"); - throw; - } - - TIME_MEASURE_FINISH(save); - if(m_show_time_stats) - MINFO("Blockchain stored OK, took: " << save << " ms"); - return true; -} -//------------------------------------------------------------------ -bool Blockchain::deinit() -{ - LOG_PRINT_L3("Blockchain::" << __func__); - - MTRACE("Stopping blockchain read/write activity"); - - // stop async service - m_async_work_idle.reset(); - m_async_pool.join_all(); - m_async_service.stop(); - - // as this should be called if handling a SIGSEGV, need to check - // if m_db is a NULL pointer (and thus may have caused the illegal - // memory operation), otherwise we may cause a loop. - if (m_db == NULL) - { - throw DB_ERROR("The db pointer is null in Blockchain, the blockchain may be corrupt!"); - } - - try - { - m_db->close(); - MTRACE("Local blockchain read/write activity stopped successfully"); - } - catch (const std::exception& e) - { - LOG_ERROR(std::string("Error closing blockchain db: ") + e.what()); - } - catch (...) - { - LOG_ERROR("There was an issue closing/storing the blockchain, shutting down now to prevent issues!"); - } - - delete m_hardfork; - m_hardfork = NULL; - delete m_db; - m_db = NULL; - return true; -} -//------------------------------------------------------------------ -// This function tells BlockchainDB to remove the top block from the -// blockchain and then returns all transactions (except the miner tx, of course) -// from it to the tx_pool -block Blockchain::pop_block_from_blockchain() -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - m_timestamps_and_difficulties_height = 0; - - block popped_block; - std::vector popped_txs; - - try - { - m_db->pop_block(popped_block, popped_txs); - } - // anything that could cause this to throw is likely catastrophic, - // so we re-throw - catch (const std::exception& e) - { - LOG_ERROR("Error popping block from blockchain: " << e.what()); - throw; - } - catch (...) - { - LOG_ERROR("Error popping block from blockchain, throwing!"); - throw; - } - - // make sure the hard fork object updates its current version - m_hardfork->on_block_popped(1); - - // return transactions from popped block to the tx_pool - for (transaction& tx : popped_txs) - { - if (!is_coinbase(tx)) - { - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - - // FIXME: HardFork - // Besides the below, popping a block should also remove the last entry - // in hf_versions. - uint8_t version = get_ideal_hard_fork_version(m_db->height()); - - // We assume that if they were in a block, the transactions are already - // known to the network as a whole. However, if we had mined that block, - // that might not be always true. Unlikely though, and always relaying - // these again might cause a spike of traffic as many nodes re-relay - // all the transactions in a popped block when a reorg happens. - bool r = m_tx_pool.add_tx(tx, tvc, true, true, false, version); - if (!r) - { - LOG_ERROR("Error returning transaction to tx_pool"); - } - } - } - - m_blocks_longhash_table.clear(); - m_scan_table.clear(); - m_blocks_txs_check.clear(); - m_check_txin_table.clear(); - - CHECK_AND_ASSERT_THROW_MES(update_next_cumulative_weight_limit(), "Error updating next cumulative weight limit"); - - m_tx_pool.on_blockchain_dec(m_db->height()-1, get_tail_id()); - invalidate_block_template_cache(); - - return popped_block; -} -//------------------------------------------------------------------ -bool Blockchain::reset_and_set_genesis_block(const block& b) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_timestamps_and_difficulties_height = 0; - m_alternative_chains.clear(); - invalidate_block_template_cache(); - m_db->reset(); - m_hardfork->init(); - - block_verification_context bvc = boost::value_initialized(); - add_new_block(b, bvc); - if (!update_next_cumulative_weight_limit()) - return false; - return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; -} -//------------------------------------------------------------------ -crypto::hash Blockchain::get_tail_id(uint64_t& height) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - height = m_db->height() - 1; - return get_tail_id(); -} -//------------------------------------------------------------------ -crypto::hash Blockchain::get_tail_id() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - return m_db->top_block_hash(); -} -//------------------------------------------------------------------ -/*TODO: this function was...poorly written. As such, I'm not entirely - * certain on what it was supposed to be doing. Need to look into this, - * but it doesn't seem terribly important just yet. - * - * puts into list a list of hashes representing certain blocks - * from the blockchain in reverse chronological order - * - * the blocks chosen, at the time of this writing, are: - * the most recent 11 - * powers of 2 less recent from there, so 13, 17, 25, etc... - * - */ -bool Blockchain::get_short_chain_history(std::list& ids) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - uint64_t i = 0; - uint64_t current_multiplier = 1; - uint64_t sz = m_db->height(); - - if(!sz) - return true; - - m_db->block_txn_start(true); - bool genesis_included = false; - uint64_t current_back_offset = 1; - while(current_back_offset < sz) - { - ids.push_back(m_db->get_block_hash_from_height(sz - current_back_offset)); - - if(sz-current_back_offset == 0) - { - genesis_included = true; - } - if(i < 10) - { - ++current_back_offset; - } - else - { - current_multiplier *= 2; - current_back_offset += current_multiplier; - } - ++i; - } - - if (!genesis_included) - { - ids.push_back(m_db->get_block_hash_from_height(0)); - } - m_db->block_txn_stop(); - - return true; -} -//------------------------------------------------------------------ -crypto::hash Blockchain::get_block_id_by_height(uint64_t height) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - try - { - return m_db->get_block_hash_from_height(height); - } - catch (const BLOCK_DNE& e) - { - } - catch (const std::exception& e) - { - MERROR(std::string("Something went wrong fetching block hash by height: ") + e.what()); - throw; - } - catch (...) - { - MERROR(std::string("Something went wrong fetching block hash by height")); - throw; - } - return null_hash; -} -//------------------------------------------------------------------ -bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // try to find block in main chain - try - { - blk = m_db->get_block(h); - if (orphan) - *orphan = false; - return true; - } - // try to find block in alternative chain - catch (const BLOCK_DNE& e) - { - blocks_ext_by_hash::const_iterator it_alt = m_alternative_chains.find(h); - if (m_alternative_chains.end() != it_alt) - { - blk = it_alt->second.bl; - if (orphan) - *orphan = true; - return true; - } - } - catch (const std::exception& e) - { - MERROR(std::string("Something went wrong fetching block by hash: ") + e.what()); - throw; - } - catch (...) - { - MERROR(std::string("Something went wrong fetching block hash by hash")); - throw; - } - - return false; -} -//------------------------------------------------------------------ -// This function aggregates the cumulative difficulties and timestamps of the -// last DIFFICULTY_BLOCKS_COUNT blocks and passes them to next_difficulty, -// returning the result of that call. Ignores the genesis block, and can use -// less blocks than desired if there aren't enough. -difficulty_type Blockchain::get_difficulty_for_next_block() -{ - if (m_fixed_difficulty) - { - return m_db->height() ? m_fixed_difficulty : 1; - } - - LOG_PRINT_L3("Blockchain::" << __func__); - - crypto::hash top_hash = get_tail_id(); - { - CRITICAL_REGION_LOCAL(m_difficulty_lock); - // we can call this without the blockchain lock, it might just give us - // something a bit out of date, but that's fine since anything which - // requires the blockchain lock will have acquired it in the first place, - // and it will be unlocked only when called from the getinfo RPC - if (top_hash == m_difficulty_for_next_block_top_hash) - return m_difficulty_for_next_block; - } - - CRITICAL_REGION_LOCAL(m_blockchain_lock); - std::vector timestamps; - std::vector difficulties; - auto height = m_db->height(); - // ND: Speedup - // 1. Keep a list of the last 735 (or less) blocks that is used to compute difficulty, - // then when the next block difficulty is queried, push the latest height data and - // pop the oldest one from the list. This only requires 1x read per height instead - // of doing 735 (DIFFICULTY_BLOCKS_COUNT). - if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1) && m_timestamps.size() >= DIFFICULTY_BLOCKS_COUNT) - { - uint64_t index = height - 1; - m_timestamps.push_back(m_db->get_block_timestamp(index)); - m_difficulties.push_back(m_db->get_block_cumulative_difficulty(index)); - - while (m_timestamps.size() > DIFFICULTY_BLOCKS_COUNT) - m_timestamps.erase(m_timestamps.begin()); - while (m_difficulties.size() > DIFFICULTY_BLOCKS_COUNT) - m_difficulties.erase(m_difficulties.begin()); - - m_timestamps_and_difficulties_height = height; - timestamps = m_timestamps; - difficulties = m_difficulties; - } - else - { - size_t offset = height - std::min < size_t > (height, static_cast(DIFFICULTY_BLOCKS_COUNT)); - if (offset == 0) - ++offset; - - timestamps.clear(); - difficulties.clear(); - if (height > offset) - { - timestamps.reserve(height - offset); - difficulties.reserve(height - offset); - } - for (; offset < height; offset++) - { - timestamps.push_back(m_db->get_block_timestamp(offset)); - difficulties.push_back(m_db->get_block_cumulative_difficulty(offset)); - } - - m_timestamps_and_difficulties_height = height; - m_timestamps = timestamps; - m_difficulties = difficulties; - } - size_t target = get_difficulty_target(); - difficulty_type diff = next_difficulty(timestamps, difficulties, target); - - CRITICAL_REGION_LOCAL1(m_difficulty_lock); - m_difficulty_for_next_block_top_hash = top_hash; - m_difficulty_for_next_block = diff; - return diff; -} -//------------------------------------------------------------------ -std::vector Blockchain::get_last_block_timestamps(unsigned int blocks) const -{ - uint64_t height = m_db->height(); - if (blocks > height) - blocks = height; - std::vector timestamps(blocks); - while (blocks--) - timestamps[blocks] = m_db->get_block_timestamp(height - blocks - 1); - return timestamps; -} -//------------------------------------------------------------------ -// This function removes blocks from the blockchain until it gets to the -// position where the blockchain switch started and then re-adds the blocks -// that had been removed. -bool Blockchain::rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // fail if rollback_height passed is too high - if (rollback_height > m_db->height()) - { - return true; - } - - m_timestamps_and_difficulties_height = 0; - - // remove blocks from blockchain until we get back to where we should be. - while (m_db->height() != rollback_height) - { - pop_block_from_blockchain(); - } - - // make sure the hard fork object updates its current version - m_hardfork->reorganize_from_chain_height(rollback_height); - - //return back original chain - for (auto& bl : original_chain) - { - block_verification_context bvc = boost::value_initialized(); - bool r = handle_block_to_main_chain(bl, bvc); - CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC! failed to add (again) block while chain switching during the rollback!"); - } - - m_hardfork->reorganize_from_chain_height(rollback_height); - - MINFO("Rollback to height " << rollback_height << " was successful."); - if (original_chain.size()) - { - MINFO("Restoration to previous blockchain successful as well."); - } - return true; -} -//------------------------------------------------------------------ -// This function attempts to switch to an alternate chain, returning -// boolean based on success therein. -bool Blockchain::switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - m_timestamps_and_difficulties_height = 0; - - // if empty alt chain passed (not sure how that could happen), return false - CHECK_AND_ASSERT_MES(alt_chain.size(), false, "switch_to_alternative_blockchain: empty chain passed"); - - // verify that main chain has front of alt chain's parent block - if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) - { - LOG_ERROR("Attempting to move to an alternate chain, but it doesn't appear to connect to the main chain!"); - return false; - } - - // pop blocks from the blockchain until the top block is the parent - // of the front block of the alt chain. - std::list disconnected_chain; - while (m_db->top_block_hash() != alt_chain.front()->second.bl.prev_id) - { - block b = pop_block_from_blockchain(); - disconnected_chain.push_front(b); - } - - auto split_height = m_db->height(); - - //connecting new alternative chain - for(auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++) - { - auto ch_ent = *alt_ch_iter; - block_verification_context bvc = boost::value_initialized(); - - // add block to main chain - bool r = handle_block_to_main_chain(ch_ent->second.bl, bvc); - - // if adding block to main chain failed, rollback to previous state and - // return false - if(!r || !bvc.m_added_to_main_chain) - { - MERROR("Failed to switch to alternative blockchain"); - - // rollback_blockchain_switching should be moved to two different - // functions: rollback and apply_chain, but for now we pretend it is - // just the latter (because the rollback was done above). - rollback_blockchain_switching(disconnected_chain, split_height); - - // FIXME: Why do we keep invalid blocks around? Possibly in case we hear - // about them again so we can immediately dismiss them, but needs some - // looking into. - add_block_as_invalid(ch_ent->second, get_block_hash(ch_ent->second.bl)); - MERROR("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl)); - m_alternative_chains.erase(*alt_ch_iter++); - - for(auto alt_ch_to_orph_iter = alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); ) - { - add_block_as_invalid((*alt_ch_to_orph_iter)->second, (*alt_ch_to_orph_iter)->first); - m_alternative_chains.erase(*alt_ch_to_orph_iter++); - } - return false; - } - } - - // if we're to keep the disconnected blocks, add them as alternates - const size_t discarded_blocks = disconnected_chain.size(); - if(!discard_disconnected_chain) - { - //pushing old chain as alternative chain - for (auto& old_ch_ent : disconnected_chain) - { - block_verification_context bvc = boost::value_initialized(); - bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc); - if(!r) - { - MERROR("Failed to push ex-main chain blocks to alternative chain "); - // previously this would fail the blockchain switching, but I don't - // think this is bad enough to warrant that. - } - } - } - - //removing alt_chain entries from alternative chains container - for (auto ch_ent: alt_chain) - { - m_alternative_chains.erase(ch_ent); - } - - m_hardfork->reorganize_from_chain_height(split_height); - - std::shared_ptr reorg_notify = m_reorg_notify; - if (reorg_notify) - reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), - "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); - - MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); - return true; -} -//------------------------------------------------------------------ -// This function calculates the difficulty target for the block being added to -// an alternate chain. -difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const -{ - if (m_fixed_difficulty) - { - return m_db->height() ? m_fixed_difficulty : 1; - } - - LOG_PRINT_L3("Blockchain::" << __func__); - std::vector timestamps; - std::vector cumulative_difficulties; - - // if the alt chain isn't long enough to calculate the difficulty target - // based on its blocks alone, need to get more blocks from the main chain - if(alt_chain.size()< DIFFICULTY_BLOCKS_COUNT) - { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // Figure out start and stop offsets for main chain blocks - size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; - size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); - main_chain_count = std::min(main_chain_count, main_chain_stop_offset); - size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; - - if(!main_chain_start_offset) - ++main_chain_start_offset; //skip genesis block - - // get difficulties and timestamps from relevant main chain blocks - for(; main_chain_start_offset < main_chain_stop_offset; ++main_chain_start_offset) - { - timestamps.push_back(m_db->get_block_timestamp(main_chain_start_offset)); - cumulative_difficulties.push_back(m_db->get_block_cumulative_difficulty(main_chain_start_offset)); - } - - // make sure we haven't accidentally grabbed too many blocks...maybe don't need this check? - CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= DIFFICULTY_BLOCKS_COUNT, false, "Internal error, alt_chain.size()[" << alt_chain.size() << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT); - - for (auto it : alt_chain) - { - timestamps.push_back(it->second.bl.timestamp); - cumulative_difficulties.push_back(it->second.cumulative_difficulty); - } - } - // if the alt chain is long enough for the difficulty calc, grab difficulties - // and timestamps from it alone - else - { - timestamps.resize(static_cast(DIFFICULTY_BLOCKS_COUNT)); - cumulative_difficulties.resize(static_cast(DIFFICULTY_BLOCKS_COUNT)); - size_t count = 0; - size_t max_i = timestamps.size()-1; - // get difficulties and timestamps from most recent blocks in alt chain - for(auto it: boost::adaptors::reverse(alt_chain)) - { - timestamps[max_i - count] = it->second.bl.timestamp; - cumulative_difficulties[max_i - count] = it->second.cumulative_difficulty; - count++; - if(count >= DIFFICULTY_BLOCKS_COUNT) - break; - } - } - - // FIXME: This will fail if fork activation heights are subject to voting - size_t target = get_ideal_hard_fork_version(bei.height) < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; - - // calculate the difficulty target for the block and return it - return next_difficulty(timestamps, cumulative_difficulties, target); -} -//------------------------------------------------------------------ -// This function does a sanity check on basic things that all miner -// transactions have in common, such as: -// one input, of type txin_gen, with height set to the block's height -// correct miner tx unlock time -// a non-overflowing tx amount (dubious necessity on this check) -bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); - CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type"); - if(boost::get(b.miner_tx.vin[0]).height != height) - { - MWARNING("The miner transaction in block has invalid height: " << boost::get(b.miner_tx.vin[0]).height << ", expected: " << height); - return false; - } - MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); - CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); - - //check outs overflow - //NOTE: not entirely sure this is necessary, given that this function is - // designed simply to make sure the total amount for a transaction - // does not overflow a uint64_t, and this transaction *is* a uint64_t... - if(!check_outs_overflow(b.miner_tx)) - { - MERROR("miner transaction has money overflow in block " << get_block_hash(b)); - return false; - } - - return true; -} -//------------------------------------------------------------------ -// This function validates the miner transaction reward -bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - //validate reward - uint64_t money_in_use = 0; - for (auto& o: b.miner_tx.vout) - money_in_use += o.amount; - partial_block_reward = false; - - if (version == 3) { - for (auto &o: b.miner_tx.vout) { - if (!is_valid_decomposed_amount(o.amount)) { - MERROR_VER("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount"); - return false; - } - } - } - - std::vector last_blocks_weights; - get_last_n_blocks_weights(last_blocks_weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (!get_block_reward(epee::misc_utils::median(last_blocks_weights), cumulative_block_weight, already_generated_coins, base_reward, version)) - { - MERROR_VER("block weight " << cumulative_block_weight << " is bigger than allowed for this blockchain"); - return false; - } - if(base_reward + fee < money_in_use) - { - MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); - return false; - } - // From hard fork 2, we allow a miner to claim less block reward than is allowed, in case a miner wants less dust - if (m_hardfork->get_current_version() < 2) - { - if(base_reward + fee != money_in_use) - { - MDEBUG("coinbase transaction doesn't use full amount of block reward: spent: " << money_in_use << ", block reward " << base_reward + fee << "(" << base_reward << "+" << fee << ")"); - return false; - } - } - else - { - // from hard fork 2, since a miner can claim less than the full block reward, we update the base_reward - // to show the amount of coins that were actually generated, the remainder will be pushed back for later - // emission. This modifies the emission curve very slightly. - CHECK_AND_ASSERT_MES(money_in_use - fee <= base_reward, false, "base reward calculation bug"); - if(base_reward + fee != money_in_use) - partial_block_reward = true; - base_reward = money_in_use - fee; - } - return true; -} -//------------------------------------------------------------------ -// get the block weights of the last blocks, and return by reference . -void Blockchain::get_last_n_blocks_weights(std::vector& weights, size_t count) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto h = m_db->height(); - - // this function is meaningless for an empty blockchain...granted it should never be empty - if(h == 0) - return; - - m_db->block_txn_start(true); - // add weight of last blocks to vector (or less, if blockchain size < count) - size_t start_offset = h - std::min(h, count); - weights.reserve(weights.size() + h - start_offset); - for(size_t i = start_offset; i < h; i++) - { - weights.push_back(m_db->get_block_weight(i)); - } - m_db->block_txn_stop(); -} -//------------------------------------------------------------------ -uint64_t Blockchain::get_current_cumulative_block_weight_limit() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - return m_current_block_cumul_weight_limit; -} -//------------------------------------------------------------------ -uint64_t Blockchain::get_current_cumulative_block_weight_median() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - return m_current_block_cumul_weight_median; -} -//------------------------------------------------------------------ -//TODO: This function only needed minor modification to work with BlockchainDB, -// and *works*. As such, to reduce the number of things that might break -// in moving to BlockchainDB, this function will remain otherwise -// unchanged for the time being. -// -// This function makes a new block for a miner to mine the hash for -// -// FIXME: this codebase references #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) -// in a lot of places. That flag is not referenced in any of the code -// nor any of the makefiles, howeve. Need to look into whether or not it's -// necessary at all. -bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - size_t median_weight; - uint64_t already_generated_coins; - uint64_t pool_cookie; - - m_tx_pool.lock(); - const auto txpool_unlocker = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); }); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - height = m_db->height(); - if (m_btc_valid) { - // The pool cookie is atomic. The lack of locking is OK, as if it changes - // just as we compare it, we'll just use a slightly old template, but - // this would be the case anyway if we'd lock, and the change happened - // just after the block template was created - if (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address)) && m_btc_nonce == ex_nonce && m_btc_pool_cookie == m_tx_pool.cookie()) { - MDEBUG("Using cached template"); - m_btc.timestamp = time(NULL); // update timestamp unconditionally - b = m_btc; - diffic = m_btc_difficulty; - expected_reward = m_btc_expected_reward; - return true; - } - MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie())); - invalidate_block_template_cache(); - } - - b.major_version = m_hardfork->get_current_version(); - b.minor_version = m_hardfork->get_ideal_version(); - b.prev_id = get_tail_id(); - b.timestamp = time(NULL); - - uint64_t median_ts; - if (!check_block_timestamp(b, median_ts)) - { - b.timestamp = median_ts; - } - - diffic = get_difficulty_for_next_block(); - CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); - - median_weight = m_current_block_cumul_weight_limit / 2; - already_generated_coins = m_db->get_block_already_generated_coins(height - 1); - - size_t txs_weight; - uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, m_hardfork->get_current_version())) - { - return false; - } - pool_cookie = m_tx_pool.cookie(); -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - size_t real_txs_weight = 0; - uint64_t real_fee = 0; - for(crypto::hash &cur_hash: b.tx_hashes) - { - auto cur_res = m_tx_pool.m_transactions.find(cur_hash); - if (cur_res == m_tx_pool.m_transactions.end()) - { - LOG_ERROR("Creating block template: error: transaction not found"); - continue; - } - tx_memory_pool::tx_details &cur_tx = cur_res->second; - real_txs_weight += cur_tx.weight; - real_fee += cur_tx.fee; - if (cur_tx.weight != get_transaction_weight(cur_tx.tx)) - { - LOG_ERROR("Creating block template: error: invalid transaction weight"); - } - if (cur_tx.tx.version == 1) - { - uint64_t inputs_amount; - if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) - { - LOG_ERROR("Creating block template: error: cannot get inputs amount"); - } - else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) - { - LOG_ERROR("Creating block template: error: invalid fee"); - } - } - else - { - if (cur_tx.fee != cur_tx.tx.rct_signatures.txnFee) - { - LOG_ERROR("Creating block template: error: invalid fee"); - } - } - } - if (txs_weight != real_txs_weight) - { - LOG_ERROR("Creating block template: error: wrongly calculated transaction weight"); - } - if (fee != real_fee) - { - LOG_ERROR("Creating block template: error: wrongly calculated fee"); - } - - MDEBUG("Creating block template: height " << height << - ", median weight " << median_weight << - ", already generated coins " << already_generated_coins << - ", transaction weight " << txs_weight << - ", fee " << fee); -#endif - - /* - two-phase miner transaction generation: we don't know exact block weight until we prepare block, but we don't know reward until we know - block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight - */ - //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight - uint8_t hf_version = m_hardfork->get_current_version(); - size_t max_outs = hf_version >= 4 ? 1 : 11; - bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); - CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); - size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx); -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - MDEBUG("Creating block template: miner tx weight " << get_transaction_weight(b.miner_tx) << - ", cumulative weight " << cumulative_weight); -#endif - for (size_t try_count = 0; try_count != 10; ++try_count) - { - r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); - - CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance"); - size_t coinbase_weight = get_transaction_weight(b.miner_tx); - if (coinbase_weight > cumulative_weight - txs_weight) - { - cumulative_weight = txs_weight + coinbase_weight; -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - MDEBUG("Creating block template: miner tx weight " << coinbase_weight << - ", cumulative weight " << cumulative_weight << " is greater than before"); -#endif - continue; - } - - if (coinbase_weight < cumulative_weight - txs_weight) - { - size_t delta = cumulative_weight - txs_weight - coinbase_weight; -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - MDEBUG("Creating block template: miner tx weight " << coinbase_weight << - ", cumulative weight " << txs_weight + coinbase_weight << - " is less than before, adding " << delta << " zero bytes"); -#endif - b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); - //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. - if (cumulative_weight != txs_weight + get_transaction_weight(b.miner_tx)) - { - CHECK_AND_ASSERT_MES(cumulative_weight + 1 == txs_weight + get_transaction_weight(b.miner_tx), false, "unexpected case: cumulative_weight=" << cumulative_weight << " + 1 is not equal txs_cumulative_weight=" << txs_weight << " + get_transaction_weight(b.miner_tx)=" << get_transaction_weight(b.miner_tx)); - b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1); - if (cumulative_weight != txs_weight + get_transaction_weight(b.miner_tx)) - { - //fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_weight - MDEBUG("Miner tx creation has no luck with delta_extra size = " << delta << " and " << delta - 1); - cumulative_weight += delta - 1; - continue; - } - MDEBUG("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count); - } - } - CHECK_AND_ASSERT_MES(cumulative_weight == txs_weight + get_transaction_weight(b.miner_tx), false, "unexpected case: cumulative_weight=" << cumulative_weight << " is not equal txs_cumulative_weight=" << txs_weight << " + get_transaction_weight(b.miner_tx)=" << get_transaction_weight(b.miner_tx)); -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - MDEBUG("Creating block template: miner tx weight " << coinbase_weight << - ", cumulative weight " << cumulative_weight << " is now good"); -#endif - - cache_block_template(b, miner_address, ex_nonce, diffic, expected_reward, pool_cookie); - return true; - } - LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); - return false; -} -//------------------------------------------------------------------ -// for an alternate chain, get the timestamps from the main chain to complete -// the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. -bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector& timestamps) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - - if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) - return true; - - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); - CHECK_AND_ASSERT_MES(start_top_height < m_db->height(), false, "internal error: passed start_height not < " << " m_db->height() -- " << start_top_height << " >= " << m_db->height()); - size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0; - timestamps.reserve(timestamps.size() + start_top_height - stop_offset); - while (start_top_height != stop_offset) - { - timestamps.push_back(m_db->get_block_timestamp(start_top_height)); - --start_top_height; - } - return true; -} -//------------------------------------------------------------------ -// If a block is to be added and its parent block is not the current -// main chain top block, then we need to see if we know about its parent block. -// If its parent block is part of a known forked chain, then we need to see -// if that chain is long enough to become the main chain and re-org accordingly -// if so. If not, we need to hang on to the block in case it becomes part of -// a long forked chain eventually. -bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_timestamps_and_difficulties_height = 0; - uint64_t block_height = get_block_height(b); - if(0 == block_height) - { - MERROR_VER("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative), but miner tx says height is 0."); - bvc.m_verifivation_failed = true; - return false; - } - // 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. - if (!m_checkpoints.is_alternative_block_allowed(get_current_blockchain_height(), block_height)) - { - MERROR_VER("Block with id: " << id << std::endl << " can't be accepted for alternative chain, block height: " << block_height << std::endl << " blockchain height: " << get_current_blockchain_height()); - bvc.m_verifivation_failed = true; - return false; - } - - // this is a cheap test - if (!m_hardfork->check_for_height(b, block_height)) - { - LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version for height " << block_height); - bvc.m_verifivation_failed = true; - return false; - } - - //block is not related with head of main chain - //first of all - look in alternative chains container - auto it_prev = m_alternative_chains.find(b.prev_id); - bool parent_in_main = m_db->block_exists(b.prev_id); - if(it_prev != m_alternative_chains.end() || parent_in_main) - { - //we have new block in alternative chain - - //build alternative subchain, front -> mainchain, back -> alternative head - blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() - std::list alt_chain; - std::vector timestamps; - while(alt_it != m_alternative_chains.end()) - { - alt_chain.push_front(alt_it); - timestamps.push_back(alt_it->second.bl.timestamp); - alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); - } - - // if block to be added connects to known blocks that aren't part of the - // main chain -- that is, if we're adding on to an alternate chain - if(alt_chain.size()) - { - // make sure alt chain doesn't somehow start past the end of the main chain - CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height"); - - // make sure that the blockchain contains the block that should connect - // this alternate chain with it. - if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) - { - MERROR("alternate chain does not appear to connect to main chain..."); - return false; - } - - // make sure block connects correctly to the main chain - auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); - CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); - complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); - } - // if block not associated with known alternate chain - else - { - // if block parent is not part of main chain or an alternate chain, - // we ignore it - CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main"); - - complete_timestamps_vector(m_db->get_block_height(b.prev_id), timestamps); - } - - // verify that the block's timestamp is within the acceptable range - // (not earlier than the median of the last X blocks) - if(!check_block_timestamp(timestamps, b)) - { - MERROR_VER("Block with id: " << id << std::endl << " for alternative chain, has invalid timestamp: " << b.timestamp); - bvc.m_verifivation_failed = true; - return false; - } - - // FIXME: consider moving away from block_extended_info at some point - block_extended_info bei = boost::value_initialized(); - bei.bl = b; - bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(b.prev_id) + 1; - - bool is_a_checkpoint; - if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) - { - LOG_ERROR("CHECKPOINT VALIDATION FAILED"); - bvc.m_verifivation_failed = true; - return false; - } - - // Check the block's hash against the difficulty target for its alt chain - difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); - CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); - crypto::hash proof_of_work = null_hash; - get_block_longhash(bei.bl, proof_of_work, bei.height); - if(!check_hash(proof_of_work, current_diff)) - { - MERROR_VER("Block with id: " << id << std::endl << " for alternative chain, does not have enough proof of work: " << proof_of_work << std::endl << " expected difficulty: " << current_diff); - bvc.m_verifivation_failed = true; - return false; - } - - if(!prevalidate_miner_transaction(b, bei.height)) - { - MERROR_VER("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative) has incorrect miner transaction."); - bvc.m_verifivation_failed = true; - return false; - } - - // FIXME: - // this brings up an interesting point: consider allowing to get block - // difficulty both by height OR by hash, not just height. - difficulty_type main_chain_cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); - if (alt_chain.size()) - { - bei.cumulative_difficulty = it_prev->second.cumulative_difficulty; - } - else - { - // passed-in block's previous block's cumulative difficulty, found on the main chain - bei.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->get_block_height(b.prev_id)); - } - bei.cumulative_difficulty += current_diff; - - // add block to alternate blocks storage, - // as well as the current "alt chain" container - auto i_res = m_alternative_chains.insert(blocks_ext_by_hash::value_type(id, bei)); - CHECK_AND_ASSERT_MES(i_res.second, false, "insertion of new alternative block returned as it already exist"); - alt_chain.push_back(i_res.first); - - // FIXME: is it even possible for a checkpoint to show up not on the main chain? - if(is_a_checkpoint) - { - //do reorganize! - MGINFO_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << ", checkpoint is found in alternative chain on height " << bei.height); - - bool r = switch_to_alternative_blockchain(alt_chain, true); - - if(r) bvc.m_added_to_main_chain = true; - else bvc.m_verifivation_failed = true; - - return r; - } - else if(main_chain_cumulative_difficulty < bei.cumulative_difficulty) //check if difficulty bigger then in main chain - { - //do reorganize! - MGINFO_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << " with cum_difficulty " << m_db->get_block_cumulative_difficulty(m_db->height() - 1) << std::endl << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty); - - bool r = switch_to_alternative_blockchain(alt_chain, false); - if (r) - bvc.m_added_to_main_chain = true; - else - bvc.m_verifivation_failed = true; - return r; - } - else - { - MGINFO_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "difficulty:\t" << current_diff); - return true; - } - } - else - { - //block orphaned - bvc.m_marked_as_orphaned = true; - MERROR_VER("Block recognized as orphaned and rejected, id = " << id << ", height " << block_height - << ", parent in alt " << (it_prev != m_alternative_chains.end()) << ", parent in main " << parent_in_main - << " (parent " << b.prev_id << ", current top " << get_tail_id() << ", chain height " << get_current_blockchain_height() << ")"); - } - - return true; -} -//------------------------------------------------------------------ -bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_offset >= m_db->height()) - return false; - - if (!get_blocks(start_offset, count, blocks)) - { - return false; - } - - for(const auto& blk : blocks) - { - std::vector missed_ids; - get_transactions_blobs(blk.second.tx_hashes, txs, missed_ids); - CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain"); - } - - return true; -} -//------------------------------------------------------------------ -bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - const uint64_t height = m_db->height(); - if(start_offset >= height) - return false; - - blocks.reserve(blocks.size() + height - start_offset); - for(size_t i = start_offset; i < start_offset + count && i < height;i++) - { - blocks.push_back(std::make_pair(m_db->get_block_blob_from_height(i), block())); - if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) - { - LOG_ERROR("Invalid block"); - return false; - } - } - return true; -} -//------------------------------------------------------------------ -//TODO: This function *looks* like it won't need to be rewritten -// to use BlockchainDB, as it calls other functions that were, -// but it warrants some looking into later. -// -//FIXME: This function appears to want to return false if any transactions -// that belong with blocks are missing, but not if blocks themselves -// are missing. -bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_db->block_txn_start(true); - rsp.current_blockchain_height = get_current_blockchain_height(); - std::vector> blocks; - get_blocks(arg.blocks, blocks, rsp.missed_ids); - - for (auto& bl: blocks) - { - std::vector missed_tx_ids; - std::vector txs; - - rsp.blocks.push_back(block_complete_entry()); - block_complete_entry& e = rsp.blocks.back(); - - // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids - // is for missed blocks, not missed transactions as well. - get_transactions_blobs(bl.second.tx_hashes, e.txs, missed_tx_ids); - - if (missed_tx_ids.size() != 0) - { - LOG_ERROR("Error retrieving blocks, missed " << missed_tx_ids.size() - << " transactions for block with hash: " << get_block_hash(bl.second) - << std::endl - ); - - // append missed transaction hashes to response missed_ids field, - // as done below if any standalone transactions were requested - // and missed. - rsp.missed_ids.insert(rsp.missed_ids.end(), missed_tx_ids.begin(), missed_tx_ids.end()); - m_db->block_txn_stop(); - return false; - } - - //pack block - e.block = std::move(bl.first); - } - //get and pack other transactions, if needed - std::vector txs; - get_transactions_blobs(arg.txs, rsp.txs, rsp.missed_ids); - - m_db->block_txn_stop(); - return true; -} -//------------------------------------------------------------------ -bool Blockchain::get_alternative_blocks(std::vector& blocks) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - blocks.reserve(m_alternative_chains.size()); - for (const auto& alt_bl: m_alternative_chains) - { - blocks.push_back(alt_bl.second.bl); - } - return true; -} -//------------------------------------------------------------------ -size_t Blockchain::get_alternative_blocks_count() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_alternative_chains.size(); -} -//------------------------------------------------------------------ -// This function adds the output specified by to the result_outs container -// unlocked and other such checks should be done by here. -uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const -{ - uint64_t num_outs = m_db->get_num_outputs(amount); - // ensure we don't include outputs that aren't yet eligible to be used - // outpouts are sorted by height - while (num_outs > 0) - { - const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); - const uint64_t height = m_db->get_tx_block_height(toi.first); - if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) - break; - --num_outs; - } - - return num_outs; -} - -crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_index) const -{ - output_data_t data = m_db->get_output_key(amount, global_index); - return data.pubkey; -} - -//------------------------------------------------------------------ -bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - res.outs.clear(); - res.outs.reserve(req.outputs.size()); - try - { - for (const auto &i: req.outputs) - { - // get tx_hash, tx_out_index from DB - const output_data_t od = m_db->get_output_key(i.amount, i.index); - tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index); - bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); - - res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first}); - } - } - catch (const std::exception &e) - { - return false; - } - return true; -} -//------------------------------------------------------------------ -void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const -{ - const auto o_data = m_db->get_output_key(amount, index); - key = o_data.pubkey; - mask = o_data.commitment; - tx_out_index toi = m_db->get_output_tx_and_index(amount, index); - unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); -} -//------------------------------------------------------------------ -bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector &distribution, uint64_t &base) const -{ - // rct outputs don't exist before v4 - if (amount == 0) - { - switch (m_nettype) - { - case STAGENET: start_height = stagenet_hard_forks[3].height; break; - case TESTNET: start_height = testnet_hard_forks[3].height; break; - case MAINNET: start_height = mainnet_hard_forks[3].height; break; - default: return false; - } - } - else - start_height = 0; - base = 0; - - if (to_height > 0 && to_height < from_height) - return false; - - const uint64_t real_start_height = start_height; - if (from_height > start_height) - start_height = from_height; - - distribution.clear(); - uint64_t db_height = m_db->height(); - if (db_height == 0) - return false; - if (to_height == 0) - to_height = db_height - 1; - if (start_height >= db_height || to_height >= db_height) - return false; - if (amount == 0) - { - std::vector heights; - heights.reserve(to_height + 1 - start_height); - for (uint64_t h = start_height; h <= to_height; ++h) - heights.push_back(h); - distribution = m_db->get_block_cumulative_rct_outputs(heights); - base = 0; - return true; - } - else - { - return m_db->get_output_distribution(amount, start_height, to_height, distribution, base); - } -} -//------------------------------------------------------------------ -// This function takes a list of block hashes from another node -// on the network to find where the split point is between us and them. -// This is used to see what to send another node that needs to sync. -bool Blockchain::find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // make sure the request includes at least the genesis block, otherwise - // how can we expect to sync from the client that the block list came from? - if(!qblock_ids.size()) - { - MCERROR("net.p2p", "Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << ", dropping connection"); - return false; - } - - m_db->block_txn_start(true); - // make sure that the last block in the request's block list matches - // the genesis block - auto gen_hash = m_db->get_block_hash_from_height(0); - if(qblock_ids.back() != gen_hash) - { - MCERROR("net.p2p", "Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block mismatch: " << std::endl << "id: " << qblock_ids.back() << ", " << std::endl << "expected: " << gen_hash << "," << std::endl << " dropping connection"); - m_db->block_txn_abort(); - return false; - } - - // Find the first block the foreign chain has that we also have. - // Assume qblock_ids is in reverse-chronological order. - auto bl_it = qblock_ids.begin(); - uint64_t split_height = 0; - for(; bl_it != qblock_ids.end(); bl_it++) - { - try - { - if (m_db->block_exists(*bl_it, &split_height)) - break; - } - catch (const std::exception& e) - { - MWARNING("Non-critical error trying to find block by hash in BlockchainDB, hash: " << *bl_it); - m_db->block_txn_abort(); - return false; - } - } - m_db->block_txn_stop(); - - // this should be impossible, as we checked that we share the genesis block, - // but just in case... - if(bl_it == qblock_ids.end()) - { - MERROR("Internal error handling connection, can't find split point"); - return false; - } - - //we start to put block ids INCLUDING last known id, just to make other side be sure - starter_offset = split_height; - return true; -} -//------------------------------------------------------------------ -uint64_t Blockchain::block_difficulty(uint64_t i) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - try - { - return m_db->get_block_difficulty(i); - } - catch (const BLOCK_DNE& e) - { - MERROR("Attempted to get block difficulty for height above blockchain height"); - } - return 0; -} -//------------------------------------------------------------------ -template void reserve_container(std::vector &v, size_t N) { v.reserve(N); } -template void reserve_container(std::list &v, size_t N) { } -//------------------------------------------------------------------ -//TODO: return type should be void, throw on exception -// alternatively, return true only if no blocks missed -template -bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - reserve_container(blocks, block_ids.size()); - for (const auto& block_hash : block_ids) - { - try - { - uint64_t height = 0; - if (m_db->block_exists(block_hash, &height)) - { - blocks.push_back(std::make_pair(m_db->get_block_blob_from_height(height), block())); - if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second)) - { - LOG_ERROR("Invalid block: " << block_hash); - blocks.pop_back(); - missed_bs.push_back(block_hash); - } - } - else - missed_bs.push_back(block_hash); - } - catch (const std::exception& e) - { - return false; - } - } - return true; -} -//------------------------------------------------------------------ -//TODO: return type should be void, throw on exception -// alternatively, return true only if no transactions missed -template -bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - reserve_container(txs, txs_ids.size()); - for (const auto& tx_hash : txs_ids) - { - try - { - cryptonote::blobdata tx; - if (pruned && m_db->get_pruned_tx_blob(tx_hash, tx)) - txs.push_back(std::move(tx)); - else if (!pruned && m_db->get_tx_blob(tx_hash, tx)) - txs.push_back(std::move(tx)); - else - missed_txs.push_back(tx_hash); - } - catch (const std::exception& e) - { - return false; - } - } - return true; -} -//------------------------------------------------------------------ -template -bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - reserve_container(txs, txs_ids.size()); - for (const auto& tx_hash : txs_ids) - { - try - { - cryptonote::blobdata tx; - if (m_db->get_tx_blob(tx_hash, tx)) - { - txs.push_back(transaction()); - if (!parse_and_validate_tx_from_blob(tx, txs.back())) - { - LOG_ERROR("Invalid transaction"); - return false; - } - } - else - missed_txs.push_back(tx_hash); - } - catch (const std::exception& e) - { - return false; - } - } - return true; -} -//------------------------------------------------------------------ -// Find the split point between us and foreign blockchain and return -// (by reference) the most recent common block hash along with up to -// BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. -bool Blockchain::find_blockchain_supplement(const std::list& qblock_ids, std::vector& hashes, uint64_t& start_height, uint64_t& current_height) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // if we can't find the split point, return false - if(!find_blockchain_supplement(qblock_ids, start_height)) - { - return false; - } - - m_db->block_txn_start(true); - current_height = get_current_blockchain_height(); - size_t count = 0; - hashes.reserve(std::max((size_t)(current_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); - for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) - { - hashes.push_back(m_db->get_block_hash_from_height(i)); - } - - m_db->block_txn_stop(); - return true; -} - -bool Blockchain::find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height); - if (result) - resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1); - - return result; -} -//------------------------------------------------------------------ -//FIXME: change argument to std::vector, low priority -// find split point between ours and foreign blockchain (or start at -// blockchain height ), and return up to max_count FULL -// blocks by reference. -bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_count) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // if a specific start height has been requested - if(req_start_block > 0) - { - // if requested height is higher than our chain, return false -- we can't help - if (req_start_block >= m_db->height()) - { - return false; - } - start_height = req_start_block; - } - else - { - if(!find_blockchain_supplement(qblock_ids, start_height)) - { - return false; - } - } - - m_db->block_txn_start(true); - total_height = get_current_blockchain_height(); - size_t count = 0, size = 0; - blocks.reserve(std::min(std::min(max_count, (size_t)10000), (size_t)(total_height - start_height))); - for(uint64_t i = start_height; i < total_height && count < max_count && (size < FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE || count < 3); i++, count++) - { - blocks.resize(blocks.size()+1); - blocks.back().first.first = m_db->get_block_blob_from_height(i); - block b; - CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first.first, b), false, "internal error, invalid block"); - blocks.back().first.second = get_miner_tx_hash ? cryptonote::get_transaction_hash(b.miner_tx) : crypto::null_hash; - std::vector mis; - std::vector txs; - get_transactions_blobs(b.tx_hashes, txs, mis, pruned); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); - size += blocks.back().first.first.size(); - for (const auto &t: txs) - size += t.size(); - - CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size(), false, "mismatched sizes of b.tx_hashes and txs"); - blocks.back().second.reserve(txs.size()); - for (size_t i = 0; i < txs.size(); ++i) - { - blocks.back().second.push_back(std::make_pair(b.tx_hashes[i], std::move(txs[i]))); - } - } - m_db->block_txn_stop(); - return true; -} -//------------------------------------------------------------------ -bool Blockchain::add_block_as_invalid(const block& bl, const crypto::hash& h) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - block_extended_info bei = AUTO_VAL_INIT(bei); - bei.bl = bl; - return add_block_as_invalid(bei, h); -} -//------------------------------------------------------------------ -bool Blockchain::add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto i_res = m_invalid_blocks.insert(std::map::value_type(h, bei)); - CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed"); - MINFO("BLOCK ADDED AS INVALID: " << h << std::endl << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size()); - return true; -} -//------------------------------------------------------------------ -bool Blockchain::have_block(const crypto::hash& id) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - if(m_db->block_exists(id)) - { - LOG_PRINT_L2("block " << id << " found in main chain"); - return true; - } - - if(m_alternative_chains.count(id)) - { - LOG_PRINT_L2("block " << id << " found in m_alternative_chains"); - return true; - } - - if(m_invalid_blocks.count(id)) - { - LOG_PRINT_L2("block " << id << " found in m_invalid_blocks"); - return true; - } - - return false; -} -//------------------------------------------------------------------ -bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - crypto::hash id = get_block_hash(bl); - return handle_block_to_main_chain(bl, id, bvc); -} -//------------------------------------------------------------------ -size_t Blockchain::get_total_transactions() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - // WARNING: this function does not take m_blockchain_lock, and thus should only call read only - // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as - // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must - // lock if it is otherwise needed. - return m_db->get_tx_count(); -} -//------------------------------------------------------------------ -// This function checks each input in the transaction to make sure it -// has not been used already, and adds its key to the container . -// -// This container should be managed by the code that validates blocks so we don't -// have to store the used keys in a given block in the permanent storage only to -// remove them later if the block fails validation. -bool Blockchain::check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - struct add_transaction_input_visitor: public boost::static_visitor - { - key_images_container& m_spent_keys; - BlockchainDB* m_db; - add_transaction_input_visitor(key_images_container& spent_keys, BlockchainDB* db) : - m_spent_keys(spent_keys), m_db(db) - { - } - bool operator()(const txin_to_key& in) const - { - const crypto::key_image& ki = in.k_image; - - // attempt to insert the newly-spent key into the container of - // keys spent this block. If this fails, the key was spent already - // in this block, return false to flag that a double spend was detected. - // - // if the insert into the block-wide spent keys container succeeds, - // check the blockchain-wide spent keys container and make sure the - // key wasn't used in another block already. - auto r = m_spent_keys.insert(ki); - if(!r.second || m_db->has_key_image(ki)) - { - //double spend detected - return false; - } - - // if no double-spend detected, return true - return true; - } - - bool operator()(const txin_gen& tx) const - { - return true; - } - bool operator()(const txin_to_script& tx) const - { - return false; - } - bool operator()(const txin_to_scripthash& tx) const - { - return false; - } - }; - - for (const txin_v& in : tx.vin) - { - if(!boost::apply_visitor(add_transaction_input_visitor(keys_this_block, m_db), in)) - { - LOG_ERROR("Double spend detected!"); - return false; - } - } - - return true; -} -//------------------------------------------------------------------ -bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - uint64_t tx_index; - if (!m_db->tx_exists(tx_id, tx_index)) - { - MERROR_VER("get_tx_outputs_gindexs failed to find transaction with id = " << tx_id); - return false; - } - - // get amount output indexes, currently referred to in parts as "output global indices", but they are actually specific to amounts - indexs = m_db->get_tx_amount_output_indices(tx_index); - if (indexs.empty()) - { - // empty indexs is only valid if the vout is empty, which is legal but rare - cryptonote::transaction tx = m_db->get_tx(tx_id); - CHECK_AND_ASSERT_MES(tx.vout.empty(), false, "internal error: global indexes for transaction " << tx_id << " is empty, and tx vout is not"); - } - - return true; -} -//------------------------------------------------------------------ -void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx) -{ -#if defined(PER_BLOCK_CHECKPOINT) - // check if we're doing per-block checkpointing - if (m_db->height() < m_blocks_hash_check.size()) - { - TIME_MEASURE_START(a); - m_blocks_txs_check.push_back(get_transaction_hash(tx)); - TIME_MEASURE_FINISH(a); - if(m_show_time_stats) - { - size_t ring_size = !tx.vin.empty() && tx.vin[0].type() == typeid(txin_to_key) ? boost::get(tx.vin[0]).key_offsets.size() : 0; - MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); - } - } -#endif -} -//------------------------------------------------------------------ -//FIXME: it seems this function is meant to be merely a wrapper around -// another function of the same name, this one adding one bit of -// functionality. Should probably move anything more than that -// (getting the hash of the block at height max_used_block_id) -// to the other function to keep everything in one place. -// This function overloads its sister function with -// an extra value (hash of highest block that holds an output used as input) -// as a return-by-reference. -bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - -#if defined(PER_BLOCK_CHECKPOINT) - // check if we're doing per-block checkpointing - if (m_db->height() < m_blocks_hash_check.size() && kept_by_block) - { - max_used_block_id = null_hash; - max_used_block_height = 0; - return true; - } -#endif - - TIME_MEASURE_START(a); - bool res = check_tx_inputs(tx, tvc, &max_used_block_height); - TIME_MEASURE_FINISH(a); - if(m_show_time_stats) - { - size_t ring_size = !tx.vin.empty() && tx.vin[0].type() == typeid(txin_to_key) ? boost::get(tx.vin[0]).key_offsets.size() : 0; - MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx) << " W: " << get_transaction_weight(tx)); - } - if (!res) - return false; - - CHECK_AND_ASSERT_MES(max_used_block_height < m_db->height(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_db->height()); - max_used_block_id = m_db->get_block_hash_from_height(max_used_block_height); - return true; -} -//------------------------------------------------------------------ -bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - const uint8_t hf_version = m_hardfork->get_current_version(); - - // from hard fork 2, we forbid dust and compound outputs - if (hf_version >= 2) { - for (auto &o: tx.vout) { - if (tx.version == 1) - { - if (!is_valid_decomposed_amount(o.amount)) { - tvc.m_invalid_output = true; - return false; - } - } - } - } - - // in a v2 tx, all outputs must have 0 amount - if (hf_version >= 3) { - if (tx.version >= 2) { - for (auto &o: tx.vout) { - if (o.amount != 0) { - tvc.m_invalid_output = true; - return false; - } - } - } - } - - // from v4, forbid invalid pubkeys - if (hf_version >= 4) { - for (const auto &o: tx.vout) { - if (o.target.type() == typeid(txout_to_key)) { - const txout_to_key& out_to_key = boost::get(o.target); - if (!crypto::check_key(out_to_key.key)) { - tvc.m_invalid_output = true; - return false; - } - } - } - } - - // from v8, allow bulletproofs - if (hf_version < 8) { - if (tx.version >= 2) { - const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); - if (bulletproof || !tx.rct_signatures.p.bulletproofs.empty()) - { - MERROR_VER("Bulletproofs are not allowed before v8"); - tvc.m_invalid_output = true; - return false; - } - } - } - - // from v9, forbid borromean range proofs - if (hf_version > 8) { - if (tx.version >= 2) { - const bool borromean = rct::is_rct_borromean(tx.rct_signatures.type); - if (borromean) - { - MERROR_VER("Borromean range proofs are not allowed after v8"); - tvc.m_invalid_output = true; - return false; - } - } - } - - // from v10, allow bulletproofs v2 - if (hf_version < HF_VERSION_SMALLER_BP) { - if (tx.version >= 2) { - if (tx.rct_signatures.type == rct::RCTTypeBulletproof2) - { - MERROR_VER("Ringct type " << (unsigned)rct::RCTTypeBulletproof2 << " is not allowed before v" << HF_VERSION_SMALLER_BP); - tvc.m_invalid_output = true; - return false; - } - } - } - - // from v11, allow only bulletproofs v2 - if (hf_version > HF_VERSION_SMALLER_BP) { - if (tx.version >= 2) { - if (tx.rct_signatures.type == rct::RCTTypeBulletproof) - { - MERROR_VER("Ringct type " << (unsigned)rct::RCTTypeBulletproof << " is not allowed from v" << (HF_VERSION_SMALLER_BP + 1)); - tvc.m_invalid_output = true; - return false; - } - } - } - - return true; -} -//------------------------------------------------------------------ -bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - for (const txin_v& in: tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, in_to_key, true); - if(have_tx_keyimg_as_spent(in_to_key.k_image)) - return true; - } - return false; -} -bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys) -{ - PERF_TIMER(expand_transaction_2); - CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); - - rct::rctSig &rv = tx.rct_signatures; - - // message - hash of the transaction prefix - rv.message = rct::hash2rct(tx_prefix_hash); - - // mixRing - full and simple store it in opposite ways - if (rv.type == rct::RCTTypeFull) - { - CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); - rv.mixRing.resize(pubkeys[0].size()); - for (size_t m = 0; m < pubkeys[0].size(); ++m) - rv.mixRing[m].clear(); - for (size_t n = 0; n < pubkeys.size(); ++n) - { - CHECK_AND_ASSERT_MES(pubkeys[n].size() <= pubkeys[0].size(), false, "More inputs that first ring"); - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - rv.mixRing[m].push_back(pubkeys[n][m]); - } - } - } - else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2) - { - CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); - rv.mixRing.resize(pubkeys.size()); - for (size_t n = 0; n < pubkeys.size(); ++n) - { - rv.mixRing[n].clear(); - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - rv.mixRing[n].push_back(pubkeys[n][m]); - } - } - } - else - { - CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast(rv.type)); - } - - // II - if (rv.type == rct::RCTTypeFull) - { - rv.p.MGs.resize(1); - rv.p.MGs[0].II.resize(tx.vin.size()); - for (size_t n = 0; n < tx.vin.size(); ++n) - rv.p.MGs[0].II[n] = rct::ki2rct(boost::get(tx.vin[n]).k_image); - } - else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2) - { - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == tx.vin.size(), false, "Bad MGs size"); - for (size_t n = 0; n < tx.vin.size(); ++n) - { - rv.p.MGs[n].II.resize(1); - rv.p.MGs[n].II[0] = rct::ki2rct(boost::get(tx.vin[n]).k_image); - } - } - else - { - CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast(rv.type)); - } - - // outPk was already done by handle_incoming_tx - - return true; -} -//------------------------------------------------------------------ -// This function validates transaction inputs and their keys. -// FIXME: consider moving functionality specific to one input into -// check_tx_input() rather than here, and use this function simply -// to iterate the inputs as necessary (splitting the task -// using threads, etc.) -bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) -{ - PERF_TIMER(check_tx_inputs); - LOG_PRINT_L3("Blockchain::" << __func__); - size_t sig_index = 0; - if(pmax_used_block_height) - *pmax_used_block_height = 0; - - crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); - - const uint8_t hf_version = m_hardfork->get_current_version(); - - // from hard fork 2, we require mixin at least 2 unless one output cannot mix with 2 others - // if one output cannot mix with 2 others, we accept at most 1 output that can mix - if (hf_version >= 2) - { - size_t n_unmixable = 0, n_mixable = 0; - size_t mixin = std::numeric_limits::max(); - const size_t min_mixin = hf_version >= HF_VERSION_MIN_MIXIN_10 ? 10 : hf_version >= HF_VERSION_MIN_MIXIN_6 ? 6 : hf_version >= HF_VERSION_MIN_MIXIN_4 ? 4 : 2; - for (const auto& txin : tx.vin) - { - // non txin_to_key inputs will be rejected below - if (txin.type() == typeid(txin_to_key)) - { - const txin_to_key& in_to_key = boost::get(txin); - if (in_to_key.amount == 0) - { - // always consider rct inputs mixable. Even if there's not enough rct - // inputs on the chain to mix with, this is going to be the case for - // just a few blocks right after the fork at most - ++n_mixable; - } - else - { - uint64_t n_outputs = m_db->get_num_outputs(in_to_key.amount); - MDEBUG("output size " << print_money(in_to_key.amount) << ": " << n_outputs << " available"); - // n_outputs includes the output we're considering - if (n_outputs <= min_mixin) - ++n_unmixable; - else - ++n_mixable; - } - if (in_to_key.key_offsets.size() - 1 < mixin) - mixin = in_to_key.key_offsets.size() - 1; - } - } - - if (((hf_version == HF_VERSION_MIN_MIXIN_10 || hf_version == HF_VERSION_MIN_MIXIN_10+1) && mixin != 10) || (hf_version >= HF_VERSION_MIN_MIXIN_10+2 && mixin > 10)) - { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has invalid ring size (" << (mixin + 1) << "), it should be 11"); - tvc.m_low_mixin = true; - return false; - } - - if (mixin < min_mixin) - { - if (n_unmixable == 0) - { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low ring size (" << (mixin + 1) << "), and no unmixable inputs"); - tvc.m_low_mixin = true; - return false; - } - if (n_mixable > 1) - { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has too low ring size (" << (mixin + 1) << "), and more than one mixable input with unmixable inputs"); - tvc.m_low_mixin = true; - return false; - } - } - - // min/max tx version based on HF, and we accept v1 txes if having a non mixable - const size_t max_tx_version = (hf_version <= 3) ? 1 : 2; - if (tx.version > max_tx_version) - { - MERROR_VER("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); - tvc.m_verifivation_failed = true; - return false; - } - const size_t min_tx_version = (n_unmixable > 0 ? 1 : (hf_version >= HF_VERSION_ENFORCE_RCT) ? 2 : 1); - if (tx.version < min_tx_version) - { - MERROR_VER("transaction version " << (unsigned)tx.version << " is lower than min accepted version " << min_tx_version); - tvc.m_verifivation_failed = true; - return false; - } - } - - // from v7, sorted ins - if (hf_version >= 7) { - const crypto::key_image *last_key_image = NULL; - for (size_t n = 0; n < tx.vin.size(); ++n) - { - const txin_v &txin = tx.vin[n]; - if (txin.type() == typeid(txin_to_key)) - { - const txin_to_key& in_to_key = boost::get(txin); - if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0) - { - MERROR_VER("transaction has unsorted inputs"); - tvc.m_verifivation_failed = true; - return false; - } - last_key_image = &in_to_key.k_image; - } - } - } - auto it = m_check_txin_table.find(tx_prefix_hash); - if(it == m_check_txin_table.end()) - { - m_check_txin_table.emplace(tx_prefix_hash, std::unordered_map()); - it = m_check_txin_table.find(tx_prefix_hash); - assert(it != m_check_txin_table.end()); - } - - std::vector> pubkeys(tx.vin.size()); - std::vector < uint64_t > results; - results.resize(tx.vin.size(), 0); - - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter; - const auto waiter_guard = epee::misc_utils::create_scope_leave_handler([&]() { waiter.wait(&tpool); }); - int threads = tpool.get_max_concurrency(); - - for (const auto& txin : tx.vin) - { - // make sure output being spent is of type txin_to_key, rather than - // e.g. txin_gen, which is only used for miner transactions - CHECK_AND_ASSERT_MES(txin.type() == typeid(txin_to_key), false, "wrong type id in tx input at Blockchain::check_tx_inputs"); - const txin_to_key& in_to_key = boost::get(txin); - - // make sure tx output has key offset(s) (is signed to be used) - CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx)); - - if(have_tx_keyimg_as_spent(in_to_key.k_image)) - { - MERROR_VER("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image)); - tvc.m_double_spend = true; - return false; - } - - if (tx.version == 1) - { - // basically, make sure number of inputs == number of signatures - CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); - -#if defined(CACHE_VIN_RESULTS) - auto itk = it->second.find(in_to_key.k_image); - if(itk != it->second.end()) - { - if(!itk->second) - { - MERROR_VER("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - return false; - } - - // txin has been verified already, skip - sig_index++; - continue; - } -#endif - } - - // make sure that output being spent matches up correctly with the - // signature spending it. - if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height)) - { - it->second[in_to_key.k_image] = false; - MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() - { - MERROR_VER(" *pmax_used_block_height: " << *pmax_used_block_height); - } - - return false; - } - - if (tx.version == 1) - { - if (threads > 1) - { - // ND: Speedup - // 1. Thread ring signature verification if possible. - tpool.submit(&waiter, boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index])), true); - } - else - { - check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]); - if (!results[sig_index]) - { - it->second[in_to_key.k_image] = false; - MERROR_VER("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - - if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() - { - MERROR_VER("*pmax_used_block_height: " << *pmax_used_block_height); - } - - return false; - } - it->second[in_to_key.k_image] = true; - } - } - - sig_index++; - } - if (tx.version == 1 && threads > 1) - waiter.wait(&tpool); - - if (tx.version == 1) - { - if (threads > 1) - { - // save results to table, passed or otherwise - bool failed = false; - for (size_t i = 0; i < tx.vin.size(); i++) - { - const txin_to_key& in_to_key = boost::get(tx.vin[i]); - it->second[in_to_key.k_image] = results[i]; - if(!failed && !results[i]) - failed = true; - } - - if (failed) - { - MERROR_VER("Failed to check ring signatures!"); - return false; - } - } - } - else - { - if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) - { - MERROR_VER("Failed to expand rct signatures!"); - return false; - } - - // from version 2, check ringct signatures - // obviously, the original and simple rct APIs use a mixRing that's indexes - // in opposite orders, because it'd be too simple otherwise... - const rct::rctSig &rv = tx.rct_signatures; - switch (rv.type) - { - case rct::RCTTypeNull: { - // we only accept no signatures for coinbase txes - MERROR_VER("Null rct signature on non-coinbase tx"); - return false; - } - case rct::RCTTypeSimple: - case rct::RCTTypeBulletproof: - case rct::RCTTypeBulletproof2: - { - // check all this, either reconstructed (so should really pass), or not - { - if (pubkeys.size() != rv.mixRing.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - for (size_t i = 0; i < pubkeys.size(); ++i) - { - if (pubkeys[i].size() != rv.mixRing[i].size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - } - - for (size_t n = 0; n < pubkeys.size(); ++n) - { - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); - return false; - } - if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) - { - MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); - return false; - } - } - } - } - - if (rv.p.MGs.size() != tx.vin.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); - return false; - } - for (size_t n = 0; n < tx.vin.size(); ++n) - { - if (rv.p.MGs[n].II.empty() || memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) - { - MERROR_VER("Failed to check ringct signatures: mismatched key image"); - return false; - } - } - - if (!rct::verRctNonSemanticsSimple(rv)) - { - MERROR_VER("Failed to check ringct signatures!"); - return false; - } - break; - } - case rct::RCTTypeFull: - { - // check all this, either reconstructed (so should really pass), or not - { - bool size_matches = true; - for (size_t i = 0; i < pubkeys.size(); ++i) - size_matches &= pubkeys[i].size() == rv.mixRing.size(); - for (size_t i = 0; i < rv.mixRing.size(); ++i) - size_matches &= pubkeys.size() == rv.mixRing[i].size(); - if (!size_matches) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - - for (size_t n = 0; n < pubkeys.size(); ++n) - { - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[m][n].dest)) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); - return false; - } - if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[m][n].mask)) - { - MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); - return false; - } - } - } - } - - if (rv.p.MGs.size() != 1) - { - MERROR_VER("Failed to check ringct signatures: Bad MGs size"); - return false; - } - if (rv.p.MGs.empty() || rv.p.MGs[0].II.size() != tx.vin.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched II/vin sizes"); - return false; - } - for (size_t n = 0; n < tx.vin.size(); ++n) - { - if (memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[0].II[n], 32)) - { - MERROR_VER("Failed to check ringct signatures: mismatched II/vin sizes"); - return false; - } - } - - if (!rct::verRct(rv, false)) - { - MERROR_VER("Failed to check ringct signatures!"); - return false; - } - break; - } - default: - MERROR_VER("Unsupported rct type: " << rv.type); - return false; - } - - // for bulletproofs, check they're only multi-output after v8 - if (rct::is_rct_bulletproof(rv.type)) - { - if (hf_version < 8) - { - for (const rct::Bulletproof &proof: rv.p.bulletproofs) - { - if (proof.V.size() > 1) - { - MERROR_VER("Multi output bulletproofs are invalid before v8"); - return false; - } - } - } - } - } - return true; -} - -//------------------------------------------------------------------ -void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector &pubkeys, const std::vector& sig, uint64_t &result) -{ - std::vector p_output_keys; - p_output_keys.reserve(pubkeys.size()); - for (auto &key : pubkeys) - { - // rct::key and crypto::public_key have the same structure, avoid object ctor/memcpy - p_output_keys.push_back(&(const crypto::public_key&)key.dest); - } - - result = crypto::check_ring_signature(tx_prefix_hash, key_image, p_output_keys, sig.data()) ? 1 : 0; -} - -//------------------------------------------------------------------ -uint64_t Blockchain::get_fee_quantization_mask() -{ - static uint64_t mask = 0; - if (mask == 0) - { - mask = 1; - for (size_t n = PER_KB_FEE_QUANTIZATION_DECIMALS; n < CRYPTONOTE_DISPLAY_DECIMAL_POINT; ++n) - mask *= 10; - } - return mask; -} - -//------------------------------------------------------------------ -uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version) -{ - const uint64_t min_block_weight = get_min_block_weight(version); - if (median_block_weight < min_block_weight) - median_block_weight = min_block_weight; - uint64_t hi, lo; - - if (version >= HF_VERSION_PER_BYTE_FEE) - { - lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); - div128_32(hi, lo, min_block_weight, &hi, &lo); - div128_32(hi, lo, median_block_weight, &hi, &lo); - assert(hi == 0); - lo /= 5; - return lo; - } - - const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; - - uint64_t unscaled_fee_base = (fee_base * min_block_weight / median_block_weight); - lo = mul128(unscaled_fee_base, block_reward, &hi); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); - - // divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't - div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); - div128_32(hi, lo, 1000000, &hi, &lo); - assert(hi == 0); - - // quantize fee up to 8 decimals - uint64_t mask = get_fee_quantization_mask(); - uint64_t qlo = (lo + mask - 1) / mask * mask; - MDEBUG("lo " << print_money(lo) << ", qlo " << print_money(qlo) << ", mask " << mask); - - return qlo; -} - -//------------------------------------------------------------------ -bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const -{ - const uint8_t version = get_current_hard_fork_version(); - const uint64_t blockchain_height = m_db->height(); - - uint64_t median = 0; - uint64_t already_generated_coins = 0; - uint64_t base_reward = 0; - if (version >= HF_VERSION_DYNAMIC_FEE) - { - median = m_current_block_cumul_weight_limit / 2; - already_generated_coins = blockchain_height ? m_db->get_block_already_generated_coins(blockchain_height - 1) : 0; - if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) - return false; - } - - uint64_t needed_fee; - if (version >= HF_VERSION_PER_BYTE_FEE) - { - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - uint64_t fee_per_byte = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? m_long_term_effective_median_block_weight : median, version); - MDEBUG("Using " << print_money(fee_per_byte) << "/byte fee"); - needed_fee = tx_weight * fee_per_byte; - // quantize fee up to 8 decimals - const uint64_t mask = get_fee_quantization_mask(); - needed_fee = (needed_fee + mask - 1) / mask * mask; - } - else - { - uint64_t fee_per_kb; - if (version < HF_VERSION_DYNAMIC_FEE) - { - fee_per_kb = FEE_PER_KB; - } - else - { - fee_per_kb = get_dynamic_base_fee(base_reward, median, version); - } - MDEBUG("Using " << print_money(fee_per_kb) << "/kB fee"); - - needed_fee = tx_weight / 1024; - needed_fee += (tx_weight % 1024) ? 1 : 0; - needed_fee *= fee_per_kb; - } - - if (fee < needed_fee - needed_fee / 50) // keep a little 2% buffer on acceptance - no integer overflow - { - MERROR_VER("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); - return false; - } - return true; -} - -//------------------------------------------------------------------ -uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const -{ - const uint8_t version = get_current_hard_fork_version(); - const uint64_t db_height = m_db->height(); - - if (version < HF_VERSION_DYNAMIC_FEE) - return FEE_PER_KB; - - if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) - grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; - - const uint64_t min_block_weight = get_min_block_weight(version); - std::vector weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); - weights.reserve(grace_blocks); - for (size_t i = 0; i < grace_blocks; ++i) - weights.push_back(min_block_weight); - - uint64_t median = epee::misc_utils::median(weights); - if(median <= min_block_weight) - median = min_block_weight; - - uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0; - uint64_t base_reward; - if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) - { - MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); - base_reward = BLOCK_REWARD_OVERESTIMATE; - } - - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - uint64_t fee = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? m_long_term_effective_median_block_weight : median, version); - const bool per_byte = version < HF_VERSION_PER_BYTE_FEE; - MDEBUG("Estimating " << grace_blocks << "-block fee at " << print_money(fee) << "/" << (per_byte ? "byte" : "kB")); - return fee; -} - -//------------------------------------------------------------------ -// This function checks to see if a tx is unlocked. unlock_time is either -// a block index or a unix time. -bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) - { - // ND: Instead of calling get_current_blockchain_height(), call m_db->height() - // directly as get_current_blockchain_height() locks the recursive mutex. - if(m_db->height()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) - return true; - else - return false; - } - else - { - //interpret as time - uint64_t current_time = static_cast(time(NULL)); - if(current_time + (get_current_hard_fork_version() < 2 ? CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 : CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2) >= unlock_time) - return true; - else - return false; - } - return false; -} -//------------------------------------------------------------------ -// This function locates all outputs associated with a given input (mixins) -// and validates that they exist and are usable. It also checks the ring -// signature for each input. -bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, const rct::rctSig &rct_signatures, std::vector &output_keys, uint64_t* pmax_related_block_height) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - - // ND: - // 1. Disable locking and make method private. - //CRITICAL_REGION_LOCAL(m_blockchain_lock); - - struct outputs_visitor - { - std::vector& m_output_keys; - const Blockchain& m_bch; - outputs_visitor(std::vector& output_keys, const Blockchain& bch) : - m_output_keys(output_keys), m_bch(bch) - { - } - bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey, const rct::key &commitment) - { - //check tx unlock time - if (!m_bch.is_tx_spendtime_unlocked(unlock_time)) - { - MERROR_VER("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time); - return false; - } - - // The original code includes a check for the output corresponding to this input - // to be a txout_to_key. This is removed, as the database does not store this info, - // but only txout_to_key outputs are stored in the DB in the first place, done in - // Blockchain*::add_output - - m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment})); - return true; - } - }; - - output_keys.clear(); - - // collect output keys - outputs_visitor vi(output_keys, *this); - if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height)) - { - MERROR_VER("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); - return false; - } - - if(txin.key_offsets.size() != output_keys.size()) - { - MERROR_VER("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); - return false; - } - if (tx_version == 1) { - CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); - } - // rct_signatures will be expanded after this - return true; -} -//------------------------------------------------------------------ -//TODO: Is this intended to do something else? Need to look into the todo there. -uint64_t Blockchain::get_adjusted_time() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - //TODO: add collecting median time - return time(NULL); -} -//------------------------------------------------------------------ -//TODO: revisit, has changed a bit on upstream -bool Blockchain::check_block_timestamp(std::vector& timestamps, const block& b, uint64_t& median_ts) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - median_ts = epee::misc_utils::median(timestamps); - - if(b.timestamp < median_ts) - { - MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); - return false; - } - - return true; -} -//------------------------------------------------------------------ -// This function grabs the timestamps from the most recent blocks, -// where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many -// blocks in the blockchain, the timestap is assumed to be valid. If there -// are, this function returns: -// true if the block's timestamp is not less than the timestamp of the -// median of the selected blocks -// false otherwise -bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) - { - MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); - return false; - } - - // if not enough blocks, no proper median yet, return true - if(m_db->height() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) - { - return true; - } - - std::vector timestamps; - auto h = m_db->height(); - - // need most recent 60 blocks, get index of first of those - size_t offset = h - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; - timestamps.reserve(h - offset); - for(;offset < h; ++offset) - { - timestamps.push_back(m_db->get_block_timestamp(offset)); - } - - return check_block_timestamp(timestamps, b, median_ts); -} -//------------------------------------------------------------------ -void Blockchain::return_tx_to_pool(std::vector &txs) -{ - uint8_t version = get_current_hard_fork_version(); - for (auto& tx : txs) - { - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - // We assume that if they were in a block, the transactions are already - // known to the network as a whole. However, if we had mined that block, - // that might not be always true. Unlikely though, and always relaying - // these again might cause a spike of traffic as many nodes re-relay - // all the transactions in a popped block when a reorg happens. - if (!m_tx_pool.add_tx(tx, tvc, true, true, false, version)) - { - MERROR("Failed to return taken transaction with hash: " << get_transaction_hash(tx) << " to tx_pool"); - } - } -} -//------------------------------------------------------------------ -bool Blockchain::flush_txes_from_pool(const std::vector &txids) -{ - CRITICAL_REGION_LOCAL(m_tx_pool); - - bool res = true; - for (const auto &txid: txids) - { - cryptonote::transaction tx; - size_t tx_weight; - uint64_t fee; - bool relayed, do_not_relay, double_spend_seen; - MINFO("Removing txid " << txid << " from the pool"); - if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, tx_weight, fee, relayed, do_not_relay, double_spend_seen)) - { - MERROR("Failed to remove txid " << txid << " from the pool"); - res = false; - } - } - return res; -} -//------------------------------------------------------------------ -// Needs to validate the block and acquire each transaction from the -// transaction mem_pool, then pass the block and transactions to -// m_db->add_block() -bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - - TIME_MEASURE_START(block_processing_time); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - TIME_MEASURE_START(t1); - - static bool seen_future_version = false; - - m_db->block_txn_start(true); - if(bl.prev_id != get_tail_id()) - { - MERROR_VER("Block with id: " << id << std::endl << "has wrong prev_id: " << bl.prev_id << std::endl << "expected: " << get_tail_id()); - bvc.m_verifivation_failed = true; -leave: - m_db->block_txn_stop(); - return false; - } - - // warn users if they're running an old version - if (!seen_future_version && bl.major_version > m_hardfork->get_ideal_version()) - { - seen_future_version = true; - const el::Level level = el::Level::Warning; - MCLOG_RED(level, "global", "**********************************************************************"); - MCLOG_RED(level, "global", "A block was seen on the network with a version higher than the last"); - MCLOG_RED(level, "global", "known one. This may be an old version of the daemon, and a software"); - MCLOG_RED(level, "global", "update may be required to sync further. Try running: update check"); - MCLOG_RED(level, "global", "**********************************************************************"); - } - - // this is a cheap test - if (!m_hardfork->check(bl)) - { - MERROR_VER("Block with id: " << id << std::endl << "has old version: " << (unsigned)bl.major_version << std::endl << "current: " << (unsigned)m_hardfork->get_current_version()); - bvc.m_verifivation_failed = true; - goto leave; - } - - TIME_MEASURE_FINISH(t1); - TIME_MEASURE_START(t2); - - // make sure block timestamp is not less than the median timestamp - // of a set number of the most recent blocks. - if(!check_block_timestamp(bl)) - { - MERROR_VER("Block with id: " << id << std::endl << "has invalid timestamp: " << bl.timestamp); - bvc.m_verifivation_failed = true; - goto leave; - } - - TIME_MEASURE_FINISH(t2); - //check proof of work - TIME_MEASURE_START(target_calculating_time); - - // get the target difficulty for the block. - // the calculation can overflow, among other failure cases, - // so we need to check the return type. - // FIXME: get_difficulty_for_next_block can also assert, look into - // changing this to throwing exceptions instead so we can clean up. - difficulty_type current_diffic = get_difficulty_for_next_block(); - CHECK_AND_ASSERT_MES(current_diffic, false, "!!!!!!!!! difficulty overhead !!!!!!!!!"); - - TIME_MEASURE_FINISH(target_calculating_time); - - TIME_MEASURE_START(longhash_calculating_time); - - crypto::hash proof_of_work = null_hash; - - // Formerly the code below contained an if loop with the following condition - // !m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height()) - // however, this caused the daemon to not bother checking PoW for blocks - // before checkpoints, which is very dangerous behaviour. We moved the PoW - // validation out of the next chunk of code to make sure that we correctly - // check PoW now. - // FIXME: height parameter is not used...should it be used or should it not - // be a parameter? - // validate proof_of_work versus difficulty target - bool precomputed = false; - bool fast_check = false; -#if defined(PER_BLOCK_CHECKPOINT) - if (m_db->height() < m_blocks_hash_check.size()) - { - auto hash = get_block_hash(bl); - const auto &expected_hash = m_blocks_hash_check[m_db->height()]; - if (expected_hash != crypto::null_hash) - { - if (memcmp(&hash, &expected_hash, sizeof(hash)) != 0) - { - MERROR_VER("Block with id is INVALID: " << id); - bvc.m_verifivation_failed = true; - goto leave; - } - fast_check = true; - } - else - { - MCINFO("verify", "No pre-validated hash at height " << m_db->height() << ", verifying fully"); - } - } - else -#endif - { - auto it = m_blocks_longhash_table.find(id); - if (it != m_blocks_longhash_table.end()) - { - precomputed = true; - proof_of_work = it->second; - } - else - proof_of_work = get_block_longhash(bl, m_db->height()); - - // validate proof_of_work versus difficulty target - if(!check_hash(proof_of_work, current_diffic)) - { - MERROR_VER("Block with id: " << id << std::endl << "does not have enough proof of work: " << proof_of_work << std::endl << "unexpected difficulty: " << current_diffic); - bvc.m_verifivation_failed = true; - goto leave; - } - } - - // If we're at a checkpoint, ensure that our hardcoded checkpoint hash - // is correct. - if(m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height())) - { - if(!m_checkpoints.check_block(get_current_blockchain_height(), id)) - { - LOG_ERROR("CHECKPOINT VALIDATION FAILED"); - bvc.m_verifivation_failed = true; - goto leave; - } - } - - TIME_MEASURE_FINISH(longhash_calculating_time); - if (precomputed) - longhash_calculating_time += m_fake_pow_calc_time; - - TIME_MEASURE_START(t3); - - // sanity check basic miner tx properties; - if(!prevalidate_miner_transaction(bl, m_db->height())) - { - MERROR_VER("Block with id: " << id << " failed to pass prevalidation"); - bvc.m_verifivation_failed = true; - goto leave; - } - - size_t coinbase_weight = get_transaction_weight(bl.miner_tx); - size_t cumulative_block_weight = coinbase_weight; - - std::vector txs; - key_images_container keys; - - uint64_t fee_summary = 0; - uint64_t t_checktx = 0; - uint64_t t_exists = 0; - uint64_t t_pool = 0; - uint64_t t_dblspnd = 0; - TIME_MEASURE_FINISH(t3); - -// XXX old code adds miner tx here - - size_t tx_index = 0; - // Iterate over the block's transaction hashes, grabbing each - // from the tx_pool and validating them. Each is then added - // to txs. Keys spent in each are added to by the double spend check. - txs.reserve(bl.tx_hashes.size()); - for (const crypto::hash& tx_id : bl.tx_hashes) - { - transaction tx; - size_t tx_weight = 0; - uint64_t fee = 0; - bool relayed = false, do_not_relay = false, double_spend_seen = false; - TIME_MEASURE_START(aa); - -// XXX old code does not check whether tx exists - if (m_db->tx_exists(tx_id)) - { - MERROR("Block with id: " << id << " attempting to add transaction already in blockchain with id: " << tx_id); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - goto leave; - } - - TIME_MEASURE_FINISH(aa); - t_exists += aa; - TIME_MEASURE_START(bb); - - // get transaction with hash from tx_pool - if(!m_tx_pool.take_tx(tx_id, tx, tx_weight, fee, relayed, do_not_relay, double_spend_seen)) - { - MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - goto leave; - } - - TIME_MEASURE_FINISH(bb); - t_pool += bb; - // add the transaction to the temp list of transactions, so we can either - // store the list of transactions all at once or return the ones we've - // taken from the tx_pool back to it if the block fails verification. - txs.push_back(tx); - TIME_MEASURE_START(dd); - - // FIXME: the storage should not be responsible for validation. - // If it does any, it is merely a sanity check. - // Validation is the purview of the Blockchain class - // - TW - // - // ND: this is not needed, db->add_block() checks for duplicate k_images and fails accordingly. - // if (!check_for_double_spend(tx, keys)) - // { - // LOG_PRINT_L0("Double spend detected in transaction (id: " << tx_id); - // bvc.m_verifivation_failed = true; - // break; - // } - - TIME_MEASURE_FINISH(dd); - t_dblspnd += dd; - TIME_MEASURE_START(cc); - -#if defined(PER_BLOCK_CHECKPOINT) - if (!fast_check) -#endif - { - // validate that transaction inputs and the keys spending them are correct. - tx_verification_context tvc; - if(!check_tx_inputs(tx, tvc)) - { - MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); - - //TODO: why is this done? make sure that keeping invalid blocks makes sense. - add_block_as_invalid(bl, id); - MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - goto leave; - } - } -#if defined(PER_BLOCK_CHECKPOINT) - else - { - // ND: if fast_check is enabled for blocks, there is no need to check - // the transaction inputs, but do some sanity checks anyway. - if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) - { - MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); - //TODO: why is this done? make sure that keeping invalid blocks makes sense. - add_block_as_invalid(bl, id); - MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - goto leave; - } - } -#endif - TIME_MEASURE_FINISH(cc); - t_checktx += cc; - fee_summary += fee; - cumulative_block_weight += tx_weight; - } - - m_blocks_txs_check.clear(); - - TIME_MEASURE_START(vmt); - uint64_t base_reward = 0; - uint64_t already_generated_coins = m_db->height() ? m_db->get_block_already_generated_coins(m_db->height() - 1) : 0; - if(!validate_miner_transaction(bl, cumulative_block_weight, fee_summary, base_reward, already_generated_coins, bvc.m_partial_block_reward, m_hardfork->get_current_version())) - { - MERROR_VER("Block with id: " << id << " has incorrect miner transaction"); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - goto leave; - } - - TIME_MEASURE_FINISH(vmt); - size_t block_weight; - difficulty_type cumulative_difficulty; - - // populate various metadata about the block to be stored alongside it. - block_weight = cumulative_block_weight; - cumulative_difficulty = current_diffic; - // In the "tail" state when the minimum subsidy (implemented in get_block_reward) is in effect, the number of - // coins will eventually exceed MONEY_SUPPLY and overflow a uint64. To prevent overflow, cap already_generated_coins - // at MONEY_SUPPLY. already_generated_coins is only used to compute the block subsidy and MONEY_SUPPLY yields a - // subsidy of 0 under the base formula and therefore the minimum subsidy >0 in the tail state. - already_generated_coins = base_reward < (MONEY_SUPPLY-already_generated_coins) ? already_generated_coins + base_reward : MONEY_SUPPLY; - if(m_db->height()) - cumulative_difficulty += m_db->get_block_cumulative_difficulty(m_db->height() - 1); - - TIME_MEASURE_FINISH(block_processing_time); - if(precomputed) - block_processing_time += m_fake_pow_calc_time; - - m_db->block_txn_stop(); - TIME_MEASURE_START(addblock); - uint64_t new_height = 0; - if (!bvc.m_verifivation_failed) - { - try - { - uint64_t long_term_block_weight = get_next_long_term_block_weight(block_weight); - new_height = m_db->add_block(bl, block_weight, long_term_block_weight, cumulative_difficulty, already_generated_coins, txs); - } - catch (const KEY_IMAGE_EXISTS& e) - { - LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what()); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - return false; - } - catch (const std::exception& e) - { - //TODO: figure out the best way to deal with this failure - LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what()); - bvc.m_verifivation_failed = true; - return_tx_to_pool(txs); - return false; - } - } - else - { - LOG_ERROR("Blocks that failed verification should not reach here"); - } - - TIME_MEASURE_FINISH(addblock); - - // do this after updating the hard fork state since the weight limit may change due to fork - if (!update_next_cumulative_weight_limit()) - { - MERROR("Failed to update next cumulative weight limit"); - pop_block_from_blockchain(); - return false; - } - - MINFO("+++++ BLOCK SUCCESSFULLY ADDED" << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "HEIGHT " << new_height-1 << ", difficulty:\t" << current_diffic << std::endl << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) << "), coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms"); - if(m_show_time_stats) - { - MINFO("Height: " << new_height << " coinbase weight: " << coinbase_weight << " cumm: " - << cumulative_block_weight << " p/t: " << block_processing_time << " (" - << target_calculating_time << "/" << longhash_calculating_time << "/" - << t1 << "/" << t2 << "/" << t3 << "/" << t_exists << "/" << t_pool - << "/" << t_checktx << "/" << t_dblspnd << "/" << vmt << "/" << addblock << ")ms"); - } - - bvc.m_added_to_main_chain = true; - ++m_sync_counter; - - // appears to be a NOP *and* is called elsewhere. wat? - m_tx_pool.on_blockchain_inc(new_height, id); - get_difficulty_for_next_block(); // just to cache it - invalidate_block_template_cache(); - - std::shared_ptr block_notify = m_block_notify; - if (block_notify) - block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL); - - return true; -} -//------------------------------------------------------------------ -uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) const -{ - PERF_TIMER(get_next_long_term_block_weight); - - const uint64_t db_height = m_db->height(); - const uint64_t nblocks = std::min(m_long_term_block_weights_window, db_height); - - const uint8_t hf_version = get_current_hard_fork_version(); - if (hf_version < HF_VERSION_LONG_TERM_BLOCK_WEIGHT) - return block_weight; - - std::vector weights; - weights.resize(nblocks); - for (uint64_t h = 0; h < nblocks; ++h) - weights[h] = m_db->get_block_long_term_weight(db_height - nblocks + h); - uint64_t long_term_median = epee::misc_utils::median(weights); - uint64_t long_term_effective_median_block_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - - uint64_t short_term_constraint = long_term_effective_median_block_weight + long_term_effective_median_block_weight * 2 / 5; - uint64_t long_term_block_weight = std::min(block_weight, short_term_constraint); - - return long_term_block_weight; -} -//------------------------------------------------------------------ -bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effective_median_block_weight) -{ - PERF_TIMER(update_next_cumulative_weight_limit); - - LOG_PRINT_L3("Blockchain::" << __func__); - - // when we reach this, the last hf version is not yet written to the db - const uint64_t db_height = m_db->height(); - const uint8_t hf_version = get_current_hard_fork_version(); - uint64_t full_reward_zone = get_min_block_weight(hf_version); - uint64_t long_term_block_weight; - - if (hf_version < HF_VERSION_LONG_TERM_BLOCK_WEIGHT) - { - std::vector weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - m_current_block_cumul_weight_median = epee::misc_utils::median(weights); - long_term_block_weight = weights.back(); - } - else - { - const uint64_t block_weight = m_db->get_block_weight(db_height - 1); - - std::vector weights, new_weights; - uint64_t long_term_median; - if (db_height == 1) - { - long_term_median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; - } - else - { - const uint64_t nblocks = std::min(m_long_term_block_weights_window, db_height - 1); - weights.resize(nblocks); - for (uint64_t h = 0; h < nblocks; ++h) - weights[h] = m_db->get_block_long_term_weight(db_height - nblocks + h); - new_weights = weights; - long_term_median = epee::misc_utils::median(weights); - } - m_long_term_effective_median_block_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - - uint64_t short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; - long_term_block_weight = std::min(block_weight, short_term_constraint); - - if (new_weights.empty()) - new_weights.resize(1); - new_weights[0] = long_term_block_weight; - long_term_median = epee::misc_utils::median(new_weights); - m_long_term_effective_median_block_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - short_term_constraint = m_long_term_effective_median_block_weight + m_long_term_effective_median_block_weight * 2 / 5; - - weights.clear(); - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - - uint64_t short_term_median = epee::misc_utils::median(weights); - uint64_t effective_median_block_weight = std::min(std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); - - m_current_block_cumul_weight_median = effective_median_block_weight; - } - - if (m_current_block_cumul_weight_median <= full_reward_zone) - m_current_block_cumul_weight_median = full_reward_zone; - - m_current_block_cumul_weight_limit = m_current_block_cumul_weight_median * 2; - - if (long_term_effective_median_block_weight) - *long_term_effective_median_block_weight = m_long_term_effective_median_block_weight; - - return true; -} -//------------------------------------------------------------------ -bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc) -{ - LOG_PRINT_L3("Blockchain::" << __func__); - //copy block here to let modify block.target - block bl = bl_; - crypto::hash id = get_block_hash(bl); - CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process - CRITICAL_REGION_LOCAL1(m_blockchain_lock); - m_db->block_txn_start(true); - if(have_block(id)) - { - LOG_PRINT_L3("block with id = " << id << " already exists"); - bvc.m_already_exists = true; - m_db->block_txn_stop(); - m_blocks_txs_check.clear(); - return false; - } - - //check that block refers to chain tail - if(!(bl.prev_id == get_tail_id())) - { - //chain switching or wrong block - bvc.m_added_to_main_chain = false; - m_db->block_txn_stop(); - bool r = handle_alternative_block(bl, id, bvc); - m_blocks_txs_check.clear(); - return r; - //never relay alternative blocks - } - - m_db->block_txn_stop(); - return handle_block_to_main_chain(bl, id, bvc); -} -//------------------------------------------------------------------ -//TODO: Refactor, consider returning a failure height and letting -// caller decide course of action. -void Blockchain::check_against_checkpoints(const checkpoints& points, bool enforce) -{ - const auto& pts = points.get_points(); - bool stop_batch; - - CRITICAL_REGION_LOCAL(m_blockchain_lock); - stop_batch = m_db->batch_start(); - for (const auto& pt : pts) - { - // if the checkpoint is for a block we don't have yet, move on - if (pt.first >= m_db->height()) - { - continue; - } - - if (!points.check_block(pt.first, m_db->get_block_hash_from_height(pt.first))) - { - // if asked to enforce checkpoints, roll back to a couple of blocks before the checkpoint - if (enforce) - { - LOG_ERROR("Local blockchain failed to pass a checkpoint, rolling back!"); - std::list empty; - rollback_blockchain_switching(empty, pt.first - 2); - } - else - { - LOG_ERROR("WARNING: local blockchain failed to pass a MoneroPulse checkpoint, and you could be on a fork. You should either sync up from scratch, OR download a fresh blockchain bootstrap, OR enable checkpoint enforcing with the --enforce-dns-checkpointing command-line option"); - } - } - } - if (stop_batch) - m_db->batch_stop(); -} -//------------------------------------------------------------------ -// returns false if any of the checkpoints loading returns false. -// That should happen only if a checkpoint is added that conflicts -// with an existing checkpoint. -bool Blockchain::update_checkpoints(const std::string& file_path, bool check_dns) -{ - if (!m_checkpoints.load_checkpoints_from_json(file_path)) - { - return false; - } - - // if we're checking both dns and json, load checkpoints from dns. - // if we're not hard-enforcing dns checkpoints, handle accordingly - if (m_enforce_dns_checkpoints && check_dns && !m_offline) - { - if (!m_checkpoints.load_checkpoints_from_dns()) - { - return false; - } - } - else if (check_dns && !m_offline) - { - checkpoints dns_points; - dns_points.load_checkpoints_from_dns(); - if (m_checkpoints.check_for_conflicts(dns_points)) - { - check_against_checkpoints(dns_points, false); - } - else - { - MERROR("One or more checkpoints fetched from DNS conflicted with existing checkpoints!"); - } - } - - check_against_checkpoints(m_checkpoints, true); - - return true; -} -//------------------------------------------------------------------ -void Blockchain::set_enforce_dns_checkpoints(bool enforce_checkpoints) -{ - m_enforce_dns_checkpoints = enforce_checkpoints; -} - -//------------------------------------------------------------------ -void Blockchain::block_longhash_worker(uint64_t height, const std::vector &blocks, std::unordered_map &map) const -{ - TIME_MEASURE_START(t); - slow_hash_allocate_state(); - - for (const auto & block : blocks) - { - if (m_cancel) - break; - crypto::hash id = get_block_hash(block); - crypto::hash pow = get_block_longhash(block, height++); - map.emplace(id, pow); - } - - slow_hash_free_state(); - TIME_MEASURE_FINISH(t); -} - -//------------------------------------------------------------------ -bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) -{ - bool success = false; - - MTRACE("Blockchain::" << __func__); - CRITICAL_REGION_BEGIN(m_blockchain_lock); - TIME_MEASURE_START(t1); - - try - { - m_db->batch_stop(); - success = true; - } - catch (const std::exception &e) - { - MERROR("Exception in cleanup_handle_incoming_blocks: " << e.what()); - } - - if (success && m_sync_counter > 0) - { - if (force_sync) - { - if(m_db_sync_mode != db_nosync) - store_blockchain(); - m_sync_counter = 0; - } - else if (m_db_sync_threshold && ((m_db_sync_on_blocks && m_sync_counter >= m_db_sync_threshold) || (!m_db_sync_on_blocks && m_bytes_to_sync >= m_db_sync_threshold))) - { - MDEBUG("Sync threshold met, syncing"); - if(m_db_sync_mode == db_async) - { - m_sync_counter = 0; - m_bytes_to_sync = 0; - m_async_service.dispatch(boost::bind(&Blockchain::store_blockchain, this)); - } - else if(m_db_sync_mode == db_sync) - { - store_blockchain(); - } - else // db_nosync - { - // DO NOTHING, not required to call sync. - } - } - } - - TIME_MEASURE_FINISH(t1); - m_blocks_longhash_table.clear(); - m_scan_table.clear(); - m_blocks_txs_check.clear(); - m_check_txin_table.clear(); - - // when we're well clear of the precomputed hashes, free the memory - if (!m_blocks_hash_check.empty() && m_db->height() > m_blocks_hash_check.size() + 4096) - { - MINFO("Dumping block hashes, we're now 4k past " << m_blocks_hash_check.size()); - m_blocks_hash_check.clear(); - m_blocks_hash_check.shrink_to_fit(); - } - - CRITICAL_REGION_END(); - m_tx_pool.unlock(); - - return success; -} - -//------------------------------------------------------------------ -//FIXME: unused parameter txs -void Blockchain::output_scan_worker(const uint64_t amount, const std::vector &offsets, std::vector &outputs, std::unordered_map &txs) const -{ - try - { - m_db->get_output_key(amount, offsets, outputs, true); - } - catch (const std::exception& e) - { - MERROR_VER("EXCEPTION: " << e.what()); - } - catch (...) - { - - } -} - -uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector &hashes) -{ - // new: . . . . . X X X X X . . . . . . - // pre: A A A A B B B B C C C C D D D D - - // easy case: height >= hashes - if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP) - return hashes.size(); - - // if we're getting old blocks, we might have jettisoned the hashes already - if (m_blocks_hash_check.empty()) - return hashes.size(); - - // find hashes encompassing those block - size_t first_index = height / HASH_OF_HASHES_STEP; - size_t last_index = (height + hashes.size() - 1) / HASH_OF_HASHES_STEP; - MDEBUG("Blocks " << height << " - " << (height + hashes.size() - 1) << " start at " << first_index << " and end at " << last_index); - - // case of not enough to calculate even a single hash - if (first_index == last_index && hashes.size() < HASH_OF_HASHES_STEP && (height + hashes.size()) % HASH_OF_HASHES_STEP) - return hashes.size(); - - // build hashes vector to hash hashes together - std::vector data; - data.reserve(hashes.size() + HASH_OF_HASHES_STEP - 1); // may be a bit too much - - // we expect height to be either equal or a bit below db height - bool disconnected = (height > m_db->height()); - size_t pop; - if (disconnected && height % HASH_OF_HASHES_STEP) - { - ++first_index; - pop = HASH_OF_HASHES_STEP - height % HASH_OF_HASHES_STEP; - } - else - { - // we might need some already in the chain for the first part of the first hash - for (uint64_t h = first_index * HASH_OF_HASHES_STEP; h < height; ++h) - { - data.push_back(m_db->get_block_hash_from_height(h)); - } - pop = 0; - } - - // push the data to check - for (const auto &h: hashes) - { - if (pop) - --pop; - else - data.push_back(h); - } - - // hash and check - uint64_t usable = first_index * HASH_OF_HASHES_STEP - height; // may start negative, but unsigned under/overflow is not UB - for (size_t n = first_index; n <= last_index; ++n) - { - if (n < m_blocks_hash_of_hashes.size()) - { - // if the last index isn't fully filled, we can't tell if valid - if (data.size() < (n - first_index) * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP) - break; - - crypto::hash hash; - cn_fast_hash(data.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); - bool valid = hash == m_blocks_hash_of_hashes[n]; - - // add to the known hashes array - if (!valid) - { - MDEBUG("invalid hash for blocks " << n * HASH_OF_HASHES_STEP << " - " << (n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP - 1)); - break; - } - - size_t end = n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP; - for (size_t i = n * HASH_OF_HASHES_STEP; i < end; ++i) - { - CHECK_AND_ASSERT_MES(m_blocks_hash_check[i] == crypto::null_hash || m_blocks_hash_check[i] == data[i - first_index * HASH_OF_HASHES_STEP], - 0, "Consistency failure in m_blocks_hash_check construction"); - m_blocks_hash_check[i] = data[i - first_index * HASH_OF_HASHES_STEP]; - } - usable += HASH_OF_HASHES_STEP; - } - else - { - // if after the end of the precomputed blocks, accept anything - usable += HASH_OF_HASHES_STEP; - if (usable > hashes.size()) - usable = hashes.size(); - } - } - MDEBUG("usable: " << usable << " / " << hashes.size()); - CHECK_AND_ASSERT_MES(usable < std::numeric_limits::max() / 2, 0, "usable is negative"); - return usable; -} - -//------------------------------------------------------------------ -// ND: Speedups: -// 1. Thread long_hash computations if possible (m_max_prepare_blocks_threads = nthreads, default = 4) -// 2. Group all amounts (from txs) and related absolute offsets and form a table of tx_prefix_hash -// vs [k_image, output_keys] (m_scan_table). This is faster because it takes advantage of bulk queries -// and is threaded if possible. The table (m_scan_table) will be used later when querying output -// keys. -bool Blockchain::prepare_handle_incoming_blocks(const std::vector &blocks_entry) -{ - MTRACE("Blockchain::" << __func__); - TIME_MEASURE_START(prepare); - bool stop_batch; - uint64_t bytes = 0; - size_t total_txs = 0; - - // Order of locking must be: - // m_incoming_tx_lock (optional) - // m_tx_pool lock - // blockchain lock - // - // Something which takes the blockchain lock may never take the txpool lock - // if it has not provably taken the txpool lock earlier - // - // The txpool lock is now taken in prepare_handle_incoming_blocks - // and released in cleanup_handle_incoming_blocks. This avoids issues - // when something uses the pool, which now uses the blockchain and - // needs a batch, since a batch could otherwise be active while the - // txpool and blockchain locks were not held - - m_tx_pool.lock(); - CRITICAL_REGION_LOCAL1(m_blockchain_lock); - - if(blocks_entry.size() == 0) - return false; - - for (const auto &entry : blocks_entry) - { - bytes += entry.block.size(); - for (const auto &tx_blob : entry.txs) - { - bytes += tx_blob.size(); - } - total_txs += entry.txs.size(); - } - m_bytes_to_sync += bytes; - while (!(stop_batch = m_db->batch_start(blocks_entry.size(), bytes))) { - m_blockchain_lock.unlock(); - m_tx_pool.unlock(); - epee::misc_utils::sleep_no_w(1000); - m_tx_pool.lock(); - m_blockchain_lock.lock(); - } - - if ((m_db->height() + blocks_entry.size()) < m_blocks_hash_check.size()) - return true; - - bool blocks_exist = false; - tools::threadpool& tpool = tools::threadpool::getInstance(); - uint64_t threads = tpool.get_max_concurrency(); - - if (blocks_entry.size() > 1 && threads > 1 && m_max_prepare_blocks_threads > 1) - { - // limit threads, default limit = 4 - if(threads > m_max_prepare_blocks_threads) - threads = m_max_prepare_blocks_threads; - - uint64_t height = m_db->height(); - int batches = blocks_entry.size() / threads; - int extra = blocks_entry.size() % threads; - MDEBUG("block_batches: " << batches); - std::vector> maps(threads); - std::vector < std::vector < block >> blocks(threads); - auto it = blocks_entry.begin(); - - for (uint64_t i = 0; i < threads; i++) - { - blocks[i].reserve(batches + 1); - for (int j = 0; j < batches; j++) - { - block block; - - if (!parse_and_validate_block_from_blob(it->block, block)) - { - std::advance(it, 1); - continue; - } - - // check first block and skip all blocks if its not chained properly - if (i == 0 && j == 0) - { - crypto::hash tophash = m_db->top_block_hash(); - if (block.prev_id != tophash) - { - MDEBUG("Skipping prepare blocks. New blocks don't belong to chain."); - return true; - } - } - if (have_block(get_block_hash(block))) - { - blocks_exist = true; - break; - } - - blocks[i].push_back(std::move(block)); - std::advance(it, 1); - } - } - - for (int i = 0; i < extra && !blocks_exist; i++) - { - block block; - - if (!parse_and_validate_block_from_blob(it->block, block)) - { - std::advance(it, 1); - continue; - } - - if (have_block(get_block_hash(block))) - { - blocks_exist = true; - break; - } - - blocks[i].push_back(std::move(block)); - std::advance(it, 1); - } - - if (!blocks_exist) - { - m_blocks_longhash_table.clear(); - uint64_t thread_height = height; - tools::threadpool::waiter waiter; - for (uint64_t i = 0; i < threads; i++) - { - tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])), true); - thread_height += blocks[i].size(); - } - - waiter.wait(&tpool); - - if (m_cancel) - return false; - - for (const auto & map : maps) - { - m_blocks_longhash_table.insert(map.begin(), map.end()); - } - } - } - - if (m_cancel) - return false; - - if (blocks_exist) - { - MDEBUG("Skipping prepare blocks. Blocks exist."); - return true; - } - - m_fake_scan_time = 0; - m_fake_pow_calc_time = 0; - - m_scan_table.clear(); - m_check_txin_table.clear(); - - TIME_MEASURE_FINISH(prepare); - m_fake_pow_calc_time = prepare / blocks_entry.size(); - - if (blocks_entry.size() > 1 && threads > 1 && m_show_time_stats) - MDEBUG("Prepare blocks took: " << prepare << " ms"); - - TIME_MEASURE_START(scantable); - - // [input] stores all unique amounts found - std::vector < uint64_t > amounts; - // [input] stores all absolute_offsets for each amount - std::map> offset_map; - // [output] stores all output_data_t for each absolute_offset - std::map> tx_map; - std::vector> txes(total_txs); - -#define SCAN_TABLE_QUIT(m) \ - do { \ - MERROR_VER(m) ;\ - m_scan_table.clear(); \ - return false; \ - } while(0); \ - - // generate sorted tables for all amounts and absolute offsets - size_t tx_index = 0; - for (const auto &entry : blocks_entry) - { - if (m_cancel) - return false; - - for (const auto &tx_blob : entry.txs) - { - if (tx_index >= txes.size()) - SCAN_TABLE_QUIT("tx_index is out of sync"); - transaction &tx = txes[tx_index].first; - crypto::hash &tx_prefix_hash = txes[tx_index].second; - ++tx_index; - - if (!parse_and_validate_tx_base_from_blob(tx_blob, tx)) - SCAN_TABLE_QUIT("Could not parse tx from incoming blocks."); - cryptonote::get_transaction_prefix_hash(tx, tx_prefix_hash); - - auto its = m_scan_table.find(tx_prefix_hash); - if (its != m_scan_table.end()) - SCAN_TABLE_QUIT("Duplicate tx found from incoming blocks."); - - m_scan_table.emplace(tx_prefix_hash, std::unordered_map>()); - its = m_scan_table.find(tx_prefix_hash); - assert(its != m_scan_table.end()); - - // get all amounts from tx.vin(s) - for (const auto &txin : tx.vin) - { - const txin_to_key &in_to_key = boost::get < txin_to_key > (txin); - - // check for duplicate - auto it = its->second.find(in_to_key.k_image); - if (it != its->second.end()) - SCAN_TABLE_QUIT("Duplicate key_image found from incoming blocks."); - - amounts.push_back(in_to_key.amount); - } - - // sort and remove duplicate amounts from amounts list - std::sort(amounts.begin(), amounts.end()); - auto last = std::unique(amounts.begin(), amounts.end()); - amounts.erase(last, amounts.end()); - - // add amount to the offset_map and tx_map - for (const uint64_t &amount : amounts) - { - if (offset_map.find(amount) == offset_map.end()) - offset_map.emplace(amount, std::vector()); - - if (tx_map.find(amount) == tx_map.end()) - tx_map.emplace(amount, std::vector()); - } - - // add new absolute_offsets to offset_map - for (const auto &txin : tx.vin) - { - const txin_to_key &in_to_key = boost::get < txin_to_key > (txin); - // no need to check for duplicate here. - auto absolute_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets); - for (const auto & offset : absolute_offsets) - offset_map[in_to_key.amount].push_back(offset); - - } - } - } - - // sort and remove duplicate absolute_offsets in offset_map - for (auto &offsets : offset_map) - { - std::sort(offsets.second.begin(), offsets.second.end()); - auto last = std::unique(offsets.second.begin(), offsets.second.end()); - offsets.second.erase(last, offsets.second.end()); - } - - // [output] stores all transactions for each tx_out_index::hash found - std::vector> transactions(amounts.size()); - - threads = tpool.get_max_concurrency(); - if (!m_db->can_thread_bulk_indices()) - threads = 1; - - if (threads > 1) - { - tools::threadpool::waiter waiter; - - for (size_t i = 0; i < amounts.size(); i++) - { - uint64_t amount = amounts[i]; - tpool.submit(&waiter, boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i])), true); - } - waiter.wait(&tpool); - } - else - { - for (size_t i = 0; i < amounts.size(); i++) - { - uint64_t amount = amounts[i]; - output_scan_worker(amount, offset_map[amount], tx_map[amount], transactions[i]); - } - } - - // now generate a table for each tx_prefix and k_image hashes - tx_index = 0; - for (const auto &entry : blocks_entry) - { - if (m_cancel) - return false; - - for (const auto &tx_blob : entry.txs) - { - if (tx_index >= txes.size()) - SCAN_TABLE_QUIT("tx_index is out of sync"); - const transaction &tx = txes[tx_index].first; - const crypto::hash &tx_prefix_hash = txes[tx_index].second; - ++tx_index; - - auto its = m_scan_table.find(tx_prefix_hash); - if (its == m_scan_table.end()) - SCAN_TABLE_QUIT("Tx not found on scan table from incoming blocks."); - - for (const auto &txin : tx.vin) - { - const txin_to_key &in_to_key = boost::get < txin_to_key > (txin); - auto needed_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets); - - std::vector outputs; - for (const uint64_t & offset_needed : needed_offsets) - { - size_t pos = 0; - bool found = false; - - for (const uint64_t &offset_found : offset_map[in_to_key.amount]) - { - if (offset_needed == offset_found) - { - found = true; - break; - } - - ++pos; - } - - if (found && pos < tx_map[in_to_key.amount].size()) - outputs.push_back(tx_map[in_to_key.amount].at(pos)); - else - break; - } - - its->second.emplace(in_to_key.k_image, outputs); - } - } - } - - TIME_MEASURE_FINISH(scantable); - if (total_txs > 0) - { - m_fake_scan_time = scantable / total_txs; - if(m_show_time_stats) - MDEBUG("Prepare scantable took: " << scantable << " ms"); - } - - return true; -} - -void Blockchain::add_txpool_tx(transaction &tx, const txpool_tx_meta_t &meta) -{ - m_db->add_txpool_tx(tx, meta); -} - -void Blockchain::update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta) -{ - m_db->update_txpool_tx(txid, meta); -} - -void Blockchain::remove_txpool_tx(const crypto::hash &txid) -{ - m_db->remove_txpool_tx(txid); -} - -uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const -{ - return m_db->get_txpool_tx_count(include_unrelayed_txes); -} - -bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const -{ - return m_db->get_txpool_tx_meta(txid, meta); -} - -bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const -{ - return m_db->get_txpool_tx_blob(txid, bd); -} - -cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) const -{ - return m_db->get_txpool_tx_blob(txid); -} - -bool Blockchain::for_all_txpool_txes(std::function f, bool include_blob, bool include_unrelayed_txes) const -{ - return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes); -} - -void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, blockchain_db_sync_mode sync_mode, bool fast_sync) -{ - if (sync_mode == db_defaultsync) - { - m_db_default_sync = true; - sync_mode = db_async; - } - m_db_sync_mode = sync_mode; - m_fast_sync = fast_sync; - m_db_sync_on_blocks = sync_on_blocks; - m_db_sync_threshold = sync_threshold; - m_max_prepare_blocks_threads = maxthreads; -} - -void Blockchain::safesyncmode(const bool onoff) -{ - /* all of this is no-op'd if the user set a specific - * --db-sync-mode at startup. - */ - if (m_db_default_sync) - { - m_db->safesyncmode(onoff); - m_db_sync_mode = onoff ? db_nosync : db_async; - } -} - -HardFork::State Blockchain::get_hard_fork_state() const -{ - return m_hardfork->get_state(); -} - -const std::vector& Blockchain::get_hard_fork_heights(network_type nettype) -{ - static const std::vector mainnet_heights = []() - { - std::vector heights; - for (const auto& i : mainnet_hard_forks) - heights.emplace_back(i.version, i.height, i.threshold, i.time); - return heights; - }(); - static const std::vector testnet_heights = []() - { - std::vector heights; - for (const auto& i : testnet_hard_forks) - heights.emplace_back(i.version, i.height, i.threshold, i.time); - return heights; - }(); - static const std::vector stagenet_heights = []() - { - std::vector heights; - for (const auto& i : stagenet_hard_forks) - heights.emplace_back(i.version, i.height, i.threshold, i.time); - return heights; - }(); - static const std::vector dummy; - switch (nettype) - { - case MAINNET: return mainnet_heights; - case TESTNET: return testnet_heights; - case STAGENET: return stagenet_heights; - default: return dummy; - } -} - -bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const -{ - return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); -} - -uint64_t Blockchain::get_difficulty_target() const -{ - return get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; -} - -std::map> Blockchain:: get_output_histogram(const std::vector &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const -{ - return m_db->get_output_histogram(amounts, unlocked, recent_cutoff, min_count); -} - -std::list>> Blockchain::get_alternative_chains() const -{ - std::list>> chains; - - for (const auto &i: m_alternative_chains) - { - const crypto::hash &top = i.first; - bool found = false; - for (const auto &j: m_alternative_chains) - { - if (j.second.bl.prev_id == top) - { - found = true; - break; - } - } - if (!found) - { - std::vector chain; - auto h = i.second.bl.prev_id; - chain.push_back(top); - blocks_ext_by_hash::const_iterator prev; - while ((prev = m_alternative_chains.find(h)) != m_alternative_chains.end()) - { - chain.push_back(h); - h = prev->second.bl.prev_id; - } - chains.push_back(std::make_pair(i.second, chain)); - } - } - return chains; -} - -void Blockchain::cancel() -{ - m_cancel = true; -} - -#if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "570ce2357b08fadac6058e34f95c5e08323f9325de260d07b091a281a948a7b0"; -void Blockchain::load_compiled_in_block_hashes() -{ - const bool testnet = m_nettype == TESTNET; - const bool stagenet = m_nettype == STAGENET; - if (m_fast_sync && get_blocks_dat_start(testnet, stagenet) != nullptr && get_blocks_dat_size(testnet, stagenet) > 0) - { - MINFO("Loading precomputed blocks (" << get_blocks_dat_size(testnet, stagenet) << " bytes)"); - - if (m_nettype == MAINNET) - { - // first check hash - crypto::hash hash; - if (!tools::sha256sum(get_blocks_dat_start(testnet, stagenet), get_blocks_dat_size(testnet, stagenet), hash)) - { - MERROR("Failed to hash precomputed blocks data"); - return; - } - MINFO("precomputed blocks hash: " << hash << ", expected " << expected_block_hashes_hash); - cryptonote::blobdata expected_hash_data; - if (!epee::string_tools::parse_hexstr_to_binbuff(std::string(expected_block_hashes_hash), expected_hash_data) || expected_hash_data.size() != sizeof(crypto::hash)) - { - MERROR("Failed to parse expected block hashes hash"); - return; - } - const crypto::hash expected_hash = *reinterpret_cast(expected_hash_data.data()); - if (hash != expected_hash) - { - MERROR("Block hash data does not match expected hash"); - return; - } - } - - if (get_blocks_dat_size(testnet, stagenet) > 4) - { - const unsigned char *p = get_blocks_dat_start(testnet, stagenet); - const uint32_t nblocks = *p | ((*(p+1))<<8) | ((*(p+2))<<16) | ((*(p+3))<<24); - if (nblocks > (std::numeric_limits::max() - 4) / sizeof(hash)) - { - MERROR("Block hash data is too large"); - return; - } - const size_t size_needed = 4 + nblocks * sizeof(crypto::hash); - if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && get_blocks_dat_size(testnet, stagenet) >= size_needed) - { - p += sizeof(uint32_t); - m_blocks_hash_of_hashes.reserve(nblocks); - for (uint32_t i = 0; i < nblocks; i++) - { - crypto::hash hash; - memcpy(hash.data, p, sizeof(hash.data)); - p += sizeof(hash.data); - m_blocks_hash_of_hashes.push_back(hash); - } - m_blocks_hash_check.resize(m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP, crypto::null_hash); - MINFO(nblocks << " block hashes loaded"); - - // FIXME: clear tx_pool because the process might have been - // terminated and caused it to store txs kept by blocks. - // The core will not call check_tx_inputs(..) for these - // transactions in this case. Consequently, the sanity check - // for tx hashes will fail in handle_block_to_main_chain(..) - CRITICAL_REGION_LOCAL(m_tx_pool); - - std::vector txs; - m_tx_pool.get_transactions(txs); - - size_t tx_weight; - uint64_t fee; - bool relayed, do_not_relay, double_spend_seen; - transaction pool_tx; - for(const transaction &tx : txs) - { - crypto::hash tx_hash = get_transaction_hash(tx); - m_tx_pool.take_tx(tx_hash, pool_tx, tx_weight, fee, relayed, do_not_relay, double_spend_seen); - } - } - } - } -} -#endif - -bool Blockchain::is_within_compiled_block_hash_area(uint64_t height) const -{ -#if defined(PER_BLOCK_CHECKPOINT) - return height < m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP; -#else - return false; -#endif -} - -void Blockchain::lock() -{ - m_blockchain_lock.lock(); -} - -void Blockchain::unlock() -{ - m_blockchain_lock.unlock(); -} - -bool Blockchain::for_all_key_images(std::function f) const -{ - return m_db->for_all_key_images(f); -} - -bool Blockchain::for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function f) const -{ - return m_db->for_blocks_range(h1, h2, f); -} - -bool Blockchain::for_all_transactions(std::function f, bool pruned) const -{ - return m_db->for_all_transactions(f, pruned); -} - -bool Blockchain::for_all_outputs(std::function f) const -{ - return m_db->for_all_outputs(f);; -} - -bool Blockchain::for_all_outputs(uint64_t amount, std::function f) const -{ - return m_db->for_all_outputs(amount, f);; -} - -void Blockchain::invalidate_block_template_cache() -{ - MDEBUG("Invalidating block template cache"); - m_btc_valid = false; -} - -void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie) -{ - MDEBUG("Setting block template cache"); - m_btc = b; - m_btc_address = address; - m_btc_nonce = nonce; - m_btc_difficulty = diff; - m_btc_expected_reward = expected_reward; - m_btc_pool_cookie = pool_cookie; - m_btc_valid = true; -} - -namespace cryptonote { -template bool Blockchain::get_transactions(const std::vector&, std::vector&, std::vector&) const; -template bool Blockchain::get_transactions_blobs(const std::vector&, std::vector&, std::vector&, bool) const; -} diff --git a/src/cn_utils/cryptonote_core/blockchain.h b/src/cn_utils/cryptonote_core/blockchain.h deleted file mode 100644 index a79ff22a1..000000000 --- a/src/cn_utils/cryptonote_core/blockchain.h +++ /dev/null @@ -1,1423 +0,0 @@ -// Copyright (c) 2014-2018, 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "syncobj.h" -#include "string_tools.h" -#include "cryptonote_basic/cryptonote_basic.h" -#include "common/util.h" -#include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "rpc/core_rpc_server_commands_defs.h" -#include "cryptonote_basic/difficulty.h" -#include "cryptonote_tx_utils.h" -#include "cryptonote_basic/verification_context.h" -#include "crypto/hash.h" -#include "checkpoints/checkpoints.h" -#include "cryptonote_basic/hardfork.h" -#include "blockchain_db/blockchain_db.h" - -namespace tools { class Notify; } - -namespace cryptonote -{ - class tx_memory_pool; - struct test_options; - - /** Declares ways in which the BlockchainDB backend should be told to sync - * - */ - enum blockchain_db_sync_mode - { - db_defaultsync, //!< user didn't specify, use db_async - db_sync, //!< handle syncing calls instead of the backing db, synchronously - db_async, //!< handle syncing calls instead of the backing db, asynchronously - db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O) - }; - - /************************************************************************/ - /* */ - /************************************************************************/ - class Blockchain - { - public: - /** - * @brief Now-defunct (TODO: remove) struct from in-memory blockchain - */ - struct transaction_chain_entry - { - transaction tx; - uint64_t m_keeper_block_height; - size_t m_blob_size; - std::vector m_global_output_indexes; - }; - - /** - * @brief container for passing a block and metadata about it on the blockchain - */ - struct block_extended_info - { - block bl; //!< the block - uint64_t height; //!< the height of the block in the blockchain - size_t block_cumulative_weight; //!< the weight of the block - difficulty_type cumulative_difficulty; //!< the accumulated difficulty after that block - uint64_t already_generated_coins; //!< the total coins minted after that block - }; - - /** - * @brief Blockchain constructor - * - * @param tx_pool a reference to the transaction pool to be kept by the Blockchain - */ - Blockchain(tx_memory_pool& tx_pool); - - /** - * @brief Initialize the Blockchain state - * - * @param db a pointer to the backing store to use for the blockchain - * @param nettype network type - * @param offline true if running offline, else false - * @param test_options test parameters - * @param fixed_difficulty fixed difficulty for testing purposes; 0 means disabled - * - * @return true on success, false if any initialization steps fail - */ - bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0); - - /** - * @brief Initialize the Blockchain state - * - * @param db a pointer to the backing store to use for the blockchain - * @param hf a structure containing hardfork information - * @param nettype network type - * @param offline true if running offline, else false - * - * @return true on success, false if any initialization steps fail - */ - bool init(BlockchainDB* db, HardFork*& hf, const network_type nettype = MAINNET, bool offline = false); - - /** - * @brief Uninitializes the blockchain state - * - * Saves to disk any state that needs to be maintained - * - * @return true on success, false if any uninitialization steps fail - */ - bool deinit(); - - /** - * @brief assign a set of blockchain checkpoint hashes - * - * @param chk_pts the set of checkpoints to assign - */ - void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - - /** - * @brief get blocks and transactions from blocks based on start height and count - * - * @param start_offset the height on the blockchain to start at - * @param count the number of blocks to get, if there are as many after start_offset - * @param blocks return-by-reference container to put result blocks in - * @param txs return-by-reference container to put result transactions in - * - * @return false if start_offset > blockchain height, else true - */ - bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const; - - /** - * @brief get blocks from blocks based on start height and count - * - * @param start_offset the height on the blockchain to start at - * @param count the number of blocks to get, if there are as many after start_offset - * @param blocks return-by-reference container to put result blocks in - * - * @return false if start_offset > blockchain height, else true - */ - bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks) const; - - /** - * @brief compiles a list of all blocks stored as alternative chains - * - * @param blocks return-by-reference container to put result blocks in - * - * @return true - */ - bool get_alternative_blocks(std::vector& blocks) const; - - /** - * @brief returns the number of alternative blocks stored - * - * @return the number of alternative blocks stored - */ - size_t get_alternative_blocks_count() const; - - /** - * @brief gets a block's hash given a height - * - * @param height the height of the block - * - * @return the hash of the block at the requested height, or a zeroed hash if there is no such block - */ - crypto::hash get_block_id_by_height(uint64_t height) const; - - /** - * @brief gets the block with a given hash - * - * @param h the hash to look for - * @param blk return-by-reference variable to put result block in - * @param orphan if non-NULL, will be set to true if not in the main chain, false otherwise - * - * @return true if the block was found, else false - */ - bool get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan = NULL) const; - - /** - * @brief performs some preprocessing on a group of incoming blocks to speed up verification - * - * @param blocks a list of incoming blocks - * - * @return false on erroneous blocks, else true - */ - bool prepare_handle_incoming_blocks(const std::vector &blocks); - - /** - * @brief incoming blocks post-processing, cleanup, and disk sync - * - * @param force_sync if true, and Blockchain is handling syncing to disk, always sync - * - * @return true - */ - bool cleanup_handle_incoming_blocks(bool force_sync = false); - - /** - * @brief search the blockchain for a transaction by hash - * - * @param id the hash to search for - * - * @return true if the tx exists, else false - */ - bool have_tx(const crypto::hash &id) const; - - /** - * @brief check if any key image in a transaction has already been spent - * - * @param tx the transaction to check - * - * @return true if any key image is already spent in the blockchain, else false - */ - bool have_tx_keyimges_as_spent(const transaction &tx) const; - - /** - * @brief check if a key image is already spent on the blockchain - * - * Whenever a transaction output is used as an input for another transaction - * (a true input, not just one of a mixing set), a key image is generated - * and stored in the transaction in order to prevent double spending. If - * this key image is seen again, the transaction using it is rejected. - * - * @param key_im the key image to search for - * - * @return true if the key image is already spent in the blockchain, else false - */ - bool have_tx_keyimg_as_spent(const crypto::key_image &key_im) const; - - /** - * @brief get the current height of the blockchain - * - * @return the height - */ - uint64_t get_current_blockchain_height() const; - - /** - * @brief get the hash of the most recent block on the blockchain - * - * @return the hash - */ - crypto::hash get_tail_id() const; - - /** - * @brief get the height and hash of the most recent block on the blockchain - * - * @param height return-by-reference variable to store the height in - * - * @return the hash - */ - crypto::hash get_tail_id(uint64_t& height) const; - - /** - * @brief returns the difficulty target the next block to be added must meet - * - * @return the target - */ - difficulty_type get_difficulty_for_next_block(); - - /** - * @brief adds a block to the blockchain - * - * Adds a new block to the blockchain. If the block's parent is not the - * current top of the blockchain, the block may be added to an alternate - * chain. If the block does not belong, is already in the blockchain - * or an alternate chain, or is invalid, return false. - * - * @param bl_ the block to be added - * @param bvc metadata about the block addition's success/failure - * - * @return true on successful addition to the blockchain, else false - */ - bool add_new_block(const block& bl_, block_verification_context& bvc); - - /** - * @brief clears the blockchain and starts a new one - * - * @param b the first block in the new chain (the genesis block) - * - * @return true on success, else false - */ - bool reset_and_set_genesis_block(const block& b); - - /** - * @brief creates a new block to mine against - * - * @param b return-by-reference block to be filled in - * @param miner_address address new coins for the block will go to - * @param di return-by-reference tells the miner what the difficulty target is - * @param height return-by-reference tells the miner what height it's mining against - * @param expected_reward return-by-reference the total reward awarded to the miner finding this block, including transaction fees - * @param ex_nonce extra data to be added to the miner transaction's extra - * - * @return true if block template filled in successfully, else false - */ - bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); - - /** - * @brief checks if a block is known about with a given hash - * - * This function checks the main chain, alternate chains, and invalid blocks - * for a block with the given hash - * - * @param id the hash to search for - * - * @return true if the block is known, else false - */ - bool have_block(const crypto::hash& id) const; - - /** - * @brief gets the total number of transactions on the main chain - * - * @return the number of transactions on the main chain - */ - size_t get_total_transactions() const; - - /** - * @brief gets the hashes for a subset of the blockchain - * - * puts into list a list of hashes representing certain blocks - * from the blockchain in reverse chronological order - * - * the blocks chosen, at the time of this writing, are: - * the most recent 11 - * powers of 2 less recent from there, so 13, 17, 25, etc... - * - * @param ids return-by-reference list to put the resulting hashes in - * - * @return true - */ - bool get_short_chain_history(std::list& ids) const; - - /** - * @brief get recent block hashes for a foreign chain - * - * Find the split point between us and foreign blockchain and return - * (by reference) the most recent common block hash along with up to - * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. - * - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param hashes the hashes to be returned, return-by-reference - * @param start_height the start height, return-by-reference - * @param current_height the current blockchain height, return-by-reference - * - * @return true if a block found in common, else false - */ - bool find_blockchain_supplement(const std::list& qblock_ids, std::vector& hashes, uint64_t& start_height, uint64_t& current_height) const; - - /** - * @brief get recent block hashes for a foreign chain - * - * Find the split point between us and foreign blockchain and return - * (by reference) the most recent common block hash along with up to - * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. - * - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param resp return-by-reference the split height and subsequent blocks' hashes - * - * @return true if a block found in common, else false - */ - bool find_blockchain_supplement(const std::list& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; - - /** - * @brief find the most recent common point between ours and a foreign chain - * - * This function takes a list of block hashes from another node - * on the network to find where the split point is between us and them. - * This is used to see what to send another node that needs to sync. - * - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param starter_offset return-by-reference the most recent common block's height - * - * @return true if a block found in common, else false - */ - bool find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) const; - - /** - * @brief get recent blocks for a foreign chain - * - * This function gets recent blocks relative to a foreign chain, starting either at - * a requested height or whatever height is the most recent ours and the foreign - * chain have in common. - * - * @param req_start_block if non-zero, specifies a start point (otherwise find most recent commonality) - * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) - * @param blocks return-by-reference the blocks and their transactions - * @param total_height return-by-reference our current blockchain height - * @param start_height return-by-reference the height of the first block returned - * @param pruned whether to return full or pruned tx blobs - * @param max_count the max number of blocks to get - * - * @return true if a block found in common or req_start_block specified, else false - */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_count) const; - - /** - * @brief retrieves a set of blocks and their transactions, and possibly other transactions - * - * the request object encapsulates a list of block hashes and a (possibly empty) list of - * transaction hashes. for each block hash, the block is fetched along with all of that - * block's transactions. Any transactions requested separately are fetched afterwards. - * - * @param arg the request - * @param rsp return-by-reference the response to fill in - * - * @return true unless any blocks or transactions are missing - */ - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - - /** - * @brief get number of outputs of an amount past the minimum spendable age - * - * @param amount the output amount - * - * @return the number of mature outputs - */ - uint64_t get_num_mature_outputs(uint64_t amount) const; - - /** - * @brief get the public key for an output - * - * @param amount the output amount - * @param global_index the output amount-global index - * - * @return the public key - */ - crypto::public_key get_output_key(uint64_t amount, uint64_t global_index) const; - - /** - * @brief gets specific outputs to mix with - * - * This function takes an RPC request for outputs to mix with - * and creates an RPC response with the resultant output indices. - * - * Outputs to mix with are specified in the request. - * - * @param req the outputs to return - * @param res return-by-reference the resultant output indices and keys - * - * @return true - */ - bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; - - /** - * @brief gets an output's key and unlocked state - * - * @param amount in - the output amount - * @param index in - the output global amount index - * @param mask out - the output's RingCT mask - * @param key out - the output's key - * @param unlocked out - the output's unlocked state - */ - void get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const; - - /** - * @brief gets per block distribution of outputs of a given amount - * - * @param amount the amount to get a ditribution for - * @param from_height the height before which we do not care about the data - * @param to_height the height after which we do not care about the data - * @param return-by-reference start_height the height of the first rct output - * @param return-by-reference distribution the start offset of the first rct output in this block (same as previous if none) - * @param return-by-reference base how many outputs of that amount are before the stated distribution - */ - bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector &distribution, uint64_t &base) const; - - /** - * @brief gets the global indices for outputs from a given transaction - * - * This function gets the global indices for all outputs belonging - * to a specific transaction. - * - * @param tx_id the hash of the transaction to fetch indices for - * @param indexs return-by-reference the global indices for the transaction's outputs - * - * @return false if the transaction does not exist, or if no indices are found, otherwise true - */ - bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector& indexs) const; - - /** - * @brief stores the blockchain - * - * If Blockchain is handling storing of the blockchain (rather than BlockchainDB), - * this initiates a blockchain save. - * - * @return true unless saving the blockchain fails - */ - bool store_blockchain(); - - /** - * @brief validates a transaction's inputs - * - * validates a transaction's inputs as correctly used and not previously - * spent. also returns the hash and height of the most recent block - * which contains an output that was used as an input to the transaction. - * The transaction's rct signatures, if any, are expanded. - * - * @param tx the transaction to validate - * @param pmax_used_block_height return-by-reference block height of most recent input - * @param max_used_block_id return-by-reference block hash of most recent input - * @param tvc returned information about tx verification - * @param kept_by_block whether or not the transaction is from a previously-verified block - * - * @return false if any input is invalid, otherwise true - */ - bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); - - /** - * @brief get fee quantization mask - * - * The dynamic fee may be quantized, to mask out the last decimal places - * - * @return the fee quantized mask - */ - static uint64_t get_fee_quantization_mask(); - - /** - * @brief get dynamic per kB or byte fee for a given block weight - * - * The dynamic fee is based on the block weight in a past window, and - * the current block reward. It is expressed by kB before v8, and - * per byte from v8. - * - * @param block_reward the current block reward - * @param median_block_weight the median block weight in the past window - * @param version hard fork version for rules and constants to use - * - * @return the fee - */ - static uint64_t get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version); - - /** - * @brief get dynamic per kB or byte fee estimate for the next few blocks - * - * The dynamic fee is based on the block weight in a past window, and - * the current block reward. It is expressed by kB before v8, and - * per byte from v8. - * This function calculates an estimate for a dynamic fee which will be - * valid for the next grace_blocks - * - * @param grace_blocks number of blocks we want the fee to be valid for - * - * @return the fee estimate - */ - uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const; - - /** - * @brief validate a transaction's fee - * - * This function validates the fee is enough for the transaction. - * This is based on the weight of the transaction, and, after a - * height threshold, on the average weight of transaction in a past window - * - * @param tx_weight the transaction weight - * @param fee the fee - * - * @return true if the fee is enough, false otherwise - */ - bool check_fee(size_t tx_weight, uint64_t fee) const; - - /** - * @brief check that a transaction's outputs conform to current standards - * - * This function checks, for example at the time of this writing, that - * each output is of the form a * 10^b (phrased differently, that if - * written out would have only one non-zero digit in base 10). - * - * @param tx the transaction to check the outputs of - * @param tvc returned info about tx verification - * - * @return false if any outputs do not conform, otherwise true - */ - bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc); - - /** - * @brief gets the block weight limit based on recent blocks - * - * @return the limit - */ - uint64_t get_current_cumulative_block_weight_limit() const; - - /** - * @brief gets the long term block weight for a new block - * - * @return the long term block weight - */ - uint64_t get_next_long_term_block_weight(uint64_t block_weight) const; - - /** - * @brief gets the block weight median based on recent blocks (same window as for the limit) - * - * @return the median - */ - uint64_t get_current_cumulative_block_weight_median() const; - - /** - * @brief gets the difficulty of the block with a given height - * - * @param i the height - * - * @return the difficulty - */ - uint64_t block_difficulty(uint64_t i) const; - - /** - * @brief gets blocks based on a list of block hashes - * - * @tparam t_ids_container a standard-iterable container - * @tparam t_blocks_container a standard-iterable container - * @tparam t_missed_container a standard-iterable container - * @param block_ids a container of block hashes for which to get the corresponding blocks - * @param blocks return-by-reference a container to store result blocks in - * @param missed_bs return-by-reference a container to store missed blocks in - * - * @return false if an unexpected exception occurs, else true - */ - template - bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const; - - /** - * @brief gets transactions based on a list of transaction hashes - * - * @tparam t_ids_container a standard-iterable container - * @tparam t_tx_container a standard-iterable container - * @tparam t_missed_container a standard-iterable container - * @param txs_ids a container of hashes for which to get the corresponding transactions - * @param txs return-by-reference a container to store result transactions in - * @param missed_txs return-by-reference a container to store missed transactions in - * @param pruned whether to return full or pruned blobs - * - * @return false if an unexpected exception occurs, else true - */ - template - bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned = false) const; - template - bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; - - //debug functions - - /** - * @brief check the blockchain against a set of checkpoints - * - * If a block fails a checkpoint and enforce is enabled, the blockchain - * will be rolled back to two blocks prior to that block. If enforce - * is disabled, as is currently the default case with DNS-based checkpoints, - * an error will be printed to the user but no other action will be taken. - * - * @param points the checkpoints to check against - * @param enforce whether or not to take action on failure - */ - void check_against_checkpoints(const checkpoints& points, bool enforce); - - /** - * @brief configure whether or not to enforce DNS-based checkpoints - * - * @param enforce the new enforcement setting - */ - void set_enforce_dns_checkpoints(bool enforce); - - /** - * @brief loads new checkpoints from a file and optionally from DNS - * - * @param file_path the path of the file to look for and load checkpoints from - * @param check_dns whether or not to check for new DNS-based checkpoints - * - * @return false if any enforced checkpoint type fails to load, otherwise true - */ - bool update_checkpoints(const std::string& file_path, bool check_dns); - - - // user options, must be called before calling init() - - /** - * @brief sets various performance options - * - * @param maxthreads max number of threads when preparing blocks for addition - * @param sync_on_blocks whether to sync based on blocks or bytes - * @param sync_threshold number of blocks/bytes to cache before syncing to database - * @param sync_mode the ::blockchain_db_sync_mode to use - * @param fast_sync sync using built-in block hashes as trusted - */ - void set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, - blockchain_db_sync_mode sync_mode, bool fast_sync); - - /** - * @brief sets a block notify object to call for every new block - * - * @param notify the notify object to call at every new block - */ - void set_block_notify(const std::shared_ptr ¬ify) { m_block_notify = notify; } - - /** - * @brief sets a reorg notify object to call for every reorg - * - * @param notify the notify object to call at every reorg - */ - void set_reorg_notify(const std::shared_ptr ¬ify) { m_reorg_notify = notify; } - - /** - * @brief Put DB in safe sync mode - */ - void safesyncmode(const bool onoff); - - /** - * @brief set whether or not to show/print time statistics - * - * @param stats the new time stats setting - */ - void set_show_time_stats(bool stats) { m_show_time_stats = stats; } - - /** - * @brief gets the hardfork voting state object - * - * @return the HardFork object - */ - HardFork::State get_hard_fork_state() const; - - /** - * @brief gets the hardfork heights of given network - * - * @return the HardFork object - */ - static const std::vector& get_hard_fork_heights(network_type nettype); - - /** - * @brief gets the current hardfork version in use/voted for - * - * @return the version - */ - uint8_t get_current_hard_fork_version() const { return m_hardfork->get_current_version(); } - - /** - * @brief returns the newest hardfork version known to the blockchain - * - * @return the version - */ - uint8_t get_ideal_hard_fork_version() const { return m_hardfork->get_ideal_version(); } - - /** - * @brief returns the next hardfork version - * - * @return the version - */ - uint8_t get_next_hard_fork_version() const { return m_hardfork->get_next_version(); } - - /** - * @brief returns the newest hardfork version voted to be enabled - * as of a certain height - * - * @param height the height for which to check version info - * - * @return the version - */ - uint8_t get_ideal_hard_fork_version(uint64_t height) const { return m_hardfork->get_ideal_version(height); } - - /** - * @brief returns the actual hardfork version for a given block height - * - * @param height the height for which to check version info - * - * @return the version - */ - uint8_t get_hard_fork_version(uint64_t height) const { return m_hardfork->get(height); } - - /** - * @brief returns the earliest block a given version may activate - * - * @return the height - */ - uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); } - - /** - * @brief get information about hardfork voting for a version - * - * @param version the version in question - * @param window the size of the voting window - * @param votes the number of votes to enable - * @param threshold the number of votes required to enable - * @param earliest_height the earliest height at which is allowed - * @param voting which version this node is voting for/using - * - * @return whether the version queried is enabled - */ - bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; - - /** - * @brief get difficulty target based on chain and hardfork version - * - * @return difficulty target - */ - uint64_t get_difficulty_target() const; - - /** - * @brief remove transactions from the transaction pool (if present) - * - * @param txids a list of hashes of transactions to be removed - * - * @return false if any removals fail, otherwise true - */ - bool flush_txes_from_pool(const std::vector &txids); - - /** - * @brief return a histogram of outputs on the blockchain - * - * @param amounts optional set of amounts to lookup - * @param unlocked whether to restrict instances to unlocked ones - * @param recent_cutoff timestamp to consider outputs as recent - * @param min_count return only amounts with at least that many instances - * - * @return a set of amount/instances - */ - std::map> get_output_histogram(const std::vector &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count = 0) const; - - /** - * @brief perform a check on all key images in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any key image fails the check, otherwise true - */ - bool for_all_key_images(std::function) const; - - /** - * @brief perform a check on all blocks in the blockchain in the given range - * - * @param h1 the start height - * @param h2 the end height - * @param std::function the check to perform, pass/fail - * - * @return false if any block fails the check, otherwise true - */ - bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function) const; - - /** - * @brief perform a check on all transactions in the blockchain - * - * @param std::function the check to perform, pass/fail - * @param bool pruned whether to return pruned txes only - * - * @return false if any transaction fails the check, otherwise true - */ - bool for_all_transactions(std::function, bool pruned) const; - - /** - * @brief perform a check on all outputs in the blockchain - * - * @param std::function the check to perform, pass/fail - * - * @return false if any output fails the check, otherwise true - */ - bool for_all_outputs(std::function) const; - - /** - * @brief perform a check on all outputs of a given amount in the blockchain - * - * @param amount the amount to iterate through - * @param std::function the check to perform, pass/fail - * - * @return false if any output fails the check, otherwise true - */ - bool for_all_outputs(uint64_t amount, std::function) const; - - /** - * @brief get a reference to the BlockchainDB in use by Blockchain - * - * @return a reference to the BlockchainDB instance - */ - const BlockchainDB& get_db() const - { - return *m_db; - } - - /** - * @brief get a reference to the BlockchainDB in use by Blockchain - * - * @return a reference to the BlockchainDB instance - */ - BlockchainDB& get_db() - { - return *m_db; - } - - /** - * @brief get a number of outputs of a specific amount - * - * @param amount the amount - * @param offsets the indices (indexed to the amount) of the outputs - * @param outputs return-by-reference the outputs collected - * @param txs unused, candidate for removal - */ - void output_scan_worker(const uint64_t amount,const std::vector &offsets, - std::vector &outputs, std::unordered_map &txs) const; - - /** - * @brief computes the "short" and "long" hashes for a set of blocks - * - * @param height the height of the first block - * @param blocks the blocks to be hashed - * @param map return-by-reference the hashes for each block - */ - void block_longhash_worker(uint64_t height, const std::vector &blocks, - std::unordered_map &map) const; - - /** - * @brief returns a set of known alternate chains - * - * @return a list of chains - */ - std::list>> get_alternative_chains() const; - - void add_txpool_tx(transaction &tx, const txpool_tx_meta_t &meta); - void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta); - void remove_txpool_tx(const crypto::hash &txid); - uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const; - bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const; - bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; - cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; - bool for_all_txpool_txes(std::function, bool include_blob = false, bool include_unrelayed_txes = true) const; - - bool is_within_compiled_block_hash_area(uint64_t height) const; - bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } - uint64_t prevalidate_block_hashes(uint64_t height, const std::vector &hashes); - - void lock(); - void unlock(); - - void cancel(); - - /** - * @brief called when we see a tx originating from a block - * - * Used for handling txes from historical blocks in a fast way - */ - void on_new_tx_from_block(const cryptonote::transaction &tx); - - /** - * @brief returns the timestamps of the last N blocks - */ - std::vector get_last_block_timestamps(unsigned int blocks) const; - -#ifndef IN_UNIT_TESTS - private: -#endif - - // TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage - typedef std::unordered_map blocks_by_id_index; - - typedef std::unordered_map transactions_container; - - typedef std::unordered_set key_images_container; - - typedef std::vector blocks_container; - - typedef std::unordered_map blocks_ext_by_hash; - - typedef std::unordered_map blocks_by_hash; - - typedef std::map>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction - - - BlockchainDB* m_db; - - tx_memory_pool& m_tx_pool; - - mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock - - // main chain - transactions_container m_transactions; - size_t m_current_block_cumul_weight_limit; - size_t m_current_block_cumul_weight_median; - - // metadata containers - std::unordered_map>> m_scan_table; - std::unordered_map m_blocks_longhash_table; - std::unordered_map> m_check_txin_table; - - // SHA-3 hashes for each block and for fast pow checking - std::vector m_blocks_hash_of_hashes; - std::vector m_blocks_hash_check; - std::vector m_blocks_txs_check; - - blockchain_db_sync_mode m_db_sync_mode; - bool m_fast_sync; - bool m_show_time_stats; - bool m_db_default_sync; - bool m_db_sync_on_blocks; - uint64_t m_db_sync_threshold; - uint64_t m_max_prepare_blocks_threads; - uint64_t m_fake_pow_calc_time; - uint64_t m_fake_scan_time; - uint64_t m_sync_counter; - uint64_t m_bytes_to_sync; - std::vector m_timestamps; - std::vector m_difficulties; - uint64_t m_timestamps_and_difficulties_height; - uint64_t m_long_term_block_weights_window; - uint64_t m_long_term_effective_median_block_weight; - - epee::critical_section m_difficulty_lock; - crypto::hash m_difficulty_for_next_block_top_hash; - difficulty_type m_difficulty_for_next_block; - - boost::asio::io_service m_async_service; - boost::thread_group m_async_pool; - std::unique_ptr m_async_work_idle; - - // all alternative chains - blocks_ext_by_hash m_alternative_chains; // crypto::hash -> block_extended_info - - // some invalid blocks - blocks_ext_by_hash m_invalid_blocks; // crypto::hash -> block_extended_info - - - checkpoints m_checkpoints; - bool m_enforce_dns_checkpoints; - - HardFork *m_hardfork; - - network_type m_nettype; - bool m_offline; - difficulty_type m_fixed_difficulty; - - std::atomic m_cancel; - - // block template cache - block m_btc; - account_public_address m_btc_address; - blobdata m_btc_nonce; - difficulty_type m_btc_difficulty; - uint64_t m_btc_pool_cookie; - uint64_t m_btc_expected_reward; - bool m_btc_valid; - - std::shared_ptr m_block_notify; - std::shared_ptr m_reorg_notify; - - /** - * @brief collects the keys for all outputs being "spent" as an input - * - * This function makes sure that each "input" in an input (mixins) exists - * and collects the public key for each from the transaction it was included in - * via the visitor passed to it. - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in the input set - * - * @tparam visitor_t a class encapsulating tx is unlocked and collect tx key - * @param tx_in_to_key a transaction input instance - * @param vis an instance of the visitor to use - * @param tx_prefix_hash the hash of the associated transaction_prefix - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * @param tx_version version of the tx, if > 1 we also get commitments - * - * @return false if any keys are not found or any inputs are not unlocked, otherwise true - */ - template - inline bool scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; - - /** - * @brief collect output public keys of a transaction input set - * - * This function locates all outputs associated with a given input set (mixins) - * and validates that they exist and are usable - * (unlocked, unspent is checked elsewhere). - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in the input set - * - * @param tx_version the transaction version - * @param txin the transaction input - * @param tx_prefix_hash the transaction prefix hash, for caching organization - * @param sig the input signature - * @param output_keys return-by-reference the public keys of the outputs in the input set - * @param rct_signatures the ringCT signatures, which are only valid if tx version > 1 - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * - * @return false if any output is not yet unlocked, or is missing, otherwise true - */ - bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector& sig, const rct::rctSig &rct_signatures, std::vector &output_keys, uint64_t* pmax_related_block_height); - - /** - * @brief validate a transaction's inputs and their keys - * - * This function validates transaction inputs and their keys. Previously - * it also performed double spend checking, but that has been moved to its - * own function. - * The transaction's rct signatures, if any, are expanded. - * - * If pmax_related_block_height is not NULL, its value is set to the height - * of the most recent block which contains an output used in any input set - * - * Currently this function calls ring signature validation for each - * transaction. - * - * @param tx the transaction to validate - * @param tvc returned information about tx verification - * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set - * - * @return false if any validation step fails, otherwise true - */ - bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL); - - /** - * @brief performs a blockchain reorganization according to the longest chain rule - * - * This function aggregates all the actions necessary to switch to a - * newly-longer chain. If any step in the reorganization process fails, - * the blockchain is reverted to its previous state. - * - * @param alt_chain the chain to switch to - * @param discard_disconnected_chain whether or not to keep the old chain as an alternate - * - * @return false if the reorganization fails, otherwise true - */ - bool switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain); - - /** - * @brief removes the most recent block from the blockchain - * - * @return the block removed - */ - block pop_block_from_blockchain(); - - /** - * @brief validate and add a new block to the end of the blockchain - * - * This function is merely a convenience wrapper around the other - * of the same name. This one passes the block's hash to the other - * as well as the block and verification context. - * - * @param bl the block to be added - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ - bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); - - /** - * @brief validate and add a new block to the end of the blockchain - * - * When a block is given to Blockchain to be added to the blockchain, it - * is passed here if it is determined to belong at the end of the current - * chain. - * - * @param bl the block to be added - * @param id the hash of the block - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ - bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); - - /** - * @brief validate and add a new block to an alternate blockchain - * - * If a block to be added does not belong to the main chain, but there - * is an alternate chain to which it should be added, that is handled - * here. - * - * @param b the block to be added - * @param id the hash of the block - * @param bvc metadata concerning the block's validity - * - * @return true if the block was added successfully, otherwise false - */ - bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); - - /** - * @brief gets the difficulty requirement for a new block on an alternate chain - * - * @param alt_chain the chain to be added to - * @param bei the block being added (and metadata, see ::block_extended_info) - * - * @return the difficulty requirement - */ - difficulty_type get_next_difficulty_for_alternative_chain(const std::list& alt_chain, block_extended_info& bei) const; - - /** - * @brief sanity checks a miner transaction before validating an entire block - * - * This function merely checks basic things like the structure of the miner - * transaction, the unlock time, and that the amount doesn't overflow. - * - * @param b the block containing the miner transaction - * @param height the height at which the block will be added - * - * @return false if anything is found wrong with the miner transaction, otherwise true - */ - bool prevalidate_miner_transaction(const block& b, uint64_t height); - - /** - * @brief validates a miner (coinbase) transaction - * - * This function makes sure that the miner calculated his reward correctly - * and that his miner transaction totals reward + fee. - * - * @param b the block containing the miner transaction to be validated - * @param cumulative_block_weight the block's weight - * @param fee the total fees collected in the block - * @param base_reward return-by-reference the new block's generated coins - * @param already_generated_coins the amount of currency generated prior to this block - * @param partial_block_reward return-by-reference true if miner accepted only partial reward - * @param version hard fork version for that transaction - * - * @return false if anything is found wrong with the miner transaction, otherwise true - */ - bool validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, bool &partial_block_reward, uint8_t version); - - /** - * @brief reverts the blockchain to its previous state following a failed switch - * - * If Blockchain fails to switch to an alternate chain when it means - * to do so, this function reverts the blockchain to how it was before - * the attempted switch. - * - * @param original_chain the chain to switch back to - * @param rollback_height the height to revert to before appending the original chain - * - * @return false if something goes wrong with reverting (very bad), otherwise true - */ - bool rollback_blockchain_switching(std::list& original_chain, uint64_t rollback_height); - - /** - * @brief gets recent block weights for median calculation - * - * get the block weights of the last blocks, and return by reference . - * - * @param sz return-by-reference the list of weights - * @param count the number of blocks to get weights for - */ - void get_last_n_blocks_weights(std::vector& weights, size_t count) const; - - /** - * @brief checks if a transaction is unlocked (its outputs spendable) - * - * This function checks to see if a transaction is unlocked. - * unlock_time is either a block index or a unix time. - * - * @param unlock_time the unlock parameter (height or time) - * - * @return true if spendable, otherwise false - */ - bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; - - /** - * @brief stores an invalid block in a separate container - * - * Storing invalid blocks allows quick dismissal of the same block - * if it is seen again. - * - * @param bl the invalid block - * @param h the block's hash - * - * @return false if the block cannot be stored for some reason, otherwise true - */ - bool add_block_as_invalid(const block& bl, const crypto::hash& h); - - /** - * @brief stores an invalid block in a separate container - * - * Storing invalid blocks allows quick dismissal of the same block - * if it is seen again. - * - * @param bei the invalid block (see ::block_extended_info) - * @param h the block's hash - * - * @return false if the block cannot be stored for some reason, otherwise true - */ - bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); - - /** - * @brief checks a block's timestamp - * - * This function grabs the timestamps from the most recent blocks, - * where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many - * blocks in the blockchain, the timestap is assumed to be valid. If there - * are, this function returns: - * true if the block's timestamp is not less than the timestamp of the - * median of the selected blocks - * false otherwise - * - * @param b the block to be checked - * @param median_ts return-by-reference the median of timestamps - * - * @return true if the block's timestamp is valid, otherwise false - */ - bool check_block_timestamp(const block& b, uint64_t& median_ts) const; - bool check_block_timestamp(const block& b) const { uint64_t median_ts; return check_block_timestamp(b, median_ts); } - - /** - * @brief checks a block's timestamp - * - * If the block is not more recent than the median of the recent - * timestamps passed here, it is considered invalid. - * - * @param timestamps a list of the most recent timestamps to check against - * @param b the block to be checked - * - * @return true if the block's timestamp is valid, otherwise false - */ - bool check_block_timestamp(std::vector& timestamps, const block& b, uint64_t& median_ts) const; - bool check_block_timestamp(std::vector& timestamps, const block& b) const { uint64_t median_ts; return check_block_timestamp(timestamps, b, median_ts); } - - /** - * @brief get the "adjusted time" - * - * Currently this simply returns the current time according to the - * user's machine. - * - * @return the current time - */ - uint64_t get_adjusted_time() const; - - /** - * @brief finish an alternate chain's timestamp window from the main chain - * - * for an alternate chain, get the timestamps from the main chain to complete - * the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. - * - * @param start_height the alternate chain's attachment height to the main chain - * @param timestamps return-by-value the timestamps set to be populated - * - * @return true unless start_height is greater than the current blockchain height - */ - bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); - - /** - * @brief calculate the block weight limit for the next block to be added - * - * @param long_term_effective_median_block_weight optionally return that value - * - * @return true - */ - bool update_next_cumulative_weight_limit(uint64_t *long_term_effective_median_block_weight = NULL); - void return_tx_to_pool(std::vector &txs); - - /** - * @brief make sure a transaction isn't attempting a double-spend - * - * @param tx the transaction to check - * @param keys_this_block a cumulative list of spent keys for the current block - * - * @return false if a double spend was detected, otherwise true - */ - bool check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const; - - /** - * @brief validates a transaction input's ring signature - * - * @param tx_prefix_hash the transaction prefix' hash - * @param key_image the key image generated from the true input - * @param pubkeys the public keys for each input in the ring signature - * @param sig the signature generated for each input in the ring signature - * @param result false if the ring signature is invalid, otherwise true - */ - void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, - const std::vector &pubkeys, const std::vector &sig, uint64_t &result); - - /** - * @brief loads block hashes from compiled-in data set - * - * A (possibly empty) set of block hashes can be compiled into the - * monero daemon binary. This function loads those hashes into - * a useful state. - */ - void load_compiled_in_block_hashes(); - - /** - * @brief expands v2 transaction data from blockchain - * - * RingCT transactions do not transmit some of their data if it - * can be reconstituted by the receiver. This function expands - * that implicit data. - */ - bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys); - - /** - * @brief invalidates any cached block template - */ - void invalidate_block_template_cache(); - - /** - * @brief stores a new cached block template - * - * At some point, may be used to push an update to miners - */ - void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie); - }; -} // namespace cryptonote diff --git a/src/cn_utils/p2p/net_node.inl b/src/cn_utils/p2p/net_node.inl deleted file mode 100644 index 9390626a8..000000000 --- a/src/cn_utils/p2p/net_node.inl +++ /dev/null @@ -1,2116 +0,0 @@ -// Copyright (c) 2014-2018, 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 - -// IP blocking adapted from Boolberry - -#include -#include -#include -#include -#include - -#include "version.h" -#include "string_tools.h" -#include "common/util.h" -#include "common/dns_utils.h" -#include "net/net_helper.h" -#include "math_helper.h" -#include "p2p_protocol_defs.h" -#include "net_peerlist_boost_serialization.h" -#include "net/local_ip.h" -#include "crypto/crypto.h" -#include "storages/levin_abstract_invoke2.h" -#include "cryptonote_core/cryptonote_core.h" - -#include -#include -#include - -#undef MONERO_DEFAULT_LOG_CATEGORY -#define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" - -#define NET_MAKE_IP(b1,b2,b3,b4) ((LPARAM)(((DWORD)(b1)<<24)+((DWORD)(b2)<<16)+((DWORD)(b3)<<8)+((DWORD)(b4)))) - -#define MIN_WANTED_SEED_NODES 12 - -namespace nodetool -{ - inline bool append_net_address(std::vector & seed_nodes, std::string const & addr, uint16_t default_port); - //----------------------------------------------------------------------------------- - template - void node_server::init_options(boost::program_options::options_description& desc) - { - command_line::add_arg(desc, arg_p2p_bind_ip); - command_line::add_arg(desc, arg_p2p_bind_port, false); - command_line::add_arg(desc, arg_p2p_external_port); - command_line::add_arg(desc, arg_p2p_allow_local_ip); - command_line::add_arg(desc, arg_p2p_add_peer); - command_line::add_arg(desc, arg_p2p_add_priority_node); - command_line::add_arg(desc, arg_p2p_add_exclusive_node); - command_line::add_arg(desc, arg_p2p_seed_node); - command_line::add_arg(desc, arg_p2p_hide_my_port); - command_line::add_arg(desc, arg_no_igd); - command_line::add_arg(desc, arg_out_peers); - command_line::add_arg(desc, arg_in_peers); - command_line::add_arg(desc, arg_tos_flag); - command_line::add_arg(desc, arg_limit_rate_up); - command_line::add_arg(desc, arg_limit_rate_down); - command_line::add_arg(desc, arg_limit_rate); - command_line::add_arg(desc, arg_save_graph); - } - //----------------------------------------------------------------------------------- - template - bool node_server::init_config() - { - // - TRY_ENTRY(); - std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; - std::ifstream p2p_data; - p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); - if(!p2p_data.fail()) - { - try - { - // first try reading in portable mode - boost::archive::portable_binary_iarchive a(p2p_data); - a >> *this; - } - catch (...) - { - // if failed, try reading in unportable mode - boost::filesystem::copy_file(state_file_path, state_file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - p2p_data.close(); - p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::in); - if(!p2p_data.fail()) - { - try - { - boost::archive::binary_iarchive a(p2p_data); - a >> *this; - } - catch (const std::exception &e) - { - MWARNING("Failed to load p2p config file, falling back to default config"); - m_peerlist = peerlist_manager(); // it was probably half clobbered by the failed load - make_default_config(); - } - } - else - { - make_default_config(); - } - } - }else - { - make_default_config(); - } - - // always recreate a new peer id - make_default_peer_id(); - - //at this moment we have hardcoded config - m_config.m_net_config.handshake_interval = P2P_DEFAULT_HANDSHAKE_INTERVAL; - m_config.m_net_config.packet_max_size = P2P_DEFAULT_PACKET_MAX_SIZE; //20 MB limit - m_config.m_net_config.config_id = 0; // initial config - m_config.m_net_config.connection_timeout = P2P_DEFAULT_CONNECTION_TIMEOUT; - m_config.m_net_config.ping_connection_timeout = P2P_DEFAULT_PING_CONNECTION_TIMEOUT; - m_config.m_net_config.send_peerlist_sz = P2P_DEFAULT_PEERS_IN_HANDSHAKE; - m_config.m_support_flags = P2P_SUPPORT_FLAGS; - - m_first_connection_maker_call = true; - CATCH_ENTRY_L0("node_server::init_config", false); - return true; - } - //----------------------------------------------------------------------------------- - template - void node_server::for_each_connection(std::function f) - { - m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntx){ - return f(cntx, cntx.peer_id, cntx.support_flags); - }); - } - //----------------------------------------------------------------------------------- - template - bool node_server::for_connection(const boost::uuids::uuid &connection_id, std::function f) - { - return m_net_server.get_config_object().for_connection(connection_id, [&](p2p_connection_context& cntx){ - return f(cntx, cntx.peer_id, cntx.support_flags); - }); - } - //----------------------------------------------------------------------------------- - template - bool node_server::is_remote_host_allowed(const epee::net_utils::network_address &address) - { - CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - auto it = m_blocked_hosts.find(address.host_str()); - if(it == m_blocked_hosts.end()) - return true; - if(time(nullptr) >= it->second) - { - m_blocked_hosts.erase(it); - MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); - return true; - } - return false; - } - //----------------------------------------------------------------------------------- - template - bool node_server::make_default_peer_id() - { - m_config.m_peer_id = crypto::rand(); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::make_default_config() - { - return make_default_peer_id(); - } - //----------------------------------------------------------------------------------- - template - bool node_server::block_host(const epee::net_utils::network_address &addr, time_t seconds) - { - CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - m_blocked_hosts[addr.host_str()] = time(nullptr) + seconds; - - // drop any connection to that IP - std::list conns; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if (cntxt.m_remote_address.is_same_host(addr)) - { - conns.push_back(cntxt.m_connection_id); - } - return true; - }); - for (const auto &c: conns) - m_net_server.get_config_object().close(c); - - MCLOG_CYAN(el::Level::Info, "global", "Host " << addr.host_str() << " blocked."); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::unblock_host(const epee::net_utils::network_address &address) - { - CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); - auto i = m_blocked_hosts.find(address.host_str()); - if (i == m_blocked_hosts.end()) - return false; - m_blocked_hosts.erase(i); - MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::add_host_fail(const epee::net_utils::network_address &address) - { - CRITICAL_REGION_LOCAL(m_host_fails_score_lock); - uint64_t fails = ++m_host_fails_score[address.host_str()]; - MDEBUG("Host " << address.host_str() << " fail score=" << fails); - if(fails > P2P_IP_FAILS_BEFORE_BLOCK) - { - auto it = m_host_fails_score.find(address.host_str()); - CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error"); - it->second = P2P_IP_FAILS_BEFORE_BLOCK/2; - block_host(address); - } - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port) - { - return epee::net_utils::create_network_address(pe, node_addr, default_port); - } - //----------------------------------------------------------------------------------- - template - bool node_server::handle_command_line( - const boost::program_options::variables_map& vm - ) - { - bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); - bool stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on); - m_nettype = testnet ? cryptonote::TESTNET : stagenet ? cryptonote::STAGENET : cryptonote::MAINNET; - - m_bind_ip = command_line::get_arg(vm, arg_p2p_bind_ip); - m_port = command_line::get_arg(vm, arg_p2p_bind_port); - m_external_port = command_line::get_arg(vm, arg_p2p_external_port); - m_allow_local_ip = command_line::get_arg(vm, arg_p2p_allow_local_ip); - m_no_igd = command_line::get_arg(vm, arg_no_igd); - m_offline = command_line::get_arg(vm, cryptonote::arg_offline); - - if (command_line::has_arg(vm, arg_p2p_add_peer)) - { - std::vector perrs = command_line::get_arg(vm, arg_p2p_add_peer); - for(const std::string& pr_str: perrs) - { - nodetool::peerlist_entry pe = AUTO_VAL_INIT(pe); - pe.id = crypto::rand(); - const uint16_t default_port = cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT; - bool r = parse_peer_from_string(pe.adr, pr_str, default_port); - if (r) - { - m_command_line_peers.push_back(pe); - continue; - } - std::vector resolved_addrs; - r = append_net_address(resolved_addrs, pr_str, default_port); - CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str); - for (const epee::net_utils::network_address& addr : resolved_addrs) - { - pe.id = crypto::rand(); - pe.adr = addr; - m_command_line_peers.push_back(pe); - } - } - } - - if(command_line::has_arg(vm, arg_save_graph)) - { - set_save_graph(true); - } - - if (command_line::has_arg(vm,arg_p2p_add_exclusive_node)) - { - if (!parse_peers_and_add_to_container(vm, arg_p2p_add_exclusive_node, m_exclusive_peers)) - return false; - } - - if (command_line::has_arg(vm, arg_p2p_add_priority_node)) - { - if (!parse_peers_and_add_to_container(vm, arg_p2p_add_priority_node, m_priority_peers)) - return false; - } - - if (command_line::has_arg(vm, arg_p2p_seed_node)) - { - if (!parse_peers_and_add_to_container(vm, arg_p2p_seed_node, m_seed_nodes)) - return false; - } - - if(command_line::has_arg(vm, arg_p2p_hide_my_port)) - m_hide_my_port = true; - - if ( !set_max_out_peers(vm, command_line::get_arg(vm, arg_out_peers) ) ) - return false; - - if ( !set_max_in_peers(vm, command_line::get_arg(vm, arg_in_peers) ) ) - return false; - - if ( !set_tos_flag(vm, command_line::get_arg(vm, arg_tos_flag) ) ) - return false; - - if ( !set_rate_up_limit(vm, command_line::get_arg(vm, arg_limit_rate_up) ) ) - return false; - - if ( !set_rate_down_limit(vm, command_line::get_arg(vm, arg_limit_rate_down) ) ) - return false; - - if ( !set_rate_limit(vm, command_line::get_arg(vm, arg_limit_rate) ) ) - return false; - - return true; - } - //----------------------------------------------------------------------------------- - inline bool append_net_address( - std::vector & seed_nodes - , std::string const & addr - , uint16_t default_port - ) - { - using namespace boost::asio; - - std::string host = addr; - std::string port = std::to_string(default_port); - size_t pos = addr.find_last_of(':'); - if (std::string::npos != pos) - { - CHECK_AND_ASSERT_MES(addr.length() - 1 != pos && 0 != pos, false, "Failed to parse seed address from string: '" << addr << '\''); - host = addr.substr(0, pos); - port = addr.substr(pos + 1); - } - MINFO("Resolving node address: host=" << host << ", port=" << port); - - io_service io_srv; - ip::tcp::resolver resolver(io_srv); - ip::tcp::resolver::query query(host, port, boost::asio::ip::tcp::resolver::query::canonical_name); - boost::system::error_code ec; - ip::tcp::resolver::iterator i = resolver.resolve(query, ec); - CHECK_AND_ASSERT_MES(!ec, false, "Failed to resolve host name '" << host << "': " << ec.message() << ':' << ec.value()); - - ip::tcp::resolver::iterator iend; - for (; i != iend; ++i) - { - ip::tcp::endpoint endpoint = *i; - if (endpoint.address().is_v4()) - { - epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()}}; - seed_nodes.push_back(na); - MINFO("Added node: " << na.str()); - } - else - { - MWARNING("IPv6 unsupported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); - } - } - return true; - } - - //----------------------------------------------------------------------------------- - template - std::set node_server::get_seed_nodes(cryptonote::network_type nettype) const - { - std::set full_addrs; - if (nettype == cryptonote::TESTNET) - { - full_addrs.insert("212.83.175.67:28080"); - full_addrs.insert("5.9.100.248:28080"); - full_addrs.insert("163.172.182.165:28080"); - full_addrs.insert("195.154.123.123:28080"); - full_addrs.insert("212.83.172.165:28080"); - } - else if (nettype == cryptonote::STAGENET) - { - full_addrs.insert("162.210.173.150:38080"); - full_addrs.insert("162.210.173.151:38080"); - } - else if (nettype == cryptonote::FAKECHAIN) - { - } - else - { - full_addrs.insert("107.152.130.98:18080"); - full_addrs.insert("212.83.175.67:18080"); - full_addrs.insert("5.9.100.248:18080"); - full_addrs.insert("163.172.182.165:18080"); - full_addrs.insert("161.67.132.39:18080"); - full_addrs.insert("198.74.231.92:18080"); - full_addrs.insert("195.154.123.123:18080"); - full_addrs.insert("212.83.172.165:18080"); - } - return full_addrs; - } - - //----------------------------------------------------------------------------------- - template - bool node_server::init(const boost::program_options::variables_map& vm) - { - std::set full_addrs; - - bool res = handle_command_line(vm); - CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); - - m_fallback_seed_nodes_added = false; - if (m_nettype == cryptonote::TESTNET) - { - memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16); - full_addrs = get_seed_nodes(cryptonote::TESTNET); - } - else if (m_nettype == cryptonote::STAGENET) - { - memcpy(&m_network_id, &::config::stagenet::NETWORK_ID, 16); - full_addrs = get_seed_nodes(cryptonote::STAGENET); - } - else - { - memcpy(&m_network_id, &::config::NETWORK_ID, 16); - if (m_exclusive_peers.empty()) - { - // for each hostname in the seed nodes list, attempt to DNS resolve and - // add the result addresses as seed nodes - // TODO: at some point add IPv6 support, but that won't be relevant - // for some time yet. - - std::vector> dns_results; - dns_results.resize(m_seed_nodes_list.size()); - - std::list dns_threads; - uint64_t result_index = 0; - for (const std::string& addr_str : m_seed_nodes_list) - { - boost::thread th = boost::thread([=, &dns_results, &addr_str] - { - MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); - // TODO: care about dnssec avail/valid - bool avail, valid; - std::vector addr_list; - - try - { - addr_list = tools::DNSResolver::instance().get_ipv4(addr_str, avail, valid); - MDEBUG("dns_threads[" << result_index << "] DNS resolve done"); - boost::this_thread::interruption_point(); - } - catch(const boost::thread_interrupted&) - { - // thread interruption request - // even if we now have results, finish thread without setting - // result variables, which are now out of scope in main thread - MWARNING("dns_threads[" << result_index << "] interrupted"); - return; - } - - MINFO("dns_threads[" << result_index << "] addr_str: " << addr_str << " number of results: " << addr_list.size()); - dns_results[result_index] = addr_list; - }); - - dns_threads.push_back(std::move(th)); - ++result_index; - } - - MDEBUG("dns_threads created, now waiting for completion or timeout of " << CRYPTONOTE_DNS_TIMEOUT_MS << "ms"); - boost::chrono::system_clock::time_point deadline = boost::chrono::system_clock::now() + boost::chrono::milliseconds(CRYPTONOTE_DNS_TIMEOUT_MS); - uint64_t i = 0; - for (boost::thread& th : dns_threads) - { - if (! th.try_join_until(deadline)) - { - MWARNING("dns_threads[" << i << "] timed out, sending interrupt"); - th.interrupt(); - } - ++i; - } - - i = 0; - for (const auto& result : dns_results) - { - MDEBUG("DNS lookup for " << m_seed_nodes_list[i] << ": " << result.size() << " results"); - // if no results for node, thread's lookup likely timed out - if (result.size()) - { - for (const auto& addr_string : result) - full_addrs.insert(addr_string + ":" + std::to_string(cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT)); - } - ++i; - } - - // append the fallback nodes if we have too few seed nodes to start with - if (full_addrs.size() < MIN_WANTED_SEED_NODES) - { - if (full_addrs.empty()) - MINFO("DNS seed node lookup either timed out or failed, falling back to defaults"); - else - MINFO("Not enough DNS seed nodes found, using fallback defaults too"); - - for (const auto &peer: get_seed_nodes(cryptonote::MAINNET)) - full_addrs.insert(peer); - m_fallback_seed_nodes_added = true; - } - } - } - - for (const auto& full_addr : full_addrs) - { - MDEBUG("Seed node: " << full_addr); - append_net_address(m_seed_nodes, full_addr, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); - } - MDEBUG("Number of seed nodes: " << m_seed_nodes.size()); - - m_config_folder = command_line::get_arg(vm, cryptonote::arg_data_dir); - - if ((m_nettype == cryptonote::MAINNET && m_port != std::to_string(::config::P2P_DEFAULT_PORT)) - || (m_nettype == cryptonote::TESTNET && m_port != std::to_string(::config::testnet::P2P_DEFAULT_PORT)) - || (m_nettype == cryptonote::STAGENET && m_port != std::to_string(::config::stagenet::P2P_DEFAULT_PORT))) { - m_config_folder = m_config_folder + "/" + m_port; - } - - res = init_config(); - CHECK_AND_ASSERT_MES(res, false, "Failed to init config."); - - res = m_peerlist.init(m_allow_local_ip); - CHECK_AND_ASSERT_MES(res, false, "Failed to init peerlist."); - - - for(auto& p: m_command_line_peers) - m_peerlist.append_with_peer_white(p); - - //only in case if we really sure that we have external visible ip - m_have_address = true; - m_ip_address = 0; - m_last_stat_request_time = 0; - - //configure self - m_net_server.set_threads_prefix("P2P"); - m_net_server.get_config_object().set_handler(this); - m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; - m_net_server.set_connection_filter(this); - - // from here onwards, it's online stuff - if (m_offline) - return res; - - //try to bind - MINFO("Binding on " << m_bind_ip << ":" << m_port); - res = m_net_server.init_server(m_port, m_bind_ip); - CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); - - m_listening_port = m_net_server.get_binded_port(); - MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listening_port); - if(m_external_port) - MDEBUG("External port defined as " << m_external_port); - - // add UPnP port mapping - if(!m_no_igd) - add_upnp_port_mapping(m_listening_port); - - return res; - } - //----------------------------------------------------------------------------------- - template - typename node_server::payload_net_handler& node_server::get_payload_object() - { - return m_payload_handler; - } - //----------------------------------------------------------------------------------- - template - bool node_server::run() - { - // creating thread to log number of connections - mPeersLoggerThread.reset(new boost::thread([&]() - { - _note("Thread monitor number of peers - start"); - while (!is_closing && !m_net_server.is_stop_signal_sent()) - { // main loop of thread - //number_of_peers = m_net_server.get_config_object().get_connections_count(); - unsigned int number_of_in_peers = 0; - unsigned int number_of_out_peers = 0; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if (cntxt.m_is_income) - { - ++number_of_in_peers; - } - else - { - ++number_of_out_peers; - } - return true; - }); // lambda - - m_current_number_of_in_peers = number_of_in_peers; - m_current_number_of_out_peers = number_of_out_peers; - - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - } // main loop of thread - _note("Thread monitor number of peers - done"); - })); // lambda - - //here you can set worker threads count - int thrds_count = 10; - - m_net_server.add_idle_handler(boost::bind(&node_server::idle_worker, this), 1000); - m_net_server.add_idle_handler(boost::bind(&t_payload_net_handler::on_idle, &m_payload_handler), 1000); - - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - - //go to loop - MINFO("Run net_service loop( " << thrds_count << " threads)..."); - if(!m_net_server.run_server(thrds_count, true, attrs)) - { - LOG_ERROR("Failed to run net tcp server!"); - } - - MINFO("net_service loop stopped."); - return true; - } - - //----------------------------------------------------------------------------------- - template - uint64_t node_server::get_connections_count() - { - return m_net_server.get_config_object().get_connections_count(); - } - //----------------------------------------------------------------------------------- - template - bool node_server::deinit() - { - kill(); - m_peerlist.deinit(); - m_net_server.deinit_server(); - // remove UPnP port mapping - if(!m_no_igd) - delete_upnp_port_mapping(m_listening_port); - return store_config(); - } - //----------------------------------------------------------------------------------- - template - bool node_server::store_config() - { - - TRY_ENTRY(); - if (!tools::create_directories_if_necessary(m_config_folder)) - { - MWARNING("Failed to create data directory: " << m_config_folder); - return false; - } - - std::string state_file_path = m_config_folder + "/" + P2P_NET_DATA_FILENAME; - std::ofstream p2p_data; - p2p_data.open( state_file_path , std::ios_base::binary | std::ios_base::out| std::ios::trunc); - if(p2p_data.fail()) - { - MWARNING("Failed to save config to file " << state_file_path); - return false; - }; - - boost::archive::portable_binary_oarchive a(p2p_data); - a << *this; - return true; - CATCH_ENTRY_L0("blockchain_storage::save", false); - - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::send_stop_signal() - { - MDEBUG("[node] sending stop signal"); - m_net_server.send_stop_signal(); - MDEBUG("[node] Stop signal sent"); - - std::list connection_ids; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - connection_ids.push_back(cntxt.m_connection_id); - return true; - }); - for (const auto &connection_id: connection_ids) - m_net_server.get_config_object().close(connection_id); - - m_payload_handler.stop(); - - return true; - } - //----------------------------------------------------------------------------------- - - - template - bool node_server::do_handshake_with_peer(peerid_type& pi, p2p_connection_context& context_, bool just_take_peerlist) - { - typename COMMAND_HANDSHAKE::request arg; - typename COMMAND_HANDSHAKE::response rsp; - get_local_node_data(arg.node_data); - m_payload_handler.get_payload_sync_data(arg.payload_data); - - epee::simple_event ev; - std::atomic hsh_result(false); - - bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, m_net_server.get_config_object(), - [this, &pi, &ev, &hsh_result, &just_take_peerlist](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) - { - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();}); - - if(code < 0) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); - return; - } - - if(rsp.node_data.network_id != m_network_id) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE Failed, wrong network! (" << epee::string_tools::get_str_from_guid_a(rsp.node_data.network_id) << "), closing connection."); - return; - } - - if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.node_data.local_time, context)) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); - add_host_fail(context.m_remote_address); - return; - } - hsh_result = true; - if(!just_take_peerlist) - { - if(!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, true)) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoked, but process_payload_sync_data returned false, dropping connection."); - hsh_result = false; - return; - } - - pi = context.peer_id = rsp.node_data.peer_id; - m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address); - - if(rsp.node_data.peer_id == m_config.m_peer_id) - { - LOG_DEBUG_CC(context, "Connection to self detected, dropping connection"); - hsh_result = false; - return; - } - LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK"); - }else - { - LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); - } - }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); - - if(r) - { - ev.wait(); - } - - if(!hsh_result) - { - LOG_WARNING_CC(context_, "COMMAND_HANDSHAKE Failed"); - m_net_server.get_config_object().close(context_.m_connection_id); - } - else - { - try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - flags_context.support_flags = support_flags; - }); - } - - return hsh_result; - } - //----------------------------------------------------------------------------------- - template - bool node_server::do_peer_timed_sync(const epee::net_utils::connection_context_base& context_, peerid_type peer_id) - { - typename COMMAND_TIMED_SYNC::request arg = AUTO_VAL_INIT(arg); - m_payload_handler.get_payload_sync_data(arg.payload_data); - - bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, m_net_server.get_config_object(), - [this](int code, const typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) - { - context.m_in_timedsync = false; - if(code < 0) - { - LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); - return; - } - - if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.local_time, context)) - { - LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); - m_net_server.get_config_object().close(context.m_connection_id ); - add_host_fail(context.m_remote_address); - } - if(!context.m_is_income) - m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address); - m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false); - }); - - if(!r) - { - LOG_WARNING_CC(context_, "COMMAND_TIMED_SYNC Failed"); - return false; - } - return true; - } - //----------------------------------------------------------------------------------- - template - size_t node_server::get_random_index_with_fixed_probability(size_t max_index) - { - //divide by zero workaround - if(!max_index) - return 0; - - size_t x = crypto::rand()%(max_index+1); - size_t res = (x*x*x)/(max_index*max_index); //parabola \/ - MDEBUG("Random connection index=" << res << "(x="<< x << ", max_index=" << max_index << ")"); - return res; - } - //----------------------------------------------------------------------------------- - template - bool node_server::is_peer_used(const peerlist_entry& peer) - { - - if(m_config.m_peer_id == peer.id) - return true;//dont make connections to ourself - - bool used = false; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) - { - used = true; - return false;//stop enumerating - } - return true; - }); - - return used; - } - //----------------------------------------------------------------------------------- - template - bool node_server::is_peer_used(const anchor_peerlist_entry& peer) - { - if(m_config.m_peer_id == peer.id) { - return true;//dont make connections to ourself - } - - bool used = false; - - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) - { - used = true; - - return false;//stop enumerating - } - - return true; - }); - - return used; - } - //----------------------------------------------------------------------------------- - template - bool node_server::is_addr_connected(const epee::net_utils::network_address& peer) - { - bool connected = false; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(!cntxt.m_is_income && peer == cntxt.m_remote_address) - { - connected = true; - return false;//stop enumerating - } - return true; - }); - - return connected; - } - -#define LOG_PRINT_CC_PRIORITY_NODE(priority, con, msg) \ - do { \ - if (priority) {\ - LOG_INFO_CC(con, "[priority]" << msg); \ - } else {\ - LOG_INFO_CC(con, msg); \ - } \ - } while(0) - - template - bool node_server::try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp) - { - if (m_current_number_of_out_peers == m_config.m_net_config.max_out_connection_count) // out peers limit - { - return false; - } - else if (m_current_number_of_out_peers > m_config.m_net_config.max_out_connection_count) - { - m_net_server.get_config_object().del_out_connections(1); - m_current_number_of_out_peers --; // atomic variable, update time = 1s - return false; - } - MDEBUG("Connecting to " << na.str() << "(peer_type=" << peer_type << ", last_seen: " - << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") - << ")..."); - - CHECK_AND_ASSERT_MES(na.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, - "Only IPv4 addresses are supported here"); - const epee::net_utils::ipv4_network_address &ipv4 = na.as(); - - typename net_server::t_connection_context con = AUTO_VAL_INIT(con); - bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), - epee::string_tools::num_to_string_fast(ipv4.port()), - m_config.m_net_config.connection_timeout, - con); - - if(!res) - { - bool is_priority = is_priority_node(na); - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str() - /*<< ", try " << try_count*/); - //m_peerlist.set_peer_unreachable(pe); - return false; - } - - peerid_type pi = AUTO_VAL_INIT(pi); - res = do_handshake_with_peer(pi, con, just_take_peerlist); - - if(!res) - { - bool is_priority = is_priority_node(na); - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " - << na.str() - /*<< ", try " << try_count*/); - return false; - } - - if(just_take_peerlist) - { - m_net_server.get_config_object().close(con.m_connection_id); - LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); - return true; - } - - peerlist_entry pe_local = AUTO_VAL_INIT(pe_local); - pe_local.adr = na; - pe_local.id = pi; - time_t last_seen; - time(&last_seen); - pe_local.last_seen = static_cast(last_seen); - m_peerlist.append_with_peer_white(pe_local); - //update last seen and push it to peerlist manager - - anchor_peerlist_entry ape = AUTO_VAL_INIT(ape); - ape.adr = na; - ape.id = pi; - ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr); - - m_peerlist.append_with_peer_anchor(ape); - - LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK."); - return true; - } - - template - bool node_server::check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp) - { - LOG_PRINT_L1("Connecting to " << na.str() << "(last_seen: " - << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") - << ")..."); - - CHECK_AND_ASSERT_MES(na.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, - "Only IPv4 addresses are supported here"); - const epee::net_utils::ipv4_network_address &ipv4 = na.as(); - - typename net_server::t_connection_context con = AUTO_VAL_INIT(con); - bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), - epee::string_tools::num_to_string_fast(ipv4.port()), - m_config.m_net_config.connection_timeout, - con); - - if (!res) { - bool is_priority = is_priority_node(na); - - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str()); - - return false; - } - - peerid_type pi = AUTO_VAL_INIT(pi); - res = do_handshake_with_peer(pi, con, true); - - if (!res) { - bool is_priority = is_priority_node(na); - - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " << na.str()); - - return false; - } - - m_net_server.get_config_object().close(con.m_connection_id); - - LOG_DEBUG_CC(con, "CONNECTION HANDSHAKED OK AND CLOSED."); - - return true; - } - -#undef LOG_PRINT_CC_PRIORITY_NODE - - //----------------------------------------------------------------------------------- - template - bool node_server::is_addr_recently_failed(const epee::net_utils::network_address& addr) - { - CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); - auto it = m_conn_fails_cache.find(addr); - if(it == m_conn_fails_cache.end()) - return false; - - if(time(NULL) - it->second > P2P_FAILED_ADDR_FORGET_SECONDS) - return false; - else - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::make_new_connection_from_anchor_peerlist(const std::vector& anchor_peerlist) - { - for (const auto& pe: anchor_peerlist) { - _note("Considering connecting (out) to peer: " << peerid_type(pe.id) << " " << pe.adr.str()); - - if(is_peer_used(pe)) { - _note("Peer is used"); - continue; - } - - if(!is_remote_host_allowed(pe.adr)) { - continue; - } - - if(is_addr_recently_failed(pe.adr)) { - continue; - } - - MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str() - << "[peer_type=" << anchor - << "] first_seen: " << epee::misc_utils::get_time_interval_string(time(NULL) - pe.first_seen)); - - if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, 0, anchor, pe.first_seen)) { - _note("Handshake failed"); - continue; - } - - return true; - } - - return false; - } - //----------------------------------------------------------------------------------- - template - bool node_server::make_new_connection_from_peerlist(bool use_white_list) - { - size_t local_peers_count = use_white_list ? m_peerlist.get_white_peers_count():m_peerlist.get_gray_peers_count(); - if(!local_peers_count) - return false;//no peers - - size_t max_random_index = std::min(local_peers_count -1, 20); - - std::set tried_peers; - - size_t try_count = 0; - size_t rand_count = 0; - while(rand_count < (max_random_index+1)*3 && try_count < 10 && !m_net_server.is_stop_signal_sent()) - { - ++rand_count; - size_t random_index; - - if (use_white_list) { - local_peers_count = m_peerlist.get_white_peers_count(); - if (!local_peers_count) - return false; - max_random_index = std::min(local_peers_count -1, 20); - random_index = get_random_index_with_fixed_probability(max_random_index); - } else { - local_peers_count = m_peerlist.get_gray_peers_count(); - if (!local_peers_count) - return false; - random_index = crypto::rand() % local_peers_count; - } - - CHECK_AND_ASSERT_MES(random_index < local_peers_count, false, "random_starter_index < peers_local.size() failed!!"); - - if(tried_peers.count(random_index)) - continue; - - tried_peers.insert(random_index); - peerlist_entry pe = AUTO_VAL_INIT(pe); - bool r = use_white_list ? m_peerlist.get_white_peer_by_index(pe, random_index):m_peerlist.get_gray_peer_by_index(pe, random_index); - CHECK_AND_ASSERT_MES(r, false, "Failed to get random peer from peerlist(white:" << use_white_list << ")"); - - ++try_count; - - _note("Considering connecting (out) to peer: " << peerid_to_string(pe.id) << " " << pe.adr.str()); - - if(is_peer_used(pe)) { - _note("Peer is used"); - continue; - } - - if(!is_remote_host_allowed(pe.adr)) - continue; - - if(is_addr_recently_failed(pe.adr)) - continue; - - MDEBUG("Selected peer: " << peerid_to_string(pe.id) << " " << pe.adr.str() - << "[peer_list=" << (use_white_list ? white : gray) - << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); - - if(!try_to_connect_and_handshake_with_new_peer(pe.adr, false, pe.last_seen, use_white_list ? white : gray)) { - _note("Handshake failed"); - continue; - } - - return true; - } - return false; - } - //----------------------------------------------------------------------------------- - template - bool node_server::connect_to_seed() - { - if (m_seed_nodes.empty() || m_offline || !m_exclusive_peers.empty()) - return true; - - size_t try_count = 0; - size_t current_index = crypto::rand()%m_seed_nodes.size(); - while(true) - { - if(m_net_server.is_stop_signal_sent()) - return false; - - if(try_to_connect_and_handshake_with_new_peer(m_seed_nodes[current_index], true)) - break; - if(++try_count > m_seed_nodes.size()) - { - if (!m_fallback_seed_nodes_added) - { - MWARNING("Failed to connect to any of seed peers, trying fallback seeds"); - current_index = m_seed_nodes.size(); - for (const auto &peer: get_seed_nodes(m_nettype)) - { - MDEBUG("Fallback seed node: " << peer); - append_net_address(m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); - } - m_fallback_seed_nodes_added = true; - if (current_index == m_seed_nodes.size()) - { - MWARNING("No fallback seeds, continuing without seeds"); - break; - } - // continue for another few cycles - } - else - { - MWARNING("Failed to connect to any of seed peers, continuing without seeds"); - break; - } - } - if(++current_index >= m_seed_nodes.size()) - current_index = 0; - } - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::connections_maker() - { - if (m_offline) return true; - if (!connect_to_peerlist(m_exclusive_peers)) return false; - - if (!m_exclusive_peers.empty()) return true; - - size_t start_conn_count = get_outgoing_connections_count(); - if(!m_peerlist.get_white_peers_count() && m_seed_nodes.size()) - { - if (!connect_to_seed()) - return false; - } - - if (!connect_to_peerlist(m_priority_peers)) return false; - - size_t expected_white_connections = (m_config.m_net_config.max_out_connection_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; - - size_t conn_count = get_outgoing_connections_count(); - if(conn_count < m_config.m_net_config.max_out_connection_count) - { - if(conn_count < expected_white_connections) - { - //start from anchor list - if(!make_expected_connections_count(anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT)) - return false; - //then do white list - if(!make_expected_connections_count(white, expected_white_connections)) - return false; - //then do grey list - if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count)) - return false; - }else - { - //start from grey list - if(!make_expected_connections_count(gray, m_config.m_net_config.max_out_connection_count)) - return false; - //and then do white list - if(!make_expected_connections_count(white, m_config.m_net_config.max_out_connection_count)) - return false; - } - } - - if (start_conn_count == get_outgoing_connections_count() && start_conn_count < m_config.m_net_config.max_out_connection_count) - { - MINFO("Failed to connect to any, trying seeds"); - if (!connect_to_seed()) - return false; - } - - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::make_expected_connections_count(PeerType peer_type, size_t expected_connections) - { - if (m_offline) - return true; - - std::vector apl; - - if (peer_type == anchor) { - m_peerlist.get_and_empty_anchor_peerlist(apl); - } - - size_t conn_count = get_outgoing_connections_count(); - //add new connections from white peers - while(conn_count < expected_connections) - { - if(m_net_server.is_stop_signal_sent()) - return false; - - if (peer_type == anchor && !make_new_connection_from_anchor_peerlist(apl)) { - break; - } - - if (peer_type == white && !make_new_connection_from_peerlist(true)) { - break; - } - - if (peer_type == gray && !make_new_connection_from_peerlist(false)) { - break; - } - - conn_count = get_outgoing_connections_count(); - } - return true; - } - - //----------------------------------------------------------------------------------- - template - size_t node_server::get_outgoing_connections_count() - { - size_t count = 0; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(!cntxt.m_is_income) - ++count; - return true; - }); - - return count; - } - //----------------------------------------------------------------------------------- - template - size_t node_server::get_incoming_connections_count() - { - size_t count = 0; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(cntxt.m_is_income) - ++count; - return true; - }); - - return count; - } - //----------------------------------------------------------------------------------- - template - bool node_server::idle_worker() - { - m_peer_handshake_idle_maker_interval.do_call(boost::bind(&node_server::peer_sync_idle_maker, this)); - m_connections_maker_interval.do_call(boost::bind(&node_server::connections_maker, this)); - m_gray_peerlist_housekeeping_interval.do_call(boost::bind(&node_server::gray_peerlist_housekeeping, this)); - m_peerlist_store_interval.do_call(boost::bind(&node_server::store_config, this)); - m_incoming_connections_interval.do_call(boost::bind(&node_server::check_incoming_connections, this)); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::check_incoming_connections() - { - if (m_offline || m_hide_my_port) - return true; - if (get_incoming_connections_count() == 0) - { - const el::Level level = el::Level::Warning; - MCLOG_RED(level, "global", "No incoming connections - check firewalls/routers allow port " << get_this_peer_port()); - } - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::peer_sync_idle_maker() - { - MDEBUG("STARTED PEERLIST IDLE HANDSHAKE"); - typedef std::list > local_connects_type; - local_connects_type cncts; - m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntxt) - { - if(cntxt.peer_id && !cntxt.m_in_timedsync) - { - cntxt.m_in_timedsync = true; - cncts.push_back(local_connects_type::value_type(cntxt, cntxt.peer_id));//do idle sync only with handshaked connections - } - return true; - }); - - std::for_each(cncts.begin(), cncts.end(), [&](const typename local_connects_type::value_type& vl){do_peer_timed_sync(vl.first, vl.second);}); - - MDEBUG("FINISHED PEERLIST IDLE HANDSHAKE"); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::fix_time_delta(std::list& local_peerlist, time_t local_time, int64_t& delta) - { - //fix time delta - time_t now = 0; - time(&now); - delta = now - local_time; - - for(peerlist_entry& be: local_peerlist) - { - if(be.last_seen > local_time) - { - MWARNING("FOUND FUTURE peerlist for entry " << be.adr.str() << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); - return false; - } - be.last_seen += delta; - } - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::handle_remote_peerlist(const std::list& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context) - { - int64_t delta = 0; - std::list peerlist_ = peerlist; - if(!fix_time_delta(peerlist_, local_time, delta)) - return false; - LOG_DEBUG_CC(context, "REMOTE PEERLIST: TIME_DELTA: " << delta << ", remote peerlist size=" << peerlist_.size()); - LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << print_peerlist_to_string(peerlist_)); - return m_peerlist.merge_peerlist(peerlist_); - } - //----------------------------------------------------------------------------------- - template - bool node_server::get_local_node_data(basic_node_data& node_data) - { - time_t local_time; - time(&local_time); - node_data.local_time = local_time; - node_data.peer_id = m_config.m_peer_id; - if(!m_hide_my_port) - node_data.my_port = m_external_port ? m_external_port : m_listening_port; - else - node_data.my_port = 0; - node_data.network_id = m_network_id; - return true; - } - //----------------------------------------------------------------------------------- -#ifdef ALLOW_DEBUG_COMMANDS - template - bool node_server::check_trust(const proof_of_trust& tr) - { - uint64_t local_time = time(NULL); - uint64_t time_delata = local_time > tr.time ? local_time - tr.time: tr.time - local_time; - if(time_delata > 24*60*60 ) - { - MWARNING("check_trust failed to check time conditions, local_time=" << local_time << ", proof_time=" << tr.time); - return false; - } - if(m_last_stat_request_time >= tr.time ) - { - MWARNING("check_trust failed to check time conditions, last_stat_request_time=" << m_last_stat_request_time << ", proof_time=" << tr.time); - return false; - } - if(m_config.m_peer_id != tr.peer_id) - { - MWARNING("check_trust failed: peer_id mismatch (passed " << tr.peer_id << ", expected " << m_config.m_peer_id<< ")"); - return false; - } - crypto::public_key pk = AUTO_VAL_INIT(pk); - epee::string_tools::hex_to_pod(::config::P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY, pk); - crypto::hash h = get_proof_of_trust_hash(tr); - if(!crypto::check_signature(h, pk, tr.sign)) - { - MWARNING("check_trust failed: sign check failed"); - return false; - } - //update last request time - m_last_stat_request_time = tr.time; - return true; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_get_stat_info(int command, typename COMMAND_REQUEST_STAT_INFO::request& arg, typename COMMAND_REQUEST_STAT_INFO::response& rsp, p2p_connection_context& context) - { - if(!check_trust(arg.tr)) - { - drop_connection(context); - return 1; - } - rsp.connections_count = m_net_server.get_config_object().get_connections_count(); - rsp.incoming_connections_count = rsp.connections_count - get_outgoing_connections_count(); - rsp.version = MONERO_VERSION_FULL; - rsp.os_version = tools::get_os_version_string(); - m_payload_handler.get_stat_info(rsp.payload_info); - return 1; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context) - { - if(!check_trust(arg.tr)) - { - drop_connection(context); - return 1; - } - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - connection_entry ce; - ce.adr = cntxt.m_remote_address; - ce.id = cntxt.peer_id; - ce.is_income = cntxt.m_is_income; - rsp.connections_list.push_back(ce); - return true; - }); - - m_peerlist.get_peerlist_full(rsp.local_peerlist_gray, rsp.local_peerlist_white); - rsp.my_id = m_config.m_peer_id; - rsp.local_time = time(NULL); - return 1; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context) - { - rsp.my_id = m_config.m_peer_id; - return 1; - } -#endif - //----------------------------------------------------------------------------------- - template - int node_server::handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context) - { - rsp.support_flags = m_config.m_support_flags; - return 1; - } - //----------------------------------------------------------------------------------- - template - void node_server::request_callback(const epee::net_utils::connection_context_base& context) - { - m_net_server.get_config_object().request_callback(context.m_connection_id); - } - //----------------------------------------------------------------------------------- - template - bool node_server::relay_notify_to_list(int command, const std::string& data_buff, const std::list &connections) - { - for(const auto& c_id: connections) - { - m_net_server.get_config_object().notify(command, data_buff, c_id); - } - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::relay_notify_to_all(int command, const std::string& data_buff, const epee::net_utils::connection_context_base& context) - { - std::list connections; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if(cntxt.peer_id && context.m_connection_id != cntxt.m_connection_id) - connections.push_back(cntxt.m_connection_id); - return true; - }); - return relay_notify_to_list(command, data_buff, connections); - } - //----------------------------------------------------------------------------------- - template - void node_server::callback(p2p_connection_context& context) - { - m_payload_handler.on_callback(context); - } - //----------------------------------------------------------------------------------- - template - bool node_server::invoke_notify_to_peer(int command, const std::string& req_buff, const epee::net_utils::connection_context_base& context) - { - int res = m_net_server.get_config_object().notify(command, req_buff, context.m_connection_id); - return res > 0; - } - //----------------------------------------------------------------------------------- - template - bool node_server::invoke_command_to_peer(int command, const std::string& req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context) - { - int res = m_net_server.get_config_object().invoke(command, req_buff, resp_buff, context.m_connection_id); - return res > 0; - } - //----------------------------------------------------------------------------------- - template - bool node_server::drop_connection(const epee::net_utils::connection_context_base& context) - { - m_net_server.get_config_object().close(context.m_connection_id); - return true; - } - //----------------------------------------------------------------------------------- - template template - bool node_server::try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb) - { - if(!node_data.my_port) - return false; - - CHECK_AND_ASSERT_MES(context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::ID, false, - "Only IPv4 addresses are supported here"); - - const epee::net_utils::network_address na = context.m_remote_address; - uint32_t actual_ip = na.as().ip(); - if(!m_peerlist.is_host_allowed(context.m_remote_address)) - return false; - std::string ip = epee::string_tools::get_ip_string_from_int32(actual_ip); - std::string port = epee::string_tools::num_to_string_fast(node_data.my_port); - epee::net_utils::network_address address{epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)}; - peerid_type pr = node_data.peer_id; - bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( - const typename net_server::t_connection_context& ping_context, - const boost::system::error_code& ec)->bool - { - if(ec) - { - LOG_WARNING_CC(ping_context, "back ping connect failed to " << address.str()); - return false; - } - COMMAND_PING::request req; - COMMAND_PING::response rsp; - //vc2010 workaround - /*std::string ip_ = ip; - std::string port_=port; - peerid_type pr_ = pr; - auto cb_ = cb;*/ - - // GCC 5.1.0 gives error with second use of uint64_t (peerid_type) variable. - peerid_type pr_ = pr; - - bool inv_call_res = epee::net_utils::async_invoke_remote_command2(ping_context.m_connection_id, COMMAND_PING::ID, req, m_net_server.get_config_object(), - [=](int code, const COMMAND_PING::response& rsp, p2p_connection_context& context) - { - if(code <= 0) - { - LOG_WARNING_CC(ping_context, "Failed to invoke COMMAND_PING to " << address.str() << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); - return; - } - - if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) - { - LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); - m_net_server.get_config_object().close(ping_context.m_connection_id); - return; - } - m_net_server.get_config_object().close(ping_context.m_connection_id); - cb(); - }); - - if(!inv_call_res) - { - LOG_WARNING_CC(ping_context, "back ping invoke failed to " << address.str()); - m_net_server.get_config_object().close(ping_context.m_connection_id); - return false; - } - return true; - }); - if(!r) - { - LOG_WARNING_CC(context, "Failed to call connect_async, network error."); - } - return r; - } - //----------------------------------------------------------------------------------- - template - bool node_server::try_get_support_flags(const p2p_connection_context& context, std::function f) - { - COMMAND_REQUEST_SUPPORT_FLAGS::request support_flags_request; - bool r = epee::net_utils::async_invoke_remote_command2 - ( - context.m_connection_id, - COMMAND_REQUEST_SUPPORT_FLAGS::ID, - support_flags_request, - m_net_server.get_config_object(), - [=](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_) - { - if(code < 0) - { - LOG_WARNING_CC(context_, "COMMAND_REQUEST_SUPPORT_FLAGS invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); - return; - } - - f(context_, rsp.support_flags); - }, - P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT - ); - - return r; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) - { - if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, false)) - { - LOG_WARNING_CC(context, "Failed to process_payload_sync_data(), dropping connection"); - drop_connection(context); - return 1; - } - - //fill response - rsp.local_time = time(NULL); - m_peerlist.get_peerlist_head(rsp.local_peerlist_new); - m_payload_handler.get_payload_sync_data(rsp.payload_data); - LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); - return 1; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) - { - if(arg.node_data.network_id != m_network_id) - { - - LOG_INFO_CC(context, "WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); - drop_connection(context); - add_host_fail(context.m_remote_address); - return 1; - } - - if(!context.m_is_income) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came not from incoming connection"); - drop_connection(context); - add_host_fail(context.m_remote_address); - return 1; - } - - if(context.peer_id) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but seems that connection already have associated peer_id (double COMMAND_HANDSHAKE?)"); - drop_connection(context); - return 1; - } - - if (m_current_number_of_in_peers >= m_config.m_net_config.max_in_connection_count) // in peers limit - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but already have max incoming connections, so dropping this one."); - drop_connection(context); - return 1; - } - - if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, true)) - { - LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection."); - drop_connection(context); - return 1; - } - - if(has_too_many_connections(context.m_remote_address)) - { - LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address"); - drop_connection(context); - return 1; - } - - //associate peer_id with this connection - context.peer_id = arg.node_data.peer_id; - context.m_in_timedsync = false; - - if(arg.node_data.peer_id != m_config.m_peer_id && arg.node_data.my_port) - { - peerid_type peer_id_l = arg.node_data.peer_id; - uint32_t port_l = arg.node_data.my_port; - //try ping to be sure that we can add this peer to peer_list - try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]() - { - CHECK_AND_ASSERT_MES(context.m_remote_address.get_type_id() == epee::net_utils::ipv4_network_address::ID, void(), - "Only IPv4 addresses are supported here"); - //called only(!) if success pinged, update local peerlist - peerlist_entry pe; - const epee::net_utils::network_address na = context.m_remote_address; - pe.adr = epee::net_utils::ipv4_network_address(na.as().ip(), port_l); - time_t last_seen; - time(&last_seen); - pe.last_seen = static_cast(last_seen); - pe.id = peer_id_l; - this->m_peerlist.append_with_peer_white(pe); - LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); - }); - } - - try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - flags_context.support_flags = support_flags; - }); - - //fill response - m_peerlist.get_peerlist_head(rsp.local_peerlist_new); - get_local_node_data(rsp.node_data); - m_payload_handler.get_payload_sync_data(rsp.payload_data); - LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); - return 1; - } - //----------------------------------------------------------------------------------- - template - int node_server::handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context) - { - LOG_DEBUG_CC(context, "COMMAND_PING"); - rsp.status = PING_OK_RESPONSE_STATUS_TEXT; - rsp.peer_id = m_config.m_peer_id; - return 1; - } - //----------------------------------------------------------------------------------- - template - bool node_server::log_peerlist() - { - std::list pl_white; - std::list pl_gray; - m_peerlist.get_peerlist_full(pl_gray, pl_white); - MINFO(ENDL << "Peerlist white:" << ENDL << print_peerlist_to_string(pl_white) << ENDL << "Peerlist gray:" << ENDL << print_peerlist_to_string(pl_gray) ); - return true; - } - //----------------------------------------------------------------------------------- - template - bool node_server::log_connections() - { - MINFO("Connections: \r\n" << print_connections_container() ); - return true; - } - //----------------------------------------------------------------------------------- - template - std::string node_server::print_connections_container() - { - - std::stringstream ss; - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - ss << cntxt.m_remote_address.str() - << " \t\tpeer_id " << cntxt.peer_id - << " \t\tconn_id " << epee::string_tools::get_str_from_guid_a(cntxt.m_connection_id) << (cntxt.m_is_income ? " INC":" OUT") - << std::endl; - return true; - }); - std::string s = ss.str(); - return s; - } - //----------------------------------------------------------------------------------- - template - void node_server::on_connection_new(p2p_connection_context& context) - { - MINFO("["<< epee::net_utils::print_connection_context(context) << "] NEW CONNECTION"); - } - //----------------------------------------------------------------------------------- - template - void node_server::on_connection_close(p2p_connection_context& context) - { - if (!m_net_server.is_stop_signal_sent() && !context.m_is_income) { - epee::net_utils::network_address na = AUTO_VAL_INIT(na); - na = context.m_remote_address; - - m_peerlist.remove_from_peer_anchor(na); - } - - m_payload_handler.on_connection_close(context); - - MINFO("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); - } - - template - bool node_server::is_priority_node(const epee::net_utils::network_address& na) - { - return (std::find(m_priority_peers.begin(), m_priority_peers.end(), na) != m_priority_peers.end()) || (std::find(m_exclusive_peers.begin(), m_exclusive_peers.end(), na) != m_exclusive_peers.end()); - } - - template template - bool node_server::connect_to_peerlist(const Container& peers) - { - for(const epee::net_utils::network_address& na: peers) - { - if(m_net_server.is_stop_signal_sent()) - return false; - - if(is_addr_connected(na)) - continue; - - try_to_connect_and_handshake_with_new_peer(na); - } - - return true; - } - - template template - bool node_server::parse_peers_and_add_to_container(const boost::program_options::variables_map& vm, const command_line::arg_descriptor > & arg, Container& container) - { - std::vector perrs = command_line::get_arg(vm, arg); - - for(const std::string& pr_str: perrs) - { - epee::net_utils::network_address na = AUTO_VAL_INIT(na); - const uint16_t default_port = cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT; - bool r = parse_peer_from_string(na, pr_str, default_port); - if (r) - { - container.push_back(na); - continue; - } - std::vector resolved_addrs; - r = append_net_address(resolved_addrs, pr_str, default_port); - CHECK_AND_ASSERT_MES(r, false, "Failed to parse or resolve address from string: " << pr_str); - for (const epee::net_utils::network_address& addr : resolved_addrs) - { - container.push_back(addr); - } - } - - return true; - } - - template - bool node_server::set_max_out_peers(const boost::program_options::variables_map& vm, int64_t max) - { - if(max == -1) { - m_config.m_net_config.max_out_connection_count = P2P_DEFAULT_CONNECTIONS_COUNT; - return true; - } - m_config.m_net_config.max_out_connection_count = max; - return true; - } - - template - bool node_server::set_max_in_peers(const boost::program_options::variables_map& vm, int64_t max) - { - if(max == -1) { - m_config.m_net_config.max_in_connection_count = -1; - return true; - } - m_config.m_net_config.max_in_connection_count = max; - return true; - } - - template - void node_server::delete_out_connections(size_t count) - { - m_net_server.get_config_object().del_out_connections(count); - } - - template - void node_server::delete_in_connections(size_t count) - { - m_net_server.get_config_object().del_in_connections(count); - } - - template - bool node_server::set_tos_flag(const boost::program_options::variables_map& vm, int flag) - { - if(flag==-1){ - return true; - } - epee::net_utils::connection >::set_tos_flag(flag); - _dbg1("Set ToS flag " << flag); - return true; - } - - template - bool node_server::set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit) - { - this->islimitup=true; - - if (limit==-1) { - limit=default_limit_up; - this->islimitup=false; - } - - epee::net_utils::connection >::set_rate_up_limit( limit ); - MINFO("Set limit-up to " << limit << " kB/s"); - return true; - } - - template - bool node_server::set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit) - { - this->islimitdown=true; - if(limit==-1) { - limit=default_limit_down; - this->islimitdown=false; - } - epee::net_utils::connection >::set_rate_down_limit( limit ); - MINFO("Set limit-down to " << limit << " kB/s"); - return true; - } - - template - bool node_server::set_rate_limit(const boost::program_options::variables_map& vm, int64_t limit) - { - int64_t limit_up = 0; - int64_t limit_down = 0; - - if(limit == -1) - { - limit_up = default_limit_up; - limit_down = default_limit_down; - } - else - { - limit_up = limit; - limit_down = limit; - } - if(!this->islimitup) { - epee::net_utils::connection >::set_rate_up_limit(limit_up); - MINFO("Set limit-up to " << limit_up << " kB/s"); - } - if(!this->islimitdown) { - epee::net_utils::connection >::set_rate_down_limit(limit_down); - MINFO("Set limit-down to " << limit_down << " kB/s"); - } - - return true; - } - - template - bool node_server::has_too_many_connections(const epee::net_utils::network_address &address) - { - const size_t max_connections = 1; - size_t count = 0; - - m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - if (cntxt.m_is_income && cntxt.m_remote_address.is_same_host(address)) { - count++; - - if (count > max_connections) { - return false; - } - } - - return true; - }); - - return count > max_connections; - } - - template - bool node_server::gray_peerlist_housekeeping() - { - if (m_offline) return true; - if (!m_exclusive_peers.empty()) return true; - - peerlist_entry pe = AUTO_VAL_INIT(pe); - - if (m_net_server.is_stop_signal_sent()) - return false; - - if (!m_peerlist.get_random_gray_peer(pe)) { - return false; - } - - bool success = check_connection_and_handshake_with_peer(pe.adr, pe.last_seen); - - if (!success) { - m_peerlist.remove_from_peer_gray(pe); - - LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); - - return true; - } - - m_peerlist.set_peer_just_seen(pe.id, pe.adr); - - LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); - - return true; - } - - template - void node_server::add_upnp_port_mapping(uint32_t port) - { - MDEBUG("Attempting to add IGD port mapping."); - int result; -#if MINIUPNPC_API_VERSION > 13 - // default according to miniupnpc.h - unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); -#else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); -#endif - UPNPUrls urls; - IGDdatas igdData; - char lanAddress[64]; - result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); - freeUPNPDevlist(deviceList); - if (result != 0) { - if (result == 1) { - std::ostringstream portString; - portString << port; - - // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly - UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); - - int portMappingResult; - portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); - if (portMappingResult != 0) { - LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); - } else { - MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); - } - } else if (result == 2) { - MWARNING("IGD was found but reported as not connected."); - } else if (result == 3) { - MWARNING("UPnP device was found but not recognized as IGD."); - } else { - MWARNING("UPNP_GetValidIGD returned an unknown result code."); - } - - FreeUPNPUrls(&urls); - } else { - MINFO("No IGD was found."); - } - } - - template - void node_server::delete_upnp_port_mapping(uint32_t port) - { - MDEBUG("Attempting to delete IGD port mapping."); - int result; -#if MINIUPNPC_API_VERSION > 13 - // default according to miniupnpc.h - unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); -#else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); -#endif - UPNPUrls urls; - IGDdatas igdData; - char lanAddress[64]; - result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); - freeUPNPDevlist(deviceList); - if (result != 0) { - if (result == 1) { - std::ostringstream portString; - portString << port; - - int portMappingResult; - portMappingResult = UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); - if (portMappingResult != 0) { - LOG_ERROR("UPNP_DeletePortMapping failed, error: " << strupnperror(portMappingResult)); - } else { - MLOG_GREEN(el::Level::Info, "Deleted IGD port mapping."); - } - } else if (result == 2) { - MWARNING("IGD was found but reported as not connected."); - } else if (result == 3) { - MWARNING("UPnP device was found but not recognized as IGD."); - } else { - MWARNING("UPNP_GetValidIGD returned an unknown result code."); - } - - FreeUPNPUrls(&urls); - } else { - MINFO("No IGD was found."); - } - } -}